diff --git a/CMakeLists.txt b/CMakeLists.txt index c925dfbe..ef8168f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,176 +1,172 @@ cmake_minimum_required(VERSION 3.5.0) # KDE Application Version, managed by release script set(RELEASE_SERVICE_VERSION_MAJOR "20") set(RELEASE_SERVICE_VERSION_MINOR "03") set(RELEASE_SERVICE_VERSION_MICRO "80") set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}") project(parley VERSION ${RELEASE_SERVICE_VERSION}) set(QT_MIN_VERSION "5.2.0") set(KF5_MIN_VERSION "5.22.0") find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) include(KDEInstallDirs) include(KDECompilerSettings NO_POLICY_SCOPE) include(KDECMakeSettings) include(FeatureSummary) #Allows QString concatenation to use a single memory allocation per source line. add_definitions(-DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS) add_definitions(-DQT_NO_URL_CAST_FROM_STRING) -if (EXISTS "${CMAKE_SOURCE_DIR}/.git") - add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x050e00) - add_definitions(-DKF_DISABLE_DEPRECATED_BEFORE_AND_AT=0x058000) -endif() find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED Core Svg Concurrent Multimedia WebEngineWidgets ) find_package(KF5 ${KF5_MIN_VERSION} MODULE REQUIRED COMPONENTS DocTools #to produce the docbook CoreAddons Config Crash I18n KIO NewStuff Kross Sonnet KCMUtils XmlGui Notifications #Plasma ) find_package(LibKEduVocDocument) set_package_properties(LibKEduVocDocument PROPERTIES TYPE REQUIRED PURPOSE "Required to manipulate the KDE word files." ) find_package(LibXslt) set_package_properties(LibXslt PROPERTIES URL "http://xmlsoft.org/XSLT" TYPE OPTIONAL PURPOSE "Required to build HTML export for Parley." ) find_package(LibXml2) set_package_properties(LibXml2 PROPERTIES URL "http://xmlsoft.org" TYPE OPTIONAL PURPOSE "Required to build HTML export for Parley." ) #@todo frameworks restore Xattica # find_package(LibAttica) # macro_log_feature(LIBATTICA_FOUND "libattica" "A library to access Open Collaboration Service providers" "https://kde.org" FALSE "" "Required to access OCS providers in get hot new stuff.") # at the end, output the configuration configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/config-parley.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-parley.h ) # let our config-parley.h be found first in any case include_directories (BEFORE ${CMAKE_CURRENT_BINARY_DIR}) # Define link libs for parley executable and unittests set( parley_LINK_LIBS LibKEduVocDocumentImport__KEduVocDocument KF5::KrossCore # KF5::ConfigCore Qt5::Core # Qt5::Qml Qt5::Svg KF5::I18n KF5::NewStuff KF5::SonnetCore KF5::SonnetUi KF5::KCMUtils KF5::XmlGui KF5::Completion KF5::ConfigWidgets KF5::ConfigGui KF5::WidgetsAddons KF5::TextWidgets KF5::KIOWidgets Qt5::Widgets Qt5::Gui Qt5::Concurrent Qt5::Multimedia KF5::Notifications KF5::Crash KF5::CoreAddons Qt5::WebEngineWidgets ) message(STATUS "cmake cflags ${CMAKE_C_FLAGS}") message(STATUS "cmake link flags ${CMAKE_EXE_LINKER_FLAGS}") if(${LIBXSLT_FOUND} AND ${LIBXML2_FOUND}) set(HAVE_LIBXSLT 1) message(STATUS "XLST found") set( parley_LINK_LIBS ${parley_LINK_LIBS} ${LIBXML2_LIBRARIES} ${LIBXSLT_LIBRARIES}) include_directories(${LIBXML2_INCLUDE_DIR} ${LIBXSLT_INCLUDE_DIR}) else(${LIBXSLT_FOUND} AND ${LIBXML2_FOUND}) set(HAVE_LIBXSLT 0) message(STATUS "XSLT not found, but not required") endif(${LIBXSLT_FOUND} AND ${LIBXML2_FOUND}) if(X11_Xrender_FOUND) message(STATUS "X11 Xrender found") set( parley_LINK_LIBS ${parley_LINK_LIBS} ${X11_LIBRARIES} ${X11_Xrender_LIB}) else(X11_Xrender_FOUND) message(STATUS "X11 Xrender not found, but not required") endif(X11_Xrender_FOUND) #define the include libraries for both the parley executable and the autotests # set( parley_INCLUDE_DIRS # # ${LIBATTICA_INCLUDE_DIR} # # libkdeedu/keduvocdocument contains the library for reading and writing vocabulary files # ${LIBKDEEDU_INCLUDE_DIR} # ) if(BUILD_TESTING) add_subdirectory( autotests ) add_subdirectory( tests ) endif() add_subdirectory( docs ) add_subdirectory( src ) add_subdirectory( icons ) add_subdirectory( plugins ) add_subdirectory( themes ) add_subdirectory( tipofday ) if(HAVE_LIBXSLT) add_subdirectory( xslt ) endif(HAVE_LIBXSLT) #@todo unused This seems unused delete December 2014 #add_subdirectory( scripts_test ) #@todo frameworks restore/rewrite the plasmoid #add_subdirectory(plasmoid) install(FILES org.kde.parley.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/src/dashboard/barwidget.cpp b/src/dashboard/barwidget.cpp index 735f36e7..49b36daa 100755 --- a/src/dashboard/barwidget.cpp +++ b/src/dashboard/barwidget.cpp @@ -1,142 +1,143 @@ /*************************************************************************** Copyright 2014 Andreas Xavier Copyright 2014 Inge Wallin ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ // Own #include "barwidget.h" // Qt #include #include #include #include +#include // KDE #include // Parley #include "collectionwidget.h" // for COLLWIDTH, etc // FIXME: Find a better home for this variable. ConfidenceColors globalColors = ConfidenceColors(); BarWidget::BarWidget(QWidget *parent) : QWidget(parent) { } BarWidget::BarWidget(WordCount *dueWords, QWidget *parent) : QWidget(parent) { QPalette palette(BarWidget::palette()); palette.setColor(backgroundRole(), Qt::white); setPalette(palette); for (int i = 0; i <= KV_MAX_GRADE; i++) { m_dueWords[i] = dueWords->grades[i]; } m_totalDueWords = dueWords->totalWords; m_percentageCompleted = dueWords->percentageCompleted(); } void BarWidget::setDue(WordCount &wc) { for (int i = 0; i <= KV_MAX_GRADE; ++i) { m_dueWords[i] = wc.grades[i]; } m_totalDueWords = wc.totalWords; update(); } void BarWidget::paintEvent(QPaintEvent *) { QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing, true); const int legendWidth = COLLWIDTH - 10; const int legendHeight = 45; const int legendOffsetY = 0; const int legendOffsetX = 0; //const int alphaValueIncrement = 35; int gradeBarWidth[9]; gradeBarWidth[8] = 0; int gradeBarOffset[9]; gradeBarOffset[8] = 0; //qDebug() << "percentage completed: " << m_percentageCompleted; //qDebug() << "Total due words: " << m_totalDueWords; if (m_percentageCompleted < 100) { for(int j = 7; j >= 0; j--) { gradeBarWidth[j] = (float)(m_dueWords[j]) / (float)(m_totalDueWords) * legendWidth; gradeBarOffset[j] = gradeBarOffset[j+1] + gradeBarWidth[j+1]; } } else { for(int j = 6; j >= 0; j--) { gradeBarWidth[j] = 0; gradeBarOffset[j] = legendWidth; } gradeBarWidth[7] = legendWidth; gradeBarOffset[7] = 0; } if (m_percentageCompleted < 100 && m_totalDueWords == 0) { for(int j = 6; j >= 0; j--) { gradeBarWidth[j] = 0; gradeBarOffset[j] = legendWidth; } gradeBarWidth[7] = legendWidth; gradeBarOffset[7] = 0; } QPen penBar(QColor(255,255,255)); painter.setPen(penBar); QRectF roundedRect(0, 0, legendWidth, legendHeight); roundedRect.adjust(1, 1, -1, -1); QPainterPath roundedPath; roundedPath.addRoundedRect(roundedRect, 8.0, 8.0); for (int i = 7; i >= 0; i--) { QRectF barElement(0 + legendOffsetX + gradeBarOffset[i], 0 + legendOffsetY, gradeBarWidth[i], legendHeight); QPainterPath barElementPath; barElementPath.addRect(barElement); QPainterPath barElementIntersectedPath = roundedPath.intersected(barElementPath); QColor color; if (m_totalDueWords == 0 && m_percentageCompleted < 100) { color = QColor(0, 0, 0, 128); } else { color = globalColors.longTermColors[i]; } painter.setBrush(QBrush(color)); painter.drawPath(barElementIntersectedPath); } QPen pen(QColor(255,255,255)); //QPen pen(QColor(0, 0, 0)); painter.setPen(pen); if (m_percentageCompleted < 100) { painter.drawText(0, 0, legendWidth, legendHeight, Qt::AlignCenter | Qt::TextWordWrap, i18np("%1 word due", "%1 words due", m_totalDueWords)); } else { painter.drawText(0, 0, legendWidth, legendHeight, Qt::AlignCenter | Qt::TextWordWrap, i18n("Fully learned")); } } diff --git a/src/dashboard/gradereferencewidget.cpp b/src/dashboard/gradereferencewidget.cpp index 1af5b7d3..0bf515a2 100644 --- a/src/dashboard/gradereferencewidget.cpp +++ b/src/dashboard/gradereferencewidget.cpp @@ -1,62 +1,63 @@ /*************************************************************************** Copyright 2014 Andreas Xavier Copyright 2014 Inge Wallin ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ // Own #include "gradereferencewidget.h" // Qt #include #include #include #include +#include // KDE #include // Parley #include "barwidget.h" // for gradeColor^WglobalColors GradeReferenceWidget::GradeReferenceWidget(QWidget *parent) : QWidget(parent) { } void GradeReferenceWidget::paintEvent(QPaintEvent *) { QPainter painter(this); const int legendWidth = this->width(); const int legendHeight = this->height(); const int legendOffsetY = 0; const int legendOffsetX = (this->width() / 2) - (legendWidth / 2); const int gradeBarWidth = this->width()/8; //const int alphaValueIncrement = 35; QRect roundedRect(0 + legendOffsetX, 0 + legendOffsetY, legendWidth, legendHeight); roundedRect.adjust(1, 1, -1, -1); QPainterPath roundedPath; roundedPath.addRoundedRect(roundedRect, 2.0, 2.0); for (int i = 7; i >= 0; --i) { QRectF barElement(0 + legendOffsetX + (7 - i) * gradeBarWidth, 0 + legendOffsetY, gradeBarWidth, legendHeight); QPainterPath barElementPath; barElementPath.addRect(barElement); QPainterPath barElementIntersectedPath = roundedPath.intersected(barElementPath); QColor color = globalColors.longTermColors[i]; painter.setBrush(QBrush(color)); painter.drawPath(barElementIntersectedPath); } } diff --git a/src/editor/vocabularydelegate.cpp b/src/editor/vocabularydelegate.cpp index 5b888147..77396bb4 100644 --- a/src/editor/vocabularydelegate.cpp +++ b/src/editor/vocabularydelegate.cpp @@ -1,372 +1,373 @@ /*************************************************************************** Copyright 2006, 2007 Peter Hedlund Copyright 2007 Frederik Gladhorn ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "vocabularydelegate.h" #include "vocabularymodel.h" #include "vocabularyfilter.h" #include "prefs.h" #include "languagesettings.h" #include "readonlycontainermodel.h" #include #include #include #include #include #include #include #include #include #include #include #include +#include using namespace Editor; VocabularyDelegate::VocabularyDelegate(QObject *parent) : QItemDelegate(parent), m_doc(0), m_translator(0) { } QSet VocabularyDelegate::getTranslations(const QModelIndex & index) const { if (Prefs::automaticTranslation() == false) return QSet(); QSet translations; //translations of this column from all the other languages int language = index.column() / VocabularyModel::EntryColumnsMAX; QString toLanguage = m_doc->identifier(language).locale(); //iterate through all the Translation columns for (int i = 0; i < index.model()->columnCount(index.parent()); i ++) { if (VocabularyModel::columnType(i) == VocabularyModel::Translation) { //translation column QString fromLanguage = m_doc->identifier(VocabularyModel::translation(i)).locale(); QString word = index.model()->index(index.row(), i, QModelIndex()).data().toString(); if (fromLanguage != toLanguage) { // qDebug() << fromLanguage << toLanguage << word; //get the word translations and add them to the translations set QSet * tr = m_translator->getTranslation(word, fromLanguage, toLanguage); if (tr) translations.unite(* (tr)); } } } return translations; } QWidget * VocabularyDelegate::createEditor(QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index) const { Q_UNUSED(option); /// as long as it's unused if (!index.isValid()) { return 0; } switch (VocabularyModel::columnType(index.column())) { case VocabularyModel::WordClass: { if (!m_doc) return 0; KComboBox *wordTypeCombo = new KComboBox(parent); WordTypeBasicModel *basicWordTypeModel = new WordTypeBasicModel(parent); wordTypeCombo->setModel(basicWordTypeModel); QTreeView *view = new QTreeView(parent); view->setModel(basicWordTypeModel); wordTypeCombo->setView(view); view->header()->setVisible(false); view->setRootIsDecorated(true); basicWordTypeModel->setDocument(m_doc); view->expandAll(); qDebug() << "index data" << index.data().toString(); //view->setCurrentItem(); return wordTypeCombo; } case VocabularyModel::Translation: if (!m_doc || !m_translator) return 0; if (VocabularyModel::columnType(index.column()) == VocabularyModel::Translation) { //get the translations of this word (fetch only with the help of scripts, if enabled) QSet translations = getTranslations(index); //create combo box //if there is only one word and that is the suggestion word (in translations) then don't create the combobox if (!translations.isEmpty() && !(translations.size() == 1 && (*translations.begin()) == index.model()->data(index, Qt::DisplayRole).toString())) { KComboBox *translationCombo = new KComboBox(parent); translationCombo->setFrame(false); translationCombo->addItems(translations.values()); translationCombo->setEditable(true); translationCombo->setFont(index.model()->data(index, Qt::FontRole).value()); translationCombo->setEditText(index.model()->data(index, Qt::DisplayRole).toString()); translationCombo->completionObject()->setItems(translations.values()); return translationCombo; } } // no break - we fall back to a line edit if there are not multiple translations fetched onlin // fallthrough default: { QLineEdit *editor = new QLineEdit(parent); editor->setFrame(false); editor->setFont(index.model()->data(index, Qt::FontRole).value()); editor->setText(index.model()->data(index, Qt::DisplayRole).toString()); QString locale = index.model()->data(index, VocabularyModel::LocaleRole).toString(); if (!locale.isEmpty()) { LanguageSettings settings(locale); settings.load(); QString layout = settings.keyboardLayout(); if (!layout.isEmpty()) { QDBusInterface kxkb(QStringLiteral("org.kde.keyboard"), QStringLiteral("/Layouts"), QStringLiteral("org.kde.KeyboardLayouts")); if (kxkb.isValid()) { kxkb.call(QStringLiteral("setLayout"), layout); } } } return editor; } } } bool VocabularyDelegate::helpEvent(QHelpEvent *event, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &index) { Q_UNUSED(view); if (event->type() == QEvent::ToolTip) { QPainterPath audioPainterPath; QPainterPath imagePainterPath; audioPainterPath.addPolygon(audioPolygon(option)); imagePainterPath.addPolygon(imagePolygon(option)); int column = columnType(index.column()); if (audioPainterPath.contains(event->pos()) && hasAudio(index) && (column == Translation || column == Pronunciation)) { QToolTip::showText(event->globalPos(), i18n("Sound file selected: %1", audioUrl(index))); } else if (imagePainterPath.contains(event->pos()) && hasImage(index) && (column == Translation || column == Pronunciation)) { QToolTip::showText(event->globalPos(), i18n("Image file selected: %1", imageUrl(index))); } else { QToolTip::hideText(); event->ignore(); } return true; } return false; } QPolygon VocabularyDelegate::audioPolygon(const QStyleOptionViewItem &option) const { QRect rect = option.rect; QPolygon polygon; polygon << QPoint(rect.x() + rect.width() - 10, rect.y()); polygon << QPoint(rect.x() + rect.width(), rect.y()); polygon << QPoint(rect.x() + rect.width(), rect.y() + 10); return polygon; } QPolygon VocabularyDelegate::imagePolygon(const QStyleOptionViewItem &option) const { QRect rect = option.rect; QPolygon polygon; polygon << QPoint(rect.x() + rect.width() - 10, rect.y() + rect.height()); polygon << QPoint(rect.x() + rect.width(), rect.y() + rect.height()); polygon << QPoint(rect.x() + rect.width(), rect.y() + rect.height() - 10); return polygon; } bool VocabularyDelegate::hasAudio(const QModelIndex &index) const { return !audioUrl(index).isEmpty(); } bool VocabularyDelegate::hasImage(const QModelIndex &index) const { return !imageUrl(index).isEmpty(); } QString VocabularyDelegate::audioUrl(const QModelIndex &index) const { QVariant audioVar = index.data(VocabularyModel::AudioRole); QString audioUrl = audioVar.toString(); return audioUrl; } QString VocabularyDelegate::imageUrl(const QModelIndex &index) const { QVariant imageVar = index.data(VocabularyModel::ImageRole); QString imageUrl = imageVar.toString(); return imageUrl; } int VocabularyDelegate::columnType(int column) { return column % EntryColumnsMAX; } void VocabularyDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QItemDelegate::paint(painter, option, index); painter->save(); int column = columnType(index.column()); if (hasAudio(index) == true && (column == Translation || column == Pronunciation)) { painter->setPen(QPen(Qt::red)); painter->setBrush(QBrush(Qt::red)); painter->drawPolygon(audioPolygon(option)); } if (hasImage(index) == true && (column == Translation || column == Pronunciation)) { painter->setPen(QPen(Qt::blue)); painter->setBrush(QBrush(Qt::blue)); painter->drawPolygon(imagePolygon(option)); } painter->restore(); } void VocabularyDelegate::setEditorData(QWidget * editor, const QModelIndex & index) const { if (!index.isValid()) { return; } switch (VocabularyModel::columnType(index.column())) { case (VocabularyModel::Translation) : { QString value = index.model()->data(index, Qt::DisplayRole).toString(); KComboBox * translationCombo = qobject_cast (editor); if (translationCombo) { translationCombo->setEditText(value); if (value.isEmpty()) { // show the translations that were fetched as popup translationCombo->showPopup(); } break; } } // fallthrough default: { QString value = index.model()->data(index, Qt::DisplayRole).toString(); QLineEdit *lineEdit = qobject_cast (editor); if (lineEdit) { lineEdit->setText(value); } } } } void VocabularyDelegate::setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const { if (!index.isValid()) { return; } switch (VocabularyModel::columnType(index.column())) { case (VocabularyModel::WordClass) : { qDebug() << "word type editor"; KComboBox *combo = qobject_cast (editor); if (!combo) { return; } qDebug() << "combo" << combo->currentText(); QModelIndex comboIndex = combo->view()->currentIndex(); KEduVocWordType* wordType = static_cast(comboIndex.internalPointer()); // the root is the same as no word type if (wordType && wordType->parent() == 0) { wordType = 0; } VocabularyFilter *filter = qobject_cast (model); VocabularyModel *vocModel = qobject_cast ((filter)->sourceModel()); Q_ASSERT(vocModel); QVariant data = vocModel->data(filter->mapToSource(index), VocabularyModel::EntryRole); KEduVocExpression *expression = data.value(); Q_ASSERT(expression); int translationId = VocabularyModel::translation(index.column()); expression->translation(translationId)->setWordType(wordType); model->setData(index, combo->currentText()); break; } case (VocabularyModel::Translation) : { QLineEdit *lineEdit = qobject_cast (editor); if (lineEdit) { model->setData(index, lineEdit->text()); } break; } default: { QLineEdit *lineEdit = qobject_cast (editor); if (lineEdit) { model->setData(index, lineEdit->text()); } } } } void VocabularyDelegate::setDocument(KEduVocDocument * doc) { m_doc = doc; } /* QPair< QString, QString > VocabularyDelegate::guessWordType(const QString & entry, int language) const { qDebug() << "guessing word type for: " << entry; QString article = entry.section(" ", 0, 0); if ( article.length() < entry.length() ) { if ( article == ->identifier(language).articles().article(KEduVocWordFlag::Singular| KEduVocWordFlag::Definite| KEduVocWordFlag::Masculine) ) { qDebug() << "Noun masculine"; return qMakePair(m_doc->wordTypes().specialTypeNoun(), m_doc->wordTypes().specialTypeNounMale()); } } return qMakePair(QString(), QString()); } */ VocabularyDelegate::WordTypeBasicModel::WordTypeBasicModel(QObject * parent) : ReadonlyContainerModel(KEduVocContainer::WordType, parent) { } KEduVocContainer * VocabularyDelegate::WordTypeBasicModel::rootContainer() const { if (!m_doc) { return 0; } return m_doc->wordTypeContainer(); } /** * Sets the member variable m_translator to a Translator object * @param translator Translator Object to be used for retrieving word translations */ void VocabularyDelegate::setTranslator(Translator* translator) { m_translator = translator; } diff --git a/src/practice/summarybarwidget.cpp b/src/practice/summarybarwidget.cpp index b1b6e6a8..1c910cf2 100644 --- a/src/practice/summarybarwidget.cpp +++ b/src/practice/summarybarwidget.cpp @@ -1,177 +1,178 @@ /*************************************************************************** Copyright 2010 Daniel Laidig *************************************************************************** *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "summarybarwidget.h" #include #include #include #include #include #include #include +#include using namespace Practice; SummaryBarWidget::SummaryBarWidget(QWidget *parent) : QWidget(parent), m_correct(0), m_wrong(0), m_notAnswered(0), m_total(0) { setMinimumHeight(30); m_layout = new QHBoxLayout(this); setLayout(m_layout); layout()->setContentsMargins(0, layout()->contentsMargins().top() + BAR_HEIGHT, 0, 0); setupCaption(); } void SummaryBarWidget::paintEvent(QPaintEvent *event) { QWidget::paintEvent(event); QPainter painter(this); QLinearGradient linearGrad(QPoint(0, 0), QPoint(rect().width(), 0)); KColorScheme scheme(QPalette::Active); QColor correctColor = scheme.foreground(KColorScheme::PositiveText).color(); QColor wrongColor = scheme.foreground(KColorScheme::NegativeText).color(); QColor notAnsweredColor = scheme.foreground(KColorScheme::NormalText).color(); if (!m_total) { return; } double correctPos = double(m_correct) / m_total; double wrongPos = correctPos + double(m_wrong) / m_total; double margin = double(2) / rect().width(); if (m_correct > 0) { linearGrad.setColorAt(qMax(correctPos - margin, 0.0), correctColor); } if (m_wrong > 0) { linearGrad.setColorAt(qMin(correctPos + margin, 1.0), wrongColor); linearGrad.setColorAt(qMax(wrongPos - margin, 0.0), wrongColor); } if (m_notAnswered > 0) { linearGrad.setColorAt(qMin(wrongPos + margin, 1.0), notAnsweredColor); } QRect r = rect(); r.setHeight(BAR_HEIGHT); r.adjust(1, 1, -1, -1); QPainterPath path; path.addRoundedRect(r, 3.0, 3.0); painter.setBrush(QBrush(linearGrad)); painter.drawPath(path); } void SummaryBarWidget::setStatistics(int correct, int wrong, int notAnswered) { m_correct = correct; m_wrong = wrong; m_notAnswered = notAnswered; m_total = m_correct + m_wrong + m_notAnswered; m_correctCaption->setText(i18nc("test results", "%1 % correct", qRound(m_correct * 100.0 / m_total))); m_correctCaption->setToolTip(correctText()); m_wrongCaption->setText(i18nc("test results", "%1 % wrong", qRound(m_wrong * 100.0 / m_total))); m_wrongCaption->setToolTip(wrongText()); m_notAnsweredCaption->setText(i18nc("test results", "%1 % not answered", qRound(m_notAnswered * 100.0 / m_total))); m_notAnsweredCaption->setToolTip(notAnsweredText()); update(); } bool SummaryBarWidget::event(QEvent *event) { if (event->type() == QEvent::ToolTip) { QHelpEvent *helpEvent = static_cast(event); if (!m_total) return QWidget::event(event); if (helpEvent->y() >= BAR_HEIGHT) { return false; } int correctPos = int(rect().width() * double(m_correct) / m_total); int wrongPos = correctPos + int(rect().width() * double(m_wrong) / m_total); if (helpEvent->x() <= correctPos) { setToolTip(correctText()); } else if (helpEvent->x() <= wrongPos) { setToolTip(wrongText()); } else { setToolTip(notAnsweredText()); } } return QWidget::event(event); } void SummaryBarWidget::setupCaption() { QLabel *correctColorLabel = new QLabel(this); m_layout->addWidget(correctColorLabel); m_correctCaption = new QLabel(this); m_layout->addWidget(m_correctCaption); m_layout->addSpacing(10); QLabel *wrongColorLabel = new QLabel(this); m_layout->addWidget(wrongColorLabel); m_wrongCaption = new QLabel(this); m_layout->addWidget(m_wrongCaption); m_layout->addSpacing(10); QLabel *notAnsweredColorLabel = new QLabel(this); m_layout->addWidget(notAnsweredColorLabel); m_notAnsweredCaption = new QLabel(this); m_layout->addWidget(m_notAnsweredCaption); m_layout->addStretch(); KColorScheme scheme(QPalette::Active); correctColorLabel->setPixmap(captionPixmap(scheme.foreground(KColorScheme::PositiveText).color())); wrongColorLabel->setPixmap(captionPixmap(scheme.foreground(KColorScheme::NegativeText).color())); notAnsweredColorLabel->setPixmap(captionPixmap(scheme.foreground(KColorScheme::NormalText).color())); } QString SummaryBarWidget::correctText() { if (!m_total) return QString(); return i18n("Answered correctly on the first attempt: %1 of %2 (%3 %)", m_correct, m_total, qRound(m_correct * 100.0 / m_total)); } QString SummaryBarWidget::wrongText() { if (!m_total) return QString(); return i18n("Answered wrong on the first attempt: %1 of %2 (%3 %)", m_wrong, m_total, qRound(m_wrong * 100.0 / m_total)); } QString SummaryBarWidget::notAnsweredText() { if (!m_total) return QString(); return i18n("Not answered during this practice: %1 of %2 (%3 %)", m_notAnswered, m_total, qRound(m_notAnswered * 100.0 / m_total)); } QPixmap SummaryBarWidget::captionPixmap(QColor color) { QImage image(20, 20, QImage::Format_ARGB32_Premultiplied); image.fill(QColor(Qt::transparent).rgba()); QPainter painter(&image); painter.setBrush(color); painter.setPen(Qt::black); QPainterPath path; path.addRoundedRect(image.rect().adjusted(0, 0, -1, -1), 3.0, 3.0); painter.drawPath(path); return QPixmap::fromImage(image); } diff --git a/src/utils.cpp b/src/utils.cpp index 66b242c9..22746344 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -1,294 +1,295 @@ /*************************************************************************** Copyright 2015 Inge Wallin ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ // Own #include "utils.h" // Qt #include #include +#include // KEduVocDocument library #include #include // Parley #include "prefs.h" // ---------------------------------------------------------------- // class WordCount WordCount::WordCount() { clear(); } void WordCount::clear() { for (int i = 0; i <= KV_MAX_GRADE; ++i) { grades[i] = 0; pregrades[i] = 0; } invalid = 0; initialWords = 0; totalWords = 0; } int WordCount::percentageCompleted() const { // To calculate the percentage done we add: // * 1..KV_MAX_GRADE points for words in the initial phase (grade = 0, pregrade > 0) // * KV_MAX_GRADE * (1..KV_MAX_GRADE) points for words in the long-term phase (grade>0) // So the maximum number of points is KV_MAX_GRADE^2 per word. // // In the final calculation, we exclude all invalid words from the percentage. int points = 0; for (int i = 0; i < KV_MAX_GRADE + 1; ++i) { points += pregrades[i] * i; points += grades[i] * KV_MAX_GRADE * i; } if (totalWords - invalid == 0) { // Don't divide by 0. return 0; } else { return 100 * points / ((totalWords - invalid) * KV_MAX_GRADE * KV_MAX_GRADE); } } void WordCount::fillFromContainer(KEduVocContainer &container, int translationIndex, KEduVocContainer::EnumEntriesRecursive recursive) { clear(); foreach (KEduVocExpression *entry, container.entries(recursive)) { KEduVocTranslation &translation(*entry->translation(translationIndex)); evaluateWord(translation, translation.text()); } } void WordCount::fillFromContainerForPracticeMode(KEduVocContainer &container, int translationIndex, const QStringList &activeConjugationTenses, KEduVocContainer::EnumEntriesRecursive recursive) { KEduVocWordFlags wordTypeToProcess(KEduVocWordFlag::NoInformation); switch (Prefs::practiceMode()) { case Prefs::EnumPracticeMode::GenderPractice: wordTypeToProcess = KEduVocWordFlag::Noun; break; case Prefs::EnumPracticeMode::ConjugationPractice: wordTypeToProcess = KEduVocWordFlag::Verb; break; case Prefs::EnumPracticeMode::ComparisonPractice: wordTypeToProcess = KEduVocWordFlag::Adjective | KEduVocWordFlag::Adverb; break; default: fillFromContainer(container, translationIndex, recursive); return; } clear(); foreach (KEduVocExpression *entry, container.entries(recursive)) { KEduVocTranslation &translation(*entry->translation(translationIndex)); if (isValidForProcessing(translation, wordTypeToProcess)) { switch (wordTypeToProcess) { case KEduVocWordFlag::Noun: { KEduVocText article = translation.article(); evaluateWord(article, translation.text()); } break; case KEduVocWordFlag::Verb: { QStringList conjugationTenses = translation.conjugationTenses(); foreach(const QString &activeTense, activeConjugationTenses) { if (conjugationTenses.contains(activeTense)) { KEduVocConjugation conj = translation.getConjugation(activeTense); foreach (KEduVocWordFlags key, conj.keys()) { KEduVocText person = conj.conjugation(key); evaluateWord(person, person.text()); } } } } break; case KEduVocWordFlag::Adjective | KEduVocWordFlag::Adverb: { KEduVocText comparative = translation.comparativeForm(); evaluateWord(comparative, comparative.text()); KEduVocText superlative = translation.superlativeForm(); evaluateWord(superlative, superlative.text()); } break; } } } } bool WordCount::isValidForProcessing(KEduVocTranslation &trans, KEduVocWordFlags wordType) const { return !trans.isEmpty() && (trans.wordType() != nullptr) && ((trans.wordType()->wordType() & wordType) != 0); } void WordCount::evaluateWord(const KEduVocText &item, const QString &text) { ++totalWords; if (text.isEmpty()) { ++invalid; } else if (item.preGrade() > 0) { // Initial phase (we assume correctness, i.e. if pregrade>0 then grade = 0) ++initialWords; ++pregrades[item.preGrade()]; } else { // Long term or unpracticed ++grades[item.grade()]; } } // ---------------------------------------------------------------- // class confidenceColors ConfidenceColors::ConfidenceColors(ColorScheme colorScheme) { initColors(colorScheme); } void ConfidenceColors::initColors(ColorScheme colorScheme) { switch (colorScheme) { case MultiColorScheme: default: // Not default at the last line. Hope this works... longTermColors[0] = QColor(25,38,41); //longTermColors[1] = QColor(Qt::yellow); longTermColors[1] = QColor(25,38,41,64); longTermColors[2] = QColor(237,21,21); longTermColors[3] = QColor(246,116,0); longTermColors[4] = QColor(201,206,59); longTermColors[5] = QColor(28,220,154); longTermColors[6] = QColor(17,209,22); longTermColors[7] = QColor(61,174,253); initialTermColor = QColor(25,38,41,64); // Find something else invalidColor = QColor(Qt::red); break; case ProgressiveColorScheme: { static const int AlphaMax = 255; static const int AlphaStep = ((AlphaMax - 10) / KV_MAX_GRADE); QColor color; // Confidence 1..max for (int grade = 1; grade <= KV_MAX_GRADE; ++grade) { color = Prefs::gradeColor(); color.setAlpha(AlphaMax - (KV_MAX_GRADE - grade) * AlphaStep); longTermColors[grade] = color; } // Unpracticed (confidence 0) color = QColor("#FFFFFF"); color.setAlpha(AlphaMax); longTermColors[0] = color; // Use one color for all initial phase values color = Prefs::preGradeColor(); color.setAlpha(AlphaMax); initialTermColor = color; // Invalid invalidColor = Prefs::invalidUnitColor(); break; } } // These two are placeholders for the wordcloud background color. frontEndColors[0] = QColor(255,221,217); frontEndColors[1] = QColor(238,232,213); } // ---------------------------------------------------------------- // Various utility functions void paintColorBar(QPainter &painter, const QRect &rect, const WordCount &wordCount, const ConfidenceColors &colors) { // The outline of the total bar. QRectF roundedRect(rect); roundedRect.adjust(1.0, 1.0, -1.0, -1.0); // Set a rounded clipping region to paint the bar segments in QPainterPath clippingPath; clippingPath.addRoundedRect(roundedRect, 2.0, 2.0); painter.setClipPath(clippingPath); qreal xPosition = 0.0; // >0: grade, 0: initial, -1: untrained, -2: invalid for (int i = KV_MAX_GRADE; i >= -2; --i) { qreal fraction; QColor color; // Get the fraction and the color if (i > 0) { // long term fraction = qreal(wordCount.grades[i]) / qreal(wordCount.totalWords); color = colors.longTermColors[i]; } else if (i == 0) { // initial term fraction = qreal(wordCount.initialWords) / qreal(wordCount.totalWords); color = colors.initialTermColor; } else if (i == -1) { // untrained (stored in longterm[0]) fraction = qreal(wordCount.grades[0]) / qreal(wordCount.totalWords); color = colors.longTermColors[0]; } else { fraction = qreal(wordCount.invalid) / qreal(wordCount.totalWords); color = colors.invalidColor; } // Create a rect from the current fraction qreal barElementWidth = fraction * roundedRect.width(); QRectF barElement(roundedRect.x() + xPosition, roundedRect.y(), barElementWidth, roundedRect.height()); xPosition += barElementWidth; // Paint! painter.setBrush(QBrush(color)); painter.drawRect(barElement); } // Draw the outline painter.setClipping(false); painter.setBrush(Qt::NoBrush); painter.drawRoundedRect(roundedRect, 2.0, 2.0); }