Warning, file /sdk/lokalize/src/msgctxtview.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 This file is part of Lokalize 0003 0004 SPDX-FileCopyrightText: 2007-2014 Nick Shaforostoff <shafff@ukr.net> 0005 SPDX-FileCopyrightText: 2018-2019 Simon Depiets <sdepiets@gmail.com> 0006 0007 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0008 */ 0009 0010 #include "msgctxtview.h" 0011 0012 #include "noteeditor.h" 0013 #include "catalog.h" 0014 #include "cmd.h" 0015 #include "prefs_lokalize.h" 0016 #include "project.h" 0017 0018 #include "lokalize_debug.h" 0019 0020 #include <klocalizedstring.h> 0021 #include <ktextedit.h> 0022 #include <kcombobox.h> 0023 0024 #include <QTime> 0025 #include <QTimer> 0026 #include <QBoxLayout> 0027 #include <QStackedLayout> 0028 #include <QLabel> 0029 #include <QStringListModel> 0030 #include <QLineEdit> 0031 #include <QTextBrowser> 0032 #include <QStringBuilder> 0033 #include <QDesktopServices> 0034 #include <QRegularExpression> 0035 0036 MsgCtxtView::MsgCtxtView(QWidget* parent, Catalog* catalog) 0037 : QDockWidget(i18nc("@title toolview name", "Unit metadata"), parent) 0038 , m_browser(new QTextBrowser(this)) 0039 , m_editor(nullptr) 0040 , m_catalog(catalog) 0041 , m_selection(0) 0042 , m_offset(0) 0043 , m_hasInfo(false) 0044 , m_hasErrorNotes(false) 0045 , m_pologyProcessInProgress(0) 0046 , m_pologyStartedReceivingOutput(false) 0047 { 0048 setObjectName(QStringLiteral("msgCtxtView")); 0049 QWidget* main = new QWidget(this); 0050 setWidget(main); 0051 m_stackedLayout = new QStackedLayout(main); 0052 m_stackedLayout->addWidget(m_browser); 0053 0054 m_browser->viewport()->setBackgroundRole(QPalette::Window); 0055 m_browser->setOpenLinks(false); 0056 connect(m_browser, &QTextBrowser::anchorClicked, this, &MsgCtxtView::anchorClicked); 0057 } 0058 0059 MsgCtxtView::~MsgCtxtView() 0060 { 0061 } 0062 0063 const QString MsgCtxtView::BR = "<br />"; 0064 0065 void MsgCtxtView::cleanup() 0066 { 0067 m_unfinishedNotes.clear(); 0068 m_tempNotes.clear(); 0069 m_pologyNotes.clear(); 0070 m_languageToolNotes.clear(); 0071 } 0072 0073 void MsgCtxtView::gotoEntry(const DocPosition& pos, int selection) 0074 { 0075 m_entry = DocPos(pos); 0076 m_selection = selection; 0077 m_offset = pos.offset; 0078 QTimer::singleShot(0, this, &MsgCtxtView::process); 0079 QTimer::singleShot(0, this, &MsgCtxtView::pology); 0080 } 0081 0082 void MsgCtxtView::process() 0083 { 0084 if (m_catalog->numberOfEntries() <= m_entry.entry) 0085 return;//because of Qt::QueuedConnection 0086 0087 if (m_stackedLayout->currentIndex()) 0088 m_unfinishedNotes[m_prevEntry] = qMakePair(m_editor->note(), m_editor->noteIndex()); 0089 0090 0091 if (m_unfinishedNotes.contains(m_entry)) { 0092 addNoteUI(); 0093 m_editor->setNote(m_unfinishedNotes.value(m_entry).first, m_unfinishedNotes.value(m_entry).second); 0094 } else 0095 m_stackedLayout->setCurrentIndex(0); 0096 0097 0098 m_prevEntry = m_entry; 0099 m_browser->clear(); 0100 0101 if (m_tempNotes.contains(m_entry.entry)) { 0102 QString html = i18nc("@info notes to translation unit which expire when the catalog is closed", "<b>Temporary notes:</b>"); 0103 html += MsgCtxtView::BR; 0104 const auto tempNotes = m_tempNotes.values(m_entry.entry); 0105 for (const QString& note : tempNotes) 0106 html += note.toHtmlEscaped() + MsgCtxtView::BR; 0107 html += MsgCtxtView::BR; 0108 m_browser->insertHtml(html.replace('\n', MsgCtxtView::BR)); 0109 } 0110 if (m_pologyNotes.contains(m_entry.entry)) { 0111 QString html = i18nc("@info notes generated by the pology check", "<b>Pology notes:</b>"); 0112 html += MsgCtxtView::BR; 0113 const auto pologyNotes = m_pologyNotes.values(m_entry.entry); 0114 for (const QString& note : pologyNotes) 0115 html += note.toHtmlEscaped() + MsgCtxtView::BR; 0116 html += MsgCtxtView::BR; 0117 m_browser->insertHtml(html.replace('\n', MsgCtxtView::BR)); 0118 } 0119 if (m_languageToolNotes.contains(m_entry.entry)) { 0120 QString html = i18nc("@info notes generated by the languagetool check", "<b>LanguageTool notes:</b>"); 0121 html += MsgCtxtView::BR; 0122 const auto languageToolNotes = m_languageToolNotes.values(m_entry.entry); 0123 for (const QString& note : languageToolNotes) 0124 html += note.toHtmlEscaped() + MsgCtxtView::BR; 0125 html += MsgCtxtView::BR; 0126 m_browser->insertHtml(html.replace('\n', MsgCtxtView::BR)); 0127 } 0128 0129 QString phaseName = m_catalog->phase(m_entry.toDocPosition()); 0130 if (!phaseName.isEmpty()) { 0131 Phase phase = m_catalog->phase(phaseName); 0132 QString html = i18nc("@info translation unit metadata", "<b>Phase:</b><br>"); 0133 if (phase.date.isValid()) 0134 html += QString(QStringLiteral("%1: ")).arg(phase.date.toString(Qt::ISODate)); 0135 html += phase.process.toHtmlEscaped(); 0136 if (!phase.contact.isEmpty()) 0137 html += QString(QStringLiteral(" (%1)")).arg(phase.contact.toHtmlEscaped()); 0138 m_browser->insertHtml(html + MsgCtxtView::BR); 0139 } 0140 0141 const QVector<Note> notes = m_catalog->notes(m_entry.toDocPosition()); 0142 m_hasErrorNotes = false; 0143 for (const Note& note : notes) 0144 m_hasErrorNotes = m_hasErrorNotes || note.content.contains(QLatin1String("[ERROR]")); 0145 0146 int realOffset = displayNotes(m_browser, m_catalog->notes(m_entry.toDocPosition()), m_entry.form, m_catalog->capabilities()&MultipleNotes); 0147 0148 QString html; 0149 const auto developerNotes = m_catalog->developerNotes(m_entry.toDocPosition()); 0150 for (const Note& note : developerNotes) { 0151 html += MsgCtxtView::BR + escapeWithLinks(note.content).replace('\n', BR); 0152 } 0153 0154 const QStringList sourceFiles = m_catalog->sourceFiles(m_entry.toDocPosition()); 0155 if (!sourceFiles.isEmpty()) { 0156 html += i18nc("@info PO comment parsing", "<br><b>Files:</b><br>"); 0157 for (const QString &sourceFile : sourceFiles) 0158 html += QString(QStringLiteral("<a href=\"src:/%1\">%2</a><br />")).arg(sourceFile, sourceFile); 0159 html.chop(6); 0160 } 0161 0162 QString msgctxt = m_catalog->context(m_entry.entry).first(); 0163 if (!msgctxt.isEmpty()) 0164 html += i18nc("@info PO comment parsing", "<br><b>Context:</b><br>") + msgctxt.toHtmlEscaped(); 0165 0166 QTextCursor t = m_browser->textCursor(); 0167 t.movePosition(QTextCursor::End); 0168 m_browser->setTextCursor(t); 0169 m_browser->insertHtml(html); 0170 0171 t.movePosition(QTextCursor::Start); 0172 t.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor, realOffset + m_offset); 0173 t.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, m_selection); 0174 m_browser->setTextCursor(t); 0175 } 0176 void MsgCtxtView::languageTool(const QString &text) 0177 { 0178 m_languageToolNotes.insert(m_entry.entry, text); 0179 m_prevEntry.entry = -1; 0180 process(); 0181 } 0182 void MsgCtxtView::pology() 0183 { 0184 if (Settings::self()->pologyEnabled() && m_pologyProcessInProgress == 0 && QFile::exists(m_catalog->url())) { 0185 QString command = Settings::self()->pologyCommandEntry(); 0186 command = command.replace(QStringLiteral("%u"), QString::number(m_entry.entry + 1)).replace(QStringLiteral("%f"), QStringLiteral("\"") + m_catalog->url() + QStringLiteral("\"")).replace(QStringLiteral("\n"), QStringLiteral(" ")); 0187 m_pologyProcess = new KProcess; 0188 m_pologyProcess->setShellCommand(command); 0189 m_pologyProcess->setOutputChannelMode(KProcess::SeparateChannels); 0190 m_pologyStartedReceivingOutput = false; 0191 connect(m_pologyProcess, &KProcess::readyReadStandardOutput, 0192 this, &MsgCtxtView::pologyReceivedStandardOutput); 0193 connect(m_pologyProcess, &KProcess::readyReadStandardError, 0194 this, &MsgCtxtView::pologyReceivedStandardError); 0195 connect(m_pologyProcess, QOverload<int, QProcess::ExitStatus>::of(&KProcess::finished), 0196 this, &MsgCtxtView::pologyHasFinished); 0197 m_pologyData = QStringLiteral(""); 0198 m_pologyProcessInProgress = m_entry.entry + 1; 0199 m_pologyProcess->start(); 0200 } else if (Settings::self()->pologyEnabled() && m_pologyProcessInProgress > 0) { 0201 QTimer::singleShot(1000, this, &MsgCtxtView::pology); 0202 } 0203 } 0204 void MsgCtxtView::pologyReceivedStandardOutput() 0205 { 0206 if (m_pologyProcessInProgress == m_entry.entry + 1) { 0207 if (!m_pologyStartedReceivingOutput) { 0208 m_pologyStartedReceivingOutput = true; 0209 } 0210 const QString grossPologyOutput = m_pologyProcess->readAllStandardOutput(); 0211 const QStringList pologyTmpLines = grossPologyOutput.split('\n', Qt::SkipEmptyParts); 0212 for (const QString &pologyTmp : pologyTmpLines) { 0213 if (pologyTmp.startsWith(QStringLiteral("[note]"))) 0214 m_pologyData += pologyTmp; 0215 } 0216 } 0217 } 0218 0219 0220 void MsgCtxtView::pologyReceivedStandardError() 0221 { 0222 if (m_pologyProcessInProgress == m_entry.entry + 1) { 0223 if (!m_pologyStartedReceivingOutput) { 0224 m_pologyStartedReceivingOutput = true; 0225 } 0226 m_pologyData += m_pologyProcess->readAllStandardError().replace('\n', MsgCtxtView::BR.toLatin1()); 0227 } 0228 } 0229 void MsgCtxtView::pologyHasFinished() 0230 { 0231 if (m_pologyProcessInProgress == m_entry.entry + 1) { 0232 if (!m_pologyStartedReceivingOutput) { 0233 m_pologyStartedReceivingOutput = true; 0234 const QString grossPologyOutput = m_pologyProcess->readAllStandardOutput(); 0235 const QStringList pologyTmpLines = grossPologyOutput.split('\n', Qt::SkipEmptyParts); 0236 if (pologyTmpLines.count() == 0) { 0237 m_pologyData += i18nc("@info The pology command didn't return anything", "(empty)"); 0238 } else { 0239 for (const QString &pologyTmp : pologyTmpLines) { 0240 if (pologyTmp.startsWith(QStringLiteral("[note]"))) 0241 m_pologyData += pologyTmp; 0242 } 0243 } 0244 } 0245 m_pologyNotes.insert(m_entry.entry, m_pologyData); 0246 m_prevEntry.entry = -1; 0247 process(); 0248 } 0249 m_pologyProcess->deleteLater(); 0250 m_pologyProcessInProgress = 0; 0251 } 0252 0253 void MsgCtxtView::addNoteUI() 0254 { 0255 anchorClicked(QUrl(QStringLiteral("note:/add"))); 0256 } 0257 0258 void MsgCtxtView::anchorClicked(const QUrl& link) 0259 { 0260 QString path = link.path().mid(1); // minus '/' 0261 0262 if (link.scheme() == QLatin1String("note")) { 0263 int capabilities = m_catalog->capabilities(); 0264 if (!m_editor) { 0265 m_editor = new NoteEditor(this); 0266 m_stackedLayout->addWidget(m_editor); 0267 connect(m_editor, &NoteEditor::accepted, this, &MsgCtxtView::noteEditAccepted); 0268 connect(m_editor, &NoteEditor::rejected, this, &MsgCtxtView::noteEditRejected); 0269 } 0270 m_editor->setNoteAuthors(m_catalog->noteAuthors()); 0271 QVector<Note> notes = m_catalog->notes(m_entry.toDocPosition()); 0272 int noteIndex = -1; //means add new note 0273 Note note; 0274 if (!path.endsWith(QLatin1String("add"))) { 0275 noteIndex = path.toInt(); 0276 note = notes.at(noteIndex); 0277 } else if (!(capabilities & MultipleNotes) && notes.size()) { 0278 noteIndex = 0; //so we don't overwrite the only possible note 0279 note = notes.first(); 0280 } 0281 m_editor->setNote(note, noteIndex); 0282 m_editor->setFromFieldVisible(capabilities & KeepsNoteAuthors); 0283 m_stackedLayout->setCurrentIndex(1); 0284 } else if (link.scheme() == QLatin1String("src")) { 0285 int pos = path.lastIndexOf(':'); 0286 Q_EMIT srcFileOpenRequested(path.left(pos), path.midRef(pos + 1).toInt()); 0287 } else if (link.scheme().contains(QLatin1String("tp"))) 0288 QDesktopServices::openUrl(link); 0289 } 0290 0291 void MsgCtxtView::noteEditAccepted() 0292 { 0293 DocPosition pos = m_entry.toDocPosition(); 0294 pos.form = m_editor->noteIndex(); 0295 m_catalog->push(new SetNoteCmd(m_catalog, pos, m_editor->note())); 0296 0297 m_prevEntry.entry = -1; process(); 0298 //m_stackedLayout->setCurrentIndex(0); 0299 //m_unfinishedNotes.remove(m_entry); 0300 noteEditRejected(); 0301 } 0302 void MsgCtxtView::noteEditRejected() 0303 { 0304 m_stackedLayout->setCurrentIndex(0); 0305 m_unfinishedNotes.remove(m_entry); 0306 Q_EMIT escaped(); 0307 } 0308 0309 void MsgCtxtView::addNote(DocPosition p, const QString& text) 0310 { 0311 p.form = -1; 0312 m_catalog->push(new SetNoteCmd(m_catalog, p, Note(text))); 0313 if (m_entry.entry == p.entry) { 0314 m_prevEntry.entry = -1; 0315 process(); 0316 } 0317 } 0318 0319 void MsgCtxtView::addTemporaryEntryNote(int entry, const QString& text) 0320 { 0321 m_tempNotes.insertMulti(entry, text); 0322 m_prevEntry.entry = -1; 0323 process(); 0324 } 0325 0326 void MsgCtxtView::removeErrorNotes() 0327 { 0328 if (!m_hasErrorNotes) return; 0329 0330 DocPosition p = m_entry.toDocPosition(); 0331 const QVector<Note> notes = m_catalog->notes(p); 0332 p.form = notes.size(); 0333 while (--(p.form) >= 0) { 0334 if (notes.at(p.form).content.contains(QLatin1String("[ERROR]"))) 0335 m_catalog->push(new SetNoteCmd(m_catalog, p, Note())); 0336 } 0337 0338 m_prevEntry.entry = -1; 0339 process(); 0340 } 0341 0342