1 /*
2     SPDX-FileCopyrightText: 2013 Andreas Cord-Landwehr <cordlandwehr@kde.org>
3 
4     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
5 */
6 
7 #include "phrase.h"
8 #include "artikulate_debug.h"
9 #include "icourse.h"
10 #include "unit.h"
11 #include <QQmlEngine>
12 #include <QTemporaryFile>
13 
Phrase()14 Phrase::Phrase()
15     : IEditablePhrase()
16     , m_type(IPhrase::Type::AllTypes)
17     , m_editState(IEditablePhrase::EditState::Unknown)
18     , m_trainingProgress(0)
19     , m_skipCounter(0)
20     , m_excludedFromUnit(false)
21 {
22     QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
23 
24     connect(this, &Phrase::idChanged, this, &Phrase::modified);
25     connect(this, &Phrase::typeChanged, this, &Phrase::modified);
26     connect(this, &Phrase::textChanged, this, &Phrase::modified);
27     connect(this, &Phrase::soundChanged, this, &Phrase::modified);
28     connect(this, &Phrase::editStateChanged, this, &Phrase::modified);
29     connect(this, &Phrase::i18nTextChanged, this, &Phrase::modified);
30     connect(this, &Phrase::phonemesChanged, this, &Phrase::modified);
31     connect(this, &Phrase::excludedChanged, this, &Phrase::modified);
32 }
33 
34 Phrase::~Phrase() = default;
35 
create()36 std::shared_ptr<Phrase> Phrase::create()
37 {
38     std::shared_ptr<Phrase> phrase(new Phrase);
39     phrase->setSelf(phrase);
40     return phrase;
41 }
42 
setSelf(std::shared_ptr<IPhrase> self)43 void Phrase::setSelf(std::shared_ptr<IPhrase> self)
44 {
45     m_self = self;
46 }
47 
self() const48 std::shared_ptr<IPhrase> Phrase::self() const
49 {
50     return m_self.lock();
51 }
52 
id() const53 QString Phrase::id() const
54 {
55     return m_id;
56 }
57 
setId(QString id)58 void Phrase::setId(QString id)
59 {
60     if (id != m_id) {
61         m_id = std::move(id);
62         emit idChanged();
63     }
64 }
65 
foreignId() const66 QString Phrase::foreignId() const
67 {
68     return m_foreignId;
69 }
70 
setForeignId(QString id)71 void Phrase::setForeignId(QString id)
72 {
73     m_foreignId = std::move(id);
74 }
75 
text() const76 QString Phrase::text() const
77 {
78     return m_text;
79 }
80 
setText(QString text)81 void Phrase::setText(QString text)
82 {
83     if (QString::compare(text, m_text) != 0) {
84         m_text = text.trimmed();
85         emit textChanged();
86     }
87 }
88 
i18nText() const89 QString Phrase::i18nText() const
90 {
91     return m_i18nText;
92 }
93 
seti18nText(QString text)94 void Phrase::seti18nText(QString text)
95 {
96     if (QString::compare(text, m_i18nText) != 0) {
97         // copy unmodified original text string
98         m_i18nText = std::move(text);
99         emit i18nTextChanged();
100     }
101 }
102 
type() const103 Phrase::Type Phrase::type() const
104 {
105     return m_type;
106 }
107 
typeString() const108 QString Phrase::typeString() const
109 {
110     switch (m_type) {
111         case IPhrase::Type::Word:
112             return QStringLiteral("word");
113         case IPhrase::Type::Expression:
114             return QStringLiteral("expression");
115         case IPhrase::Type::Sentence:
116             return QStringLiteral("sentence");
117         case IPhrase::Type::Paragraph:
118             return QStringLiteral("paragraph");
119         default:
120             return QStringLiteral("ERROR_UNKNOWN_TYPE");
121     }
122 }
123 
setType(Phrase::Type type)124 void Phrase::setType(Phrase::Type type)
125 {
126     if (m_type == type) {
127         return;
128     }
129     m_type = type;
130     emit typeChanged();
131 }
132 
setType(const QString & typeString)133 void Phrase::setType(const QString &typeString)
134 {
135     if (typeString == QLatin1String("word")) {
136         setType(IPhrase::Type::Word);
137         return;
138     }
139     if (typeString == QLatin1String("expression")) {
140         setType(IPhrase::Type::Expression);
141         return;
142     }
143     if (typeString == QLatin1String("sentence")) {
144         setType(IPhrase::Type::Sentence);
145         return;
146     }
147     if (typeString == QLatin1String("paragraph")) {
148         setType(IPhrase::Type::Paragraph);
149         return;
150     }
151     qCWarning(ARTIKULATE_CORE()) << "Cannot set type from unknown identifier, aborting";
152     return;
153 }
154 
editState() const155 Phrase::EditState Phrase::editState() const
156 {
157     return m_editState;
158 }
159 
editStateString() const160 QString Phrase::editStateString() const
161 {
162     switch (m_editState) {
163         case IEditablePhrase::EditState::Unknown:
164             return QStringLiteral("unknown");
165         case IEditablePhrase::EditState::Translated:
166             return QStringLiteral("translated");
167         case IEditablePhrase::EditState::Completed:
168             return QStringLiteral("completed");
169     }
170     Q_UNREACHABLE();
171 }
172 
setEditState(Phrase::EditState state)173 void Phrase::setEditState(Phrase::EditState state)
174 {
175     if (m_editState == state) {
176         return;
177     }
178     m_editState = state;
179     emit editStateChanged();
180 }
181 
setEditState(const QString & stateString)182 void Phrase::setEditState(const QString &stateString)
183 {
184     if (stateString.isEmpty()) {
185         return;
186     }
187     if (stateString == QLatin1String("unknown")) {
188         setEditState(IEditablePhrase::EditState::Unknown);
189         return;
190     }
191     if (stateString == QLatin1String("translated")) {
192         setEditState(IEditablePhrase::EditState::Translated);
193         return;
194     }
195     if (stateString == QLatin1String("completed")) {
196         setEditState(IEditablePhrase::EditState::Completed);
197         return;
198     }
199     qCWarning(ARTIKULATE_LOG) << "Cannot set edit state from unknown identifier " << stateString << ", aborting";
200     return;
201 }
202 
unit() const203 std::shared_ptr<IUnit> Phrase::unit() const
204 {
205     return m_unit.lock();
206 }
207 
setUnit(std::shared_ptr<IUnit> unit)208 void Phrase::setUnit(std::shared_ptr<IUnit> unit)
209 {
210     Q_ASSERT(unit);
211     if (unit == m_unit.lock()) {
212         return;
213     }
214     m_unit = unit;
215     emit unitChanged();
216 }
217 
sound() const218 QUrl Phrase::sound() const
219 {
220     return m_nativeSoundFile;
221 }
222 
setSound(QUrl soundFile)223 void Phrase::setSound(QUrl soundFile)
224 {
225     if (!soundFile.isValid() || soundFile.isEmpty()) {
226         qCWarning(ARTIKULATE_LOG) << "Not setting empty sound file path.";
227         return;
228     }
229     m_nativeSoundFile = std::move(soundFile);
230     emit soundChanged();
231 }
232 
soundFileUrl() const233 QString Phrase::soundFileUrl() const
234 {
235     return m_nativeSoundFile.toLocalFile();
236 }
237 
soundFileOutputPath() const238 QString Phrase::soundFileOutputPath() const
239 {
240     if (m_nativeSoundFile.isEmpty()) {
241         QString outputDir = m_unit.lock()->course()->file().path() + '/';
242         // TODO take care that this is proper ASCII
243         return outputDir + id() + ".ogg";
244     } else {
245         return soundFileUrl();
246     }
247 }
248 
setSoundFileUrl()249 void Phrase::setSoundFileUrl()
250 {
251     if (soundFileOutputPath() != m_nativeSoundFile.toLocalFile()) {
252         m_nativeSoundFile = QUrl::fromLocalFile(soundFileOutputPath());
253         emit soundChanged();
254         emit modified();
255     }
256 }
257 
isExcluded() const258 bool Phrase::isExcluded() const
259 {
260     return m_excludedFromUnit;
261 }
262 
setExcluded(bool excluded)263 void Phrase::setExcluded(bool excluded)
264 {
265     if (excluded == m_excludedFromUnit) {
266         return;
267     }
268     m_excludedFromUnit = excluded;
269     emit excludedChanged();
270 }
271 
progress() const272 int Phrase::progress() const
273 {
274     return static_cast<int>(m_trainingProgress);
275 }
276 
setProgress(int value)277 void Phrase::setProgress(int value)
278 {
279     Q_ASSERT(value >= 0);
280     if (value < 0) {
281         value = 0;
282     }
283     if (m_trainingProgress == static_cast<uint>(value)) {
284         return;
285     }
286     m_trainingProgress = static_cast<uint>(value);
287     emit progressChanged();
288 }
updateProgress(Phrase::Progress progress)289 void Phrase::updateProgress(Phrase::Progress progress)
290 {
291     // logic of progress computation:
292     // a) if skipped 3 times in a row, decrease progress
293     // b) if done and skipped less than two times in a row, increase progress
294     if (progress == Progress::Done) {
295         m_skipCounter = 0;
296         if (m_trainingProgress < 3) {
297             ++m_trainingProgress;
298             emit progressChanged();
299         }
300         return;
301     }
302     if (progress == Progress::Skip) {
303         ++m_skipCounter;
304         if (m_skipCounter > 2 && m_trainingProgress > 0) {
305             --m_trainingProgress;
306             emit progressChanged();
307         }
308         return;
309     }
310 }
311 
phonemes() const312 QVector<Phoneme *> Phrase::phonemes() const
313 {
314     return m_phonemes;
315 }
316 
hasPhoneme(Phoneme * phoneme)317 bool Phrase::hasPhoneme(Phoneme *phoneme)
318 {
319     return m_phonemes.contains(phoneme);
320 }
321 
addPhoneme(Phoneme * phoneme)322 void Phrase::addPhoneme(Phoneme *phoneme)
323 {
324     if (!m_phonemes.contains(phoneme)) {
325         m_phonemes.append(phoneme);
326         emit phonemesChanged();
327         // FIXME tell Unit to also send corresponding signal!
328     }
329 }
330 
removePhoneme(Phoneme * phoneme)331 void Phrase::removePhoneme(Phoneme *phoneme)
332 {
333     if (m_phonemes.removeOne(phoneme)) {
334         emit phonemesChanged();
335         // FIXME tell Unit to also send corresponding signal!
336     }
337 }
338