/**************************************************************************** ** ** Copyright (C) 2020 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "qmldebugtranslationwidget.h" #include "qmlpreviewruncontrol.h" #include "qmlpreviewplugin.h" #include "projectfileselectionswidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { QObject *getPreviewPlugin() { auto pluginIt = std::find_if(ExtensionSystem::PluginManager::plugins().begin(), ExtensionSystem::PluginManager::plugins().end(), [](const ExtensionSystem::PluginSpec *p) { return p->name() == "QmlPreview"; }); if (pluginIt != ExtensionSystem::PluginManager::plugins().constEnd()) return (*pluginIt)->plugin(); return nullptr; } } namespace QmlPreview { QmlDebugTranslationWidget::QmlDebugTranslationWidget(QWidget *parent, TestLanguageGetter languagesGetterMethod) : QWidget(parent) , m_testLanguagesGetter(languagesGetterMethod) , m_warningColor(Qt::red) //, m_foundTrColor(Qt::green) // invalid color -> init without the frame , m_lastWarningColor(m_warningColor) , m_lastfoundTrColor(Qt::green) { auto mainLayout = new QVBoxLayout(this); auto buttonGroup = new QButtonGroup(this); // it gets the text from updateCurrentEditor method m_singleFileButton = new QRadioButton(); m_singleFileButton->setChecked(true); buttonGroup->addButton(m_singleFileButton); const QString projectSettingsKey = "QmlPreview.DisabledDebugTranslationFiles"; const ProjectExplorer::FileType filterFileType = ProjectExplorer::FileType::QML; m_checkableProjectFileView = new ProjectFileSelectionsWidget(projectSettingsKey, filterFileType); m_checkableProjectFileView->setVisible(false); connect(m_checkableProjectFileView, &ProjectFileSelectionsWidget::selectionChanged, this, &QmlDebugTranslationWidget::setFiles); m_multipleFileButton = new QRadioButton(tr("multiple files")); buttonGroup->addButton(m_multipleFileButton); connect(m_multipleFileButton, &QAbstractButton::toggled, m_checkableProjectFileView, &QWidget::setVisible); connect(m_multipleFileButton, &QAbstractButton::toggled, this, &QmlDebugTranslationWidget::updateFiles); mainLayout->addWidget(m_singleFileButton); mainLayout->addWidget(m_multipleFileButton); mainLayout->addWidget(m_checkableProjectFileView); // language checkboxes are add in updateAvailableTranslations method m_selectLanguageLayout = new QHBoxLayout; mainLayout->addLayout(m_selectLanguageLayout); auto settingsLayout = new QHBoxLayout(); mainLayout->addLayout(settingsLayout); auto elideWarningCheckBox = new QCheckBox(tr("Enable elide warning")); connect(elideWarningCheckBox, &QCheckBox::stateChanged, [this] (int state) { m_elideWarning = (state == Qt::Checked); }); settingsLayout->addWidget(elideWarningCheckBox); auto warningColorCheckbox = new QCheckBox(tr("select Warning color: ")); settingsLayout->addWidget(warningColorCheckbox); auto warningColorButton = new Utils::QtColorButton(); connect(warningColorCheckbox, &QCheckBox::stateChanged, [warningColorButton, this] (int state) { if (state == Qt::Checked) { warningColorButton->setColor(m_lastWarningColor); warningColorButton->setEnabled(true); } else { m_lastWarningColor = warningColorButton->color(); warningColorButton->setColor({}); warningColorButton->setEnabled(false); } }); connect(warningColorButton, &Utils::QtColorButton::colorChanged, [this](const QColor &color) { m_warningColor = color; }); warningColorCheckbox->setCheckState(Qt::Checked); settingsLayout->addWidget(warningColorButton); auto foundTrColorCheckbox = new QCheckBox(tr("select found 'tr' color: ")); settingsLayout->addWidget(foundTrColorCheckbox); auto foundTrColorButton = new Utils::QtColorButton(); foundTrColorButton->setDisabled(true); connect(foundTrColorCheckbox, &QCheckBox::stateChanged, [foundTrColorButton, this] (int state) { if (state == Qt::Checked) { foundTrColorButton->setColor(m_lastfoundTrColor); foundTrColorButton->setEnabled(true); } else { m_lastfoundTrColor = foundTrColorButton->color(); foundTrColorButton->setColor({}); foundTrColorButton->setEnabled(false); } }); connect(foundTrColorButton, &Utils::QtColorButton::colorChanged, [this](const QColor &color) { m_foundTrColor = color; }); settingsLayout->addWidget(foundTrColorButton); settingsLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding)); auto controlLayout = new QHBoxLayout; mainLayout->addLayout(controlLayout); auto showLogButton = new QToolButton; showLogButton->setText(tr("Show log")); showLogButton->setCheckable(true); controlLayout->addWidget(showLogButton); // TODO: do we still need this buttons? // auto pauseButton = new QToolButton; // pauseButton->setText(tr("Pause")); // pauseButton->setCheckable(true); // controlLayout->addWidget(pauseButton); // auto onTheFlyButton = new QToolButton; // onTheFlyButton->setText(tr("On the fly")); // controlLayout->addWidget(onTheFlyButton); m_runTestButton = new QPushButton(); m_runTestButton->setCheckable(true); m_runTestButton->setText(runButtonText()); connect(m_runTestButton, &QPushButton::toggled, [this](bool checked) { m_runTestButton->setText(runButtonText(checked)); }); connect(m_runTestButton, &QPushButton::clicked, [this](bool checked) { if (checked) runTest(); else { if (m_currentRunControl) m_currentRunControl->initiateStop(); // TODO: what happens if we already have a preview running? // QmlPreviewPlugin::stopAllRunControls(); // qWarning() << "not implemented"; // TODO: stop still running tests } }); controlLayout->addWidget(m_runTestButton); m_runOutputWindow = new Core::OutputWindow(Core::Context("QmlPreview.DebugTranslation"), "QmlPreview/OutputWindow/Zoom"); m_runOutputWindow->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_runOutputWindow->setReadOnly(true); m_runOutputWindow->setVisible(false); mainLayout->addWidget(m_runOutputWindow); QSpacerItem *endSpacerItem = new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Expanding); mainLayout->addItem(endSpacerItem); connect(showLogButton, &QToolButton::toggled, m_runOutputWindow, [this, mainLayout, endSpacerItem](bool checked) { m_runOutputWindow->setVisible(checked); if (m_runOutputWindow->isVisible()) mainLayout->takeAt(mainLayout->count() - 1); else mainLayout->addItem(endSpacerItem); }); auto loadLogButton = new QToolButton; loadLogButton->setText(tr("Load")); controlLayout->addWidget(loadLogButton); connect(loadLogButton, &QToolButton::clicked, this, &QmlDebugTranslationWidget::loadLogFile); auto saveLogButton = new QToolButton; saveLogButton->setText(tr("Save")); controlLayout->addWidget(saveLogButton); connect(saveLogButton, &QToolButton::clicked, this, &QmlDebugTranslationWidget::saveLogToFile); auto clearButton = new QToolButton; clearButton->setText(tr("Clear")); controlLayout->addWidget(clearButton); connect(clearButton, &QToolButton::clicked, this, &QmlDebugTranslationWidget::clear); Core::EditorManager *editorManager = Core::EditorManager::instance(); connect(editorManager, &Core::EditorManager::currentEditorChanged, this, &QmlDebugTranslationWidget::updateCurrentEditor); updateCurrentEditor(Core::EditorManager::currentEditor()); connect(ProjectExplorer::SessionManager::instance(), &ProjectExplorer::SessionManager::startupProjectChanged, this, &QmlDebugTranslationWidget::updateCurrentTranslations); updateStartupProjectTranslations(); ProjectExplorer::TaskHub::addCategory("QmlPreview.Translation", tr("Translation issues")); } QmlDebugTranslationWidget::~QmlDebugTranslationWidget() { } void QmlDebugTranslationWidget::updateCurrentEditor(const Core::IEditor *editor) { if (editor && editor->document()) m_currentFilePath = editor->document()->filePath(); else m_currentFilePath.clear(); m_singleFileButton->setText(singleFileButtonText(m_currentFilePath.toString())); updateFiles(); } void QmlDebugTranslationWidget::updateStartupProjectTranslations() { updateCurrentTranslations(ProjectExplorer::SessionManager::startupProject()); } QColor QmlDebugTranslationWidget::warningColor() { return m_warningColor; } QColor QmlDebugTranslationWidget::foundTrColor() { return m_foundTrColor; } void QmlDebugTranslationWidget::updateCurrentTranslations(ProjectExplorer::Project *project) { m_testLanguages.clear(); for (int i = m_selectLanguageLayout->count()-1; i >= 0; --i) { auto layoutItem = m_selectLanguageLayout->takeAt(i); delete layoutItem->widget(); delete layoutItem; } if (!project) return; if (auto multiLanguageAspect = QmlProjectManager::QmlMultiLanguageAspect::current(project)) { connect(multiLanguageAspect, &QmlProjectManager::QmlMultiLanguageAspect::changed, this, &QmlDebugTranslationWidget::updateStartupProjectTranslations, Qt::UniqueConnection); auto languageLabel = new QLabel(); languageLabel->setText(tr("Select which language should be tested:")); m_selectLanguageLayout->addWidget(languageLabel); if (multiLanguageAspect->value()) { addLanguageCheckBoxes({multiLanguageAspect->currentLocale()}); if (m_testLanguagesGetter) { auto addTestLanguages = new QPushButton(tr("Add Test Languages")); m_selectLanguageLayout->addWidget(addTestLanguages); connect(addTestLanguages, &QPushButton::clicked, [this]() { addLanguageCheckBoxes(m_testLanguagesGetter()); }); } } else { QString errorMessage; addLanguageCheckBoxes(project->availableQmlPreviewTranslations(&errorMessage)); } m_selectLanguageLayout->addItem(new QSpacerItem(20, 20, QSizePolicy::Expanding, QSizePolicy::Minimum)); } } void QmlDebugTranslationWidget::updateFiles() { if (m_multipleFileButton->isChecked()) setFiles(m_checkableProjectFileView->checkedFiles()); else setFiles({m_currentFilePath}); } void QmlDebugTranslationWidget::setFiles(const Utils::FilePaths &filePathes) { m_selectedFilePaths = filePathes; } void QmlDebugTranslationWidget::runTest() { m_runOutputWindow->grayOutOldContent(); auto runControl = new ProjectExplorer::RunControl(ProjectExplorer::Constants::QML_PREVIEW_RUN_MODE); QTC_ASSERT(runControl, qWarning("Can not create a QmlPreviewRunner"); return;); auto previewPlugin = qobject_cast(getPreviewPlugin()); connect(runControl, &ProjectExplorer::RunControl::started, [this, runControl, previewPlugin]() { //Q_ASSERT(m_currentRunControl == nullptr); //TODO: who deletes the runcontrol m_currentRunControl = runControl; m_runOutputWindow->setLineParsers( ProjectExplorer::OutputFormatterFactory::createFormatters(runControl->target())); int timerCounter = 1; const auto testLanguageList = m_testLanguages; if (m_elideWarning) previewPlugin->changeElideWarning(true); auto testLanguages = [previewPlugin, runControl, testLanguageList](int timerCounter, const QString &previewedFile) { for (auto language : testLanguageList) { QTimer::singleShot(timerCounter * 1000, previewPlugin, [previewPlugin, runControl, language, previewedFile]() { if (runControl && runControl->isRunning()) { if (!previewedFile.isEmpty()) previewPlugin->setPreviewedFile(previewedFile); previewPlugin->setLocaleIsoCode(language); } }); } }; for (auto filePath : m_selectedFilePaths) { testLanguages(timerCounter++, filePath.toString()); } }); connect(runControl, &ProjectExplorer::RunControl::stopped, [this]() { m_runTestButton->setChecked(false); //delete m_currentRunControl; // who deletes the runcontrol? m_currentRunControl = nullptr; if (auto previewPlugin = qobject_cast(getPreviewPlugin())) previewPlugin->setLocaleIsoCode(m_lastUsedLanguageBeforeTest); }); connect(runControl, &ProjectExplorer::RunControl::appendMessage, this, &QmlDebugTranslationWidget::appendMessage); if (auto project = ProjectExplorer::SessionManager::startupProject()) { if (auto target = project->activeTarget()) { if (auto multiLanguageAspect = QmlProjectManager::QmlMultiLanguageAspect::current(target)) m_lastUsedLanguageBeforeTest = multiLanguageAspect->currentLocale(); if (auto runConfiguration = target->activeRunConfiguration()) { runControl->setRunConfiguration(runConfiguration); if (runControl->createMainWorker()) { previewPlugin->setLocaleIsoCode(QString()); runControl->initiateStart(); } } } } } void QmlDebugTranslationWidget::clear() { m_runOutputWindow->clear(); ProjectExplorer::TaskHub::clearTasks("QmlPreview.Translation"); } QString QmlDebugTranslationWidget::currentDir() const { return m_lastDir.isEmpty() ? ProjectExplorer::ProjectTree::currentFilePath().parentDir().toString() : m_lastDir; } void QmlDebugTranslationWidget::setCurrentDir(const QString &path) { m_lastDir = path; const QString currentDir = m_lastDir.isEmpty() ? ProjectExplorer::ProjectTree::currentFilePath().parentDir().toString() : m_lastDir; } void QmlDebugTranslationWidget::loadLogFile() { const auto fileName = QFileDialog::getOpenFileName(this, QStringLiteral("Open File"), currentDir()); if (!fileName.isEmpty()) { setCurrentDir(QFileInfo(fileName).absolutePath()); QFile f(fileName); if (f.open(QFile::ReadOnly)) { clear(); while (!f.atEnd()) appendMessage(QString::fromUtf8(f.readLine()), Utils::DebugFormat); } else { // TODO: maybe add this message to log and tasks qWarning() << "Failed to open" << fileName << ":" << f.errorString(); } } } void QmlDebugTranslationWidget::saveLogToFile() { const QString fileName = QFileDialog::getSaveFileName( this, tr("Choose file to save logged issues."), currentDir()); if (!fileName.isEmpty()) { setCurrentDir(QFileInfo(fileName).absolutePath()); QFile f(fileName); if (f.open(QFile::WriteOnly | QFile::Text)) f.write(m_runOutputWindow->toPlainText().toUtf8()); } } void QmlDebugTranslationWidget::appendMessage(const QString &message, Utils::OutputFormat format) { const auto newLine = QRegularExpression("[\r\n]"); const auto messages = message.split(newLine, Qt::SkipEmptyParts); if (messages.count() > 1) { for (auto m : messages) appendMessage(m + "\n", format); return; } const QString serviceSeperator = ": QQmlDebugTranslationService: "; if (!message.contains(serviceSeperator)) return; QString locationString = message; locationString = locationString.split(serviceSeperator).first(); static const QRegularExpression qmlLineColumnLink("^(" QT_QML_URL_REGEXP ")" // url ":(\\d+)" // line ":(\\d+)$"); // column const QRegularExpressionMatch qmlLineColumnMatch = qmlLineColumnLink.match(locationString); auto fileLine = -1; QUrl fileUrl; if (qmlLineColumnMatch.hasMatch()) { fileUrl = QUrl(qmlLineColumnMatch.captured(1)); fileLine = qmlLineColumnMatch.captured(2).toInt(); } m_runOutputWindow->appendMessage(message, format); auto type = ProjectExplorer::Task::TaskType::Warning; auto description = message.split(serviceSeperator).at(1); auto filePath = Utils::FilePath::fromString(fileUrl.toLocalFile()); auto category = "QmlPreview.Translation"; auto icon = Utils::Icons::WARNING.icon(); ProjectExplorer::TaskHub::addTask(ProjectExplorer::Task(type, description, filePath, fileLine, category, icon, ProjectExplorer::Task::NoOptions)); } QString QmlDebugTranslationWidget::singleFileButtonText(const QString &filePath) { auto buttonText = tr("current file: %1"); if (filePath.isEmpty()) return buttonText.arg(tr("empty")); return buttonText.arg(filePath); } QString QmlDebugTranslationWidget::runButtonText(bool isRunning) { if (isRunning) { return tr("Stop"); } return tr("Run language tests"); } void QmlDebugTranslationWidget::addLanguageCheckBoxes(const QStringList &languages) { for (auto language : languages) { auto languageCheckBox = new QCheckBox(language); m_selectLanguageLayout->addWidget(languageCheckBox); connect(languageCheckBox, &QCheckBox::stateChanged, [this, language] (int state) { if (state == Qt::Checked) m_testLanguages.append(language); else m_testLanguages.removeAll(language); }); languageCheckBox->setChecked(true); } } } // namespace QmlPreview