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