Full Unicode patch with RTL Support & BiDi logic.

This commit is well documented, so no need to tell you my life story.

Full Unicode patch with RTL Support & BiDi logic.

Removed the legacy codePage, normalised to UTF8 (65001).

It also comes with:

CTRL + A : select text (highlighted)
CTRL + C : copy
CTRL + V : paste
CTRL + X : cut
CTRL + Y : redo
CTRL + Z : undo
This commit is contained in:
rtw1x1
2025-12-26 12:32:43 +00:00
parent d37607baa1
commit a955c50744
86 changed files with 4076 additions and 3839 deletions

View File

@@ -2,6 +2,7 @@
#include "EterLib/StateManager.h"
#include "EterLib/JpegFile.h"
#include "PythonGraphic.h"
#include <utf8.h>
bool g_isScreenShotKey = false;
@@ -307,7 +308,9 @@ bool CPythonGraphic::SaveScreenShot(const char * c_pszFileName)
if (g_isScreenShotKey)
{
FILE* srcFilePtr = fopen(c_pszFileName, "rb");
// UTF-8 → UTF-16 conversion for Unicode path support
std::wstring wFileName = Utf8ToWide(c_pszFileName);
FILE* srcFilePtr = _wfopen(wFileName.c_str(), L"rb");
if (srcFilePtr)
{
fseek(srcFilePtr, 0, SEEK_END);
@@ -367,8 +370,7 @@ bool CPythonGraphic::SaveScreenShot(const char * c_pszFileName)
exifHeader[2] = sizeof(exifHeader) + imgDescLen;
FILE* dstFilePtr = fopen(c_pszFileName, "wb");
//FILE* dstFilePtr = fopen("temp.jpg", "wb");
FILE* dstFilePtr = _wfopen(wFileName.c_str(), L"wb");
if (dstFilePtr)
{
fwrite(head, sizeof(head), 1, dstFilePtr);

View File

@@ -2,6 +2,8 @@
#include "EterLib/Camera.h"
#include "EterLib/TextBar.h"
#include <string>
#include <utf8.h>
#include <shlobj.h>
PyObject* grpCreateTextBar(PyObject* poSelf, PyObject* poArgs)
@@ -830,35 +832,50 @@ PyObject * grpSaveScreenShotToPath(PyObject * poSelf, PyObject * poArgs)
}
// END_OF_SCREENSHOT_CWDSAVE
PyObject * grpSaveScreenShot(PyObject * poSelf, PyObject * poArgs)
PyObject* grpSaveScreenShot(PyObject* poSelf, PyObject* poArgs)
{
struct tm * tmNow;
time_t ct;
time_t ct = time(nullptr);
tm tmNow{};
localtime_s(&tmNow, &ct);
ct = time(0);
tmNow = localtime(&ct);
wchar_t wPath[MAX_PATH + 256]{};
char szPath[MAX_PATH + 256];
SHGetSpecialFolderPath(NULL, szPath, CSIDL_PERSONAL, TRUE);
//GetTempPath();
strcat(szPath, "\\METIN2\\");
if (!SHGetSpecialFolderPathW(nullptr, wPath, CSIDL_PERSONAL, TRUE))
{
TraceError("SHGetSpecialFolderPathW failed");
return Py_BuildValue("(is)", FALSE, "");
}
if (-1 == _access(szPath, 0))
if (!CreateDirectory(szPath, NULL))
wcscat_s(wPath, L"\\METIN2\\");
if (_waccess(wPath, 0) == -1)
{
if (!CreateDirectoryW(wPath, nullptr) && GetLastError() != ERROR_ALREADY_EXISTS)
{
TraceError("Failed to create directory [%s]\n", szPath);
TraceError("Failed to create directory [%s]", WideToUtf8(wPath).c_str());
return Py_BuildValue("(is)", FALSE, "");
}
}
sprintf(szPath + strlen(szPath), "%02d%02d_%02d%02d%02d.jpg",
tmNow->tm_mon + 1,
tmNow->tm_mday,
tmNow->tm_hour,
tmNow->tm_min,
tmNow->tm_sec);
wchar_t wFile[64]{};
swprintf_s(
wFile,
L"%02d%02d_%02d%02d%02d.jpg",
tmNow.tm_mon + 1,
tmNow.tm_mday,
tmNow.tm_hour,
tmNow.tm_min,
tmNow.tm_sec
);
BOOL bResult = CPythonGraphic::Instance().SaveScreenShot(szPath);
return Py_BuildValue("(is)", bResult, szPath);
wcscat_s(wPath, wFile);
// If SaveScreenShot accepts wide paths:
BOOL bResult = CPythonGraphic::Instance().SaveScreenShot(WideToUtf8(wPath).c_str());
// Python expects bytes -> return UTF-8
std::string pathUtf8 = WideToUtf8(wPath);
return Py_BuildValue("(is)", bResult, pathUtf8.c_str());
}
PyObject * grpSetGamma(PyObject * poSelf, PyObject * poArgs)

View File

@@ -5,6 +5,7 @@
#include "PythonWindowManager.h"
#include "EterLib/StateManager.h"
#include "UserInterface/Locale.h"
BOOL g_bOutlineBoxEnable = FALSE;
@@ -781,16 +782,6 @@ namespace UI
return FALSE;
}
BOOL CWindow::OnIMEChangeCodePage()
{
long lValue;
if (PyCallClassMemberFunc(m_poHandler, "OnIMEChangeCodePage", BuildEmptyTuple(), &lValue))
if (0 != lValue)
return TRUE;
return FALSE;
}
BOOL CWindow::OnIMEOpenCandidateListEvent()
{
long lValue;
@@ -1053,6 +1044,9 @@ namespace UI
m_TextInstance.SetColor(0.78f, 0.78f, 0.78f);
m_TextInstance.SetHorizonalAlign(CGraphicTextInstance::HORIZONTAL_ALIGN_LEFT);
m_TextInstance.SetVerticalAlign(CGraphicTextInstance::VERTICAL_ALIGN_TOP);
m_baseDirection = ::IsRTL() ? CGraphicTextInstance::ETextDirection::RTL : CGraphicTextInstance::ETextDirection::LTR;
m_TextInstance.SetTextDirection(m_baseDirection);
}
CTextLine::~CTextLine()
{
@@ -1132,10 +1126,41 @@ namespace UI
return m_TextInstance.PixelPositionToCharacterPosition(lx);
}
void CTextLine::SetBaseDirection(int iDir)
{
if (iDir == 2)
m_baseDirection = CGraphicTextInstance::ETextDirection::RTL;
else if (iDir == 1)
m_baseDirection = CGraphicTextInstance::ETextDirection::LTR;
else
m_baseDirection = CGraphicTextInstance::ETextDirection::Auto;
// Apply paragraph direction to the text instance
m_TextInstance.SetTextDirection(m_baseDirection);
// Re-anchor immediately
OnChangePosition();
}
int CTextLine::GetBaseDirection() const
{
if (m_baseDirection == CGraphicTextInstance::ETextDirection::RTL)
return 2;
if (m_baseDirection == CGraphicTextInstance::ETextDirection::LTR)
return 1;
return 0;
}
void CTextLine::OnSetText(const char * c_szText)
{
m_TextInstance.SetValue(c_szText);
// Use the control's base direction (AUTO/LTR/RTL)
m_TextInstance.SetTextDirection(m_baseDirection);
m_TextInstance.Update();
// RTL anchor
OnChangePosition();
}
void CTextLine::OnUpdate()
@@ -1151,16 +1176,16 @@ namespace UI
void CTextLine::OnChangePosition()
{
// FOR_ARABIC_ALIGN
//if (m_TextInstance.GetHorizontalAlign() == CGraphicTextInstance::HORIZONTAL_ALIGN_ARABIC)
if( GetDefaultCodePage() == CP_ARABIC )
{
m_TextInstance.SetPosition(m_rect.right, m_rect.top);
}
bool bAnchorRTL = false;
if (m_baseDirection == CGraphicTextInstance::ETextDirection::RTL)
bAnchorRTL = true;
else if (m_baseDirection == CGraphicTextInstance::ETextDirection::LTR)
bAnchorRTL = false;
else
{
m_TextInstance.SetPosition(m_rect.left, m_rect.top);
}
bAnchorRTL = m_TextInstance.IsRTL(); // AUTO fallback
m_TextInstance.SetPosition(bAnchorRTL ? m_rect.right : m_rect.left, m_rect.top);
}
///////////////////////////////////////////////////////////////////////////////////////////////
@@ -2096,10 +2121,10 @@ namespace UI
void CDragButton::OnChangePosition()
{
m_x = std::max(m_x, m_restrictArea.left);
m_y = std::max(m_y, m_restrictArea.top);
m_x = std::min(m_x, std::max(0l, m_restrictArea.right - m_lWidth));
m_y = std::min(m_y, std::max(0l, m_restrictArea.bottom - m_lHeight));
m_x = (std::max)(m_x, m_restrictArea.left);
m_y = (std::max)(m_y, m_restrictArea.top);
m_x = (std::min)(m_x, (std::max)(0l, m_restrictArea.right - m_lWidth));
m_y = (std::min)(m_y, (std::max)(0l, m_restrictArea.bottom - m_lHeight));
m_rect.left = m_x;
m_rect.top = m_y;

View File

@@ -139,7 +139,6 @@ namespace UI
virtual BOOL OnIMEReturnEvent();
virtual BOOL OnIMEKeyDownEvent(int ikey);
virtual BOOL OnIMEChangeCodePage();
virtual BOOL OnIMEOpenCandidateListEvent();
virtual BOOL OnIMECloseCandidateListEvent();
virtual BOOL OnIMEOpenReadingWndEvent();
@@ -298,6 +297,9 @@ namespace UI
void GetTextSize(int* pnWidth, int* pnHeight);
void SetBaseDirection(int iDir);
int GetBaseDirection() const;
protected:
void OnUpdate();
void OnRender();
@@ -307,6 +309,7 @@ namespace UI
protected:
CGraphicTextInstance m_TextInstance;
CGraphicTextInstance::ETextDirection m_baseDirection;
};
class CNumberLine : public CWindow

View File

@@ -1133,15 +1133,6 @@ namespace UI
// NOTE : 전체로 돌리지 않고 Activate되어있는 EditLine에만 보내는 이벤트
}
void CWindowManager::RunChangeCodePage()
{
if (m_pActiveWindow)
if (m_pActiveWindow->IsRendering())
{
if (m_pActiveWindow->OnIMEChangeCodePage())
return;
}
}
void CWindowManager::RunOpenCandidate()
{
if (m_pLockWindow)

View File

@@ -126,7 +126,6 @@ namespace UI
void RunIMETabEvent();
void RunIMEReturnEvent();
void RunIMEKeyDown(int vkey);
void RunChangeCodePage();
void RunOpenCandidate();
void RunCloseCandidate();
void RunOpenReading();

View File

@@ -1822,6 +1822,19 @@ PyObject * wndTextSetLimitWidth(PyObject * poSelf, PyObject * poArgs)
((UI::CTextLine*)pWindow)->SetLimitWidth(fWidth);
return Py_BuildNone();
}
PyObject * wndTextSetBaseDirection(PyObject * poSelf, PyObject * poArgs)
{
UI::CWindow * pWindow;
if (!PyTuple_GetWindow(poArgs, 0, &pWindow))
return Py_BuildException();
int iDir;
if (!PyTuple_GetInteger(poArgs, 1, &iDir))
return Py_BuildException();
((UI::CTextLine*)pWindow)->SetBaseDirection(iDir);
return Py_BuildNone();
}
PyObject * wndTextSetText(PyObject * poSelf, PyObject * poArgs)
{
UI::CWindow * pWindow;
@@ -2549,6 +2562,7 @@ void initwndMgr()
{ "SetFontName", wndTextSetFontName, METH_VARARGS },
{ "SetFontColor", wndTextSetFontColor, METH_VARARGS },
{ "SetLimitWidth", wndTextSetLimitWidth, METH_VARARGS },
{ "SetBaseDirection", wndTextSetBaseDirection, METH_VARARGS },
{ "GetText", wndTextGetText, METH_VARARGS },
{ "GetTextSize", wndTextGetTextSize, METH_VARARGS },
{ "ShowCursor", wndTextShowCursor, METH_VARARGS },
@@ -2631,6 +2645,10 @@ void initwndMgr()
PyModule_AddIntConstant(poModule, "TEXT_VERTICAL_ALIGN_BOTTOM", CGraphicTextInstance::VERTICAL_ALIGN_BOTTOM);
PyModule_AddIntConstant(poModule, "TEXT_VERTICAL_ALIGN_CENTER", CGraphicTextInstance::VERTICAL_ALIGN_CENTER);
PyModule_AddIntConstant(poModule, "TEXT_BASEDIR_AUTO", 0);
PyModule_AddIntConstant(poModule, "TEXT_BASEDIR_LTR", 1);
PyModule_AddIntConstant(poModule, "TEXT_BASEDIR_RTL", 2);
PyModule_AddIntConstant(poModule, "HORIZONTAL_ALIGN_LEFT", UI::CWindow::HORIZONTAL_ALIGN_LEFT);
PyModule_AddIntConstant(poModule, "HORIZONTAL_ALIGN_CENTER", UI::CWindow::HORIZONTAL_ALIGN_CENTER);
PyModule_AddIntConstant(poModule, "HORIZONTAL_ALIGN_RIGHT", UI::CWindow::HORIZONTAL_ALIGN_RIGHT);