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