forked from Public-Mirror/Textractor
allow replacement in regex filter and make names consistent
This commit is contained in:
parent
4219115a40
commit
084f26a72d
@ -303,8 +303,8 @@ namespace
|
|||||||
|
|
||||||
QDialog dialog(This, Qt::WindowCloseButtonHint);
|
QDialog dialog(This, Qt::WindowCloseButtonHint);
|
||||||
QFormLayout layout(&dialog);
|
QFormLayout layout(&dialog);
|
||||||
QCheckBox cjkCheckBox(&dialog);
|
QCheckBox CJKCheck(&dialog);
|
||||||
layout.addRow(SEARCH_CJK, &cjkCheckBox);
|
layout.addRow(SEARCH_CJK, &CJKCheck);
|
||||||
QDialogButtonBox confirm(QDialogButtonBox::Ok | QDialogButtonBox::Help | QDialogButtonBox::Retry, &dialog);
|
QDialogButtonBox confirm(QDialogButtonBox::Ok | QDialogButtonBox::Help | QDialogButtonBox::Retry, &dialog);
|
||||||
layout.addRow(&confirm);
|
layout.addRow(&confirm);
|
||||||
confirm.button(QDialogButtonBox::Ok)->setText(START_HOOK_SEARCH);
|
confirm.button(QDialogButtonBox::Ok)->setText(START_HOOK_SEARCH);
|
||||||
@ -323,29 +323,29 @@ namespace
|
|||||||
{
|
{
|
||||||
QDialog dialog(This, Qt::WindowCloseButtonHint);
|
QDialog dialog(This, Qt::WindowCloseButtonHint);
|
||||||
QFormLayout layout(&dialog);
|
QFormLayout layout(&dialog);
|
||||||
QLineEdit textInput(&dialog);
|
QLineEdit textEdit(&dialog);
|
||||||
layout.addRow(TEXT, &textInput);
|
layout.addRow(TEXT, &textEdit);
|
||||||
QSpinBox codepageInput(&dialog);
|
QSpinBox codepageSpin(&dialog);
|
||||||
codepageInput.setMaximum(INT_MAX);
|
codepageSpin.setMaximum(INT_MAX);
|
||||||
codepageInput.setValue(sp.codepage);
|
codepageSpin.setValue(sp.codepage);
|
||||||
layout.addRow(CODEPAGE, &codepageInput);
|
layout.addRow(CODEPAGE, &codepageSpin);
|
||||||
QDialogButtonBox confirm(QDialogButtonBox::Ok);
|
QDialogButtonBox confirm(QDialogButtonBox::Ok);
|
||||||
QObject::connect(&confirm, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
|
QObject::connect(&confirm, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
|
||||||
layout.addRow(&confirm);
|
layout.addRow(&confirm);
|
||||||
if (!dialog.exec()) return;
|
if (!dialog.exec()) return;
|
||||||
wcsncpy_s(sp.text, S(textInput.text()).c_str(), PATTERN_SIZE - 1);
|
wcsncpy_s(sp.text, S(textEdit.text()).c_str(), PATTERN_SIZE - 1);
|
||||||
try { Host::FindHooks(selectedProcessId, sp); } catch (std::out_of_range) {}
|
try { Host::FindHooks(selectedProcessId, sp); } catch (std::out_of_range) {}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
filter.setPattern(cjkCheckBox.isChecked() ? "[\\x{3000}-\\x{a000}]{4,}" : "[\\x{0020}-\\x{1000}]{4,}");
|
filter.setPattern(CJKCheck.isChecked() ? "[\\x{3000}-\\x{a000}]{4,}" : "[\\x{0020}-\\x{1000}]{4,}");
|
||||||
if (customSettings)
|
if (customSettings)
|
||||||
{
|
{
|
||||||
QDialog dialog(This, Qt::WindowCloseButtonHint);
|
QDialog dialog(This, Qt::WindowCloseButtonHint);
|
||||||
QFormLayout layout(&dialog);
|
QFormLayout layout(&dialog);
|
||||||
QLineEdit patternInput(x64 ? "CC CC 48 89" : "55 8B EC", &dialog);
|
QLineEdit patternEdit(x64 ? "CC CC 48 89" : "55 8B EC", &dialog);
|
||||||
assert(QByteArray::fromHex(patternInput.text().toUtf8()) == QByteArray((const char*)sp.pattern, sp.length));
|
assert(QByteArray::fromHex(patternEdit.text().toUtf8()) == QByteArray((const char*)sp.pattern, sp.length));
|
||||||
layout.addRow(SEARCH_PATTERN, &patternInput);
|
layout.addRow(SEARCH_PATTERN, &patternEdit);
|
||||||
for (auto [value, label] : Array<int&, const char*>{
|
for (auto [value, label] : Array<int&, const char*>{
|
||||||
{ sp.searchTime, SEARCH_DURATION },
|
{ sp.searchTime, SEARCH_DURATION },
|
||||||
{ sp.offset, PATTERN_OFFSET },
|
{ sp.offset, PATTERN_OFFSET },
|
||||||
@ -359,36 +359,36 @@ namespace
|
|||||||
layout.addRow(label, spinBox);
|
layout.addRow(label, spinBox);
|
||||||
QObject::connect(spinBox, qOverload<int>(&QSpinBox::valueChanged), [&value](int newValue) { value = newValue; });
|
QObject::connect(spinBox, qOverload<int>(&QSpinBox::valueChanged), [&value](int newValue) { value = newValue; });
|
||||||
}
|
}
|
||||||
QLineEdit boundInput(QFileInfo(S(GetModuleFilename(selectedProcessId).value_or(L""))).fileName(), &dialog);
|
QLineEdit boundEdit(QFileInfo(S(GetModuleFilename(selectedProcessId).value_or(L""))).fileName(), &dialog);
|
||||||
layout.addRow(SEARCH_MODULE, &boundInput);
|
layout.addRow(SEARCH_MODULE, &boundEdit);
|
||||||
for (auto [value, label] : Array<uintptr_t&, const char*>{
|
for (auto [value, label] : Array<uintptr_t&, const char*>{
|
||||||
{ sp.minAddress, MIN_ADDRESS },
|
{ sp.minAddress, MIN_ADDRESS },
|
||||||
{ sp.maxAddress, MAX_ADDRESS },
|
{ sp.maxAddress, MAX_ADDRESS },
|
||||||
{ sp.padding, STRING_OFFSET },
|
{ sp.padding, STRING_OFFSET },
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
auto input = new QLineEdit(QString::number(value, 16), &dialog);
|
auto edit = new QLineEdit(QString::number(value, 16), &dialog);
|
||||||
layout.addRow(label, input);
|
layout.addRow(label, edit);
|
||||||
QObject::connect(input, &QLineEdit::textEdited, [&value](QString text) { if (uintptr_t newValue = text.toULongLong(&ok, 16); ok) value = newValue; });
|
QObject::connect(edit, &QLineEdit::textEdited, [&value](QString text) { if (uintptr_t newValue = text.toULongLong(&ok, 16); ok) value = newValue; });
|
||||||
}
|
}
|
||||||
QLineEdit filterInput(filter.pattern(), &dialog);
|
QLineEdit filterEdit(filter.pattern(), &dialog);
|
||||||
layout.addRow(HOOK_SEARCH_FILTER, &filterInput);
|
layout.addRow(HOOK_SEARCH_FILTER, &filterEdit);
|
||||||
QPushButton startButton(START_HOOK_SEARCH, &dialog);
|
QPushButton startButton(START_HOOK_SEARCH, &dialog);
|
||||||
layout.addWidget(&startButton);
|
layout.addWidget(&startButton);
|
||||||
QObject::connect(&startButton, &QPushButton::clicked, &dialog, &QDialog::accept);
|
QObject::connect(&startButton, &QPushButton::clicked, &dialog, &QDialog::accept);
|
||||||
if (!dialog.exec()) return;
|
if (!dialog.exec()) return;
|
||||||
if (patternInput.text().contains('.'))
|
if (patternEdit.text().contains('.'))
|
||||||
{
|
{
|
||||||
wcsncpy_s(sp.exportModule, S(patternInput.text()).c_str(), MAX_MODULE_SIZE - 1);
|
wcsncpy_s(sp.exportModule, S(patternEdit.text()).c_str(), MAX_MODULE_SIZE - 1);
|
||||||
sp.length = 1;
|
sp.length = 1;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
QByteArray pattern = QByteArray::fromHex(patternInput.text().replace("??", QString::number(XX, 16)).toUtf8());
|
QByteArray pattern = QByteArray::fromHex(patternEdit.text().replace("??", QString::number(XX, 16)).toUtf8());
|
||||||
memcpy(sp.pattern, pattern.data(), sp.length = min(pattern.size(), PATTERN_SIZE));
|
memcpy(sp.pattern, pattern.data(), sp.length = min(pattern.size(), PATTERN_SIZE));
|
||||||
}
|
}
|
||||||
wcsncpy_s(sp.boundaryModule, S(boundInput.text()).c_str(), MAX_MODULE_SIZE - 1);
|
wcsncpy_s(sp.boundaryModule, S(boundEdit.text()).c_str(), MAX_MODULE_SIZE - 1);
|
||||||
filter.setPattern(filterInput.text());
|
filter.setPattern(filterEdit.text());
|
||||||
if (!filter.isValid()) filter.setPattern(".");
|
if (!filter.isValid()) filter.setPattern(".");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -467,12 +467,12 @@ namespace
|
|||||||
layout.addRow(label, spinBox);
|
layout.addRow(label, spinBox);
|
||||||
QObject::connect(&saveButton, &QPushButton::clicked, [spinBox, label, &settings, &value] { settings.setValue(label, value = spinBox->value()); });
|
QObject::connect(&saveButton, &QPushButton::clicked, [spinBox, label, &settings, &value] { settings.setValue(label, value = spinBox->value()); });
|
||||||
}
|
}
|
||||||
QComboBox localeComboBox(&dialog);
|
QComboBox localeCombo(&dialog);
|
||||||
assert(PROMPT == 0 && ALWAYS == 1 && NEVER == 2);
|
assert(PROMPT == 0 && ALWAYS == 1 && NEVER == 2);
|
||||||
localeComboBox.addItems({ { "Prompt", "Always", "Never" } });
|
localeCombo.addItems({ { "Prompt", "Always", "Never" } });
|
||||||
localeComboBox.setCurrentIndex(settings.value(CONFIG_JP_LOCALE, PROMPT).toInt());
|
localeCombo.setCurrentIndex(settings.value(CONFIG_JP_LOCALE, PROMPT).toInt());
|
||||||
layout.addRow(CONFIG_JP_LOCALE, &localeComboBox);
|
layout.addRow(CONFIG_JP_LOCALE, &localeCombo);
|
||||||
QObject::connect(&localeComboBox, qOverload<int>(&QComboBox::activated), [&settings](int i) { settings.setValue(CONFIG_JP_LOCALE, i); });
|
QObject::connect(&localeCombo, qOverload<int>(&QComboBox::activated), [&settings](int i) { settings.setValue(CONFIG_JP_LOCALE, i); });
|
||||||
layout.addWidget(&saveButton);
|
layout.addWidget(&saveButton);
|
||||||
QObject::connect(&saveButton, &QPushButton::clicked, &dialog, &QDialog::accept);
|
QObject::connect(&saveButton, &QPushButton::clicked, &dialog, &QDialog::accept);
|
||||||
dialog.setWindowTitle(SETTINGS);
|
dialog.setWindowTitle(SETTINGS);
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
extern const wchar_t* TRANSLATION_ERROR;
|
extern const wchar_t* TRANSLATION_ERROR;
|
||||||
|
|
||||||
extern Synchronized<std::wstring> translateTo, apiKey;
|
extern Synchronized<std::wstring> translateTo, authKey;
|
||||||
|
|
||||||
const char* TRANSLATION_PROVIDER = "Bing Translate";
|
const char* TRANSLATION_PROVIDER = "Bing Translate";
|
||||||
const char* GET_API_KEY_FROM = "https://www.microsoft.com/en-us/translator/business/trial/#get-started";
|
const char* GET_API_KEY_FROM = "https://www.microsoft.com/en-us/translator/business/trial/#get-started";
|
||||||
@ -84,14 +84,14 @@ int tokenCount = 30, tokenRestoreDelay = 60000, maxSentenceSize = 1000;
|
|||||||
|
|
||||||
std::pair<bool, std::wstring> Translate(const std::wstring& text)
|
std::pair<bool, std::wstring> Translate(const std::wstring& text)
|
||||||
{
|
{
|
||||||
if (!apiKey->empty())
|
if (!authKey->empty())
|
||||||
if (HttpRequest httpRequest{
|
if (HttpRequest httpRequest{
|
||||||
L"Mozilla/5.0 Textractor",
|
L"Mozilla/5.0 Textractor",
|
||||||
L"api.cognitive.microsofttranslator.com",
|
L"api.cognitive.microsofttranslator.com",
|
||||||
L"POST",
|
L"POST",
|
||||||
FormatString(L"/translate?api-version=3.0&to=%s", translateTo.Copy()).c_str(),
|
FormatString(L"/translate?api-version=3.0&to=%s", translateTo.Copy()).c_str(),
|
||||||
FormatString(R"([{"text":"%s"}])", JSON::Escape(WideStringToString(text))),
|
FormatString(R"([{"text":"%s"}])", JSON::Escape(WideStringToString(text))),
|
||||||
FormatString(L"Content-Type: application/json; charset=UTF-8\r\nOcp-Apim-Subscription-Key:%s", apiKey.Copy()).c_str()
|
FormatString(L"Content-Type: application/json; charset=UTF-8\r\nOcp-Apim-Subscription-Key:%s", authKey.Copy()).c_str()
|
||||||
})
|
})
|
||||||
if (auto translation = Copy(JSON::Parse(httpRequest.response)[0][L"translations"][0][L"text"].String())) return { true, translation.value() };
|
if (auto translation = Copy(JSON::Parse(httpRequest.response)[0][L"translations"][0][L"text"].String())) return { true, translation.value() };
|
||||||
else return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, httpRequest.response) };
|
else return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, httpRequest.response) };
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
extern const wchar_t* TRANSLATION_ERROR;
|
extern const wchar_t* TRANSLATION_ERROR;
|
||||||
extern const char* USE_PREV_SENTENCE_CONTEXT;
|
extern const char* USE_PREV_SENTENCE_CONTEXT;
|
||||||
|
|
||||||
extern Synchronized<std::wstring> translateTo, apiKey;
|
extern Synchronized<std::wstring> translateTo, authKey;
|
||||||
|
|
||||||
const char* TRANSLATION_PROVIDER = "DeepL Translate";
|
const char* TRANSLATION_PROVIDER = "DeepL Translate";
|
||||||
const char* GET_API_KEY_FROM = "https://www.deepl.com/pro.html";
|
const char* GET_API_KEY_FROM = "https://www.deepl.com/pro.html";
|
||||||
@ -32,20 +32,20 @@ int keyType = CAT;
|
|||||||
|
|
||||||
std::pair<bool, std::wstring> Translate(const std::wstring& text)
|
std::pair<bool, std::wstring> Translate(const std::wstring& text)
|
||||||
{
|
{
|
||||||
if (!apiKey->empty())
|
if (!authKey->empty())
|
||||||
if (HttpRequest httpRequest{
|
if (HttpRequest httpRequest{
|
||||||
L"Mozilla/5.0 Textractor",
|
L"Mozilla/5.0 Textractor",
|
||||||
L"api.deepl.com",
|
L"api.deepl.com",
|
||||||
L"POST",
|
L"POST",
|
||||||
keyType == CAT ? L"/v1/translate" : L"/v2/translate",
|
keyType == CAT ? L"/v1/translate" : L"/v2/translate",
|
||||||
FormatString("text=%S&auth_key=%S&target_lang=%S", Escape(text), apiKey.Copy(), translateTo.Copy()),
|
FormatString("text=%S&auth_key=%S&target_lang=%S", Escape(text), authKey.Copy(), translateTo.Copy()),
|
||||||
L"Content-Type: application/x-www-form-urlencoded"
|
L"Content-Type: application/x-www-form-urlencoded"
|
||||||
}; httpRequest && (!httpRequest.response.empty() || (httpRequest = HttpRequest{
|
}; httpRequest && (!httpRequest.response.empty() || (httpRequest = HttpRequest{
|
||||||
L"Mozilla/5.0 Textractor",
|
L"Mozilla/5.0 Textractor",
|
||||||
L"api.deepl.com",
|
L"api.deepl.com",
|
||||||
L"POST",
|
L"POST",
|
||||||
(keyType = !keyType) == CAT ? L"/v1/translate" : L"/v2/translate",
|
(keyType = !keyType) == CAT ? L"/v1/translate" : L"/v2/translate",
|
||||||
FormatString("text=%S&auth_key=%S&target_lang=%S", Escape(text), apiKey.Copy(), translateTo.Copy()),
|
FormatString("text=%S&auth_key=%S&target_lang=%S", Escape(text), authKey.Copy(), translateTo.Copy()),
|
||||||
L"Content-Type: application/x-www-form-urlencoded"
|
L"Content-Type: application/x-www-form-urlencoded"
|
||||||
})))
|
})))
|
||||||
// Response formatted as JSON: translation starts with text":" and ends with "}]
|
// Response formatted as JSON: translation starts with text":" and ends with "}]
|
||||||
|
@ -54,10 +54,10 @@ BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved
|
|||||||
display->addRow(CHROME_LOCATION, chromePathEdit);
|
display->addRow(CHROME_LOCATION, chromePathEdit);
|
||||||
auto statusLabel = new QLabel("Stopped");
|
auto statusLabel = new QLabel("Stopped");
|
||||||
auto startButton = new QPushButton(START_DEVTOOLS), stopButton = new QPushButton(STOP_DEVTOOLS);
|
auto startButton = new QPushButton(START_DEVTOOLS), stopButton = new QPushButton(STOP_DEVTOOLS);
|
||||||
auto headlessCheckBox = new QCheckBox();
|
auto headlessCheck = new QCheckBox();
|
||||||
headlessCheckBox->setChecked(settings.value(HEADLESS_MODE, true).toBool());
|
headlessCheck->setChecked(settings.value(HEADLESS_MODE, true).toBool());
|
||||||
QObject::connect(headlessCheckBox, &QCheckBox::clicked, [](bool headless) { settings.setValue(HEADLESS_MODE, headless); });
|
QObject::connect(headlessCheck, &QCheckBox::clicked, [](bool headless) { settings.setValue(HEADLESS_MODE, headless); });
|
||||||
QObject::connect(startButton, &QPushButton::clicked, [statusLabel, chromePathEdit, headlessCheckBox] {
|
QObject::connect(startButton, &QPushButton::clicked, [statusLabel, chromePathEdit, headlessCheck] {
|
||||||
DevTools::Start(
|
DevTools::Start(
|
||||||
S(chromePathEdit->text()),
|
S(chromePathEdit->text()),
|
||||||
[statusLabel](QString status)
|
[statusLabel](QString status)
|
||||||
@ -82,14 +82,14 @@ BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved
|
|||||||
FormatString(LR"({"userAgent":"%s"})", userAgent->replace(userAgent->find(L"Headless"), 8, L"")));
|
FormatString(LR"({"userAgent":"%s"})", userAgent->replace(userAgent->find(L"Headless"), 8, L"")));
|
||||||
}).detach();
|
}).detach();
|
||||||
},
|
},
|
||||||
headlessCheckBox->isChecked()
|
headlessCheck->isChecked()
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
QObject::connect(stopButton, &QPushButton::clicked, &DevTools::Close);
|
QObject::connect(stopButton, &QPushButton::clicked, &DevTools::Close);
|
||||||
auto buttons = new QHBoxLayout();
|
auto buttons = new QHBoxLayout();
|
||||||
buttons->addWidget(startButton);
|
buttons->addWidget(startButton);
|
||||||
buttons->addWidget(stopButton);
|
buttons->addWidget(stopButton);
|
||||||
display->addRow(HEADLESS_MODE, headlessCheckBox);
|
display->addRow(HEADLESS_MODE, headlessCheck);
|
||||||
auto autoStartButton = new QCheckBox();
|
auto autoStartButton = new QCheckBox();
|
||||||
autoStartButton->setChecked(settings.value(AUTO_START, false).toBool());
|
autoStartButton->setChecked(settings.value(AUTO_START, false).toBool());
|
||||||
QObject::connect(autoStartButton, &QCheckBox::clicked, [](bool autoStart) { settings.setValue(AUTO_START, autoStart); });
|
QObject::connect(autoStartButton, &QCheckBox::clicked, [](bool autoStart) { settings.setValue(AUTO_START, autoStart); });
|
||||||
|
@ -55,14 +55,14 @@ struct PrettyWindow : QDialog
|
|||||||
settings.beginGroup(name);
|
settings.beginGroup(name);
|
||||||
QFont font = ui.display->font();
|
QFont font = ui.display->font();
|
||||||
if (font.fromString(settings.value(FONT, font.toString()).toString())) ui.display->setFont(font);
|
if (font.fromString(settings.value(FONT, font.toString()).toString())) ui.display->setFont(font);
|
||||||
setBgColor(settings.value(BG_COLOR, bgColor).value<QColor>());
|
SetBackgroundColor(settings.value(BG_COLOR, backgroundColor).value<QColor>());
|
||||||
setTextColor(settings.value(TEXT_COLOR, textColor()).value<QColor>());
|
SetTextColor(settings.value(TEXT_COLOR, TextColor()).value<QColor>());
|
||||||
outliner.color = settings.value(OUTLINE_COLOR, outliner.color).value<QColor>();
|
outliner.color = settings.value(OUTLINE_COLOR, outliner.color).value<QColor>();
|
||||||
outliner.size = settings.value(OUTLINE_SIZE, outliner.size).toDouble();
|
outliner.size = settings.value(OUTLINE_SIZE, outliner.size).toDouble();
|
||||||
menu.addAction(FONT, this, &PrettyWindow::RequestFont);
|
menu.addAction(FONT, this, &PrettyWindow::RequestFont);
|
||||||
menu.addAction(BG_COLOR, [this] { setBgColor(colorPrompt(this, bgColor, BG_COLOR)); });
|
menu.addAction(BG_COLOR, [this] { SetBackgroundColor(colorPrompt(this, backgroundColor, BG_COLOR)); });
|
||||||
menu.addAction(TEXT_COLOR, [this] { setTextColor(colorPrompt(this, textColor(), TEXT_COLOR)); });
|
menu.addAction(TEXT_COLOR, [this] { SetTextColor(colorPrompt(this, TextColor(), TEXT_COLOR)); });
|
||||||
QAction* outlineAction = menu.addAction(TEXT_OUTLINE, this, &PrettyWindow::setOutline);
|
QAction* outlineAction = menu.addAction(TEXT_OUTLINE, this, &PrettyWindow::SetOutline);
|
||||||
outlineAction->setCheckable(true);
|
outlineAction->setCheckable(true);
|
||||||
outlineAction->setChecked(outliner.size >= 0);
|
outlineAction->setChecked(outliner.size >= 0);
|
||||||
connect(ui.display, &QLabel::customContextMenuRequested, [this](QPoint point) { menu.exec(mapToGlobal(point)); });
|
connect(ui.display, &QLabel::customContextMenuRequested, [this](QPoint point) { menu.exec(mapToGlobal(point)); });
|
||||||
@ -89,28 +89,28 @@ private:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void setBgColor(QColor color)
|
void SetBackgroundColor(QColor color)
|
||||||
{
|
{
|
||||||
if (!color.isValid()) return;
|
if (!color.isValid()) return;
|
||||||
if (color.alpha() == 0) color.setAlpha(1);
|
if (color.alpha() == 0) color.setAlpha(1);
|
||||||
bgColor = color;
|
backgroundColor = color;
|
||||||
repaint();
|
repaint();
|
||||||
settings.setValue(BG_COLOR, color.name(QColor::HexArgb));
|
settings.setValue(BG_COLOR, color.name(QColor::HexArgb));
|
||||||
};
|
};
|
||||||
|
|
||||||
QColor textColor()
|
QColor TextColor()
|
||||||
{
|
{
|
||||||
return ui.display->palette().color(QPalette::WindowText);
|
return ui.display->palette().color(QPalette::WindowText);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setTextColor(QColor color)
|
void SetTextColor(QColor color)
|
||||||
{
|
{
|
||||||
if (!color.isValid()) return;
|
if (!color.isValid()) return;
|
||||||
ui.display->setPalette(QPalette(color, {}, {}, {}, {}, {}, {}));
|
ui.display->setPalette(QPalette(color, {}, {}, {}, {}, {}, {}));
|
||||||
settings.setValue(TEXT_COLOR, color.name(QColor::HexArgb));
|
settings.setValue(TEXT_COLOR, color.name(QColor::HexArgb));
|
||||||
};
|
};
|
||||||
|
|
||||||
void setOutline(bool enable)
|
void SetOutline(bool enable)
|
||||||
{
|
{
|
||||||
if (enable)
|
if (enable)
|
||||||
{
|
{
|
||||||
@ -125,10 +125,10 @@ private:
|
|||||||
|
|
||||||
void paintEvent(QPaintEvent*) override
|
void paintEvent(QPaintEvent*) override
|
||||||
{
|
{
|
||||||
QPainter(this).fillRect(rect(), bgColor);
|
QPainter(this).fillRect(rect(), backgroundColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
QColor bgColor{ palette().window().color() };
|
QColor backgroundColor{ palette().window().color() };
|
||||||
struct : QGraphicsEffect
|
struct : QGraphicsEffect
|
||||||
{
|
{
|
||||||
void draw(QPainter* painter) override
|
void draw(QPainter* painter) override
|
||||||
@ -163,10 +163,10 @@ public:
|
|||||||
maxSentenceSize = settings.value(MAX_SENTENCE_SIZE, maxSentenceSize).toInt();
|
maxSentenceSize = settings.value(MAX_SENTENCE_SIZE, maxSentenceSize).toInt();
|
||||||
|
|
||||||
for (auto [name, default, slot] : Array<const char*, bool, void(ExtraWindow::*)(bool)>{
|
for (auto [name, default, slot] : Array<const char*, bool, void(ExtraWindow::*)(bool)>{
|
||||||
{ TOPMOST, false, &ExtraWindow::setTopmost },
|
{ TOPMOST, false, &ExtraWindow::SetTopmost },
|
||||||
{ SIZE_LOCK, false, &ExtraWindow::setLock },
|
{ SIZE_LOCK, false, &ExtraWindow::SetLock },
|
||||||
{ SHOW_ORIGINAL, true, &ExtraWindow::setShowOriginal },
|
{ SHOW_ORIGINAL, true, &ExtraWindow::SetShowOriginal },
|
||||||
{ DICTIONARY, false, &ExtraWindow::setUseDictionary },
|
{ DICTIONARY, false, &ExtraWindow::SetUseDictionary },
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
// delay processing anything until Textractor has finished initializing
|
// delay processing anything until Textractor has finished initializing
|
||||||
@ -206,26 +206,26 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setTopmost(bool topmost)
|
void SetTopmost(bool topmost)
|
||||||
{
|
{
|
||||||
for (auto window : { winId(), dictionaryWindow.winId() })
|
for (auto window : { winId(), dictionaryWindow.winId() })
|
||||||
SetWindowPos((HWND)window, topmost ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
SetWindowPos((HWND)window, topmost ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
||||||
settings.setValue(TOPMOST, topmost);
|
settings.setValue(TOPMOST, topmost);
|
||||||
};
|
};
|
||||||
|
|
||||||
void setLock(bool locked)
|
void SetLock(bool locked)
|
||||||
{
|
{
|
||||||
setSizeGripEnabled(!locked);
|
setSizeGripEnabled(!locked);
|
||||||
settings.setValue(SIZE_LOCK, this->locked = locked);
|
settings.setValue(SIZE_LOCK, this->locked = locked);
|
||||||
};
|
};
|
||||||
|
|
||||||
void setShowOriginal(bool showOriginal)
|
void SetShowOriginal(bool showOriginal)
|
||||||
{
|
{
|
||||||
if (!showOriginal && settings.value(SHOW_ORIGINAL, false).toBool()) QMessageBox::information(this, SHOW_ORIGINAL, SHOW_ORIGINAL_INFO);
|
if (!showOriginal && settings.value(SHOW_ORIGINAL, false).toBool()) QMessageBox::information(this, SHOW_ORIGINAL, SHOW_ORIGINAL_INFO);
|
||||||
settings.setValue(SHOW_ORIGINAL, this->showOriginal = showOriginal);
|
settings.setValue(SHOW_ORIGINAL, this->showOriginal = showOriginal);
|
||||||
};
|
};
|
||||||
|
|
||||||
void setUseDictionary(bool useDictionary)
|
void SetUseDictionary(bool useDictionary)
|
||||||
{
|
{
|
||||||
if (useDictionary)
|
if (useDictionary)
|
||||||
{
|
{
|
||||||
@ -239,11 +239,11 @@ private:
|
|||||||
settings.setValue(DICTIONARY, this->useDictionary = useDictionary);
|
settings.setValue(DICTIONARY, this->useDictionary = useDictionary);
|
||||||
}
|
}
|
||||||
|
|
||||||
void computeDictionaryPosition(QPoint mouse)
|
void ComputeDictionaryPosition(QPoint mouse)
|
||||||
{
|
{
|
||||||
QString sentence = ui.display->text();
|
QString sentence = ui.display->text();
|
||||||
const QFont& font = ui.display->font();
|
const QFont& font = ui.display->font();
|
||||||
if (cachedDisplayInfo.compareExchange(ui.display))
|
if (cachedDisplayInfo.CompareExchange(ui.display))
|
||||||
{
|
{
|
||||||
QFontMetrics fontMetrics(font, ui.display);
|
QFontMetrics fontMetrics(font, ui.display);
|
||||||
textPositionMap.clear();
|
textPositionMap.clear();
|
||||||
@ -269,7 +269,7 @@ private:
|
|||||||
if (i == textPositionMap.size() || (mouse - textPositionMap[i]).manhattanLength() > font.pointSize() * 3) return dictionaryWindow.hide();
|
if (i == textPositionMap.size() || (mouse - textPositionMap[i]).manhattanLength() > font.pointSize() * 3) return dictionaryWindow.hide();
|
||||||
if (sentence.mid(i) == dictionaryWindow.term) return dictionaryWindow.ShowDefinition();
|
if (sentence.mid(i) == dictionaryWindow.term) return dictionaryWindow.ShowDefinition();
|
||||||
dictionaryWindow.ui.display->setFixedWidth(ui.display->width() * 3 / 4);
|
dictionaryWindow.ui.display->setFixedWidth(ui.display->width() * 3 / 4);
|
||||||
dictionaryWindow.setTerm(sentence.mid(i));
|
dictionaryWindow.SetTerm(sentence.mid(i));
|
||||||
int left = i == 0 ? 0 : textPositionMap[i - 1].x(), right = textPositionMap[i].x(),
|
int left = i == 0 ? 0 : textPositionMap[i - 1].x(), right = textPositionMap[i].x(),
|
||||||
x = textPositionMap[i].x() > ui.display->width() / 2 ? -dictionaryWindow.width() + (right * 3 + left) / 4 : (left * 3 + right) / 4, y = 0;
|
x = textPositionMap[i].x() > ui.display->width() / 2 ? -dictionaryWindow.width() + (right * 3 + left) / 4 : (left * 3 + right) / 4, y = 0;
|
||||||
for (auto point : textPositionMap) if (point.y() > y && point.y() < textPositionMap[i].y()) y = point.y();
|
for (auto point : textPositionMap) if (point.y() > y && point.y() < textPositionMap[i].y()) y = point.y();
|
||||||
@ -278,7 +278,7 @@ private:
|
|||||||
|
|
||||||
bool eventFilter(QObject*, QEvent* event) override
|
bool eventFilter(QObject*, QEvent* event) override
|
||||||
{
|
{
|
||||||
if (useDictionary && event->type() == QEvent::MouseMove) computeDictionaryPosition(((QMouseEvent*)event)->localPos().toPoint());
|
if (useDictionary && event->type() == QEvent::MouseMove) ComputeDictionaryPosition(((QMouseEvent*)event)->localPos().toPoint());
|
||||||
if (event->type() == QEvent::MouseButtonPress) dictionaryWindow.hide();
|
if (event->type() == QEvent::MouseButtonPress) dictionaryWindow.hide();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -309,7 +309,7 @@ private:
|
|||||||
class
|
class
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
bool compareExchange(QLabel* display)
|
bool CompareExchange(QLabel* display)
|
||||||
{
|
{
|
||||||
if (display->text() == text && display->font() == font && display->width() == width) return false;
|
if (display->text() == text && display->font() == font && display->width() == width) return false;
|
||||||
text = display->text();
|
text = display->text();
|
||||||
@ -389,7 +389,7 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void setTerm(QString term)
|
void SetTerm(QString term)
|
||||||
{
|
{
|
||||||
this->term = term;
|
this->term = term;
|
||||||
UpdateDictionary();
|
UpdateDictionary();
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
extern const wchar_t* TRANSLATION_ERROR;
|
extern const wchar_t* TRANSLATION_ERROR;
|
||||||
|
|
||||||
extern Synchronized<std::wstring> translateTo, apiKey;
|
extern Synchronized<std::wstring> translateTo, authKey;
|
||||||
|
|
||||||
const char* TRANSLATION_PROVIDER = "Google Translate";
|
const char* TRANSLATION_PROVIDER = "Google Translate";
|
||||||
const char* GET_API_KEY_FROM = "https://codelabs.developers.google.com/codelabs/cloud-translation-intro";
|
const char* GET_API_KEY_FROM = "https://codelabs.developers.google.com/codelabs/cloud-translation-intro";
|
||||||
@ -126,12 +126,12 @@ int tokenCount = 30, tokenRestoreDelay = 60000, maxSentenceSize = 1000;
|
|||||||
|
|
||||||
std::pair<bool, std::wstring> Translate(const std::wstring& text)
|
std::pair<bool, std::wstring> Translate(const std::wstring& text)
|
||||||
{
|
{
|
||||||
if (!apiKey->empty())
|
if (!authKey->empty())
|
||||||
if (HttpRequest httpRequest{
|
if (HttpRequest httpRequest{
|
||||||
L"Mozilla/5.0 Textractor",
|
L"Mozilla/5.0 Textractor",
|
||||||
L"translation.googleapis.com",
|
L"translation.googleapis.com",
|
||||||
L"POST",
|
L"POST",
|
||||||
FormatString(L"/language/translate/v2?format=text&target=%s&key=%s", translateTo.Copy(), apiKey.Copy()).c_str(),
|
FormatString(L"/language/translate/v2?format=text&target=%s&key=%s", translateTo.Copy(), authKey.Copy()).c_str(),
|
||||||
FormatString(R"({"q":["%s"]})", JSON::Escape(WideStringToString(text)))
|
FormatString(R"({"q":["%s"]})", JSON::Escape(WideStringToString(text)))
|
||||||
})
|
})
|
||||||
if (auto translation = Copy(JSON::Parse(httpRequest.response)[L"data"][L"translations"][0][L"translatedText"].String())) return { true, translation.value() };
|
if (auto translation = Copy(JSON::Parse(httpRequest.response)[L"data"][L"translations"][0][L"translatedText"].String())) return { true, translation.value() };
|
||||||
|
@ -12,6 +12,7 @@ extern const char* CURRENT_FILTER;
|
|||||||
const char* REGEX_SAVE_FILE = "SavedRegexFilters.txt";
|
const char* REGEX_SAVE_FILE = "SavedRegexFilters.txt";
|
||||||
|
|
||||||
std::optional<std::wregex> regex;
|
std::optional<std::wregex> regex;
|
||||||
|
std::wstring replace;
|
||||||
std::shared_mutex m;
|
std::shared_mutex m;
|
||||||
DWORD (*GetSelectedProcessId)() = nullptr;
|
DWORD (*GetSelectedProcessId)() = nullptr;
|
||||||
|
|
||||||
@ -24,16 +25,17 @@ public:
|
|||||||
Localize();
|
Localize();
|
||||||
ui.setupUi(this);
|
ui.setupUi(this);
|
||||||
|
|
||||||
connect(ui.input, &QLineEdit::textEdited, this, &Window::setRegex);
|
connect(ui.regexEdit, &QLineEdit::textEdited, this, &Window::SetRegex);
|
||||||
connect(ui.save, &QPushButton::clicked, this, &Window::saveRegex);
|
connect(ui.replaceEdit, &QLineEdit::textEdited, this, &Window::SetReplace);
|
||||||
|
connect(ui.saveButton, &QPushButton::clicked, this, &Window::Save);
|
||||||
|
|
||||||
setWindowTitle(REGEX_FILTER);
|
setWindowTitle(REGEX_FILTER);
|
||||||
QMetaObject::invokeMethod(this, &QWidget::show, Qt::QueuedConnection);
|
QMetaObject::invokeMethod(this, &QWidget::show, Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setRegex(QString regex)
|
void SetRegex(QString regex)
|
||||||
{
|
{
|
||||||
ui.input->setText(regex);
|
ui.regexEdit->setText(regex);
|
||||||
std::scoped_lock lock(m);
|
std::scoped_lock lock(m);
|
||||||
if (!regex.isEmpty()) try { ::regex = S(regex); }
|
if (!regex.isEmpty()) try { ::regex = S(regex); }
|
||||||
catch (std::regex_error) { return ui.output->setText(INVALID_REGEX); }
|
catch (std::regex_error) { return ui.output->setText(INVALID_REGEX); }
|
||||||
@ -41,13 +43,21 @@ public:
|
|||||||
ui.output->setText(QString(CURRENT_FILTER).arg(regex));
|
ui.output->setText(QString(CURRENT_FILTER).arg(regex));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SetReplace(QString replace)
|
||||||
|
{
|
||||||
|
ui.replaceEdit->setText(replace);
|
||||||
|
std::scoped_lock lock(m);
|
||||||
|
::replace = S(replace);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void saveRegex()
|
void Save()
|
||||||
{
|
{
|
||||||
auto formatted = FormatString(
|
auto formatted = FormatString(
|
||||||
L"\xfeff|PROCESS|%s|FILTER|%s|END|\r\n",
|
L"\xfeff|PROCESS|%s|FILTER|%s|REPLACE|%s|END|\r\n",
|
||||||
GetModuleFilename(GetSelectedProcessId()).value_or(FormatString(L"Error getting name of process 0x%X", GetSelectedProcessId())),
|
GetModuleFilename(GetSelectedProcessId()).value_or(FormatString(L"Error getting name of process 0x%X", GetSelectedProcessId())),
|
||||||
S(ui.input->text())
|
S(ui.regexEdit->text()),
|
||||||
|
S(ui.replaceEdit->text())
|
||||||
);
|
);
|
||||||
std::ofstream(REGEX_SAVE_FILE, std::ios::binary | std::ios::app).write((const char*)formatted.c_str(), formatted.size() * sizeof(wchar_t));
|
std::ofstream(REGEX_SAVE_FILE, std::ios::binary | std::ios::app).write((const char*)formatted.c_str(), formatted.size() * sizeof(wchar_t));
|
||||||
}
|
}
|
||||||
@ -62,12 +72,16 @@ bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
|
|||||||
if (sentenceInfo["current select"] && !regex) if (auto processName = GetModuleFilename(sentenceInfo["process id"]))
|
if (sentenceInfo["current select"] && !regex) if (auto processName = GetModuleFilename(sentenceInfo["process id"]))
|
||||||
{
|
{
|
||||||
std::ifstream stream(REGEX_SAVE_FILE, std::ios::binary);
|
std::ifstream stream(REGEX_SAVE_FILE, std::ios::binary);
|
||||||
BlockMarkupIterator savedFilters(stream, Array<std::wstring_view>{ L"|PROCESS|", L"|FILTER|" });
|
BlockMarkupIterator savedFilters(stream, Array<std::wstring_view>{ L"|PROCESS|", L"|FILTER|", L"|REPLACE|" });
|
||||||
std::vector<std::wstring> regexes;
|
std::vector<std::array<std::wstring, 3>> regexes;
|
||||||
while (auto read = savedFilters.Next()) if (read->at(0) == processName) regexes.push_back(std::move(read->at(1)));
|
while (auto read = savedFilters.Next()) if (read->at(0) == processName) regexes.push_back(std::move(read.value()));
|
||||||
if (!regexes.empty()) QMetaObject::invokeMethod(&window, [regex = S(regexes.back())] { window.setRegex(regex); }, Qt::BlockingQueuedConnection);
|
if (!regexes.empty()) QMetaObject::invokeMethod(&window, [regex = S(regexes.back()[1]), replace = S(regexes.back()[2])]
|
||||||
|
{
|
||||||
|
window.SetRegex(regex);
|
||||||
|
window.SetReplace(replace);
|
||||||
|
}, Qt::BlockingQueuedConnection);
|
||||||
}
|
}
|
||||||
std::shared_lock l(m);
|
std::shared_lock l(m);
|
||||||
if (regex) sentence = std::regex_replace(sentence, regex.value(), L"");
|
if (regex) sentence = std::regex_replace(sentence, regex.value(), replace);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -6,13 +6,27 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>350</width>
|
<width>500</width>
|
||||||
<height>105</height>
|
<height>107</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout">
|
<layout class="QVBoxLayout">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLineEdit" name="input"/>
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="regexEdit"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>=></string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="replaceEdit"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="output">
|
<widget class="QLabel" name="output">
|
||||||
@ -25,7 +39,7 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="save">
|
<widget class="QPushButton" name="saveButton">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Save</string>
|
<string>Save</string>
|
||||||
</property>
|
</property>
|
||||||
|
@ -30,7 +30,7 @@ const std::string TRANSLATION_CACHE_FILE = FormatString("%s Translation Cache.tx
|
|||||||
|
|
||||||
QFormLayout* display;
|
QFormLayout* display;
|
||||||
Settings settings;
|
Settings settings;
|
||||||
Synchronized<std::wstring> translateTo = L"en", apiKey;
|
Synchronized<std::wstring> translateTo = L"en", authKey;
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@ -57,16 +57,16 @@ public:
|
|||||||
|
|
||||||
settings.beginGroup(TRANSLATION_PROVIDER);
|
settings.beginGroup(TRANSLATION_PROVIDER);
|
||||||
|
|
||||||
auto languageBox = new QComboBox(this);
|
auto languageCombo = new QComboBox(this);
|
||||||
languageBox->addItems(languages);
|
languageCombo->addItems(languages);
|
||||||
int language = -1;
|
int language = -1;
|
||||||
if (settings.contains(LANGUAGE)) language = languageBox->findText(settings.value(LANGUAGE).toString(), Qt::MatchEndsWith);
|
if (settings.contains(LANGUAGE)) language = languageCombo->findText(settings.value(LANGUAGE).toString(), Qt::MatchEndsWith);
|
||||||
if (language < 0) language = languageBox->findText(NATIVE_LANGUAGE, Qt::MatchStartsWith);
|
if (language < 0) language = languageCombo->findText(NATIVE_LANGUAGE, Qt::MatchStartsWith);
|
||||||
if (language < 0) language = languageBox->findText("English", Qt::MatchStartsWith);
|
if (language < 0) language = languageCombo->findText("English", Qt::MatchStartsWith);
|
||||||
languageBox->setCurrentIndex(language);
|
languageCombo->setCurrentIndex(language);
|
||||||
saveLanguage(languageBox->currentText());
|
saveLanguage(languageCombo->currentText());
|
||||||
display->addRow(TRANSLATE_TO, languageBox);
|
display->addRow(TRANSLATE_TO, languageCombo);
|
||||||
connect(languageBox, &QComboBox::currentTextChanged, this, &Window::saveLanguage);
|
connect(languageCombo, &QComboBox::currentTextChanged, this, &Window::saveLanguage);
|
||||||
for (auto [value, label] : Array<bool&, const char*>{
|
for (auto [value, label] : Array<bool&, const char*>{
|
||||||
{ translateSelectedOnly, TRANSLATE_SELECTED_THREAD_ONLY },
|
{ translateSelectedOnly, TRANSLATE_SELECTED_THREAD_ONLY },
|
||||||
{ rateLimitAll, RATE_LIMIT_ALL_THREADS },
|
{ rateLimitAll, RATE_LIMIT_ALL_THREADS },
|
||||||
@ -95,12 +95,12 @@ public:
|
|||||||
}
|
}
|
||||||
if (GET_API_KEY_FROM)
|
if (GET_API_KEY_FROM)
|
||||||
{
|
{
|
||||||
auto keyInput = new QLineEdit(settings.value(API_KEY).toString(), this);
|
auto keyEdit = new QLineEdit(settings.value(API_KEY).toString(), this);
|
||||||
apiKey->assign(S(keyInput->text()));
|
authKey->assign(S(keyEdit->text()));
|
||||||
QObject::connect(keyInput, &QLineEdit::textChanged, [](QString key) { settings.setValue(API_KEY, S(apiKey->assign(S(key)))); });
|
QObject::connect(keyEdit, &QLineEdit::textChanged, [](QString key) { settings.setValue(API_KEY, S(authKey->assign(S(key)))); });
|
||||||
auto keyLabel = new QLabel(QString("<a href=\"%1\">%2</a>").arg(GET_API_KEY_FROM, API_KEY), this);
|
auto keyLabel = new QLabel(QString("<a href=\"%1\">%2</a>").arg(GET_API_KEY_FROM, API_KEY), this);
|
||||||
keyLabel->setOpenExternalLinks(true);
|
keyLabel->setOpenExternalLinks(true);
|
||||||
display->addRow(keyLabel, keyInput);
|
display->addRow(keyLabel, keyEdit);
|
||||||
}
|
}
|
||||||
|
|
||||||
setWindowTitle(TRANSLATION_PROVIDER);
|
setWindowTitle(TRANSLATION_PROVIDER);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user