File indexing completed on 2024-10-06 03:36:05
0001 /* 0002 SPDX-FileCopyrightText: 2006, 2007 Peter Hedlund <peter.hedlund@kdemail.net> 0003 SPDX-FileCopyrightText: 2007-2008 Frederik Gladhorn <frederik.gladhorn@kdemail.net> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 ///@file vocabularyview.cpp 0009 0010 #include "vocabularyview.h" 0011 #include "vocabularyheaderview.h" 0012 0013 #include "vocabularydelegate.h" 0014 #include "vocabularyfilter.h" 0015 #include "vocabularymimedata.h" 0016 #include "vocabularymodel.h" 0017 0018 #include "documentsettings.h" 0019 #include "editor/editor.h" 0020 #include "prefs.h" 0021 #include "vocabularycolumnsdialog.h" 0022 0023 #include <KEduVocExpression> 0024 #include <KEduVocTranslation> 0025 0026 #include <QApplication> 0027 #include <QClipboard> 0028 #include <QHeaderView> 0029 #include <QPainter> 0030 #include <QResizeEvent> 0031 #include <QTimer> 0032 0033 #include <KActionCollection> 0034 #include <KLocalizedString> 0035 #include <KMessageBox> 0036 #include <KNotification> 0037 #include <KToggleAction> 0038 #include <QAction> 0039 #include <QUrl> 0040 #include <languagesettings.h> 0041 #include <sonnet/backgroundchecker.h> 0042 0043 using namespace Editor; 0044 0045 VocabularyView::VocabularyView(EditorWindow *parent) 0046 : QTableView(parent) 0047 { 0048 installEventFilter(this); 0049 0050 setHorizontalHeader(new VocabularyHeaderView(Qt::Horizontal, this)); 0051 0052 horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive); 0053 horizontalHeader()->setSectionsMovable(true); 0054 setEditTriggers(QAbstractItemView::AnyKeyPressed | QAbstractItemView::EditKeyPressed | QAbstractItemView::DoubleClicked); 0055 0056 setSortingEnabled(true); 0057 setTabKeyNavigation(true); 0058 0059 m_vocabularyDelegate = new VocabularyDelegate(this); 0060 setItemDelegate(m_vocabularyDelegate); 0061 0062 setFrameStyle(QFrame::NoFrame); 0063 setAlternatingRowColors(true); 0064 0065 // Enable context menus 0066 setContextMenuPolicy(Qt::ActionsContextMenu); 0067 horizontalHeader()->setContextMenuPolicy(Qt::ActionsContextMenu); 0068 0069 setWordWrap(true); 0070 setDragEnabled(true); 0071 0072 // smooth scrolling horizontally, otherwise it tries to jump from item to item. 0073 setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); 0074 0075 m_appendEntryAction = new QAction(this); 0076 parent->actionCollection()->addAction(QStringLiteral("edit_append"), m_appendEntryAction); 0077 parent->actionCollection()->setDefaultShortcut(m_appendEntryAction, QKeySequence(Qt::Key_Insert)); 0078 m_appendEntryAction->setIcon(QIcon::fromTheme(QStringLiteral("list-add-card"))); 0079 m_appendEntryAction->setText(i18n("&Add New Entry")); 0080 connect(m_appendEntryAction, &QAction::triggered, this, &VocabularyView::appendEntry); 0081 m_appendEntryAction->setShortcut(QKeySequence(Qt::Key_Insert)); 0082 m_appendEntryAction->setWhatsThis(i18n("Append a new row to the vocabulary")); 0083 m_appendEntryAction->setToolTip(m_appendEntryAction->whatsThis()); 0084 m_appendEntryAction->setStatusTip(m_appendEntryAction->whatsThis()); 0085 addAction(m_appendEntryAction); 0086 0087 m_deleteEntriesAction = new QAction(this); 0088 parent->actionCollection()->addAction(QStringLiteral("edit_remove_selected_area"), m_deleteEntriesAction); 0089 parent->actionCollection()->setDefaultShortcut(m_deleteEntriesAction, QKeySequence::Delete); 0090 m_deleteEntriesAction->setIcon(QIcon::fromTheme(QStringLiteral("list-remove-card"))); 0091 m_deleteEntriesAction->setText(i18n("&Delete Entry")); 0092 connect(m_deleteEntriesAction, &QAction::triggered, this, &VocabularyView::deleteSelectedEntries); 0093 m_deleteEntriesAction->setShortcut(QKeySequence::Delete); 0094 m_deleteEntriesAction->setWhatsThis(i18n("Delete the selected rows")); 0095 m_deleteEntriesAction->setToolTip(m_deleteEntriesAction->whatsThis()); 0096 m_deleteEntriesAction->setStatusTip(m_deleteEntriesAction->whatsThis()); 0097 addAction(m_deleteEntriesAction); 0098 0099 QAction *separator = new QAction(this); 0100 separator->setSeparator(true); 0101 addAction(separator); 0102 0103 m_copyAction = KStandardAction::copy(this, SLOT(slotEditCopy()), parent->actionCollection()); 0104 parent->actionCollection()->setDefaultShortcut(m_copyAction, QKeySequence::Copy); 0105 m_copyAction->setWhatsThis(i18n("Copy")); 0106 m_copyAction->setToolTip(m_copyAction->whatsThis()); 0107 m_copyAction->setStatusTip(m_copyAction->whatsThis()); 0108 addAction(m_copyAction); 0109 0110 m_cutAction = KStandardAction::cut(this, SLOT(slotCutEntry()), parent->actionCollection()); 0111 parent->actionCollection()->setDefaultShortcut(m_cutAction, QKeySequence::Cut); 0112 m_cutAction->setWhatsThis(i18n("Cut")); 0113 m_cutAction->setToolTip(m_cutAction->whatsThis()); 0114 m_cutAction->setStatusTip(m_cutAction->whatsThis()); 0115 addAction(m_cutAction); 0116 0117 m_pasteAction = KStandardAction::paste(this, SLOT(slotEditPaste()), parent->actionCollection()); 0118 parent->actionCollection()->setDefaultShortcut(m_pasteAction, QKeySequence::Paste); 0119 m_pasteAction->setWhatsThis(i18n("Paste")); 0120 m_pasteAction->setToolTip(m_pasteAction->whatsThis()); 0121 m_pasteAction->setStatusTip(m_pasteAction->whatsThis()); 0122 addAction(m_pasteAction); 0123 0124 m_selectAllAction = KStandardAction::selectAll(this, SLOT(selectAll()), parent->actionCollection()); 0125 parent->actionCollection()->setDefaultShortcut(m_selectAllAction, QKeySequence::SelectAll); 0126 m_selectAllAction->setWhatsThis(i18n("Select all rows")); 0127 m_selectAllAction->setToolTip(m_selectAllAction->whatsThis()); 0128 m_selectAllAction->setStatusTip(m_selectAllAction->whatsThis()); 0129 0130 m_clearSelectionAction = KStandardAction::deselect(this, SLOT(clearSelection()), parent->actionCollection()); 0131 parent->actionCollection()->setDefaultShortcut(m_clearSelectionAction, QKeySequence::Deselect); 0132 m_clearSelectionAction->setWhatsThis(i18n("Deselect all rows")); 0133 m_clearSelectionAction->setToolTip(m_clearSelectionAction->whatsThis()); 0134 m_clearSelectionAction->setStatusTip(m_clearSelectionAction->whatsThis()); 0135 0136 // vocabulary columns dialog 0137 QAction *vocabularyColumnsDialogAction = new QAction(this); 0138 parent->actionCollection()->addAction(QStringLiteral("show_vocabulary_columns_dialog"), vocabularyColumnsDialogAction); 0139 vocabularyColumnsDialogAction->setIcon(QIcon::fromTheme(QStringLiteral("view-file-columns"))); 0140 vocabularyColumnsDialogAction->setText(i18n("Vocabulary Columns...")); 0141 vocabularyColumnsDialogAction->setWhatsThis(i18n("Toggle display of individual vocabulary columns")); 0142 vocabularyColumnsDialogAction->setToolTip(vocabularyColumnsDialogAction->whatsThis()); 0143 vocabularyColumnsDialogAction->setStatusTip(vocabularyColumnsDialogAction->whatsThis()); 0144 horizontalHeader()->addAction(vocabularyColumnsDialogAction); 0145 addAction(vocabularyColumnsDialogAction); 0146 connect(vocabularyColumnsDialogAction, &QAction::triggered, this, &VocabularyView::slotShowVocabularyColumnsDialog); 0147 } 0148 0149 void VocabularyView::setFilter(VocabularyFilter *model) 0150 { 0151 QTableView::setModel(model); 0152 0153 m_model = model; 0154 connect(selectionModel(), &QItemSelectionModel::currentChanged, this, &VocabularyView::slotCurrentChanged); 0155 connect(selectionModel(), &QItemSelectionModel::selectionChanged, this, &VocabularyView::slotSelectionChanged); 0156 slotSelectionChanged(QItemSelection(), QItemSelection()); 0157 } 0158 0159 void VocabularyView::slotCurrentChanged(const QModelIndex ¤t, const QModelIndex &previous) 0160 { 0161 Q_UNUSED(previous); 0162 KEduVocExpression *entry = nullptr; 0163 if (current.isValid()) { 0164 entry = model()->data(current, VocabularyModel::EntryRole).value<KEduVocExpression *>(); 0165 } 0166 Q_EMIT translationChanged(entry, VocabularyModel::translation(current.column())); 0167 } 0168 0169 void VocabularyView::reset() 0170 { 0171 QTableView::reset(); 0172 Q_EMIT translationChanged(nullptr, 0); 0173 0174 QList<int> visibleColumns; 0175 if (m_doc) { 0176 DocumentSettings ds(m_doc->url().url()); 0177 ds.load(); 0178 visibleColumns = ds.visibleColumns(); 0179 0180 KConfig parleyConfig(QStringLiteral("parleyrc")); 0181 KConfigGroup documentGroup(&parleyConfig, "Document " + m_doc->url().url()); 0182 QByteArray state = documentGroup.readEntry("VocabularyColumns", QByteArray()); 0183 0184 if (!horizontalHeader()->restoreState(state)) { 0185 resizeColumnsToContents(); 0186 } 0187 } 0188 0189 horizontalHeader()->setSectionsMovable(true); 0190 0191 for (int i = 0; i < model()->columnCount(QModelIndex()); i++) { 0192 if (i < visibleColumns.size()) { 0193 setColumnHidden(i, !visibleColumns.value(i)); 0194 } else { 0195 if (VocabularyModel::columnType(i) != VocabularyModel::Translation) { 0196 setColumnHidden(i, true); 0197 } 0198 } 0199 } 0200 } 0201 0202 void VocabularyView::saveColumnVisibility() const 0203 { 0204 if (!m_doc) { 0205 return; 0206 } 0207 0208 // Generate a QList<int> for saving 0209 QList<int> visibleList; 0210 for (int i = 0; i < m_model->columnCount(); ++i) { 0211 visibleList.append(static_cast<int>(!isColumnHidden(i))); 0212 } 0213 0214 DocumentSettings ds(m_doc->url().url()); 0215 ds.setVisibleColumns(visibleList); 0216 ds.save(); 0217 0218 horizontalHeader()->saveState(); 0219 KConfig parleyConfig(QStringLiteral("parleyrc")); 0220 KConfigGroup documentGroup(&parleyConfig, "Document " + m_doc->url().url()); 0221 documentGroup.writeEntry("VocabularyColumns", horizontalHeader()->saveState()); 0222 } 0223 0224 void VocabularyView::appendEntry() 0225 { 0226 QModelIndex newIndex = m_model->appendEntry(); 0227 scrollTo(newIndex); 0228 selectionModel()->clear(); // otherwise proxy mapping gets screwed for some reason 0229 selectionModel()->select(newIndex, QItemSelectionModel::ClearAndSelect); 0230 selectionModel()->setCurrentIndex(newIndex, QItemSelectionModel::ClearAndSelect); 0231 edit(newIndex); 0232 } 0233 0234 void VocabularyView::appendChar(const QChar &c) 0235 { 0236 const QModelIndex &index = selectionModel()->currentIndex(); 0237 m_model->setData(index, QString(m_model->data(index).toString() + c)); 0238 } 0239 0240 void VocabularyView::deleteSelectedEntries(bool askConfirmation) 0241 { 0242 QSet<int> rows; 0243 const QModelIndexList selectedIndexes = selectionModel()->selectedIndexes(); 0244 for (const QModelIndex &index : selectedIndexes) { 0245 rows.insert(index.row()); 0246 } 0247 0248 bool del = true; 0249 if (askConfirmation) { 0250 del = KMessageBox::Continue 0251 == KMessageBox::warningContinueCancel( 0252 this, 0253 i18np("Do you really want to delete the selected entry?", "Do you really want to delete the selected %1 entries?", rows.count()), 0254 i18n("Delete"), 0255 KStandardGuiItem::del()); 0256 } 0257 0258 if (del) { 0259 Q_EMIT translationChanged(nullptr, 0); 0260 while (!selectionModel()->selectedIndexes().isEmpty()) { 0261 m_model->removeRows(selectionModel()->selectedIndexes()[0].row(), 1, QModelIndex()); 0262 } 0263 } 0264 } 0265 0266 void VocabularyView::slotEditCopy() 0267 { 0268 QModelIndexList sortedIndexes = selectionModel()->selectedIndexes(); 0269 std::sort(sortedIndexes.begin(), sortedIndexes.end()); 0270 QMimeData *mimeData = m_model->mimeData(sortedIndexes); 0271 0272 QClipboard *clipboard = QApplication::clipboard(); 0273 clipboard->setMimeData(mimeData); 0274 } 0275 0276 void VocabularyView::slotEditPaste() 0277 { 0278 QClipboard *clipboard = QApplication::clipboard(); 0279 const QMimeData *mimeData = clipboard->mimeData(); 0280 const VocabularyMimeData *vocMimeData = qobject_cast<const VocabularyMimeData *>(mimeData); 0281 if (vocMimeData) { 0282 qDebug() << "Clipboard contains vocabulary mime data."; 0283 QList<VocabularyMimeData::MimeExpression> expressionList = vocMimeData->expressionList(); 0284 for (const VocabularyMimeData::MimeExpression &mimeEntry : qAsConst(expressionList)) { 0285 KEduVocExpression *pasteExpression = new KEduVocExpression(mimeEntry.expression); 0286 m_model->appendEntry(pasteExpression); 0287 0288 // find word type (create if not found) 0289 KEduVocWordType *type = m_doc->wordTypeContainer(); 0290 for (auto iter = mimeEntry.wordTypes.cbegin(); iter != mimeEntry.wordTypes.cend(); ++iter) { 0291 const int translation = iter.key(); 0292 // append if needed 0293 const QStringList wordType = mimeEntry.wordTypes.value(translation).wordType; 0294 for (const QString &typeName : wordType) { 0295 qDebug() << mimeEntry.wordTypes.value(translation).wordType; 0296 KEduVocContainer *childType = type->childContainer(typeName); 0297 if (!childType) { 0298 // the doc does not contain the right word type - create it 0299 childType = new KEduVocWordType(typeName); 0300 type->appendChildContainer(childType); 0301 } 0302 type = static_cast<KEduVocWordType *>(childType); 0303 } 0304 pasteExpression->translation(translation)->setWordType(type); 0305 // check for special type stuff 0306 if (type->wordType() != mimeEntry.wordTypes.value(translation).grammarType) { 0307 if (type->wordType() == KEduVocWordFlag::NoInformation) { 0308 type->setWordType(mimeEntry.wordTypes.value(translation).grammarType); 0309 } 0310 } 0311 } 0312 } 0313 } else if (mimeData->hasText()) { 0314 qDebug() << "Clipboard contains text data."; 0315 // split at newline 0316 QStringList lines = clipboard->text().split('\n'); 0317 for (const QString &line : qAsConst(lines)) { 0318 // split at tabs or semicolon: 0319 m_model->appendEntry(new KEduVocExpression(line.split(QRegularExpression(QStringLiteral("[\t;]")), Qt::KeepEmptyParts))); 0320 } 0321 } 0322 } 0323 0324 void VocabularyView::slotCutEntry() 0325 { 0326 slotEditCopy(); 0327 deleteSelectedEntries(false); 0328 } 0329 0330 void VocabularyView::slotSelectionChanged(const QItemSelection &, const QItemSelection &) 0331 { 0332 bool hasSelection = selectionModel()->hasSelection(); 0333 m_deleteEntriesAction->setEnabled(hasSelection); 0334 m_clearSelectionAction->setEnabled(hasSelection); 0335 m_copyAction->setEnabled(hasSelection); 0336 m_cutAction->setEnabled(hasSelection); 0337 } 0338 0339 void VocabularyView::setDocument(const std::shared_ptr<KEduVocDocument> &doc) 0340 { 0341 m_doc = doc; 0342 m_vocabularyDelegate->setDocument(doc); 0343 QTimer::singleShot(0, this, SLOT(reset())); 0344 } 0345 0346 void VocabularyView::slotShowVocabularyColumnsDialog() 0347 { 0348 VocabularyColumnsDialog *dialog = new VocabularyColumnsDialog(m_doc.get(), this); 0349 0350 if (dialog->exec() == QDialog::Accepted) { 0351 reset(); 0352 } 0353 } 0354 0355 void VocabularyView::checkSpelling(int language) 0356 { 0357 if (!m_model->rowCount()) { 0358 KMessageBox::information(this, i18n("Nothing to spell check.")); 0359 return; 0360 } 0361 0362 if (!m_spellChecker) { 0363 m_spellChecker = new Sonnet::BackgroundChecker(this); 0364 m_spellDialog = new Sonnet::Dialog(m_spellChecker, this); 0365 connect(m_spellDialog, SIGNAL(done(QString)), this, SLOT(continueSpelling())); 0366 connect(m_spellDialog, &Sonnet::Dialog::misspelling, this, &VocabularyView::misspelling); 0367 connect(m_spellDialog, &Sonnet::Dialog::replace, this, &VocabularyView::spellingReplace); 0368 } 0369 0370 m_spellColumn = language * VocabularyModel::EntryColumnsMAX; 0371 m_spellRow = -1; 0372 if (m_spellColumn < 0) { 0373 return; 0374 } 0375 0376 QString locale = m_doc->identifier(language).locale(); 0377 LanguageSettings settings(locale); 0378 QString spellCode = settings.spellChecker().isEmpty() ? locale : settings.spellChecker(); 0379 m_spellChecker->changeLanguage(spellCode); 0380 if (!m_spellChecker->speller().isValid()) { 0381 qDebug() << "Invalid Language, popup here!"; 0382 KNotification::event(KNotification::Warning, 0383 i18nc("@title of a popup", "No Spell Checker Available"), 0384 i18nc("@popupmessage", "Either the language set up is incorrect or no spellchecker was installed for this locale: %1.", locale)); 0385 } 0386 m_spellDialog->show(); 0387 continueSpelling(); 0388 } 0389 0390 void VocabularyView::continueSpelling() 0391 { 0392 qDebug() << "Check spelling: " << m_spellRow << m_spellColumn; 0393 ++m_spellRow; 0394 while (m_spellRow < m_model->rowCount()) { 0395 QModelIndex index = m_model->index(m_spellRow, m_spellColumn); 0396 qDebug() << " " << m_model->data(index).toString(); 0397 if (!m_model->data(index).toString().isEmpty()) { 0398 m_spellDialog->setBuffer(m_model->data(index).toString()); 0399 break; 0400 } else { 0401 ++m_spellRow; 0402 } 0403 } 0404 } 0405 0406 void VocabularyView::selectIndex(const QModelIndex &newIndex) 0407 { 0408 selectionModel()->select(newIndex, QItemSelectionModel::ClearAndSelect); 0409 selectionModel()->setCurrentIndex(newIndex, QItemSelectionModel::ClearAndSelect); 0410 scrollTo(newIndex); 0411 } 0412 0413 bool VocabularyView::eventFilter(QObject *obj, QEvent *event) 0414 { 0415 if (event->type() == QEvent::KeyPress && Prefs::smartAppend()) { 0416 QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event); 0417 if (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) { 0418 if (selectionModel()->currentIndex().row() == m_model->rowCount() - 1) { 0419 appendEntry(); 0420 } 0421 } 0422 } 0423 // standard event processing 0424 return QObject::eventFilter(obj, event); 0425 } 0426 0427 void VocabularyView::misspelling(const QString &word, int start) 0428 { 0429 Q_UNUSED(word) 0430 Q_UNUSED(start) 0431 QModelIndex index = m_model->index(m_spellRow, m_spellColumn); 0432 selectIndex(index); 0433 } 0434 0435 void VocabularyView::spellingReplace(const QString &oldWord, int start, const QString &newWord) 0436 { 0437 qDebug() << oldWord << start << newWord; 0438 QModelIndex index = m_model->index(m_spellRow, m_spellColumn); 0439 QString data = index.data().toString(); 0440 QString newData = data.replace(start, oldWord.length(), newWord); 0441 qDebug() << "Changing " << data << " to " << newData; 0442 m_model->setData(index, newData); 0443 } 0444 0445 QModelIndexList VocabularyView::getSelectedIndexes() const 0446 { 0447 return selectionModel()->selectedIndexes(); 0448 } 0449 0450 #include "moc_vocabularyview.cpp"