1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "globals.h"
5#include "mainwindow.h"
6#include "messagemodel.h"
7#include "phrase.h"
8#include "phraseview.h"
9#include "phrasemodel.h"
10#include "simtexth.h"
11
12#include <QHeaderView>
13#include <QKeyEvent>
14#include <QSettings>
15#include <QShortcut>
16#include <QTreeView>
17#include <QWidget>
18#include <QDebug>
19
20
21QT_BEGIN_NAMESPACE
22
23static QString phraseViewHeaderKey()
24{
25 return settingPath("PhraseViewHeader");
26}
27
28PhraseView::PhraseView(MultiDataModel *model, QList<QHash<QString, QList<Phrase *> > > *phraseDict, QWidget *parent)
29 : QTreeView(parent),
30 m_dataModel(model),
31 m_phraseDict(phraseDict),
32 m_modelIndex(-1),
33 m_doGuesses(true)
34{
35 setObjectName(QLatin1String("phrase list view"));
36
37 m_phraseModel = new PhraseModel(this);
38
39 setModel(m_phraseModel);
40 setAlternatingRowColors(true);
41 setSelectionBehavior(QAbstractItemView::SelectRows);
42 setSelectionMode(QAbstractItemView::SingleSelection);
43 setRootIsDecorated(false);
44 setItemsExpandable(false);
45
46 for (int i = 0; i < 9; ++i) {
47 const auto key = static_cast<Qt::Key>(int(Qt::Key_1) + i);
48 auto shortCut = new QShortcut(Qt::CTRL | key, this);
49 connect(sender: shortCut, signal: &QShortcut::activated, context: this,
50 slot: [i, this]() { this->guessShortcut(nkey: i); });
51 }
52
53 header()->setSectionResizeMode(QHeaderView::Interactive);
54 header()->setSectionsClickable(true);
55 header()->restoreState(state: QSettings().value(key: phraseViewHeaderKey()).toByteArray());
56
57 connect(sender: this, signal: &QAbstractItemView::activated,
58 context: this, slot: &PhraseView::selectPhrase);
59}
60
61PhraseView::~PhraseView()
62{
63 QSettings().setValue(key: phraseViewHeaderKey(), value: header()->saveState());
64 deleteGuesses();
65}
66
67void PhraseView::toggleGuessing()
68{
69 m_doGuesses = !m_doGuesses;
70 update();
71}
72
73void PhraseView::update()
74{
75 setSourceText(model: m_modelIndex, sourceText: m_sourceText);
76}
77
78
79void PhraseView::contextMenuEvent(QContextMenuEvent *event)
80{
81 QModelIndex index = indexAt(p: event->pos());
82 if (!index.isValid())
83 return;
84
85 QMenu *contextMenu = new QMenu(this);
86
87 QAction *insertAction = new QAction(tr("Insert"), contextMenu);
88 connect(sender: insertAction, signal: &QAction::triggered,
89 context: this, slot: &PhraseView::selectCurrentPhrase);
90
91 QAction *editAction = new QAction(tr("Edit"), contextMenu);
92 connect(sender: editAction, signal: &QAction::triggered,
93 context: this, slot: &PhraseView::editPhrase);
94 Qt::ItemFlags isFromPhraseBook = model()->flags(index) & Qt::ItemIsEditable;
95 editAction->setEnabled(isFromPhraseBook);
96
97 QAction *gotoAction = new QAction(tr("Go to"), contextMenu);
98 connect(sender: gotoAction, signal: &QAction::triggered,
99 context: this, slot: &PhraseView::gotoMessageFromGuess);
100 gotoAction->setEnabled(!isFromPhraseBook);
101
102 contextMenu->addAction(insertAction);
103 contextMenu->addAction(editAction);
104 contextMenu->addAction(gotoAction);
105
106 contextMenu->exec(event->globalPos());
107 event->accept();
108}
109
110void PhraseView::mouseDoubleClickEvent(QMouseEvent *event)
111{
112 QModelIndex index = indexAt(p: event->pos());
113 if (!index.isValid())
114 return;
115
116 emit phraseSelected(latestModel: m_modelIndex, phrase: m_phraseModel->phrase(index)->target());
117 event->accept();
118}
119
120void PhraseView::guessShortcut(int key)
121{
122 const auto phrases = m_phraseModel->phraseList();
123 for (const Phrase *phrase : phrases)
124 if (phrase->shortcut() == key) {
125 emit phraseSelected(latestModel: m_modelIndex, phrase: phrase->target());
126 return;
127 }
128}
129
130void PhraseView::selectPhrase(const QModelIndex &index)
131{
132 emit phraseSelected(latestModel: m_modelIndex, phrase: m_phraseModel->phrase(index)->target());
133}
134
135void PhraseView::selectCurrentPhrase()
136{
137 emit phraseSelected(latestModel: m_modelIndex, phrase: m_phraseModel->phrase(index: currentIndex())->target());
138}
139
140void PhraseView::editPhrase()
141{
142 edit(index: currentIndex());
143}
144
145void PhraseView::gotoMessageFromGuess()
146{
147 emit setCurrentMessageFromGuess(modelIndex: m_modelIndex,
148 cand: m_phraseModel->phrase(index: currentIndex())->candidate());
149}
150
151void PhraseView::setMaxCandidates(const int max)
152{
153 m_maxCandidates = max;
154 emit showFewerGuessesAvailable(canShow: m_maxCandidates > DefaultMaxCandidates);
155}
156
157void PhraseView::moreGuesses()
158{
159 setMaxCandidates(m_maxCandidates + DefaultMaxCandidates);
160 setSourceText(model: m_modelIndex, sourceText: m_sourceText);
161}
162
163void PhraseView::fewerGuesses()
164{
165 setMaxCandidates(m_maxCandidates - DefaultMaxCandidates);
166 setSourceText(model: m_modelIndex, sourceText: m_sourceText);
167}
168
169void PhraseView::resetNumGuesses()
170{
171 setMaxCandidates(DefaultMaxCandidates);
172 setSourceText(model: m_modelIndex, sourceText: m_sourceText);
173}
174
175static CandidateList similarTextHeuristicCandidates(MultiDataModel *model, int mi,
176 const char *text, int maxCandidates)
177{
178 QList<int> scores;
179 CandidateList candidates;
180
181 StringSimilarityMatcher stringmatcher(QString::fromLatin1(ba: text));
182
183 for (MultiDataModelIterator it(model, mi); it.isValid(); ++it) {
184 MessageItem *m = it.current();
185 if (!m)
186 continue;
187
188 TranslatorMessage mtm = m->message();
189 if (mtm.type() == TranslatorMessage::Unfinished
190 || mtm.translation().isEmpty())
191 continue;
192
193 QString s = m->text();
194
195 int score = stringmatcher.getSimilarityScore(strCandidate: s);
196
197 if (candidates.size() == maxCandidates && score > scores[maxCandidates - 1])
198 candidates.removeLast();
199 if (candidates.size() < maxCandidates && score >= textSimilarityThreshold ) {
200 Candidate cand(mtm.context(), s, mtm.comment(), mtm.translation());
201
202 int i;
203 for (i = 0; i < candidates.size(); ++i) {
204 if (score >= scores.at(i)) {
205 if (score == scores.at(i)) {
206 if (candidates.at(i) == cand)
207 goto continue_outer_loop;
208 } else {
209 break;
210 }
211 }
212 }
213 scores.insert(i, t: score);
214 candidates.insert(i, t: cand);
215 }
216 continue_outer_loop:
217 ;
218 }
219 return candidates;
220}
221
222
223void PhraseView::setSourceText(int model, const QString &sourceText)
224{
225 m_modelIndex = model;
226 m_sourceText = sourceText;
227 m_phraseModel->removePhrases();
228 deleteGuesses();
229
230 if (model < 0)
231 return;
232
233 const auto phrases = getPhrases(model, sourceText);
234 for (Phrase *p : phrases)
235 m_phraseModel->addPhrase(p);
236
237 if (!sourceText.isEmpty() && m_doGuesses) {
238 const CandidateList cl = similarTextHeuristicCandidates(model: m_dataModel, mi: model,
239 text: sourceText.toLatin1(), maxCandidates: m_maxCandidates);
240 int n = 0;
241 for (const Candidate &candidate : cl) {
242 QString def;
243 if (n < 9)
244 def = tr(s: "Guess from '%1' (%2)")
245 .arg(args: candidate.context, args: QKeySequence(Qt::CTRL | (Qt::Key_0 + (n + 1)))
246 .toString(format: QKeySequence::NativeText));
247 else
248 def = tr(s: "Guess from '%1'").arg(a: candidate.context);
249 Phrase *guess = new Phrase(candidate.source, candidate.translation, def, candidate, n);
250 m_guesses.append(t: guess);
251 m_phraseModel->addPhrase(p: guess);
252 ++n;
253 }
254 }
255}
256
257QList<Phrase *> PhraseView::getPhrases(int model, const QString &source)
258{
259 QList<Phrase *> phrases;
260 const QString f = MainWindow::friendlyString(str: source);
261 const QStringList lookupWords = f.split(sep: QLatin1Char(' '));
262
263 for (const QString &s : lookupWords) {
264 if (m_phraseDict->at(i: model).contains(key: s)) {
265 const auto phraseList = m_phraseDict->at(i: model).value(key: s);
266 for (Phrase *p : phraseList) {
267 if (f.contains(s: MainWindow::friendlyString(str: p->source())))
268 phrases.append(t: p);
269 }
270 }
271 }
272 return phrases;
273}
274
275void PhraseView::deleteGuesses()
276{
277 qDeleteAll(c: m_guesses);
278 m_guesses.clear();
279}
280
281QT_END_NAMESPACE
282

source code of qttools/src/linguist/linguist/phraseview.cpp