File indexing completed on 2022-11-29 13:00:36

0001 /*
0002     SPDX-FileCopyrightText: 2006, 2007 Peter Hedlund <peter.hedlund@kdemail.net>
0003     SPDX-FileCopyrightText: 2007 Frederik Gladhorn <frederik.gladhorn@kdemail.net>
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "vocabularydelegate.h"
0008 #include "languagesettings.h"
0009 #include "prefs.h"
0010 #include "vocabularyfilter.h"
0011 #include "vocabularymodel.h"
0012 #include <KComboBox>
0013 #include <KEduVocExpression>
0014 #include <KEduVocWordtype>
0015 #include <KLocalizedString>
0016 #include <QCompleter>
0017 #include <QDBusInterface>
0018 #include <QDebug>
0019 #include <QFutureWatcher>
0020 #include <QHeaderView>
0021 #include <QKeyEvent>
0022 #include <QLineEdit>
0023 #include <QPainter>
0024 #include <QPainterPath>
0025 #include <QToolTip>
0026 #include <QTreeView>
0027 
0028 using namespace Editor;
0029 
0030 VocabularyDelegate::VocabularyDelegate(QObject *parent)
0031     : QItemDelegate(parent)
0032 {
0033 }
0034 
0035 QWidget *VocabularyDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
0036 {
0037     Q_UNUSED(option) /// as long as it's unused
0038 
0039     if (!index.isValid()) {
0040         return nullptr;
0041     }
0042 
0043     switch (VocabularyModel::columnType(index.column())) {
0044     case VocabularyModel::WordClass: {
0045         if (!m_doc) {
0046             return nullptr;
0047         }
0048         KComboBox *wordTypeCombo = new KComboBox(parent);
0049 
0050         WordTypeBasicModel *basicWordTypeModel = new WordTypeBasicModel(parent);
0051         wordTypeCombo->setModel(basicWordTypeModel);
0052         QTreeView *view = new QTreeView(parent);
0053 
0054         view->setModel(basicWordTypeModel);
0055         wordTypeCombo->setView(view);
0056 
0057         view->header()->setVisible(false);
0058         view->setRootIsDecorated(true);
0059 
0060         basicWordTypeModel->setDocument(m_doc);
0061         view->expandAll();
0062 
0063         qDebug() << "index data" << index.data().toString();
0064         // view->setCurrentItem();
0065 
0066         return wordTypeCombo;
0067     }
0068 
0069     case VocabularyModel::Translation: {
0070         if (!m_doc) {
0071             return nullptr;
0072         }
0073 
0074         // always create combo box for translation selection, because translations are gained async
0075         QLineEdit *lineedit = new QLineEdit(parent);
0076         lineedit->setFrame(false);
0077         lineedit->setFont(index.model()->data(index, Qt::FontRole).value<QFont>());
0078         lineedit->setText(index.model()->data(index, Qt::DisplayRole).toString());
0079 
0080         if (m_translator.isTranslateShellAvailable() && Prefs::automaticTranslation()) {
0081             QString targetLanguage = m_doc->identifier(index.column() / VocabularyModel::EntryColumnsMAX).locale();
0082             QString fromLanguage;
0083             QString word;
0084             for (int i = 0; i < index.model()->columnCount(index.parent()); i++) {
0085                 if (word.isEmpty() && VocabularyModel::columnType(i) == VocabularyModel::entryColumns::Translation) { // translation column
0086                     fromLanguage = m_doc->identifier(VocabularyModel::translation(i)).locale();
0087                     word = index.model()->index(index.row(), i, QModelIndex()).data().toString();
0088                 }
0089             }
0090 
0091             auto result = m_translator.translateAsync(word, fromLanguage, targetLanguage);
0092             QFutureWatcher<TranslateShellAdapter::Translation> *watcher = new QFutureWatcher<TranslateShellAdapter::Translation>();
0093             watcher->setFuture(result);
0094             connect(watcher, &QFutureWatcher<TranslateShellAdapter::Translation>::finished, lineedit, [lineedit, watcher]() {
0095                 if (!watcher->future().result().m_error && !watcher->future().result().m_suggestions.isEmpty()) {
0096                     QCompleter *completer = new QCompleter(watcher->future().result().m_suggestions, lineedit);
0097                     completer->setCaseSensitivity(Qt::CaseInsensitive);
0098                     completer->setCompletionMode(QCompleter::UnfilteredPopupCompletion);
0099                     lineedit->setCompleter(completer);
0100                 }
0101                 watcher->deleteLater();
0102             });
0103         }
0104 
0105         return lineedit;
0106     }
0107         // no break - we fall back to a line edit if there are not multiple translations fetched online
0108         // fallthrough
0109     default: {
0110         QLineEdit *editor = new QLineEdit(parent);
0111         editor->setFrame(false);
0112         editor->setFont(index.model()->data(index, Qt::FontRole).value<QFont>());
0113         editor->setText(index.model()->data(index, Qt::DisplayRole).toString());
0114 
0115         QString locale = index.model()->data(index, VocabularyModel::LocaleRole).toString();
0116         if (!locale.isEmpty()) {
0117             LanguageSettings settings(locale);
0118             settings.load();
0119             QString layout = settings.keyboardLayout();
0120             if (!layout.isEmpty()) {
0121                 QDBusInterface kxkb(QStringLiteral("org.kde.keyboard"), QStringLiteral("/Layouts"), QStringLiteral("org.kde.KeyboardLayouts"));
0122                 if (kxkb.isValid()) {
0123                     kxkb.call(QStringLiteral("setLayout"), layout);
0124                 }
0125             }
0126         }
0127         return editor;
0128     }
0129     }
0130 }
0131 
0132 bool VocabularyDelegate::helpEvent(QHelpEvent *event, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &index)
0133 {
0134     Q_UNUSED(view)
0135 
0136     if (event->type() == QEvent::ToolTip) {
0137         QPainterPath audioPainterPath;
0138         QPainterPath imagePainterPath;
0139         audioPainterPath.addPolygon(audioPolygon(option));
0140         imagePainterPath.addPolygon(imagePolygon(option));
0141 
0142         int column = columnType(index.column());
0143 
0144         if (audioPainterPath.contains(event->pos()) && hasAudio(index) && (column == Translation || column == Pronunciation)) {
0145             QToolTip::showText(event->globalPos(), i18n("Sound file selected: %1", audioUrl(index)));
0146         } else if (imagePainterPath.contains(event->pos()) && hasImage(index) && (column == Translation || column == Pronunciation)) {
0147             QToolTip::showText(event->globalPos(), i18n("Image file selected: %1", imageUrl(index)));
0148         } else {
0149             QToolTip::hideText();
0150             event->ignore();
0151         }
0152         return true;
0153     }
0154     return false;
0155 }
0156 
0157 QPolygon VocabularyDelegate::audioPolygon(const QStyleOptionViewItem &option) const
0158 {
0159     QRect rect = option.rect;
0160     QPolygon polygon;
0161     polygon << QPoint(rect.x() + rect.width() - 10, rect.y());
0162     polygon << QPoint(rect.x() + rect.width(), rect.y());
0163     polygon << QPoint(rect.x() + rect.width(), rect.y() + 10);
0164     return polygon;
0165 }
0166 
0167 QPolygon VocabularyDelegate::imagePolygon(const QStyleOptionViewItem &option) const
0168 {
0169     QRect rect = option.rect;
0170     QPolygon polygon;
0171     polygon << QPoint(rect.x() + rect.width() - 10, rect.y() + rect.height());
0172     polygon << QPoint(rect.x() + rect.width(), rect.y() + rect.height());
0173     polygon << QPoint(rect.x() + rect.width(), rect.y() + rect.height() - 10);
0174     return polygon;
0175 }
0176 
0177 bool VocabularyDelegate::hasAudio(const QModelIndex &index) const
0178 {
0179     return !audioUrl(index).isEmpty();
0180 }
0181 
0182 bool VocabularyDelegate::hasImage(const QModelIndex &index) const
0183 {
0184     return !imageUrl(index).isEmpty();
0185 }
0186 
0187 QString VocabularyDelegate::audioUrl(const QModelIndex &index) const
0188 {
0189     QVariant audioVar = index.data(VocabularyModel::AudioRole);
0190     QString audioUrl = audioVar.toString();
0191     return audioUrl;
0192 }
0193 
0194 QString VocabularyDelegate::imageUrl(const QModelIndex &index) const
0195 {
0196     QVariant imageVar = index.data(VocabularyModel::ImageRole);
0197     QString imageUrl = imageVar.toString();
0198     return imageUrl;
0199 }
0200 
0201 int VocabularyDelegate::columnType(int column)
0202 {
0203     return column % EntryColumnsMAX;
0204 }
0205 
0206 void VocabularyDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
0207 {
0208     QItemDelegate::paint(painter, option, index);
0209     painter->save();
0210 
0211     int column = columnType(index.column());
0212 
0213     if (hasAudio(index) == true && (column == Translation || column == Pronunciation)) {
0214         painter->setPen(QPen(Qt::red));
0215         painter->setBrush(QBrush(Qt::red));
0216         painter->drawPolygon(audioPolygon(option));
0217     }
0218     if (hasImage(index) == true && (column == Translation || column == Pronunciation)) {
0219         painter->setPen(QPen(Qt::blue));
0220         painter->setBrush(QBrush(Qt::blue));
0221         painter->drawPolygon(imagePolygon(option));
0222     }
0223     painter->restore();
0224 }
0225 
0226 void VocabularyDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
0227 {
0228     if (!index.isValid()) {
0229         return;
0230     }
0231 
0232     switch (VocabularyModel::columnType(index.column())) {
0233     case (VocabularyModel::Translation): {
0234         QString value = index.model()->data(index, Qt::DisplayRole).toString();
0235         KComboBox *translationCombo = qobject_cast<KComboBox *>(editor);
0236         if (translationCombo) {
0237             translationCombo->setEditText(value);
0238             if (value.isEmpty()) {
0239                 // show the translations that were fetched as popup
0240                 translationCombo->showPopup();
0241             }
0242             break;
0243         }
0244     }
0245     // fallthrough
0246     default: {
0247         QString value = index.model()->data(index, Qt::DisplayRole).toString();
0248 
0249         QLineEdit *lineEdit = qobject_cast<QLineEdit *>(editor);
0250         if (lineEdit) {
0251             lineEdit->setText(value);
0252         }
0253     }
0254     }
0255 }
0256 
0257 void VocabularyDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
0258 {
0259     if (!index.isValid()) {
0260         return;
0261     }
0262 
0263     switch (VocabularyModel::columnType(index.column())) {
0264     case (VocabularyModel::WordClass): {
0265         qDebug() << "word type editor";
0266         KComboBox *combo = qobject_cast<KComboBox *>(editor);
0267         if (!combo) {
0268             return;
0269         }
0270         qDebug() << "combo" << combo->currentText();
0271         QModelIndex comboIndex = combo->view()->currentIndex();
0272         KEduVocWordType *wordType = static_cast<KEduVocWordType *>(comboIndex.internalPointer());
0273 
0274         // the root is the same as no word type
0275         if (wordType && wordType->parent() == nullptr) {
0276             wordType = nullptr;
0277         }
0278 
0279         VocabularyFilter *filter = qobject_cast<VocabularyFilter *>(model);
0280         VocabularyModel *vocModel = qobject_cast<VocabularyModel *>((filter)->sourceModel());
0281         Q_ASSERT(vocModel);
0282         QVariant data = vocModel->data(filter->mapToSource(index), VocabularyModel::EntryRole);
0283 
0284         KEduVocExpression *expression = data.value<KEduVocExpression *>();
0285         Q_ASSERT(expression);
0286         int translationId = VocabularyModel::translation(index.column());
0287 
0288         expression->translation(translationId)->setWordType(wordType);
0289         model->setData(index, combo->currentText());
0290         break;
0291     }
0292     case (VocabularyModel::Translation): {
0293         QLineEdit *lineEdit = qobject_cast<QLineEdit *>(editor);
0294         if (lineEdit) {
0295             model->setData(index, lineEdit->text());
0296         }
0297         break;
0298     }
0299     default: {
0300         QLineEdit *lineEdit = qobject_cast<QLineEdit *>(editor);
0301         if (lineEdit) {
0302             model->setData(index, lineEdit->text());
0303         }
0304     }
0305     }
0306 }
0307 
0308 void VocabularyDelegate::setDocument(const std::shared_ptr<KEduVocDocument> &doc)
0309 {
0310     m_doc = doc;
0311 }
0312 
0313 /*
0314 QPair< QString, QString > VocabularyDelegate::guessWordType(const QString & entry, int language) const
0315 {
0316     qDebug() << "guessing word type for: " << entry;
0317 
0318     QString article = entry.section(" ", 0, 0);
0319     if ( article.length() < entry.length() ) {
0320         if ( article == ->identifier(language).articles().article(KEduVocWordFlag::Singular| KEduVocWordFlag::Definite| KEduVocWordFlag::Masculine) ) {
0321             qDebug() << "Noun masculine";
0322             return qMakePair(m_doc->wordTypes().specialTypeNoun(), m_doc->wordTypes().specialTypeNounMale());
0323         }
0324 
0325     }
0326     return qMakePair(QString(), QString());
0327 }
0328 */
0329 
0330 VocabularyDelegate::WordTypeBasicModel::WordTypeBasicModel(QObject *parent)
0331     : ReadonlyContainerModel(KEduVocContainer::WordType, parent)
0332 {
0333 }
0334 
0335 KEduVocContainer *VocabularyDelegate::WordTypeBasicModel::rootContainer() const
0336 {
0337     if (!m_doc) {
0338         return nullptr;
0339     }
0340     return m_doc->wordTypeContainer();
0341 }