File indexing completed on 2023-11-26 03:55:53
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 } 0342 0343 #include "moc_vocabularydelegate.cpp"