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