File indexing completed on 2025-01-12 04:35:49
0001 /*************************************************************************** 0002 * SPDX-License-Identifier: GPL-2.0-or-later 0003 * * 0004 * SPDX-FileCopyrightText: 2004-2019 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 "valuelist.h" 0021 0022 #include <typeinfo> 0023 0024 #include <QTreeView> 0025 #include <QHeaderView> 0026 #include <QGridLayout> 0027 #include <QStringListModel> 0028 #include <QScrollBar> 0029 #include <QLineEdit> 0030 #include <QComboBox> 0031 #include <QTimer> 0032 #include <QSortFilterProxyModel> 0033 #include <QAction> 0034 0035 #include <KConfigGroup> 0036 #include <KLocalizedString> 0037 #include <KToggleAction> 0038 #include <KSharedConfig> 0039 0040 #include <BibTeXFields> 0041 #include <Entry> 0042 #include <file/FileView> 0043 #include <ValueListModel> 0044 #include <models/FileModel> 0045 0046 class ValueList::Private 0047 { 0048 private: 0049 ValueList *p; 0050 ValueListDelegate *delegate; 0051 0052 public: 0053 KSharedConfigPtr config; 0054 const QString configGroupName; 0055 const QString configKeyFieldName, configKeyShowCountColumn, configKeySortByCountAction, configKeyHeaderState; 0056 0057 FileView *fileView; 0058 QTreeView *treeviewFieldValues; 0059 ValueListModel *model; 0060 QSortFilterProxyModel *sortingModel; 0061 QComboBox *comboboxFieldNames; 0062 QLineEdit *lineeditFilter; 0063 const int countWidth; 0064 KToggleAction *showCountColumnAction; 0065 KToggleAction *sortByCountAction; 0066 0067 Private(ValueList *parent) 0068 : p(parent), config(KSharedConfig::openConfig(QStringLiteral("kbibtexrc"))), configGroupName(QStringLiteral("Value List Docklet")), 0069 configKeyFieldName(QStringLiteral("FieldName")), configKeyShowCountColumn(QStringLiteral("ShowCountColumn")), 0070 configKeySortByCountAction(QStringLiteral("SortByCountAction")), configKeyHeaderState(QStringLiteral("HeaderState")), 0071 fileView(nullptr), model(nullptr), sortingModel(nullptr), 0072 #if QT_VERSION >= 0x050b00 0073 countWidth(8 + parent->fontMetrics().horizontalAdvance(i18n("Count"))) 0074 #else // QT_VERSION >= 0x050b00 0075 countWidth(8 + parent->fontMetrics().width(i18n("Count"))) 0076 #endif // QT_VERSION >= 0x050b00 0077 { 0078 setupGUI(); 0079 initialize(); 0080 } 0081 0082 void setupGUI() { 0083 QBoxLayout *layout = new QVBoxLayout(p); 0084 layout->setContentsMargins(0, 0, 0, 0); 0085 layout->setSpacing(0); 0086 0087 auto nestedLayout = new QVBoxLayout(); 0088 nestedLayout->setContentsMargins( 0089 p->style()->pixelMetric(QStyle::PM_LayoutLeftMargin), 0090 p->style()->pixelMetric(QStyle::PM_LayoutTopMargin), 0091 p->style()->pixelMetric(QStyle::PM_LayoutBottomMargin), 0092 p->style()->pixelMetric(QStyle::PM_LayoutRightMargin)); 0093 nestedLayout->setSpacing(p->style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing)); 0094 layout->addLayout(nestedLayout); 0095 0096 comboboxFieldNames = new QComboBox(p); 0097 comboboxFieldNames->setEditable(true); 0098 nestedLayout->addWidget(comboboxFieldNames); 0099 0100 lineeditFilter = new QLineEdit(p); 0101 nestedLayout->addWidget(lineeditFilter); 0102 lineeditFilter->setClearButtonEnabled(true); 0103 lineeditFilter->setPlaceholderText(i18n("Filter value list")); 0104 0105 treeviewFieldValues = new QTreeView(p); 0106 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 0107 treeviewFieldValues->setProperty("_breeze_borders_sides", QVariant::fromValue(QFlags{Qt::TopEdge})); 0108 #endif 0109 layout->addWidget(treeviewFieldValues); 0110 treeviewFieldValues->setEditTriggers(QAbstractItemView::EditKeyPressed); 0111 treeviewFieldValues->setSortingEnabled(true); 0112 treeviewFieldValues->sortByColumn(0, Qt::AscendingOrder); 0113 delegate = new ValueListDelegate(treeviewFieldValues); 0114 treeviewFieldValues->setItemDelegate(delegate); 0115 treeviewFieldValues->setRootIsDecorated(false); 0116 treeviewFieldValues->setSelectionMode(QTreeView::SingleSelection); 0117 treeviewFieldValues->setAlternatingRowColors(true); 0118 treeviewFieldValues->header()->setSectionResizeMode(QHeaderView::Fixed); 0119 0120 treeviewFieldValues->setContextMenuPolicy(Qt::ActionsContextMenu); 0121 // Create context menu item to start renaming the current value in all bibliography entries 0122 QAction *action = new QAction(QIcon::fromTheme(QStringLiteral("edit-rename")), i18n("Edit to replace all occurrences"), p); 0123 connect(action, &QAction::triggered, p, &ValueList::listItemStartRenaming); 0124 treeviewFieldValues->addAction(action); 0125 // Create context menu item to remove current value from all bibliography entries 0126 action = new QAction(QIcon::fromTheme(QStringLiteral("edit-table-delete-row")), i18n("Remove all occurrences"), p); 0127 connect(action, &QAction::triggered, p, &ValueList::deleteAllOccurrences); 0128 treeviewFieldValues->addAction(action); 0129 0130 p->setEnabled(false); 0131 0132 connect(comboboxFieldNames, static_cast<void(QComboBox::*)(int)>(&QComboBox::activated), p, &ValueList::update); 0133 connect(comboboxFieldNames, static_cast<void(QComboBox::*)(int)>(&QComboBox::activated), lineeditFilter, &QLineEdit::clear); 0134 connect(treeviewFieldValues, &QTreeView::activated, p, &ValueList::listItemActivated); 0135 connect(delegate, &ValueListDelegate::closeEditor, treeviewFieldValues, &QTreeView::reset); 0136 0137 /// add context menu to header 0138 treeviewFieldValues->header()->setContextMenuPolicy(Qt::ActionsContextMenu); 0139 showCountColumnAction = new KToggleAction(i18n("Show Count Column"), treeviewFieldValues); 0140 connect(showCountColumnAction, &QAction::triggered, p, &ValueList::showCountColumnToggled); 0141 treeviewFieldValues->header()->addAction(showCountColumnAction); 0142 0143 sortByCountAction = new KToggleAction(i18n("Sort by Count"), treeviewFieldValues); 0144 connect(sortByCountAction, &QAction::triggered, p, &ValueList::sortByCountToggled); 0145 treeviewFieldValues->header()->addAction(sortByCountAction); 0146 } 0147 0148 void setComboboxFieldNamesCurrentItem(const QString &text) { 0149 int index = comboboxFieldNames->findData(text, Qt::UserRole, Qt::MatchExactly); 0150 if (index < 0) index = comboboxFieldNames->findData(text, Qt::UserRole, Qt::MatchStartsWith); 0151 if (index < 0) index = comboboxFieldNames->findData(text, Qt::UserRole, Qt::MatchContains); 0152 if (index >= 0) comboboxFieldNames->setCurrentIndex(index); 0153 } 0154 0155 void initialize() { 0156 lineeditFilter->clear(); 0157 comboboxFieldNames->clear(); 0158 for (const auto &fd : const_cast<const BibTeXFields &>(BibTeXFields::instance())) { 0159 if (!fd.upperCamelCaseAlt.isEmpty()) continue; /// keep only "single" fields and not combined ones like "Author or Editor" 0160 if (fd.upperCamelCase.startsWith(QLatin1Char('^'))) continue; /// skip "type" and "id" (those are marked with '^') 0161 comboboxFieldNames->addItem(fd.label, fd.upperCamelCase); 0162 } 0163 // Sort the combo box locale-aware. Thus we need a SortFilterProxyModel 0164 QSortFilterProxyModel *proxy = new QSortFilterProxyModel(comboboxFieldNames); 0165 proxy->setSortLocaleAware(true); 0166 proxy->setSourceModel(comboboxFieldNames->model()); 0167 comboboxFieldNames->model()->setParent(proxy); 0168 comboboxFieldNames->setModel(proxy); 0169 comboboxFieldNames->model()->sort(0); 0170 0171 KConfigGroup configGroup(config, configGroupName); 0172 const QString fieldName = configGroup.readEntry(configKeyFieldName, QString(Entry::ftAuthor)); 0173 setComboboxFieldNamesCurrentItem(fieldName); 0174 showCountColumnAction->setChecked(configGroup.readEntry(configKeyShowCountColumn, true)); 0175 sortByCountAction->setChecked(configGroup.readEntry(configKeySortByCountAction, false)); 0176 sortByCountAction->setEnabled(!showCountColumnAction->isChecked()); 0177 QByteArray headerState = configGroup.readEntry(configKeyHeaderState, QByteArray()); 0178 treeviewFieldValues->header()->restoreState(headerState); 0179 0180 connect(treeviewFieldValues->header(), &QHeaderView::sortIndicatorChanged, p, &ValueList::columnsChanged); 0181 } 0182 0183 void update() { 0184 const QString fieldName = currentField(); 0185 0186 delegate->setFieldName(fieldName); 0187 model = fileView == nullptr ? nullptr : fileView->valueListModel(fieldName); 0188 QAbstractItemModel *usedModel = model; 0189 if (usedModel != nullptr) { 0190 model->setShowCountColumn(showCountColumnAction->isChecked()); 0191 model->setSortBy(sortByCountAction->isChecked() ? ValueListModel::SortBy::Count : ValueListModel::SortBy::Text); 0192 0193 if (sortingModel != nullptr) delete sortingModel; 0194 sortingModel = new QSortFilterProxyModel(p); 0195 sortingModel->setSourceModel(model); 0196 if (treeviewFieldValues->header()->isSortIndicatorShown()) 0197 sortingModel->sort(treeviewFieldValues->header()->sortIndicatorSection(), treeviewFieldValues->header()->sortIndicatorOrder()); 0198 else 0199 sortingModel->sort(1, Qt::DescendingOrder); 0200 sortingModel->setSortRole(ValueListModel::SortRole); 0201 sortingModel->setFilterKeyColumn(0); 0202 sortingModel->setFilterCaseSensitivity(Qt::CaseInsensitive); 0203 sortingModel->setFilterRole(ValueListModel::SearchTextRole); 0204 connect(lineeditFilter, &QLineEdit::textEdited, sortingModel, &QSortFilterProxyModel::setFilterFixedString); 0205 sortingModel->setSortLocaleAware(true); 0206 usedModel = sortingModel; 0207 } 0208 treeviewFieldValues->setModel(usedModel); 0209 0210 KConfigGroup configGroup(config, configGroupName); 0211 configGroup.writeEntry(configKeyFieldName, fieldName); 0212 config->sync(); 0213 } 0214 0215 QString currentField() const { 0216 QString field = comboboxFieldNames->itemData(comboboxFieldNames->currentIndex()).toString(); 0217 if (field.isEmpty()) field = comboboxFieldNames->currentText(); 0218 return field; 0219 } 0220 }; 0221 0222 ValueList::ValueList(QWidget *parent) 0223 : QWidget(parent), d(new Private(this)) 0224 { 0225 QTimer::singleShot(500, this, [this]() { 0226 resizeEvent(nullptr); 0227 }); 0228 } 0229 0230 ValueList::~ValueList() 0231 { 0232 delete d; 0233 } 0234 0235 void ValueList::setFileView(FileView *fileView) 0236 { 0237 if (d->fileView != nullptr) 0238 disconnect(d->fileView, &FileView::destroyed, this, &ValueList::editorDestroyed); 0239 d->fileView = fileView; 0240 if (d->fileView != nullptr) 0241 connect(d->fileView, &FileView::destroyed, this, &ValueList::editorDestroyed); 0242 update(); 0243 resizeEvent(nullptr); 0244 } 0245 0246 void ValueList::update() 0247 { 0248 d->update(); 0249 setEnabled(d->fileView != nullptr); 0250 } 0251 0252 void ValueList::resizeEvent(QResizeEvent *) 0253 { 0254 int widgetWidth = d->treeviewFieldValues->size().width() - d->treeviewFieldValues->verticalScrollBar()->size().width() - 8; 0255 d->treeviewFieldValues->setColumnWidth(0, widgetWidth - d->countWidth); 0256 d->treeviewFieldValues->setColumnWidth(1, d->countWidth); 0257 } 0258 0259 void ValueList::listItemActivated(const QModelIndex &index) 0260 { 0261 const QString itemText = d->sortingModel->mapToSource(index).data(ValueListModel::SearchTextRole).toString(); 0262 if (itemText.isEmpty()) 0263 return; 0264 0265 SortFilterFileModel::FilterQuery fq; 0266 fq.terms = QStringList{itemText}; 0267 fq.combination = SortFilterFileModel::FilterCombination::EveryTerm; 0268 fq.field = d->currentField(); 0269 if (fq.field.isEmpty()) 0270 return; 0271 fq.searchPDFfiles = false; 0272 0273 setEnabled(false); // FIXME necesary? 0274 d->fileView->setFilterBarFilter(fq); 0275 setEnabled(true); // FIXME necesary? 0276 } 0277 0278 void ValueList::listItemStartRenaming() 0279 { 0280 // Get current index from sorted model 0281 const QModelIndex sortedIndex = d->treeviewFieldValues->currentIndex(); 0282 // Make the tree view start and editing delegate on the index 0283 d->treeviewFieldValues->edit(sortedIndex); 0284 } 0285 0286 void ValueList::deleteAllOccurrences() 0287 { 0288 /// Get current index from sorted model 0289 QModelIndex sortedIndex = d->treeviewFieldValues->currentIndex(); 0290 /// Get "real" index from original model, but resort to sibling in first column 0291 QModelIndex realIndex = d->sortingModel->mapToSource(sortedIndex); 0292 realIndex = realIndex.sibling(realIndex.row(), 0); 0293 0294 /// Remove current index from data model 0295 d->model->removeValue(realIndex); 0296 /// Notify main editor about change it its data 0297 d->fileView->externalModification(); 0298 } 0299 0300 void ValueList::showCountColumnToggled() 0301 { 0302 if (d->model != nullptr) 0303 d->model->setShowCountColumn(d->showCountColumnAction->isChecked()); 0304 if (d->showCountColumnAction->isChecked()) 0305 resizeEvent(nullptr); 0306 0307 d->sortByCountAction->setEnabled(!d->showCountColumnAction->isChecked()); 0308 0309 KConfigGroup configGroup(d->config, d->configGroupName); 0310 configGroup.writeEntry(d->configKeyShowCountColumn, d->showCountColumnAction->isChecked()); 0311 d->config->sync(); 0312 } 0313 0314 void ValueList::sortByCountToggled() 0315 { 0316 if (d->model != nullptr) 0317 d->model->setSortBy(d->sortByCountAction->isChecked() ? ValueListModel::SortBy::Count : ValueListModel::SortBy::Text); 0318 0319 KConfigGroup configGroup(d->config, d->configGroupName); 0320 configGroup.writeEntry(d->configKeySortByCountAction, d->sortByCountAction->isChecked()); 0321 d->config->sync(); 0322 } 0323 0324 void ValueList::columnsChanged() 0325 { 0326 QByteArray headerState = d->treeviewFieldValues->header()->saveState(); 0327 KConfigGroup configGroup(d->config, d->configGroupName); 0328 configGroup.writeEntry(d->configKeyHeaderState, headerState); 0329 d->config->sync(); 0330 0331 resizeEvent(nullptr); 0332 } 0333 0334 void ValueList::editorDestroyed() { 0335 /// Reset internal variable to NULL to avoid 0336 /// accessing invalid pointer/data later 0337 d->fileView = nullptr; 0338 }