ZRCola/ZRCola/zrcolacomppnl.cpp
Simon Rozman 34e409ef2f UI: Introduce PUA warning
Private-Use-Area characters are not correctly rendered unless ZRCola
font is used. The PUA characters are now displayed in blue.

This commit also required to upgrade (de)composition text box to use
RichEdit control.

Signed-off-by: Simon Rozman <simon@rozman.si>
2021-04-02 15:40:17 +02:00

481 lines
17 KiB
C++

/*
Copyright © 2015-2021 Amebis
This file is part of ZRCola.
ZRCola is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ZRCola is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with ZRCola. If not, see <http://www.gnu.org/licenses/>.
*/
#include "pch.h"
static inline bool is_pua(_In_ wchar_t c)
{
return L'\ue000' <= c && c <= L'\uf8ff';
}
//////////////////////////////////////////////////////////////////////////
// wxZRColaComposerPanel
//////////////////////////////////////////////////////////////////////////
wxZRColaComposerPanel::wxZRColaComposerPanel(wxWindow* parent) :
m_sourceChanged(false),
m_destinationChanged(false),
m_sourceRestyled(false),
m_destinationRestyled(false),
m_styleNormal(*wxBLACK, *wxWHITE, wxFont(20, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxT("ZRCola"))),
m_stylePUA(*wxBLUE, *wxWHITE, wxFont(20, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxT("ZRCola"))),
m_selSource(0, 0),
m_selDestination(0, 0),
wxZRColaComposerPanelBase(parent)
{
// RichEdit control has no inner margins by default.
m_source->SetMargins(FromDIP(wxPoint(5, 2)));
m_destination->SetMargins(FromDIP(wxPoint(5, 2)));
m_source->PushEventHandler(&m_keyhandler);
// Restore the previously saved state (if exists).
wxString fileName(GetStateFileName());
if (wxFileExists(fileName)) {
wxFFile file(fileName, wxT("rb"));
if (file.IsOpened()) {
// Load source text.
unsigned __int64 n;
file.Read(&n, sizeof(n));
if (!file.Error()) {
wxString source;
file.Read(wxStringBuffer(source, (size_t)n), sizeof(wchar_t)*(size_t)n);
if (!file.Error()) {
// Load destination text.
file.Read(&n, sizeof(n));
if (!file.Error()) {
wxString destination;
file.Read(wxStringBuffer(destination, (size_t)n), sizeof(wchar_t)*(size_t)n);
if (!file.Error()) {
// Restore state.
m_source->SetValue(source);
m_source->GetSelection(&m_selSource.first, &m_selSource.second);
SetHexValue(m_sourceHex, m_selSourceHex, m_mappingSourceHex, source.GetData(), source.Length(), m_selSource.first, m_selSource.second);
m_sourceChanged = false;
m_destination->SetValue(destination);
m_destination->GetSelection(&m_selDestination.first, &m_selDestination.second);
SetHexValue(m_destinationHex, m_selDestinationHex, m_mappingDestinationHex, destination.GetData(), destination.Length(), m_selDestination.first, m_selDestination.second);
m_destinationChanged = false;
}
}
}
}
}
}
}
wxZRColaComposerPanel::~wxZRColaComposerPanel()
{
m_source->PopEventHandler();
// This is a controlled exit. Purge saved state.
wxString fileName(GetStateFileName());
if (wxFileExists(fileName))
wxRemoveFile(fileName);
}
void wxZRColaComposerPanel::SynchronizePanels()
{
if (m_sourceChanged) {
m_timerSave.Stop();
auto app = dynamic_cast<ZRColaApp*>(wxTheApp);
wxString src = m_source->GetValue();
size_t len = src.Length();
std::wstring dst(src.data(), len), dst2;
ZRCola::mapping_vector map;
m_mapping.clear();
// Other translations
const ZRCola::transetid_t *sets_begin, *sets_end;
GetTranslationSeq(sets_begin, sets_end);
for (auto s = sets_begin; s != sets_end; ++s) {
app->m_t_db.Translate(*s, dst.data(), dst.size(), dst2, &map);
m_mapping.push_back(std::move(map));
dst = std::move(dst2);
}
if (app->m_mainWnd->m_composition) {
// ZRCola decompose first, then re-compose.
app->m_t_db.TranslateInv(ZRCOLA_TRANSEQID_DEFAULT, dst.data(), dst.size(), dst2, &map);
m_mapping.push_back(std::move(map));
app->m_t_db.Translate(ZRCOLA_TRANSEQID_DEFAULT, dst2.data(), dst2.size(), dst, &map);
m_mapping.push_back(std::move(map));
}
m_source->GetSelection(&m_selSource.first, &m_selSource.second);
// Update source HEX dump.
SetHexValue(m_sourceHex, m_selSourceHex, m_mappingSourceHex, src.data(), len, m_selSource.first, m_selSource.second);
// Update destination text, and its HEX dump.
m_destination->SetValue(dst);
m_destination->SetSelection(
m_selDestination.first = (long)MapToDestination(m_selSource.first ),
m_selDestination.second = (long)MapToDestination(m_selSource.second));
SetHexValue(m_destinationHex, m_selDestinationHex, m_mappingDestinationHex, dst.data(), dst.length(), m_selDestination.first, m_selDestination.second);
// Schedule state save after 3s.
m_timerSave.Start(3000, true);
} else if (m_destinationChanged) {
m_timerSave.Stop();
auto app = dynamic_cast<ZRColaApp*>(wxTheApp);
wxString src = m_destination->GetValue();
size_t len = src.Length();
std::wstring dst(src.data(), len), dst2;
ZRCola::mapping_vector map;
m_mapping.clear();
if (app->m_mainWnd->m_composition) {
// ZRCola decompose.
app->m_t_db.TranslateInv(ZRCOLA_TRANSEQID_DEFAULT, dst.data(), dst.size(), &app->m_lc_db, app->m_mainWnd->m_settings->m_lang, dst2, &map);
dst = std::move(dst2);
map.invert();
m_mapping.push_back(std::move(map));
}
// Other translations
const ZRCola::transetid_t *sets_begin, *sets_end;
GetTranslationSeq(sets_begin, sets_end);
for (auto s = sets_end; (s--) != sets_begin;) {
app->m_t_db.TranslateInv(*s, dst.data(), dst.size(), dst2, &map);
dst = std::move(dst2);
map.invert();
m_mapping.push_back(std::move(map));
}
m_destination->GetSelection(&m_selDestination.first, &m_selDestination.second);
// Update destination HEX dump.
SetHexValue(m_destinationHex, m_selDestinationHex, m_mappingDestinationHex, src.data(), len, m_selDestination.first, m_selDestination.second);
// Update source text, and its HEX dump.
m_source->SetValue(dst);
m_source->SetSelection(
m_selSource.first = (long)MapToSource(m_selDestination.first ),
m_selSource.second = (long)MapToSource(m_selDestination.second));
SetHexValue(m_sourceHex, m_selSourceHex, m_mappingSourceHex, dst.data(), dst.length(), m_selSource.first, m_selSource.second);
// Schedule state save after 3s.
m_timerSave.Start(3000, true);
}
m_sourceChanged = false;
m_destinationChanged = false;
}
void wxZRColaComposerPanel::OnSourcePaint(wxPaintEvent& event)
{
event.Skip();
if (m_sourceRestyled)
return;
long from, to;
m_source->GetSelection(&from, &to);
if (m_selSource.first != from || m_selSource.second != to) {
// Save new selection first, to avoid loop.
m_selSource.first = from;
m_selSource.second = to;
m_sourceHex->SetSelection(
m_selSourceHex.first = (long)m_mappingSourceHex.to_dst(from),
m_selSourceHex.second = (long)m_mappingSourceHex.to_dst(to ));
m_destination->SetSelection(
m_selDestination.first = (long)MapToDestination(from),
m_selDestination.second = (long)MapToDestination(to ));
m_destinationHex->SetSelection(
m_selDestinationHex.first = (long)m_mappingDestinationHex.to_dst(m_selDestination.first ),
m_selDestinationHex.second = (long)m_mappingDestinationHex.to_dst(m_selDestination.second));
}
}
void wxZRColaComposerPanel::OnSourceHexPaint(wxPaintEvent& event)
{
event.Skip();
long from, to;
m_sourceHex->GetSelection(&from, &to);
if (m_selSourceHex.first != from || m_selSourceHex.second != to) {
// Save new selection first, to avoid loop.
m_selSourceHex.first = from;
m_selSourceHex.second = to;
m_source->SetSelection(
m_selSource.first = (long)m_mappingSourceHex.to_src(from),
m_selSource.second = (long)m_mappingSourceHex.to_src(to ));
m_destination->SetSelection(
m_selDestination.first = (long)MapToDestination(m_selSource.first ),
m_selDestination.second = (long)MapToDestination(m_selSource.second));
m_destinationHex->SetSelection(
m_selDestinationHex.first = (long)m_mappingDestinationHex.to_dst(m_selDestination.first ),
m_selDestinationHex.second = (long)m_mappingDestinationHex.to_dst(m_selDestination.second));
}
}
void wxZRColaComposerPanel::OnSourceText(wxCommandEvent& event)
{
event.Skip();
if (m_sourceRestyled)
return;
// Set the flag the source text changed to trigger idle-time translation.
m_sourceChanged = true;
m_sourceRestyled = true;
m_source->SetStyle(0, GetWindowTextLength(m_source->GetHWND()), m_styleNormal);
m_sourceRestyled = false;
}
void wxZRColaComposerPanel::OnDestinationPaint(wxPaintEvent& event)
{
event.Skip();
if (m_destinationRestyled)
return;
long from, to;
m_destination->GetSelection(&from, &to);
if (m_selDestination.first != from || m_selDestination.second != to) {
// Save new selection first, to avoid loop.
m_selDestination.first = from;
m_selDestination.second = to;
m_destinationHex->SetSelection(
m_selDestinationHex.first = (long)m_mappingDestinationHex.to_dst(from),
m_selDestinationHex.second = (long)m_mappingDestinationHex.to_dst(to ));
m_source->SetSelection(
m_selSource.first = (long)MapToSource(from),
m_selSource.second = (long)MapToSource(to ));
m_sourceHex->SetSelection(
m_selSourceHex.first = (long)m_mappingSourceHex.to_dst(m_selSource.first ),
m_selSourceHex.second = (long)m_mappingSourceHex.to_dst(m_selSource.second));
}
}
void wxZRColaComposerPanel::OnDestinationHexPaint(wxPaintEvent& event)
{
event.Skip();
long from, to;
m_destinationHex->GetSelection(&from, &to);
if (m_selDestinationHex.first != from || m_selDestinationHex.second != to) {
// Save new selection first, to avoid loop.
m_selDestinationHex.first = from;
m_selDestinationHex.second = to;
m_destination->SetSelection(
m_selDestination.first = (long)m_mappingDestinationHex.to_src(from),
m_selDestination.second = (long)m_mappingDestinationHex.to_src(to ));
m_source->SetSelection(
m_selSource.first = (long)MapToSource(m_selDestination.first ),
m_selSource.second = (long)MapToSource(m_selDestination.second));
m_sourceHex->SetSelection(
m_selSourceHex.first = (long)m_mappingSourceHex.to_dst(m_selSource.first ),
m_selSourceHex.second = (long)m_mappingSourceHex.to_dst(m_selSource.second));
}
}
void wxZRColaComposerPanel::OnDestinationText(wxCommandEvent& event)
{
event.Skip();
if (m_destinationRestyled)
return;
// Set the flag the destination text changed to trigger idle-time inverse translation.
m_destinationChanged = true;
auto app = dynamic_cast<ZRColaApp*>(wxTheApp);
m_destinationRestyled = true;
if (app->m_mainWnd->m_warnPUA) {
wxString src = m_destination->GetValue();
size_t len = src.Length();
for (size_t i = 0, j; i < len;) {
bool pua_i = is_pua(src[i]);
for (j = i + 1; j < len && pua_i == is_pua(src[j]); j++);
m_destination->SetStyle((long)i, (long)j, pua_i ? m_stylePUA : m_styleNormal);
i = j;
}
} else
m_destination->SetStyle(0, GetWindowTextLength(m_destination->GetHWND()), m_styleNormal);
m_destinationRestyled = false;
}
void wxZRColaComposerPanel::OnSaveTimer(wxTimerEvent& event)
{
wxString fileName(GetStateFileName());
wxFFile file(fileName, wxT("wb"));
if (file.IsOpened()) {
{
// Save source text.
wxString text = m_source->GetValue();
size_t len = text.Length();
file.Write(&len, sizeof(len));
file.Write((const wchar_t*)text, sizeof(wchar_t)*len);
}
{
// Save destination text.
wxString text = m_destination->GetValue();
size_t len = text.Length();
file.Write(&len, sizeof(len));
file.Write((const wchar_t*)text, sizeof(wchar_t)*len);
}
}
event.Skip();
}
wxString wxZRColaComposerPanel::GetStateFileName()
{
wxString path;
path = wxFileName::GetTempDir();
if (!wxEndsWithPathSeparator(path))
path += wxFILE_SEP_PATH;
if (!wxDirExists(path))
wxMkdir(path);
wxString fileName(path);
fileName += wxT("ZRColaComposerPanel-state.tmp");
return fileName;
}
void wxZRColaComposerPanel::SetHexValue(wxTextCtrl *wnd, std::pair<long, long> &range, ZRCola::mapping_vector &mapping, const wchar_t *src, size_t len, long from, long to)
{
wxString hex;
bool first = true;
mapping.clear();
for (size_t i = 0; i < len && src[i]; i++) {
wchar_t c = src[i];
if (c == L'\n') {
hex += L"\r\n";
first = true;
} else {
hex += wxString::Format(first ? wxT("%04X") : wxT(" %04X"), src[i]);
mapping.push_back(ZRCola::mapping(i + 1, hex.Length()));
first = false;
}
}
wnd->SetValue(hex);
wnd->SetSelection(
range.first = (long)mapping.to_dst(from),
range.second = (long)mapping.to_dst(to ));
}
//////////////////////////////////////////////////////////////////////////
// wxPersistentZRColaComposerPanel
//////////////////////////////////////////////////////////////////////////
const int wxPersistentZRColaComposerPanel::s_guiLevel = 1;
wxPersistentZRColaComposerPanel::wxPersistentZRColaComposerPanel(wxZRColaComposerPanel *wnd) : wxPersistentWindow<wxZRColaComposerPanel>(wnd)
{
}
wxString wxPersistentZRColaComposerPanel::GetKind() const
{
return wxT(wxPERSIST_TLW_KIND);
}
void wxPersistentZRColaComposerPanel::Save() const
{
auto wnd = static_cast<wxZRColaComposerPanel*>(GetWindow()); // dynamic_cast is not reliable as we are typically called late in the wxTopLevelWindowMSW destructor.
SaveValue(wxT("guiLevel" ), s_guiLevel);
SaveValue(wxT("dpiX" ), wxClientDC(wnd).GetPPI().x);
SaveValue(wxT("splitDecomposed"), wnd->m_splitterSource ->GetSashPosition());
SaveValue(wxT("splitComposed" ), wnd->m_splitterDestination->GetSashPosition());
}
bool wxPersistentZRColaComposerPanel::Restore()
{
auto wnd = dynamic_cast<wxZRColaComposerPanel*>(GetWindow());
int guiLevel;
if (!RestoreValue(wxT("guiLevel"), &guiLevel) || guiLevel != s_guiLevel)
return true;
int dpiHorz = wxClientDC(wnd).GetPPI().x;
int dpiHorzVal;
int sashVal;
if (!RestoreValue(wxT("dpiX"), &dpiHorzVal))
dpiHorzVal = 96;
if (RestoreValue(wxT("splitDecomposed"), &sashVal)) {
// wxFormBuilder sets initial splitter stash in idle event handler after GUI settles. Overriding our loaded value. Disconnect it's idle event handler.
wnd->m_splitterSource->Disconnect( wxEVT_IDLE, wxIdleEventHandler( wxZRColaComposerPanelBase::m_splitterSourceOnIdle ), NULL, wnd );
wnd->m_splitterSource->SetSashPosition(wxMulDivInt32(sashVal, dpiHorz, dpiHorzVal));
}
if (RestoreValue(wxT("splitComposed"), &sashVal)) {
// wxFormBuilder sets initial splitter stash in idle event handler after GUI settles. Overriding our loaded value. Disconnect it's idle event handler.
wnd->m_splitterDestination->Disconnect( wxEVT_IDLE, wxIdleEventHandler( wxZRColaComposerPanelBase::m_splitterDestinationOnIdle ), NULL, wnd );
wnd->m_splitterDestination->SetSashPosition(wxMulDivInt32(sashVal, dpiHorz, dpiHorzVal));
}
return true;
}