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
phraseViewHeaderKey()47 static QString phraseViewHeaderKey()
48 {
49 return settingPath("PhraseViewHeader");
50 }
51
PhraseView(MultiDataModel * model,QList<QHash<QString,QList<Phrase * >>> * phraseDict,QWidget * parent)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(QSettings().value(phraseViewHeaderKey()).toByteArray());
76
77 connect(this, SIGNAL(activated(QModelIndex)), this, SLOT(selectPhrase(QModelIndex)));
78 }
79
~PhraseView()80 PhraseView::~PhraseView()
81 {
82 QSettings().setValue(phraseViewHeaderKey(), header()->saveState());
83 deleteGuesses();
84 }
85
toggleGuessing()86 void PhraseView::toggleGuessing()
87 {
88 m_doGuesses = !m_doGuesses;
89 update();
90 }
91
update()92 void PhraseView::update()
93 {
94 setSourceText(m_modelIndex, m_sourceText);
95 }
96
97
contextMenuEvent(QContextMenuEvent * event)98 void PhraseView::contextMenuEvent(QContextMenuEvent *event)
99 {
100 QModelIndex index = indexAt(event->pos());
101 if (!index.isValid())
102 return;
103
104 QMenu *contextMenu = new QMenu(this);
105
106 QAction *insertAction = new QAction(tr("Insert"), contextMenu);
107 connect(insertAction, SIGNAL(triggered()), this, SLOT(selectPhrase()));
108
109 QAction *editAction = new QAction(tr("Edit"), contextMenu);
110 connect(editAction, SIGNAL(triggered()), this, SLOT(editPhrase()));
111 Qt::ItemFlags isFromPhraseBook = model()->flags(index) & Qt::ItemIsEditable;
112 editAction->setEnabled(isFromPhraseBook);
113
114 QAction *gotoAction = new QAction(tr("Go to"), contextMenu);
115 connect(gotoAction, SIGNAL(triggered()), this, SLOT(gotoMessageFromGuess()));
116 gotoAction->setEnabled(!isFromPhraseBook);
117
118 contextMenu->addAction(insertAction);
119 contextMenu->addAction(editAction);
120 contextMenu->addAction(gotoAction);
121
122 contextMenu->exec(event->globalPos());
123 event->accept();
124 }
125
mouseDoubleClickEvent(QMouseEvent * event)126 void PhraseView::mouseDoubleClickEvent(QMouseEvent *event)
127 {
128 QModelIndex index = indexAt(event->pos());
129 if (!index.isValid())
130 return;
131
132 emit phraseSelected(m_modelIndex, m_phraseModel->phrase(index)->target());
133 event->accept();
134 }
135
guessShortcut(int key)136 void PhraseView::guessShortcut(int key)
137 {
138 foreach (const Phrase *phrase, m_phraseModel->phraseList())
139 if (phrase->shortcut() == key) {
140 emit phraseSelected(m_modelIndex, phrase->target());
141 return;
142 }
143 }
144
selectPhrase(const QModelIndex & index)145 void PhraseView::selectPhrase(const QModelIndex &index)
146 {
147 emit phraseSelected(m_modelIndex, m_phraseModel->phrase(index)->target());
148 }
149
selectPhrase()150 void PhraseView::selectPhrase()
151 {
152 emit phraseSelected(m_modelIndex, m_phraseModel->phrase(currentIndex())->target());
153 }
154
editPhrase()155 void PhraseView::editPhrase()
156 {
157 edit(currentIndex());
158 }
159
gotoMessageFromGuess()160 void PhraseView::gotoMessageFromGuess()
161 {
162 emit setCurrentMessageFromGuess(m_modelIndex,
163 m_phraseModel->phrase(currentIndex())->candidate());
164 }
165
setMaxCandidates(const int max)166 void PhraseView::setMaxCandidates(const int max)
167 {
168 m_maxCandidates = max;
169 emit showFewerGuessesAvailable(m_maxCandidates > DefaultMaxCandidates);
170 }
171
moreGuesses()172 void PhraseView::moreGuesses()
173 {
174 setMaxCandidates(m_maxCandidates + DefaultMaxCandidates);
175 setSourceText(m_modelIndex, m_sourceText);
176 }
177
fewerGuesses()178 void PhraseView::fewerGuesses()
179 {
180 setMaxCandidates(m_maxCandidates - DefaultMaxCandidates);
181 setSourceText(m_modelIndex, m_sourceText);
182 }
183
resetNumGuesses()184 void PhraseView::resetNumGuesses()
185 {
186 setMaxCandidates(DefaultMaxCandidates);
187 setSourceText(m_modelIndex, m_sourceText);
188 }
189
similarTextHeuristicCandidates(MultiDataModel * model,int mi,const char * text,int maxCandidates)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(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(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, score);
229 candidates.insert(i, cand);
230 }
231 continue_outer_loop:
232 ;
233 }
234 return candidates;
235 }
236
237
setSourceText(int model,const QString & sourceText)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(m_dataModel, model,
253 sourceText.toLatin1(), m_maxCandidates);
254 int n = 0;
255 foreach (const Candidate &candidate, cl) {
256 QString def;
257 if (n < 9)
258 def = tr("Guess from '%1' (%2)")
259 .arg(candidate.context, QKeySequence(Qt::CTRL | (Qt::Key_0 + (n + 1)))
260 .toString(QKeySequence::NativeText));
261 else
262 def = tr("Guess from '%1'").arg(candidate.context);
263 Phrase *guess = new Phrase(candidate.source, candidate.translation, def, candidate, n);
264 m_guesses.append(guess);
265 m_phraseModel->addPhrase(guess);
266 ++n;
267 }
268 }
269 }
270
getPhrases(int model,const QString & source)271 QList<Phrase *> PhraseView::getPhrases(int model, const QString &source)
272 {
273 QList<Phrase *> phrases;
274 QString f = MainWindow::friendlyString(source);
275 QStringList lookupWords = f.split(QLatin1Char(' '));
276
277 foreach (const QString &s, lookupWords) {
278 if (m_phraseDict->at(model).contains(s)) {
279 foreach (Phrase *p, m_phraseDict->at(model).value(s)) {
280 if (f.contains(MainWindow::friendlyString(p->source())))
281 phrases.append(p);
282 }
283 }
284 }
285 return phrases;
286 }
287
deleteGuesses()288 void PhraseView::deleteGuesses()
289 {
290 qDeleteAll(m_guesses);
291 m_guesses.clear();
292 }
293
294 QT_END_NAMESPACE
295