File indexing completed on 2024-05-19 05:05:31
0001 /*************************************************************************** 0002 * SPDX-License-Identifier: GPL-2.0-or-later 0003 * * 0004 * SPDX-FileCopyrightText: 2004-2023 Thomas Fischer <fischer@unix-ag.uni-kl.de> 0005 * * 0006 * This program is free software; you can redistribute it and/or modify * 0007 * it under the terms of the GNU General Public License as published by * 0008 * the Free Software Foundation; either version 2 of the License, or * 0009 * (at your option) any later version. * 0010 * * 0011 * This program is distributed in the hope that it will be useful, * 0012 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 0013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 0014 * GNU General Public License for more details. * 0015 * * 0016 * You should have received a copy of the GNU General Public License * 0017 * along with this program; if not, see <https://www.gnu.org/licenses/>. * 0018 ***************************************************************************/ 0019 0020 #include "valuelistmodel.h" 0021 0022 #include <typeinfo> 0023 0024 #include <QApplication> 0025 #include <QTextDocument> 0026 #include <QAbstractTextDocumentLayout> 0027 #include <QListView> 0028 #include <QLineEdit> 0029 #include <QGridLayout> 0030 #include <QStringListModel> 0031 #include <QPainter> 0032 #include <QFrame> 0033 #include <QLayout> 0034 #include <QHeaderView> 0035 #include <QComboBox> 0036 0037 #include <KLocalizedString> 0038 #include <KColorScheme> 0039 0040 #include <BibTeXFields> 0041 #include <Preferences> 0042 #include <Entry> 0043 #include <models/FileModel> 0044 #include "widgets/starrating.h" 0045 #include "field/fieldlineedit.h" 0046 #include "logging_gui.h" 0047 0048 class ValueListDelegate::Private { 0049 public: 0050 QTreeView *treeView; 0051 ValueListDelegate *parent; 0052 QString fieldName; 0053 0054 Private(QTreeView *_treeView, ValueListDelegate *_parent) 0055 : treeView(_treeView), parent(_parent) 0056 { 0057 // nothing 0058 } 0059 }; 0060 0061 ValueListDelegate::ValueListDelegate(QTreeView *parent) 0062 : QStyledItemDelegate(parent), d(new ValueListDelegate::Private(parent, this)) 0063 { 0064 // nothing 0065 } 0066 0067 ValueListDelegate::~ValueListDelegate() 0068 { 0069 delete d; 0070 } 0071 0072 QWidget *ValueListDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &sovi, const QModelIndex &index) const 0073 { 0074 if (index.column() == 0) { 0075 const FieldDescription &fd = BibTeXFields::instance().find(d->fieldName); 0076 FieldLineEdit *fieldLineEdit = new FieldLineEdit(fd.preferredTypeFlag, fd.typeFlags, false, parent); 0077 fieldLineEdit->setAutoFillBackground(true); 0078 return fieldLineEdit; 0079 } else 0080 return QStyledItemDelegate::createEditor(parent, sovi, index); 0081 } 0082 0083 void ValueListDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const 0084 { 0085 if (index.column() == 0) { 0086 FieldLineEdit *fieldLineEdit = qobject_cast<FieldLineEdit *>(editor); 0087 if (fieldLineEdit != nullptr) 0088 fieldLineEdit->reset(index.model()->data(index, Qt::EditRole).value<Value>()); 0089 } 0090 } 0091 0092 void ValueListDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const 0093 { 0094 FieldLineEdit *fieldLineEdit = qobject_cast<FieldLineEdit *>(editor); 0095 if (fieldLineEdit != nullptr) { 0096 Value v; 0097 fieldLineEdit->apply(v); 0098 if (v.count() == 1) /// field should contain exactly one value item (no zero, not two or more) 0099 model->setData(index, QVariant::fromValue(v)); 0100 } 0101 } 0102 0103 QSize ValueListDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const 0104 { 0105 QSize size = QStyledItemDelegate::sizeHint(option, index); 0106 size.setHeight(qMax(size.height(), option.fontMetrics.height() * 3 / 2)); // TODO calculate height better 0107 return size; 0108 } 0109 0110 void ValueListDelegate::initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const 0111 { 0112 QStyledItemDelegate::initStyleOption(option, index); 0113 if (option->decorationPosition != QStyleOptionViewItem::Top) { 0114 /// remove text from style (do not draw text) 0115 option->text.clear(); 0116 } 0117 0118 } 0119 0120 void ValueListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &_option, const QModelIndex &index) const 0121 { 0122 QStyleOptionViewItem option = _option; 0123 0124 /// code heavily inspired by kdepimlibs-4.6.3/akonadi/collectionstatisticsdelegate.cpp 0125 0126 /// save painter's state, restored before leaving this function 0127 painter->save(); 0128 0129 /// first, paint the basic, but without the text. We remove the text 0130 /// in initStyleOption(), which gets called by QStyledItemDelegate::paint(). 0131 QStyledItemDelegate::paint(painter, option, index); 0132 0133 /// now, we retrieve the correct style option by calling intiStyleOption from 0134 /// the superclass. 0135 QStyledItemDelegate::initStyleOption(&option, index); 0136 QString field = option.text; 0137 const bool customPainting {index.column() == 0 && d->fieldName.toLower() == Entry::ftStarRating}; 0138 0139 /// now calculate the rectangle for the text 0140 QStyle *s = d->treeView->style(); 0141 const QWidget *widget = option.widget; 0142 const QRect textRect = s->subElementRect(QStyle::SE_ItemViewItemText, &option, widget); 0143 0144 if (option.state & QStyle::State_Selected) { 0145 /// selected lines are drawn with different color 0146 painter->setPen(option.palette.highlightedText().color()); 0147 } 0148 0149 /// count will be empty unless only one column is shown 0150 const QString count = index.column() == 0 && index.model()->columnCount() == 1 ? QString(QStringLiteral(" (%1)")).arg(index.data(ValueListModel::CountRole).toInt()) : QString(); 0151 0152 /// squeeze the folder text if it is to big and calculate the rectangles 0153 /// where the folder text and the unread count will be drawn to 0154 const QFontMetrics fm(painter->fontMetrics()); 0155 #if QT_VERSION >= 0x050b00 0156 const int countWidth = fm.horizontalAdvance(count); 0157 int fieldWidth = customPainting ? textRect.width() - countWidth - 8 : fm.horizontalAdvance(field); 0158 #else // QT_VERSION >= 0x050b00 0159 const int countWidth = fm.width(count); 0160 int fieldWidth = customPainting ? textRect.width() - countWidth - 8 : fm.width(field); 0161 #endif // QT_VERSION >= 0x050b00 0162 if (countWidth + fieldWidth > textRect.width()) { 0163 /// text plus count is too wide for column, cut text and insert "..." 0164 field = fm.elidedText(field, Qt::ElideRight, textRect.width() - countWidth - 8); 0165 fieldWidth = textRect.width() - countWidth - 12; 0166 } 0167 0168 /// determine rects to draw field 0169 int top = textRect.top() + (textRect.height() - fm.height()) / 2; 0170 QRect fieldRect = textRect; 0171 QRect countRect = textRect; 0172 fieldRect.setTop(top); 0173 fieldRect.setHeight(fm.height()); 0174 0175 if (d->treeView->header()->visualIndex(index.column()) == 0) { 0176 /// left-align text 0177 fieldRect.setLeft(fieldRect.left() + 4); ///< hm, indent necessary? 0178 fieldRect.setRight(fieldRect.left() + fieldWidth); 0179 } else { 0180 /// right-align text 0181 fieldRect.setRight(fieldRect.right() - 4); ///< hm, indent necessary? 0182 fieldRect.setLeft(fieldRect.right() - fieldWidth); ///< hm, indent necessary? 0183 } 0184 0185 if (customPainting) { 0186 static StarRatingPainter starrating; 0187 bool ok = false; 0188 const float percent = field.toFloat(&ok); 0189 if (ok && percent >= 0.0 && percent <= 100.0) { 0190 starrating.paint(painter, fieldRect, percent); 0191 } 0192 } else { 0193 // Draw field name 0194 painter->drawText(fieldRect, Qt::AlignLeft, field); 0195 } 0196 0197 if (!count.isEmpty()) { 0198 /// determine rects to draw count 0199 countRect.setTop(top); 0200 countRect.setHeight(fm.height()); 0201 countRect.setLeft(fieldRect.right()); 0202 0203 /// use bold font 0204 QFont font = painter->font(); 0205 font.setBold(true); 0206 painter->setFont(font); 0207 /// determine color for count number 0208 const QColor countColor = (option.state & QStyle::State_Selected) ? KColorScheme(QPalette::Active, KColorScheme::Selection).foreground(KColorScheme::LinkText).color() : KColorScheme(QPalette::Active, KColorScheme::View).foreground(KColorScheme::LinkText).color(); 0209 painter->setPen(countColor); 0210 0211 /// draw count 0212 painter->drawText(countRect, Qt::AlignLeft, count); 0213 } 0214 0215 /// restore painter's state 0216 painter->restore(); 0217 } 0218 0219 void ValueListDelegate::setFieldName(const QString &fieldName) { 0220 d->fieldName = fieldName; 0221 } 0222 0223 ValueListModel::ValueListModel(const File *bibtexFile, const QString &fieldName, QObject *parent) 0224 : QAbstractTableModel(parent), file(bibtexFile), fName(fieldName.toLower()), showCountColumn(true), sortBy(SortBy::Text) 0225 { 0226 readConfiguration(); 0227 updateValues(); 0228 NotificationHub::registerNotificationListener(this, NotificationHub::EventConfigurationChanged); 0229 } 0230 0231 int ValueListModel::rowCount(const QModelIndex &parent) const 0232 { 0233 return parent == QModelIndex() ? values.count() : 0; 0234 } 0235 0236 int ValueListModel::columnCount(const QModelIndex &parent) const 0237 { 0238 return parent == QModelIndex() ? (showCountColumn ? 2 : 1) : 0; 0239 } 0240 0241 QVariant ValueListModel::data(const QModelIndex &index, int role) const 0242 { 0243 if (index.row() >= values.count() || index.column() >= 2) 0244 return QVariant(); 0245 if (role == Qt::DisplayRole || role == Qt::ToolTipRole) { 0246 if (index.column() == 0) { 0247 if (fName == Entry::ftColor) { 0248 QString text = values[index.row()].text; 0249 if (text.isEmpty()) return QVariant(); 0250 QString colorText = colorToLabel[text]; 0251 if (colorText.isEmpty()) return QVariant(text); 0252 return QVariant(colorText); 0253 } else if (fName == Entry::ftStarRating) { 0254 bool ok = false; 0255 const double percent {StarRatingPainter::roundToNearestHalfStarPercent(values[index.row()].text.toFloat(&ok))}; 0256 if (ok) 0257 return QVariant(QString::number(percent, 'f', 2)); 0258 } 0259 0260 return QVariant(values[index.row()].text); 0261 } else 0262 return QVariant(values[index.row()].count); 0263 } else if (role == SortRole) { 0264 static const QRegularExpression ignoredInSorting(QStringLiteral("[{}\\\\]+")); 0265 0266 QString buffer = values[index.row()].sortBy.isEmpty() ? values[index.row()].text : values[index.row()].sortBy; 0267 buffer = buffer.remove(ignoredInSorting).toLower(); 0268 0269 if ((showCountColumn && index.column() == 1) || (!showCountColumn && sortBy == SortBy::Count)) { 0270 /// Sort by string consisting of a zero-padded count and the lower-case text, 0271 /// for example "0000000051keyword" 0272 /// Used if (a) two columns are shown (showCountColumn is true) and column 1 0273 /// (the count column) is to be sorted or (b) if only one column is shown 0274 /// (showCountColumn is false) and this single column is to be sorted by count. 0275 return QString(QStringLiteral("%1%2")).arg(values[index.row()].count, 10, 10, QLatin1Char('0')).arg(buffer); 0276 } else { 0277 /// Otherwise use lower-case text for sorting 0278 return QVariant(buffer); 0279 } 0280 } else if (role == SearchTextRole) { 0281 return QVariant(values[index.row()].text); 0282 } else if (role == Qt::EditRole) 0283 return QVariant::fromValue(values[index.row()].value); 0284 else if (role == CountRole) 0285 return QVariant(values[index.row()].count); 0286 else 0287 return QVariant(); 0288 } 0289 0290 bool ValueListModel::setData(const QModelIndex &index, const QVariant &value, int role) 0291 { 0292 Q_ASSERT_X(file != nullptr, "ValueListModel::setData", "You cannot set data if there is no BibTeX file associated with this value list."); 0293 0294 // Continue only if in edit role and first column is to be changed 0295 if (role == Qt::EditRole && index.column() == 0) { 0296 // Fetch the string as it was shown before the editing started 0297 QString origText = data(index, Qt::DisplayRole).toString(); 0298 /// Special treatment for colors 0299 if (fName == Entry::ftColor) { 0300 /// for colors, convert color (RGB) to the associated label 0301 QString color = colorToLabel.key(origText); 0302 if (!color.isEmpty()) origText = color; 0303 } 0304 0305 /// Retrieve the Value object containing the user-entered data 0306 Value newValue = value.value<Value>(); /// nice variable names ... ;-) 0307 if (newValue.isEmpty()) { 0308 qCWarning(LOG_KBIBTEX_GUI) << "Cannot replace with empty value"; 0309 return false; 0310 } 0311 0312 /// Fetch the string representing the new, user-entered value 0313 const QString newText = PlainTextValue::text(newValue); 0314 if (newText == origText) { 0315 qCWarning(LOG_KBIBTEX_GUI) << "Skipping to replace value with itself"; 0316 return false; 0317 } 0318 0319 bool success = searchAndReplaceValueInEntries(index, newValue) && searchAndReplaceValueInModel(index, newValue); 0320 return success; 0321 } 0322 return false; 0323 } 0324 0325 Qt::ItemFlags ValueListModel::flags(const QModelIndex &index) const 0326 { 0327 Qt::ItemFlags result = QAbstractTableModel::flags(index); 0328 /// make first column editable 0329 if (index.column() == 0) 0330 result |= Qt::ItemIsEditable; 0331 return result; 0332 } 0333 0334 QVariant ValueListModel::headerData(int section, Qt::Orientation orientation, int role) const 0335 { 0336 if (section >= 2 || orientation != Qt::Horizontal || role != Qt::DisplayRole) 0337 return QVariant(); 0338 else if ((section == 0 && columnCount() == 2) || (columnCount() == 1 && sortBy == SortBy::Text)) 0339 return QVariant(i18n("Value")); 0340 else 0341 return QVariant(i18n("Count")); 0342 } 0343 0344 void ValueListModel::removeValue(const QModelIndex &index) 0345 { 0346 removeValueFromEntries(index); 0347 removeValueFromModel(index); 0348 } 0349 0350 void ValueListModel::setShowCountColumn(bool showCountColumn) 0351 { 0352 beginResetModel(); 0353 this->showCountColumn = showCountColumn; 0354 endResetModel(); 0355 } 0356 0357 void ValueListModel::setSortBy(SortBy sortBy) 0358 { 0359 beginResetModel(); 0360 this->sortBy = sortBy; 0361 endResetModel(); 0362 } 0363 0364 void ValueListModel::notificationEvent(int eventId) 0365 { 0366 if (eventId == NotificationHub::EventConfigurationChanged) { 0367 beginResetModel(); 0368 readConfiguration(); 0369 endResetModel(); 0370 } 0371 } 0372 0373 void ValueListModel::readConfiguration() 0374 { 0375 // Load mapping from color value to label from Preferences 0376 colorToLabel.clear(); 0377 for (QVector<QPair<QString, QString>>::ConstIterator it = Preferences::instance().colorCodes().constBegin(); it != Preferences::instance().colorCodes().constEnd(); ++it) 0378 colorToLabel.insert(it->first, it->second); 0379 } 0380 0381 void ValueListModel::updateValues() 0382 { 0383 values.clear(); 0384 if (file == nullptr) return; 0385 0386 for (const auto &element : const_cast<const File &>(*file)) { 0387 QSharedPointer<const Entry> entry = element.dynamicCast<const Entry>(); 0388 if (!entry.isNull() && entry->contains(fName)) { 0389 const Value &v {entry->value(fName)}; 0390 if (v.isEmpty()) 0391 qCWarning(LOG_KBIBTEX_GUI) << "value for key" << fName << "in entry" << entry->id() << "is empty"; 0392 if (fName == Entry::ftStarRating) { 0393 bool ok = false; 0394 const double percent {StarRatingPainter::roundToNearestHalfStarPercent(PlainTextValue::text(v).toFloat(&ok))}; 0395 if (ok) { 0396 const QString text {QString::number(percent, 'f', 2)}; 0397 const QString zeroPadded {QString(QStringLiteral("%1")).arg(static_cast<int>(percent * 1000.0), 6, 10, QLatin1Char('0'))}; 0398 QSharedPointer<PlainText> plainText {QSharedPointer<PlainText>(new PlainText(text))}; 0399 insertText(text, plainText, zeroPadded); 0400 } 0401 } else 0402 insertValue(v); 0403 } 0404 } 0405 } 0406 0407 void ValueListModel::insertValue(const Value &value) 0408 { 0409 for (const QSharedPointer<ValueItem> &item : value) { 0410 const QString text = PlainTextValue::text(*item); 0411 if (text.isEmpty()) continue; ///< skip empty values 0412 0413 insertText(text, item); 0414 } 0415 } 0416 0417 void ValueListModel::insertText(const QString &text, const QSharedPointer<ValueItem> &item, const QString &sortBy) 0418 { 0419 int index = indexOf(text); 0420 if (index < 0) { 0421 // Previously unknown text 0422 ValueLine newValueLine; 0423 newValueLine.text = text; 0424 newValueLine.count = 1; 0425 newValueLine.value.append(item); 0426 0427 // Memorize sorting criterium: 0428 // * if a sorting criterion is given via 'sortBy', use it 0429 // * for persons, use last name first 0430 // * in any other case, use lower case 0431 const QSharedPointer<Person> person = item.dynamicCast<Person>(); 0432 newValueLine.sortBy = !sortBy.isEmpty() ? sortBy : (person.isNull() ? text.toLower() : person->lastName().toLower() + QStringLiteral(" ") + person->firstName().toLower()); 0433 0434 values << newValueLine; 0435 } else { 0436 ++values[index].count; 0437 } 0438 } 0439 0440 int ValueListModel::indexOf(const QString &text) 0441 { 0442 QString color; 0443 QString cmpText = text; 0444 if (fName == Entry::ftColor && !(color = colorToLabel.key(text, QString())).isEmpty()) 0445 cmpText = color; 0446 if (cmpText.isEmpty()) 0447 qCWarning(LOG_KBIBTEX_GUI) << "Should never happen"; 0448 0449 int i = 0; 0450 /// TODO this is really slow for large data sets: O(n^2) 0451 /// maybe use a hash table instead? 0452 for (const ValueLine &valueLine : const_cast<const ValueLineList &>(values)) { 0453 if (valueLine.text == cmpText) 0454 return i; 0455 ++i; 0456 } 0457 return -1; 0458 } 0459 0460 bool ValueListModel::searchAndReplaceValueInEntries(const QModelIndex &index, const Value &newValue) 0461 { 0462 /// Fetch the string representing the new, user-entered value 0463 const QString newText = PlainTextValue::text(newValue); 0464 0465 if (newText.isEmpty()) 0466 return false; 0467 0468 /// Fetch the string as it was shown before the editing started 0469 QString origText = data(index, Qt::DisplayRole).toString(); 0470 /// Special treatment for colors 0471 if (fName == Entry::ftColor) { 0472 /// for colors, convert color (RGB) to the associated label 0473 QString color = colorToLabel.key(origText); 0474 if (!color.isEmpty()) origText = color; 0475 } 0476 0477 /// Go through all elements in the current file 0478 for (const QSharedPointer<Element> &element : const_cast<const File &>(*file)) { 0479 QSharedPointer<Entry> entry = element.dynamicCast<Entry>(); 0480 /// Process only Entry objects 0481 if (!entry.isNull()) { 0482 /// Go through every key-value pair in entry (author, title, ...) 0483 for (Entry::Iterator eit = entry->begin(); eit != entry->end(); ++eit) { 0484 /// Fetch key-value pair's key 0485 const QString key = eit.key().toLower(); 0486 /// Process only key-value pairs that are filtered for (e.g. only keywords) 0487 if (key == fName) { 0488 eit.value().replace(origText, newValue.first()); 0489 break; 0490 } 0491 } 0492 } 0493 } 0494 0495 return true; 0496 } 0497 0498 bool ValueListModel::searchAndReplaceValueInModel(const QModelIndex &index, const Value &newValue) 0499 { 0500 /// Fetch the string representing the new, user-entered value 0501 const QString newText = PlainTextValue::text(newValue); 0502 if (newText.isEmpty()) 0503 return false; 0504 0505 const int row = index.row(); 0506 0507 /// Test if user-entered text exists already in model's data 0508 /// newTextAlreadyInListIndex will be row of duplicate or 0509 /// -1 if new text is unique 0510 int newTextAlreadyInListIndex = -1; 0511 for (int r = values.count() - 1; newTextAlreadyInListIndex < 0 && r >= 0; --r) { 0512 if (row != r && values[r].text == newText) 0513 newTextAlreadyInListIndex = r; 0514 } 0515 0516 if (newTextAlreadyInListIndex < 0) { 0517 /// User-entered text is unique, so simply replace 0518 /// old text with new text 0519 values[row].text = newText; 0520 values[row].value = newValue; 0521 const QSharedPointer<Person> person = newValue.first().dynamicCast<Person>(); 0522 values[row].sortBy = person.isNull() ? QString() : person->lastName() + QStringLiteral(" ") + person->firstName(); 0523 } else { 0524 /// The user-entered text existed before 0525 0526 const int lastRow = values.count() - 1; 0527 if (row != lastRow) { 0528 /// Unless duplicate is last one in list, 0529 /// overwrite edited row with last row's value 0530 values[row].text = values[lastRow].text; 0531 values[row].value = values[lastRow].value; 0532 values[row].sortBy = values[lastRow].sortBy; 0533 } 0534 0535 /// Remove last row, which is no longer used 0536 beginRemoveRows(QModelIndex(), lastRow, lastRow); 0537 values.remove(lastRow); 0538 endRemoveRows(); 0539 } 0540 0541 /// Notify Qt about data changed 0542 Q_EMIT dataChanged(index, index); 0543 0544 return true; 0545 } 0546 0547 void ValueListModel::removeValueFromEntries(const QModelIndex &index) 0548 { 0549 /// Retrieve the Value object containing the user-entered data 0550 const Value toBeDeletedValue = values[index.row()].value; 0551 if (toBeDeletedValue.isEmpty()) { 0552 return; 0553 } 0554 const QString toBeDeletedText = PlainTextValue::text(toBeDeletedValue); 0555 if (toBeDeletedText.isEmpty()) { 0556 return; 0557 } 0558 0559 /// Go through all elements in the current file 0560 for (const QSharedPointer<Element> &element : const_cast<const File &>(*file)) { 0561 QSharedPointer<Entry> entry = element.dynamicCast<Entry>(); 0562 /// Process only Entry objects 0563 if (!entry.isNull()) { 0564 /// Go through every key-value pair in entry (author, title, ...) 0565 for (Entry::Iterator eit = entry->begin(); eit != entry->end(); ++eit) { 0566 /// Fetch key-value pair's key 0567 const QString key = eit.key().toLower(); 0568 /// Process only key-value pairs that are filtered for (e.g. only keywords) 0569 if (key == fName) { 0570 /// Fetch the key-value pair's value's textual representation 0571 const QString valueFullText = PlainTextValue::text(eit.value()); 0572 if (valueFullText == toBeDeletedText) { 0573 /// If the key-value pair's value's textual representation is the same 0574 /// as the value to be delted, remove this key-value pair 0575 /// This test is usually true for keys like title, year, or edition. 0576 entry->remove(key); /// This would break the Iterator, but code "breakes" from loop anyways 0577 } else { 0578 /// The test above failed, but the delete operation may have 0579 /// to be applied to a ValueItem inside the value. 0580 /// Possible keys for such a case include author, editor, or keywords. 0581 0582 /// Process each ValueItem inside this Value 0583 for (Value::Iterator vit = eit.value().begin(); vit != eit.value().end();) { 0584 /// Similar procedure as for full values above: 0585 /// If a ValueItem's textual representation is the same 0586 /// as the shown string which has be deleted, remove the 0587 /// ValueItem from this Value. If the Value becomes empty, 0588 /// remove Value as well. 0589 const QString valueItemText = PlainTextValue::text(* (*vit)); 0590 if (valueItemText == toBeDeletedText) { 0591 /// Erase old ValueItem from this Value 0592 vit = eit.value().erase(vit); 0593 } else 0594 ++vit; 0595 } 0596 0597 if (eit.value().isEmpty()) { 0598 /// This value does no longer contain any ValueItems. 0599 entry->remove(key); /// This would break the Iterator, but code "breakes" from loop anyways 0600 } 0601 } 0602 break; 0603 } 0604 } 0605 } 0606 } 0607 } 0608 0609 void ValueListModel::removeValueFromModel(const QModelIndex &index) 0610 { 0611 const int row = index.row(); 0612 const int lastRow = values.count() - 1; 0613 0614 if (row != lastRow) { 0615 /// Unless duplicate is last one in list, 0616 /// overwrite edited row with last row's value 0617 values[row].text = values[lastRow].text; 0618 values[row].value = values[lastRow].value; 0619 values[row].sortBy = values[lastRow].sortBy; 0620 0621 Q_EMIT dataChanged(index, index); 0622 } 0623 0624 /// Remove last row, which is no longer used 0625 beginRemoveRows(QModelIndex(), lastRow, lastRow); 0626 values.remove(lastRow); 0627 endRemoveRows(); 0628 }