BuildmLearn Toolkit  2.0.4
BuildmLearn Toolkit is an easy-to-use program that helps users make mobile apps without any knowledge of application development.
 All Classes Functions Enumerations Groups Pages
flashcardeditor.cpp
1 /*
2  Copyright (c) 2012, BuildmLearn Contributors listed at http://buildmlearn.org/people/
3  All rights reserved.
4 
5  Redistribution and use in source and binary forms, with or without
6  modification, are permitted provided that the following conditions are met:
7 
8  * Redistributions of source code must retain the above copyright notice, this
9  list of conditions and the following disclaimer.
10 
11  * Redistributions in binary form must reproduce the above copyright notice,
12  this list of conditions and the following disclaimer in the documentation
13  and/or other materials provided with the distribution.
14 
15  * Neither the name of the BuildmLearn nor the names of its
16  contributors may be used to endorse or promote products derived from
17  this software without specific prior written permission.
18 
19  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30 
31 #include "templates/flashcard/flashcardeditor.h"
32 
33 #include "templates/flashcard/flashcardquestion.h"
34 #include "templates/flashcard/flashcardcore.h"
35 #include "templates/flashcard/flashcardentrypoint.h"
36 #include "miscellaneous/iconfactory.h"
37 #include "miscellaneous/iofactory.h"
38 #include "core/templatefactory.h"
39 
40 #include <QTimer>
41 #include <QFileDialog>
42 
43 
44 FlashCardEditor::FlashCardEditor(TemplateCore *core, QWidget *parent)
45  : TemplateEditor(core, parent), m_ui(new Ui::FlashCardEditor) {
46  m_ui->setupUi(this);
47 
48  // Set validators.
49  QRegExpValidator *author_validator = new QRegExpValidator(this);
50  QRegExpValidator *title_validator = new QRegExpValidator(this);
51 
52  author_validator->setRegExp(QRegExp(".{,50}"));
53  title_validator->setRegExp(QRegExp(".{,100}"));
54 
55  m_ui->m_txtAuthor->lineEdit()->setValidator(author_validator);
56  m_ui->m_txtName->lineEdit()->setValidator(title_validator);
57 
58  // Set validators.
59  QRegExpValidator *question_validator = new QRegExpValidator(this);
60  QRegExpValidator *hint_validator = new QRegExpValidator(this);
61 
62  question_validator->setRegExp(QRegExp(".{,100}"));
63  hint_validator->setRegExp(QRegExp(".{,30}"));
64 
65  m_ui->m_txtQuestion->lineEdit()->setValidator(question_validator);
66  m_ui->m_txtHint->lineEdit()->setValidator(hint_validator);
67 
68  // Set tab order.
69  QList<QWidget*> tab_order_widgets;
70  tab_order_widgets << m_ui->m_txtQuestion->lineEdit() << m_ui->m_btnPictureSelect <<
71  m_ui->m_txtAnswer->lineEdit() << m_ui->m_txtHint->lineEdit() <<
72  m_ui->m_txtAuthor->lineEdit() << m_ui->m_txtName->lineEdit() <<
73  m_ui->m_listQuestions << m_ui->m_btnQuestionAdd << m_ui->m_btnQuestionRemove <<
74  m_ui->m_btnQuestionUp << m_ui->m_btnQuestionDown;
75 
76  for (int i = 1; i < tab_order_widgets.size(); i++) {
77  setTabOrder(tab_order_widgets.at(i - 1), tab_order_widgets.at(i));
78  }
79 
80  m_ui->m_txtNumberOfQuestions->lineEdit()->setEnabled(false);
81 
82  m_ui->m_lblPictureFile->label()->setWordWrap(true);
83  m_ui->m_txtAnswer->lineEdit()->setPlaceholderText(tr("Answer for the answer"));
84  m_ui->m_txtHint->lineEdit()->setPlaceholderText(tr("Hint for the answer"));
85 
86  m_ui->m_lblPictureFile->setStatus(WidgetWithStatus::Error, QString(), tr("No picture selected"));
87  m_ui->m_txtAuthor->lineEdit()->setPlaceholderText(tr("Author of this quiz"));
88  m_ui->m_txtName->lineEdit()->setPlaceholderText(tr("Name of this quiz"));
89 
91 
92  m_ui->m_btnQuestionAdd->setIcon(factory->fromTheme("item-add"));
93  m_ui->m_btnQuestionRemove->setIcon(factory->fromTheme("item-remove"));
94  m_ui->m_btnQuestionUp->setIcon(factory->fromTheme("move-up"));
95  m_ui->m_btnQuestionDown->setIcon(factory->fromTheme("move-down"));
96 
97  connect(m_ui->m_btnPictureSelect, SIGNAL(clicked()), this, SLOT(selectPicture()));
98  connect(m_ui->m_txtAuthor->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(onAuthorChanged(QString)));
99  connect(m_ui->m_txtQuestion, SIGNAL(textEdited(QString)), this, SLOT(saveQuestion()));
100  connect(m_ui->m_txtName->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(onNameChanged(QString)));
101  connect(m_ui->m_txtAnswer->lineEdit(), SIGNAL(textEdited(QString)), this, SLOT(onAnswerChanged(QString)));
102  connect(m_ui->m_txtHint->lineEdit(), SIGNAL(textEdited(QString)), this, SLOT(onHintChanged(QString)));
103  connect(m_ui->m_btnQuestionAdd, SIGNAL(clicked()), this, SLOT(addQuestion()));
104  connect(m_ui->m_btnQuestionRemove, SIGNAL(clicked()), this, SLOT(removeQuestion()));
105  connect(m_ui->m_listQuestions, SIGNAL(currentRowChanged(int)), this, SLOT(loadQuestion(int)));
106  connect(m_ui->m_btnQuestionUp, SIGNAL(clicked()), this, SLOT(moveQuestionUp()));
107  connect(m_ui->m_btnQuestionDown, SIGNAL(clicked()), this, SLOT(moveQuestionDown()));
108 
109  setEditorsEnabled(false);
110  updateQuestionCount();
111 
112  qRegisterMetaType<FlashCardQuestion>("FlashCardQuestion");
113 
114  checkAuthor();
115  checkName();
116  loadQuestion(-1);
117 }
118 
119 FlashCardEditor::~FlashCardEditor() {
120  qDebug("Destroying FlashCardEditor instance.");
121 
122  delete m_ui;
123 }
124 
125 void FlashCardEditor::updateQuestionCount() {
126  m_ui->m_txtNumberOfQuestions->lineEdit()->setText(QString::number(m_ui->m_listQuestions->count()));
127 
128  if (m_ui->m_listQuestions->count() > 0) {
129  m_ui->m_txtNumberOfQuestions->setStatus(WidgetWithStatus::Ok, tr("Collection contains at least one question."));
130  }
131  else {
132  m_ui->m_txtNumberOfQuestions->setStatus(WidgetWithStatus::Error, tr("Collection does not contain any questions."));
133  }
134 }
135 
137  return
138  !m_ui->m_txtName->lineEdit()->text().simplified().isEmpty() &&
139  !m_ui->m_txtAuthor->lineEdit()->text().simplified().isEmpty() &&
140  !activeQuestions().isEmpty();
141 }
142 
144  /*if (!canGenerateApplications()) {
145  return QString();
146  }*/
147 
148  QDomDocument source_document = qApp->templateManager()->generateBundleHeader(core()->entryPoint()->typeIndentifier(),
149  m_ui->m_txtAuthor->lineEdit()->text(),
150  QString(),
151  m_ui->m_txtName->lineEdit()->text(),
152  QString(),
153  "1");
154  FIND_DATA_ELEMENT(data_element, source_document);
155 
156  foreach (const FlashCardQuestion &question, activeQuestions()) {
157  QDomElement item_element = source_document.createElement("item");
158 
159  // Fill in details about question.
160  QDomElement question_element = source_document.createElement("question");
161  QDomElement answer_element = source_document.createElement("answer");
162  QDomElement hint_element = source_document.createElement("hint");
163  QDomElement image_element = source_document.createElement("image");
164 
165  question_element.appendChild(source_document.createTextNode(question.question()));
166  answer_element.appendChild(source_document.createTextNode(question.answer()));
167  hint_element.appendChild(source_document.createTextNode(question.hint()));
168 
169  // Read file with image, convert it to base64 and insert into XML bundle.
170  QByteArray picture_encoded = IOFactory::fileToBase64(question.picturePath());
171 
172  if (picture_encoded.isEmpty() || picture_encoded.isNull()) {
173  return QString();
174  }
175 
176  image_element.appendChild(source_document.createTextNode(QString::fromUtf8(picture_encoded)));
177  item_element.appendChild(question_element);
178  item_element.appendChild(answer_element);
179  item_element.appendChild(hint_element);
180  item_element.appendChild(image_element);
181 
182  data_element.appendChild(item_element);
183  }
184 
185  return source_document.toString(XML_BUNDLE_INDENTATION);
186 }
187 
188 bool FlashCardEditor::loadBundleData(const QString &bundle_data) {
189  QDomDocument bundle_document;
190  bundle_document.setContent(bundle_data);
191 
192  QDomNodeList items = bundle_document.documentElement().elementsByTagName("item");
193 
194  for (int i = 0; i < items.size(); i++) {
195  QDomNode item = items.at(i);
196 
197  if (item.isElement()) {
198  QString question = item.namedItem("question").toElement().text();
199  QString answer = item.namedItem("answer").toElement().text();
200  QString hint = item.namedItem("hint").toElement().text();
201  QString image_data = item.namedItem("image").toElement().text();
202 
203  if (question.isEmpty() || answer.isEmpty() || image_data.isEmpty()) {
204  // TODO: error
205  continue;
206  }
207  else {
208  // TODO: POKRACOVAT TADY, prevadeni z base64 do souboru blbne.
209  // https://www.google.cz/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=qt%20base64%20to%20file
210  QString output_directory = qApp->templateManager()->tempDirectory();
211  QString target_image_file = output_directory +
212  QString("/image_%1.png").arg(i);
213 
214  if (IOFactory::base64ToFile(image_data, target_image_file)) {
215  // Picture from the item was saved to disk.
216  addQuestion(question, answer, hint, target_image_file);
217  }
218  else {
219  // TODO: errro
220  }
221  }
222  }
223  else {
224  continue;
225  }
226  }
227 
228  // Load author & name.
229  m_ui->m_txtAuthor->lineEdit()->setText(bundle_document.documentElement().namedItem("author").namedItem("name").toElement().text());
230  m_ui->m_txtName->lineEdit()->setText(bundle_document.documentElement().namedItem("title").toElement().text());
231 
232  return true;
233 }
234 
235 QList<FlashCardQuestion> FlashCardEditor::activeQuestions() const {
236  QList<FlashCardQuestion> questions;
237 
238  for (int i = 0; i < m_ui->m_listQuestions->count(); i++) {
239  questions.append(m_ui->m_listQuestions->item(i)->data(Qt::UserRole).value<FlashCardQuestion>());
240  }
241 
242  return questions;
243 }
244 
246  return m_ui->m_txtName->lineEdit()->text();
247 }
248 
250  return m_ui->m_txtAuthor->lineEdit()->text();
251 }
252 
253 void FlashCardEditor::checkAuthor() {
254  if (m_ui->m_txtAuthor->lineEdit()->text().isEmpty()) {
255  m_ui->m_txtAuthor->setStatus(WidgetWithStatus::Error,
256  tr("No author is specified."));
257  }
258  else {
259  m_ui->m_txtAuthor->setStatus(WidgetWithStatus::Ok,
260  tr("Author is specified."));
261  }
262 }
263 
264 void FlashCardEditor::checkHint() {
265  if (m_ui->m_txtHint->lineEdit()->text().isEmpty()) {
266  m_ui->m_txtHint->setStatus(WidgetWithStatus::Warning,
267  tr("Hint is not specified."));
268  }
269  else {
270  m_ui->m_txtHint->setStatus(WidgetWithStatus::Ok,
271  tr("Hint is specified."));
272  }
273 }
274 
275 void FlashCardEditor::checkQuestion() {
276  if (m_ui->m_txtQuestion->lineEdit()->text().isEmpty()) {
277  m_ui->m_txtQuestion->setStatus(WidgetWithStatus::Warning,
278  tr("Question is not specified."));
279  }
280  else {
281  m_ui->m_txtQuestion->setStatus(WidgetWithStatus::Ok,
282  tr("Question is specified."));
283  }
284 }
285 
286 void FlashCardEditor::checkAnswer() {
287  if (m_ui->m_txtAnswer->lineEdit()->text().isEmpty()) {
288  m_ui->m_txtAnswer->setStatus(WidgetWithStatus::Error,
289  tr("Answer is not specified."));
290  }
291  else {
292  m_ui->m_txtAnswer->setStatus(WidgetWithStatus::Ok,
293  tr("Answer is specified."));
294  }
295 }
296 
297 void FlashCardEditor::checkName() {
298  if (m_ui->m_txtName->lineEdit()->text().isEmpty()) {
299  m_ui->m_txtName->setStatus(WidgetWithStatus::Error,
300  tr("No collection name is specified."));
301  }
302  else {
303  m_ui->m_txtName->setStatus(WidgetWithStatus::Ok,
304  tr("Collection name is specified."));
305  }
306 }
307 
308 void FlashCardEditor::configureUpDown() {
309  if (m_ui->m_listQuestions->count() > 1) {
310  int index = m_ui->m_listQuestions->currentRow();
311 
312  if (index == 0) {
313  m_ui->m_btnQuestionUp->setEnabled(false);
314  m_ui->m_btnQuestionDown->setEnabled(true);
315  }
316  else if (index == m_ui->m_listQuestions->count() - 1) {
317  m_ui->m_btnQuestionUp->setEnabled(true);
318  m_ui->m_btnQuestionDown->setEnabled(false);
319  }
320  else {
321  m_ui->m_btnQuestionUp->setEnabled(true);
322  m_ui->m_btnQuestionDown->setEnabled(true);
323  }
324  }
325  else {
326  m_ui->m_btnQuestionUp->setEnabled(false);
327  m_ui->m_btnQuestionDown->setEnabled(false);
328  }
329 }
330 
331 void FlashCardEditor::moveQuestionUp() {
332  int index = m_ui->m_listQuestions->currentRow();
333 
334  m_ui->m_listQuestions->insertItem(index - 1, m_ui->m_listQuestions->takeItem(index));
335  m_ui->m_listQuestions->setCurrentRow(index - 1);
336 
337  emit changed();
338 }
339 
340 void FlashCardEditor::moveQuestionDown() {
341  int index = m_ui->m_listQuestions->currentRow();
342 
343  m_ui->m_listQuestions->insertItem(index + 1, m_ui->m_listQuestions->takeItem(index));
344  m_ui->m_listQuestions->setCurrentRow(index + 1);
345 
346  emit changed();
347 }
348 
349 void FlashCardEditor::loadPicture(const QString& picture_path) {
350  if (!picture_path.isEmpty()) {
351  m_ui->m_lblPictureView->setPixmap(QPixmap(picture_path).scaled(m_ui->m_lblPictureView->size(),
352  Qt::KeepAspectRatio));
353  m_ui->m_lblPictureFile->setStatus(WidgetWithStatus::Ok,
354  tr("Picture is selected."),
355  tr("Picture is selected."));
356  }
357  else {
358  m_ui->m_lblPictureView->setPixmap(QPixmap());
359  m_ui->m_lblPictureFile->setStatus(WidgetWithStatus::Error,
360  tr("Picture is not selected."),
361  tr("No picture is selected."));
362  }
363 
364  m_ui->m_lblPictureFile->label()->setToolTip(QDir::toNativeSeparators(picture_path));
365 }
366 
367 void FlashCardEditor::addQuestion(const QString &question,
368  const QString &answer,
369  const QString &hint,
370  const QString &picture_path) {
371  int marked_question = m_ui->m_listQuestions->currentRow();
372  FlashCardQuestion new_question;
373  QListWidgetItem *new_item = new QListWidgetItem();
374 
375  new_question.setQuestion(question);
376  new_question.setHint(hint);
377  new_question.setAnswer(answer);
378  new_question.setPicturePath(picture_path);
379 
380  new_item->setText(new_question.question());
381  new_item->setData(Qt::UserRole, QVariant::fromValue(new_question));
382 
383  if (m_ui->m_listQuestions->count() == 0) {
384  // We are adding first question.
385  setEditorsEnabled(true);
386 
387  m_ui->m_btnQuestionRemove->setEnabled(true);
388 
389  m_ui->m_listQuestions->insertItem(0, new_item);
390  m_ui->m_listQuestions->setCurrentRow(0);
391  }
392  else {
393  m_ui->m_listQuestions->insertItem(marked_question + 1, new_item);
394  m_ui->m_listQuestions->setCurrentRow(marked_question + 1);
395  }
396 
397  updateQuestionCount();
398 }
399 
400 void FlashCardEditor::addQuestion() {
401  addQuestion(tr("What animal do you see on the picture?"),
402  tr("cat"),
403  tr("This animal is hated by dog."),
404  APP_TEMPLATES_PATH + QDir::separator() +
405  core()->entryPoint()->baseFolder() + QDir::separator() +
406  "cat.png");
407  launch();
408  emit changed();
409 }
410 
411 void FlashCardEditor::setEditorsEnabled(bool enabled) {
412  m_ui->m_groupQuestionEditor->setEnabled(enabled);
413 }
414 
415 void FlashCardEditor::loadQuestion(int index) {
416  m_ui->m_txtQuestion->blockSignals(true);
417  m_ui->m_lblPictureFile->label()->blockSignals(true);
418 
419  if (index >= 0) {
420  FlashCardQuestion question = m_ui->m_listQuestions->item(index)->data(Qt::UserRole).value<FlashCardQuestion>();
421 
422  m_ui->m_txtQuestion->lineEdit()->setText(question.question());
423  m_ui->m_txtAnswer->lineEdit()->setText(question.answer());
424  m_ui->m_txtHint->lineEdit()->setText(question.hint());
425  loadPicture(question.picturePath());
426 
427  m_activeQuestion = question;
428  }
429  else {
430  m_ui->m_txtQuestion->lineEdit()->setText(QString());
431  m_ui->m_txtAnswer->lineEdit()->setText(QString());
432  m_ui->m_txtHint->lineEdit()->setText(QString());
433  loadPicture(QString());
434  }
435 
436  m_ui->m_txtQuestion->blockSignals(false);
437  m_ui->m_lblPictureFile->label()->blockSignals(false);
438 
439  checkAnswer();
440  checkHint();
441 
442  QTimer::singleShot(0, this, SLOT(configureUpDown()));
443 }
444 
445 void FlashCardEditor::saveQuestion() {
446  m_activeQuestion.setQuestion(m_ui->m_txtQuestion->lineEdit()->text());
447  m_activeQuestion.setAnswer(m_ui->m_txtAnswer->lineEdit()->text());
448  m_activeQuestion.setHint(m_ui->m_txtHint->lineEdit()->text());
449  m_activeQuestion.setPicturePath(m_ui->m_lblPictureFile->label()->toolTip());
450 
451  m_ui->m_listQuestions->currentItem()->setData(Qt::UserRole, QVariant::fromValue(m_activeQuestion));
452  m_ui->m_listQuestions->currentItem()->setText(m_activeQuestion.question());
453 
454  emit changed();
455 }
456 
457 void FlashCardEditor::removeQuestion() {
458  int current_row = m_ui->m_listQuestions->currentRow();
459 
460  if (current_row >= 0) {
461  if (m_ui->m_listQuestions->count() == 1) {
462  // We are removing last visible question.
463  setEditorsEnabled(false);
464 
465  m_ui->m_btnQuestionRemove->setEnabled(false);
466  }
467 
468  delete m_ui->m_listQuestions->takeItem(current_row);
469  }
470 
471  updateQuestionCount();
472  launch();
473  emit changed();
474 }
475 
476 void FlashCardEditor::onAnswerChanged(const QString& new_answer) {
477  Q_UNUSED(new_answer)
478 
479  checkAnswer();
480  saveQuestion();
481 }
482 
483 void FlashCardEditor::onHintChanged(const QString& new_hint) {
484  Q_UNUSED(new_hint)
485 
486  checkHint();
487  saveQuestion();
488 }
489 
490 void FlashCardEditor::onAuthorChanged(const QString& new_author) {
491  Q_UNUSED(new_author)
492 
493  checkAuthor();
494 
495  launch();
496  emit changed();
497 }
498 
499 void FlashCardEditor::onNameChanged(const QString& new_name) {
500  Q_UNUSED(new_name)
501 
502  checkName();
503 
504  launch();
505  emit changed();
506 }
507 
508 void FlashCardEditor::selectPicture() {
509  QString selected_picture = QFileDialog::getOpenFileName(this, tr("Select picture for question"),
510  m_ui->m_lblPictureFile->label()->toolTip(),
511  tr("Images (*.gif *.jpg *.png)"),
512  0);
513 
514  if (!selected_picture.isEmpty()) {
515  loadPicture(selected_picture);
516  saveQuestion();
517  }
518 }
static bool base64ToFile(const QString &source_data, const QString &target_file)
Takes base64 byte array and saves it into file.
Definition: iofactory.cpp:125
bool loadBundleData(const QString &bundle_data)
Loads editor state from XML bundle.
TemplateCore * core() const
Access to associated template core.
static QByteArray fileToBase64(const QString &file_name)
Takes input file and reads it into base64 byte array.
Definition: iofactory.cpp:111
void changed()
Emitted everytime any child widget of editor changes its contents.
Represents the editor of the template.
Icon theme manipulator and provider.
Definition: iconfactory.h:47
QString authorName()
Access to author name of current editor.
The core class container for single template.
Definition: templatecore.h:43
QString projectName()
Access to project name of current editor.
QIcon fromTheme(const QString &name)
Returns icon from active theme.
Definition: iconfactory.h:58
virtual void launch()
Executed when given template with this editor is launched.
QString generateBundleData()
Generates RAW data which represent data of this template.
static IconFactory * instance()
Singleton getter.
Definition: iconfactory.cpp:48
bool canGenerateApplications()
Specifies if template can generate applications or not.