ML: Refactored Hot-Reload System
This commit is contained in:
@@ -19,7 +19,6 @@ import uiScriptLocale
|
|||||||
|
|
||||||
# Multi-language hot-reload system
|
# Multi-language hot-reload system
|
||||||
from uilocaleselector import LocaleSelector
|
from uilocaleselector import LocaleSelector
|
||||||
from uilocalechange import LocaleChangeManager
|
|
||||||
|
|
||||||
LOGIN_DELAY_SEC = 0.0
|
LOGIN_DELAY_SEC = 0.0
|
||||||
SKIP_LOGIN_PHASE = False
|
SKIP_LOGIN_PHASE = False
|
||||||
@@ -157,7 +156,6 @@ class LoginWindow(ui.ScriptWindow):
|
|||||||
|
|
||||||
# Multi-language hot-reload system
|
# Multi-language hot-reload system
|
||||||
self.localeSelector = None
|
self.localeSelector = None
|
||||||
self.localeChangeManager = LocaleChangeManager(self)
|
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
net.ClearPhaseWindow(net.PHASE_WINDOW_LOGIN, self)
|
net.ClearPhaseWindow(net.PHASE_WINDOW_LOGIN, self)
|
||||||
@@ -599,11 +597,94 @@ class LoginWindow(ui.ScriptWindow):
|
|||||||
self.stream.SetPhaseWindow(0)
|
self.stream.SetPhaseWindow(0)
|
||||||
|
|
||||||
def __OnLocaleChanged(self, newLocaleCode):
|
def __OnLocaleChanged(self, newLocaleCode):
|
||||||
|
"""Handle locale change - save config, reload, and refresh UI"""
|
||||||
|
import dbg
|
||||||
|
|
||||||
|
# 1) Save locale code to config/locale.cfg
|
||||||
|
try:
|
||||||
|
import os
|
||||||
|
if not os.path.exists("config"):
|
||||||
|
os.makedirs("config")
|
||||||
|
with open("config/locale.cfg", "w") as f:
|
||||||
|
f.write(newLocaleCode)
|
||||||
|
dbg.TraceError("Saved new locale to config: %s" % newLocaleCode)
|
||||||
|
except Exception as e:
|
||||||
|
dbg.TraceError("Failed to save locale.cfg: %s" % str(e))
|
||||||
|
return
|
||||||
|
|
||||||
|
# 2) Call C++ to reload locale data (C++ data + Python modules)
|
||||||
|
if not app.ReloadLocale():
|
||||||
|
dbg.TraceError("app.ReloadLocale() failed")
|
||||||
|
return
|
||||||
|
|
||||||
|
dbg.TraceError("Locale changed successfully, refreshing UI...")
|
||||||
|
|
||||||
|
# 3) Refresh all UI text elements with new locale
|
||||||
|
self.__RefreshLocaleUI()
|
||||||
|
|
||||||
|
def __RefreshLocaleUI(self):
|
||||||
"""
|
"""
|
||||||
Callback when user confirms locale change.
|
Refresh all UI text elements after locale change
|
||||||
All the heavy lifting is done by LocaleChangeManager - this is just 3 lines!
|
|
||||||
|
Uses the generic uiLocaleRefresh module to update all visible text elements
|
||||||
|
without needing to reload the entire UI.
|
||||||
"""
|
"""
|
||||||
self.localeChangeManager.ReloadWithNewLocale(newLocaleCode, "uiscript/LoginWindow.py")
|
import uiScriptLocale
|
||||||
|
import uiLocaleRefresh
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Refresh button and text elements using element mapping
|
||||||
|
elementMapping = {
|
||||||
|
self.serverInfo: "LOGIN_DEFAULT_SERVERADDR",
|
||||||
|
self.selectConnectButton: "LOGIN_SELECT_BUTTON",
|
||||||
|
self.loginButton: "LOGIN_CONNECT",
|
||||||
|
self.loginExitButton: "LOGIN_EXIT",
|
||||||
|
self.serverSelectButton: "OK",
|
||||||
|
self.serverExitButton: "LOGIN_SELECT_EXIT",
|
||||||
|
}
|
||||||
|
uiLocaleRefresh.RefreshByMapping(elementMapping)
|
||||||
|
|
||||||
|
# Refresh ServerBoard Title (special case - accessed via GetChild)
|
||||||
|
try:
|
||||||
|
serverBoardTitle = self.GetChild("Title")
|
||||||
|
serverBoardTitle.SetText(uiScriptLocale.LOGIN_SELECT_TITLE)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Rebuild login error message dictionary with new locale strings
|
||||||
|
loginFailureTemplate = {
|
||||||
|
"ALREADY" : "LOGIN_FAILURE_ALREAY",
|
||||||
|
"NOID" : "LOGIN_FAILURE_NOT_EXIST_ID",
|
||||||
|
"WRONGPWD" : "LOGIN_FAILURE_WRONG_PASSWORD",
|
||||||
|
"FULL" : "LOGIN_FAILURE_TOO_MANY_USER",
|
||||||
|
"SHUTDOWN" : "LOGIN_FAILURE_SHUTDOWN",
|
||||||
|
"REPAIR" : "LOGIN_FAILURE_REPAIR_ID",
|
||||||
|
"BLOCK" : "LOGIN_FAILURE_BLOCK_ID",
|
||||||
|
"BESAMEKEY" : "LOGIN_FAILURE_BE_SAME_KEY",
|
||||||
|
"NOTAVAIL" : "LOGIN_FAILURE_NOT_AVAIL",
|
||||||
|
"NOBILL" : "LOGIN_FAILURE_NOBILL",
|
||||||
|
"BLKLOGIN" : "LOGIN_FAILURE_BLOCK_LOGIN",
|
||||||
|
"WEBBLK" : "LOGIN_FAILURE_WEB_BLOCK",
|
||||||
|
"BADSCLID" : "LOGIN_FAILURE_WRONG_SOCIALID",
|
||||||
|
"AGELIMIT" : "LOGIN_FAILURE_SHUTDOWN_TIME",
|
||||||
|
}
|
||||||
|
self.loginFailureMsgDict = uiLocaleRefresh.RebuildDictionary(loginFailureTemplate, "localeInfo")
|
||||||
|
|
||||||
|
# Recreate locale selector to ensure it's on top with updated text
|
||||||
|
if self.localeSelector:
|
||||||
|
self.localeSelector.Destroy()
|
||||||
|
self.localeSelector = None
|
||||||
|
|
||||||
|
self.localeSelector = LocaleSelector()
|
||||||
|
self.localeSelector.Create(self)
|
||||||
|
self.localeSelector.SetLocaleChangedEvent(ui.__mem_func__(self.__OnLocaleChanged))
|
||||||
|
self.localeSelector.Show()
|
||||||
|
self.localeSelector.SetTop()
|
||||||
|
|
||||||
|
except:
|
||||||
|
# import dbg
|
||||||
|
# dbg.TraceError("LoginWindow.__RefreshLocaleUI failed")
|
||||||
|
pass
|
||||||
|
|
||||||
def __SetServerInfo(self, name):
|
def __SetServerInfo(self, name):
|
||||||
net.SetServerInfo(name.strip())
|
net.SetServerInfo(name.strip())
|
||||||
|
|||||||
@@ -31,9 +31,29 @@ VIRTUAL_KEY_SYMBOLS = "!@#$%^&*()_+|{}:'<>?~"
|
|||||||
VIRTUAL_KEY_NUMBERS = "1234567890-=\[];',./`"
|
VIRTUAL_KEY_NUMBERS = "1234567890-=\[];',./`"
|
||||||
VIRTUAL_KEY_SYMBOLS_BR = "!@#$%^&*()_+|{}:'<>?~aaaaeeeiioooouuc"
|
VIRTUAL_KEY_SYMBOLS_BR = "!@#$%^&*()_+|{}:'<>?~aaaaeeeiioooouuc"
|
||||||
|
|
||||||
# Load locale data by specific path
|
# Multi-language hot-reload support
|
||||||
def LoadLocaleData():
|
def LoadLocaleData():
|
||||||
app.LoadLocaleData(app.GetLocalePath())
|
"""
|
||||||
|
Reload all game locale text strings from locale_game.txt
|
||||||
|
|
||||||
|
Called by app.ReloadLocale() when the user changes language.
|
||||||
|
Reloads locale_game.txt and updates all module-level locale strings.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True on success, False on failure
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
localePath = app.GetLocalePath()
|
||||||
|
localeFilePath = "{:s}/locale_game.txt".format(localePath)
|
||||||
|
|
||||||
|
# Reload locale_game.txt - this updates all global variables in this module
|
||||||
|
LoadLocaleFile(localeFilePath, globals())
|
||||||
|
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
# import dbg
|
||||||
|
# dbg.TraceError("localeInfo.LoadLocaleData failed")
|
||||||
|
return False
|
||||||
|
|
||||||
# Load locale_game.txt
|
# Load locale_game.txt
|
||||||
def LoadLocaleFile(srcFileName, localeDict):
|
def LoadLocaleFile(srcFileName, localeDict):
|
||||||
|
|||||||
@@ -1,472 +0,0 @@
|
|||||||
# -*- 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()
|
|
||||||
242
assets/root/uilocalerefresh.py
Normal file
242
assets/root/uilocalerefresh.py
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
"""
|
||||||
|
Generic UI Locale Refresh System
|
||||||
|
This module provides automatic locale refresh for UI windows without hardcoding element names.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import dbg
|
||||||
|
import ui
|
||||||
|
|
||||||
|
class LocaleRefreshHelper:
|
||||||
|
"""
|
||||||
|
Helper class to automatically refresh UI text elements when locale changes.
|
||||||
|
Works by re-reading the original UI script and applying new locale strings.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.scriptCache = {} # Cache loaded UI scripts
|
||||||
|
|
||||||
|
def RefreshWindow(self, window, scriptPath):
|
||||||
|
"""
|
||||||
|
Automatically refresh all text elements in a window by re-reading the UI script.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
window: The ui.ScriptWindow instance to refresh
|
||||||
|
scriptPath: Path to the UI script file (e.g., "UIScript/LoginWindow.py")
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Number of elements successfully refreshed
|
||||||
|
"""
|
||||||
|
import uiScriptLocale
|
||||||
|
import localeInfo
|
||||||
|
|
||||||
|
dbg.TraceError("LocaleRefreshHelper: Refreshing window from %s" % scriptPath)
|
||||||
|
|
||||||
|
# Load the UI script to get the original text definitions
|
||||||
|
try:
|
||||||
|
scriptData = self._LoadUIScript(scriptPath)
|
||||||
|
except Exception as e:
|
||||||
|
dbg.TraceError("LocaleRefreshHelper: Failed to load script %s: %s" % (scriptPath, str(e)))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# Recursively refresh all elements
|
||||||
|
refreshCount = self._RefreshElement(window, scriptData.get("window", {}), window)
|
||||||
|
|
||||||
|
dbg.TraceError("LocaleRefreshHelper: Refreshed %d elements" % refreshCount)
|
||||||
|
return refreshCount
|
||||||
|
|
||||||
|
def RefreshElementsByMapping(self, elementMap):
|
||||||
|
"""
|
||||||
|
Refresh UI elements using a manual mapping dictionary.
|
||||||
|
Useful for elements that can't be auto-detected.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
elementMap: Dict of {element_instance: locale_string_name}
|
||||||
|
|
||||||
|
Example:
|
||||||
|
mapping = {
|
||||||
|
self.loginButton: "LOGIN_CONNECT",
|
||||||
|
self.exitButton: "LOGIN_EXIT"
|
||||||
|
}
|
||||||
|
helper.RefreshElementsByMapping(mapping)
|
||||||
|
"""
|
||||||
|
import uiScriptLocale
|
||||||
|
import localeInfo
|
||||||
|
|
||||||
|
refreshCount = 0
|
||||||
|
for element, localeKey in elementMap.items():
|
||||||
|
try:
|
||||||
|
# Try uiScriptLocale first, then localeInfo
|
||||||
|
if hasattr(uiScriptLocale, localeKey):
|
||||||
|
text = getattr(uiScriptLocale, localeKey)
|
||||||
|
elif hasattr(localeInfo, localeKey):
|
||||||
|
text = getattr(localeInfo, localeKey)
|
||||||
|
else:
|
||||||
|
dbg.TraceError("LocaleRefreshHelper: Locale key not found: %s" % localeKey)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Set the text
|
||||||
|
if hasattr(element, 'SetText'):
|
||||||
|
element.SetText(text)
|
||||||
|
refreshCount += 1
|
||||||
|
except Exception as e:
|
||||||
|
dbg.TraceError("LocaleRefreshHelper: Failed to refresh element with key %s: %s" % (localeKey, str(e)))
|
||||||
|
|
||||||
|
return refreshCount
|
||||||
|
|
||||||
|
def RefreshDictionaries(self, targetDict, localeModule="localeInfo"):
|
||||||
|
"""
|
||||||
|
Rebuild a dictionary with fresh locale strings.
|
||||||
|
Useful for error message dictionaries, etc.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
targetDict: Dictionary to rebuild with format {key: "LOCALE_CONSTANT_NAME"}
|
||||||
|
localeModule: Name of the locale module ("localeInfo" or "uiScriptLocale")
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
New dictionary with fresh locale values
|
||||||
|
|
||||||
|
Example:
|
||||||
|
template = {
|
||||||
|
"WRONGPWD": "LOGIN_FAILURE_WRONG_PASSWORD",
|
||||||
|
"FULL": "LOGIN_FAILURE_TOO_MANY_USER"
|
||||||
|
}
|
||||||
|
newDict = helper.RefreshDictionaries(template)
|
||||||
|
"""
|
||||||
|
import localeInfo
|
||||||
|
import uiScriptLocale
|
||||||
|
|
||||||
|
module = localeInfo if localeModule == "localeInfo" else uiScriptLocale
|
||||||
|
newDict = {}
|
||||||
|
|
||||||
|
for key, localeKey in targetDict.items():
|
||||||
|
if hasattr(module, localeKey):
|
||||||
|
newDict[key] = getattr(module, localeKey)
|
||||||
|
else:
|
||||||
|
dbg.TraceError("LocaleRefreshHelper: Locale key not found: %s" % localeKey)
|
||||||
|
|
||||||
|
return newDict
|
||||||
|
|
||||||
|
def _LoadUIScript(self, scriptPath):
|
||||||
|
"""Load and cache a UI script file."""
|
||||||
|
if scriptPath in self.scriptCache:
|
||||||
|
return self.scriptCache[scriptPath]
|
||||||
|
|
||||||
|
# Execute the UI script to get its data
|
||||||
|
scriptData = {}
|
||||||
|
try:
|
||||||
|
execfile(scriptPath, scriptData)
|
||||||
|
self.scriptCache[scriptPath] = scriptData
|
||||||
|
except Exception as e:
|
||||||
|
dbg.TraceError("LocaleRefreshHelper: Failed to execute script %s: %s" % (scriptPath, str(e)))
|
||||||
|
raise
|
||||||
|
|
||||||
|
return scriptData
|
||||||
|
|
||||||
|
def _RefreshElement(self, windowInstance, elementDef, currentElement):
|
||||||
|
"""
|
||||||
|
Recursively refresh an element and its children.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
windowInstance: The root window instance
|
||||||
|
elementDef: Element definition from UI script
|
||||||
|
currentElement: Current UI element instance
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Number of elements refreshed
|
||||||
|
"""
|
||||||
|
import uiScriptLocale
|
||||||
|
import localeInfo
|
||||||
|
|
||||||
|
refreshCount = 0
|
||||||
|
|
||||||
|
# If this element has text defined in the script, refresh it
|
||||||
|
if isinstance(elementDef, dict) and "text" in elementDef:
|
||||||
|
textDef = elementDef["text"]
|
||||||
|
|
||||||
|
# Check if it's a locale reference (starts with uiScriptLocale or localeInfo)
|
||||||
|
if isinstance(textDef, str):
|
||||||
|
text = self._ResolveLocaleString(textDef)
|
||||||
|
if text and hasattr(currentElement, 'SetText'):
|
||||||
|
try:
|
||||||
|
currentElement.SetText(text)
|
||||||
|
refreshCount += 1
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Recursively process children
|
||||||
|
if isinstance(elementDef, dict) and "children" in elementDef:
|
||||||
|
children = elementDef.get("children", [])
|
||||||
|
for childDef in children:
|
||||||
|
if isinstance(childDef, dict) and "name" in childDef:
|
||||||
|
childName = childDef["name"]
|
||||||
|
try:
|
||||||
|
childElement = windowInstance.GetChild(childName)
|
||||||
|
refreshCount += self._RefreshElement(windowInstance, childDef, childElement)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return refreshCount
|
||||||
|
|
||||||
|
def _ResolveLocaleString(self, textDef):
|
||||||
|
"""
|
||||||
|
Resolve a locale string reference to its current value.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
textDef: String like "uiScriptLocale.LOGIN_CONNECT" or direct text
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The resolved locale string or None
|
||||||
|
"""
|
||||||
|
import uiScriptLocale
|
||||||
|
import localeInfo
|
||||||
|
|
||||||
|
# Check if it's a locale reference
|
||||||
|
if "uiScriptLocale." in str(textDef):
|
||||||
|
# Extract the attribute name
|
||||||
|
parts = str(textDef).split(".")
|
||||||
|
if len(parts) >= 2:
|
||||||
|
attrName = parts[-1]
|
||||||
|
if hasattr(uiScriptLocale, attrName):
|
||||||
|
return getattr(uiScriptLocale, attrName)
|
||||||
|
|
||||||
|
elif "localeInfo." in str(textDef):
|
||||||
|
parts = str(textDef).split(".")
|
||||||
|
if len(parts) >= 2:
|
||||||
|
attrName = parts[-1]
|
||||||
|
if hasattr(localeInfo, attrName):
|
||||||
|
return getattr(localeInfo, attrName)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# Global helper instance for easy access
|
||||||
|
_globalHelper = LocaleRefreshHelper()
|
||||||
|
|
||||||
|
def RefreshWindowByScript(window, scriptPath):
|
||||||
|
"""
|
||||||
|
Convenience function to refresh a window using its UI script.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
window: The ui.ScriptWindow instance
|
||||||
|
scriptPath: Path to UI script (e.g., "UIScript/LoginWindow.py")
|
||||||
|
"""
|
||||||
|
return _globalHelper.RefreshWindow(window, scriptPath)
|
||||||
|
|
||||||
|
def RefreshByMapping(elementMap):
|
||||||
|
"""
|
||||||
|
Convenience function to refresh elements by mapping.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
elementMap: Dict of {element: "LOCALE_KEY"}
|
||||||
|
"""
|
||||||
|
return _globalHelper.RefreshElementsByMapping(elementMap)
|
||||||
|
|
||||||
|
def RebuildDictionary(template, localeModule="localeInfo"):
|
||||||
|
"""
|
||||||
|
Convenience function to rebuild a dictionary with fresh locale strings.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
template: Dict of {key: "LOCALE_KEY"}
|
||||||
|
localeModule: "localeInfo" or "uiScriptLocale"
|
||||||
|
"""
|
||||||
|
return _globalHelper.RefreshDictionaries(template, localeModule)
|
||||||
@@ -47,4 +47,49 @@ EMPIREDESC_B = "%s/empiredesc_b.txt" % (name)
|
|||||||
EMPIREDESC_C = "%s/empiredesc_c.txt" % (name)
|
EMPIREDESC_C = "%s/empiredesc_c.txt" % (name)
|
||||||
|
|
||||||
LOCALE_INTERFACE_FILE_NAME = "%s/locale_interface.txt" % (name)
|
LOCALE_INTERFACE_FILE_NAME = "%s/locale_interface.txt" % (name)
|
||||||
LoadLocaleFile(LOCALE_INTERFACE_FILE_NAME, locals())
|
LoadLocaleFile(LOCALE_INTERFACE_FILE_NAME, locals())
|
||||||
|
|
||||||
|
def LoadLocaleData():
|
||||||
|
"""
|
||||||
|
Reload all UI locale strings from locale_interface.txt
|
||||||
|
|
||||||
|
Called by app.ReloadLocale() when the user changes language.
|
||||||
|
Updates all locale-dependent paths and reloads locale_interface.txt.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True on success, False on failure
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Update all locale-dependent paths
|
||||||
|
global name, LOCALE_UISCRIPT_PATH, LOGIN_PATH, EMPIRE_PATH, GUILD_PATH, SELECT_PATH, WINDOWS_PATH, MAPNAME_PATH
|
||||||
|
global JOBDESC_WARRIOR_PATH, JOBDESC_ASSASSIN_PATH, JOBDESC_SURA_PATH, JOBDESC_SHAMAN_PATH
|
||||||
|
global EMPIREDESC_A, EMPIREDESC_B, EMPIREDESC_C, LOCALE_INTERFACE_FILE_NAME
|
||||||
|
|
||||||
|
name = app.GetLocalePath()
|
||||||
|
LOCALE_UISCRIPT_PATH = "%s/ui/" % (name)
|
||||||
|
LOGIN_PATH = "%s/ui/login/" % (name)
|
||||||
|
EMPIRE_PATH = "%s/ui/empire/" % (name)
|
||||||
|
GUILD_PATH = "%s/ui/guild/" % (name)
|
||||||
|
SELECT_PATH = "%s/ui/select/" % (name)
|
||||||
|
WINDOWS_PATH = "%s/ui/windows/" % (name)
|
||||||
|
MAPNAME_PATH = "%s/ui/mapname/" % (name)
|
||||||
|
|
||||||
|
JOBDESC_WARRIOR_PATH = "%s/jobdesc_warrior.txt" % (name)
|
||||||
|
JOBDESC_ASSASSIN_PATH = "%s/jobdesc_assassin.txt" % (name)
|
||||||
|
JOBDESC_SURA_PATH = "%s/jobdesc_sura.txt" % (name)
|
||||||
|
JOBDESC_SHAMAN_PATH = "%s/jobdesc_shaman.txt" % (name)
|
||||||
|
|
||||||
|
EMPIREDESC_A = "%s/empiredesc_a.txt" % (name)
|
||||||
|
EMPIREDESC_B = "%s/empiredesc_b.txt" % (name)
|
||||||
|
EMPIREDESC_C = "%s/empiredesc_c.txt" % (name)
|
||||||
|
|
||||||
|
LOCALE_INTERFACE_FILE_NAME = "%s/locale_interface.txt" % (name)
|
||||||
|
|
||||||
|
# Reload locale_interface.txt - this updates all UI strings
|
||||||
|
LoadLocaleFile(LOCALE_INTERFACE_FILE_NAME, globals())
|
||||||
|
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
# import dbg
|
||||||
|
# dbg.TraceError("uiScriptLocale.LoadLocaleData failed")
|
||||||
|
return False
|
||||||
Reference in New Issue
Block a user