File indexing completed on 2024-04-28 05:08:23
0001 /*************************************************************************** 0002 Copyright (C) 2001-2020 Robby Stephenson <robby@periapsis.org> 0003 ***************************************************************************/ 0004 0005 /*************************************************************************** 0006 * * 0007 * This program is free software; you can redistribute it and/or * 0008 * modify it under the terms of the GNU General Public License as * 0009 * published by the Free Software Foundation; either version 2 of * 0010 * the License or (at your option) version 3 or any later version * 0011 * accepted by the membership of KDE e.V. (or its successor approved * 0012 * by the membership of KDE e.V.), which shall act as a proxy * 0013 * defined in Section 14 of version 3 of the license. * 0014 * * 0015 * This program is distributed in the hope that it will be useful, * 0016 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 0017 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 0018 * GNU General Public License for more details. * 0019 * * 0020 * You should have received a copy of the GNU General Public License * 0021 * along with this program. If not, see <http://www.gnu.org/licenses/>. * 0022 * * 0023 ***************************************************************************/ 0024 0025 #include "groupview.h" 0026 #include "collection.h" 0027 #include "field.h" 0028 #include "entry.h" 0029 #include "entrygroup.h" 0030 #include "filter.h" 0031 #include "controller.h" 0032 #include "models/entrygroupmodel.h" 0033 #include "models/groupsortmodel.h" 0034 #include "models/modelmanager.h" 0035 #include "models/models.h" 0036 #include "gui/countdelegate.h" 0037 #include "tellico_debug.h" 0038 0039 #include <KLocalizedString> 0040 0041 #include <QMenu> 0042 #include <QIcon> 0043 #include <QStringList> 0044 #include <QColor> 0045 #include <QHeaderView> 0046 #include <QContextMenuEvent> 0047 0048 using Tellico::GroupView; 0049 0050 GroupView::GroupView(QWidget* parent_) 0051 : GUI::TreeView(parent_), m_notSortedYet(true) { 0052 header()->setSectionResizeMode(QHeaderView::Stretch); 0053 setHeaderHidden(false); 0054 setSelectionMode(QAbstractItemView::ExtendedSelection); 0055 0056 connect(this, &GroupView::expanded, this, &GroupView::slotExpanded); 0057 connect(this, &GroupView::collapsed, this, &GroupView::slotCollapsed); 0058 connect(this, &GroupView::doubleClicked, this, &GroupView::slotDoubleClicked); 0059 connect(header(), &QHeaderView::sortIndicatorChanged, this, &GroupView::slotSortingChanged); 0060 0061 m_groupOpenIconName = QStringLiteral("folder-open"); 0062 m_groupClosedIconName = QStringLiteral("folder"); 0063 EntryGroupModel* groupModel = new EntryGroupModel(this); 0064 GroupSortModel* sortModel = new GroupSortModel(this); 0065 sortModel->setSourceModel(groupModel); 0066 setModel(sortModel); 0067 setItemDelegate(new GUI::CountDelegate(this)); 0068 0069 ModelManager::self()->setGroupModel(model()); 0070 } 0071 0072 Tellico::EntryGroupModel* GroupView::sourceModel() const { 0073 return static_cast<EntryGroupModel*>(sortModel()->sourceModel()); 0074 } 0075 0076 void GroupView::addCollection(Tellico::Data::CollPtr coll_) { 0077 if(!coll_) { 0078 myWarning() << "null coll pointer!"; 0079 return; 0080 } 0081 0082 m_coll = coll_; 0083 // if the collection doesn't have the grouped field, and it's not the pseudo-group, 0084 // change it to default 0085 if(m_groupBy.isEmpty() || (!coll_->hasField(m_groupBy) && m_groupBy != Data::Collection::s_peopleGroupName)) { 0086 m_groupBy = coll_->defaultGroupField(); 0087 } 0088 0089 // when the coll gets set for the first time, the pixmaps need to be updated 0090 if((m_coll->hasField(m_groupBy) && m_coll->fieldByName(m_groupBy)->formatType() == FieldFormat::FormatName) 0091 || m_groupBy == Data::Collection::s_peopleGroupName) { 0092 m_groupOpenIconName = QStringLiteral(":/icons/person-open"); 0093 m_groupClosedIconName = QStringLiteral(":/icons/person"); 0094 } 0095 0096 updateHeader(); 0097 populateCollection(); 0098 setEntrySortField(m_entrySortField); 0099 } 0100 0101 void GroupView::removeCollection(Tellico::Data::CollPtr coll_) { 0102 if(!coll_) { 0103 myWarning() << "null coll pointer!"; 0104 return; 0105 } 0106 0107 blockSignals(true); 0108 slotReset(); 0109 blockSignals(false); 0110 } 0111 0112 void GroupView::populateCollection() { 0113 if(!m_coll) { 0114 return; 0115 } 0116 0117 // myDebug() << m_groupBy; 0118 if(m_groupBy.isEmpty()) { 0119 m_groupBy = m_coll->defaultGroupField(); 0120 } 0121 0122 setUpdatesEnabled(false); 0123 slotReset(); // delete all groups 0124 0125 // if there's no group field, just return 0126 if(m_groupBy.isEmpty()) { 0127 setUpdatesEnabled(true); 0128 return; 0129 } 0130 0131 Data::EntryGroupDict* dict = m_coll->entryGroupDictByName(m_groupBy); 0132 if(!dict) { // could happen if m_groupBy is non empty, but there are no entries with a value 0133 setUpdatesEnabled(true); 0134 return; 0135 } 0136 0137 // add all the groups 0138 sourceModel()->addGroups(dict->values(), m_groupClosedIconName); 0139 // still need to change icon for empty group 0140 foreach(Data::EntryGroup* group, *dict) { 0141 if(group->hasEmptyGroupName()) { 0142 QModelIndex emptyGroupIndex = sourceModel()->indexFromGroup(group); 0143 sourceModel()->setData(emptyGroupIndex, QLatin1String("folder-red"), Qt::DecorationRole); 0144 break; 0145 } 0146 } 0147 0148 setUpdatesEnabled(true); 0149 // must re-sort in order to update view 0150 model()->sort(0, sortOrder()); 0151 } 0152 0153 // don't 'shadow' QListView::setSelected 0154 void GroupView::setEntrySelected(Tellico::Data::EntryPtr entry_) { 0155 // DEBUG_LINE; 0156 // if entry_ is null pointer, set no selected 0157 if(!entry_) { 0158 // don't move this one outside the block since it calls setCurrentItem(0) 0159 clearSelection(); 0160 return; 0161 } 0162 0163 // if the selected entry is the same as the current one, just return 0164 QModelIndex curr = currentIndex(); 0165 if(entry_ == sourceModel()->entry(curr)) { 0166 return; 0167 } 0168 0169 // have to find a group whose field is the same as currently shown 0170 if(m_groupBy.isEmpty()) { 0171 myDebug() << "no group field"; 0172 return; 0173 } 0174 0175 Data::EntryGroup* group = nullptr; 0176 foreach(Data::EntryGroup* tmpGroup, entry_->groups()) { 0177 if(tmpGroup->fieldName() == m_groupBy) { 0178 group = tmpGroup; 0179 break; 0180 } 0181 } 0182 if(!group) { 0183 myDebug() << "entry is not in any current groups!"; 0184 return; 0185 } 0186 0187 QModelIndex index = sourceModel()->indexFromGroup(group); 0188 0189 clearSelection(); 0190 for(; index.isValid(); index = index.sibling(index.row()+1, 0)) { 0191 if(sourceModel()->entry(index) == entry_) { 0192 blockSignals(true); 0193 selectionModel()->select(index, QItemSelectionModel::Select); 0194 setCurrentIndex(index); 0195 blockSignals(false); 0196 scrollTo(index); 0197 break; 0198 } 0199 } 0200 } 0201 0202 void GroupView::modifyField(Tellico::Data::CollPtr, Tellico::Data::FieldPtr, Tellico::Data::FieldPtr newField_) { 0203 if(newField_->name() == m_groupBy) { 0204 updateHeader(newField_); 0205 } 0206 // if the grouping changed at all, our groups got deleted out from under us 0207 populateCollection(); 0208 } 0209 0210 void GroupView::slotReset() { 0211 sourceModel()->clear(); 0212 } 0213 0214 void GroupView::slotModifyGroups(Tellico::Data::CollPtr coll_, QList<Tellico::Data::EntryGroup*> groups_) { 0215 if(!coll_ || groups_.isEmpty()) { 0216 myWarning() << "null coll or group pointer!"; 0217 return; 0218 } 0219 0220 /* for each group 0221 - remove existing empty ones 0222 - modify existing ones 0223 - add new ones 0224 */ 0225 foreach(Data::EntryGroup* group, groups_) { 0226 // if the entries aren't grouped by field of the modified group, 0227 // we don't care, so skip 0228 if(m_groupBy != group->fieldName()) { 0229 continue; 0230 } 0231 0232 // myDebug() << group->fieldName() << "/" << group->groupName(); 0233 QModelIndex groupIndex = sourceModel()->indexFromGroup(group); 0234 if(groupIndex.isValid()) { 0235 if(group->isEmpty()) { 0236 sourceModel()->removeGroup(group); 0237 continue; 0238 } 0239 sourceModel()->modifyGroup(group); 0240 } else { 0241 if(group->isEmpty()) { 0242 myDebug() << "trying to add empty group"; 0243 continue; 0244 } 0245 addGroup(group); 0246 } 0247 } 0248 // don't want any selected 0249 clearSelection(); 0250 } 0251 0252 void GroupView::contextMenuEvent(QContextMenuEvent* event_) { 0253 QModelIndex index = indexAt(event_->pos()); 0254 if(!index.isValid()) { 0255 return; 0256 } 0257 0258 QMenu menu(this); 0259 // no parent means it's a top-level item 0260 if(!index.parent().isValid()) { 0261 menu.addAction(QIcon::fromTheme(QStringLiteral("arrow-down-double")), 0262 i18n("Expand All Groups"), this, &QTreeView::expandAll); 0263 menu.addAction(QIcon::fromTheme(QStringLiteral("arrow-up-double")), 0264 i18n("Collapse All Groups"), this, &QTreeView::collapseAll); 0265 menu.addAction(QIcon::fromTheme(QStringLiteral("view-filter")), 0266 i18n("Filter by Group"), this, &GroupView::slotFilterGroup); 0267 } else { 0268 Controller::self()->plugEntryActions(&menu); 0269 } 0270 menu.addSeparator(); 0271 QMenu* sortMenu = Controller::self()->plugSortActions(&menu); 0272 connect(sortMenu, &QMenu::triggered, this, &GroupView::slotSortMenuActivated); 0273 menu.exec(event_->globalPos()); 0274 } 0275 0276 void GroupView::slotCollapsed(const QModelIndex& index_) { 0277 if(model()->data(index_, Qt::DecorationRole).toString() == m_groupOpenIconName) { 0278 model()->setData(index_, m_groupClosedIconName, Qt::DecorationRole); 0279 } 0280 } 0281 0282 void GroupView::slotExpanded(const QModelIndex& index_) { 0283 if(model()->data(index_, Qt::DecorationRole).toString() == m_groupClosedIconName) { 0284 model()->setData(index_, m_groupOpenIconName, Qt::DecorationRole); 0285 } 0286 } 0287 0288 void GroupView::setGroupField(const QString& groupField_) { 0289 // myDebug() << groupField_; 0290 if(groupField_.isEmpty() || groupField_ == m_groupBy) { 0291 return; 0292 } 0293 0294 m_groupBy = groupField_; 0295 if(!m_coll) { 0296 return; // can't do anything yet, but still need to set the variable 0297 } 0298 if((m_coll->hasField(groupField_) && m_coll->fieldByName(groupField_)->formatType() == Tellico::FieldFormat::FormatName) 0299 || groupField_ == Tellico::Data::Collection::s_peopleGroupName) { 0300 m_groupOpenIconName = QStringLiteral(":/icons/person-open"); 0301 m_groupClosedIconName = QStringLiteral(":/icons/person"); 0302 } else { 0303 m_groupOpenIconName = QStringLiteral("folder-open"); 0304 m_groupClosedIconName = QStringLiteral("folder"); 0305 } 0306 updateHeader(); 0307 populateCollection(); 0308 } 0309 0310 QString GroupView::entrySortField() const { 0311 return m_entrySortField; 0312 } 0313 0314 void GroupView::setEntrySortField(const QString& groupSortName_) { 0315 m_entrySortField = groupSortName_; 0316 GroupSortModel* model = static_cast<GroupSortModel*>(sortModel()); 0317 Q_ASSERT(model); 0318 model->setEntrySortField(groupSortName_); 0319 } 0320 0321 void GroupView::slotFilterGroup() { 0322 QModelIndexList indexes = selectionModel()->selectedIndexes(); 0323 if(indexes.isEmpty()) { 0324 return; 0325 } 0326 0327 FilterPtr filter(new Filter(Filter::MatchAny)); 0328 foreach(const QModelIndex& index, indexes) { 0329 // the indexes pointing to a group have no parent 0330 if(index.parent().isValid()) { 0331 continue; 0332 } 0333 0334 if(!model()->hasChildren(index)) { //ignore empty items 0335 continue; 0336 } 0337 // need to check for people group 0338 if(m_groupBy == Data::Collection::s_peopleGroupName) { 0339 QModelIndex firstChild = model()->index(0, 0, index); 0340 QModelIndex sourceIndex = sortModel()->mapToSource(firstChild); 0341 Data::EntryPtr entry = sourceModel()->entry(sourceIndex); 0342 Q_ASSERT(entry); 0343 Data::FieldList fields = entry->collection()->peopleFields(); 0344 foreach(Data::FieldPtr field, fields) { 0345 filter->append(new FilterRule(field->name(), 0346 FieldFormat::matchValueRegularExpression(model()->data(index).toString()), 0347 FilterRule::FuncRegExp)); 0348 } 0349 } else { 0350 Data::EntryGroup* group = model()->data(index, GroupPtrRole).value<Data::EntryGroup*>(); 0351 if(group) { 0352 if(group->hasEmptyGroupName()) { 0353 filter->append(new FilterRule(m_groupBy, QString(), FilterRule::FuncEquals)); 0354 } else { 0355 // if the field does not allow multiple values and is not a table 0356 // then can just do an equal match 0357 Data::FieldPtr field = group->at(0)->collection()->fieldByName(group->fieldName()); 0358 if(field && field->type() != Data::Field::Table && !field->hasFlag(Data::Field::AllowMultiple)) { 0359 filter->append(new FilterRule(m_groupBy, group->groupName(), FilterRule::FuncEquals)); 0360 } else { 0361 filter->append(new FilterRule(m_groupBy, 0362 FieldFormat::matchValueRegularExpression(group->groupName()), 0363 FilterRule::FuncRegExp)); 0364 } 0365 } 0366 } 0367 } 0368 } 0369 0370 if(!filter->isEmpty()) { 0371 emit signalUpdateFilter(filter); 0372 } 0373 } 0374 0375 void GroupView::slotDoubleClicked(const QModelIndex& index_) { 0376 QModelIndex realIndex = sortModel()->mapToSource(index_); 0377 Data::EntryPtr entry = sourceModel()->entry(realIndex); 0378 if(entry) { 0379 Controller::self()->editEntry(entry); 0380 } 0381 } 0382 0383 // this gets called when header() is clicked, so cycle through 0384 void GroupView::slotSortingChanged(int col_, Qt::SortOrder order_) { 0385 Q_UNUSED(col_); 0386 if(order_ == Qt::AscendingOrder && !m_notSortedYet) { // cycle through after ascending 0387 if(sortModel()->sortRole() == RowCountRole) { 0388 sortModel()->setSortRole(Qt::DisplayRole); 0389 } else { 0390 sortModel()->setSortRole(RowCountRole); 0391 } 0392 } 0393 0394 updateHeader(); 0395 m_notSortedYet = false; 0396 } 0397 0398 void GroupView::slotSortMenuActivated(QAction* action_) { 0399 Data::FieldPtr field = action_->data().value<Data::FieldPtr>(); 0400 Q_ASSERT(field); 0401 if(!field) { 0402 return; 0403 } 0404 setEntrySortField(field->name()); 0405 } 0406 0407 void GroupView::updateHeader(Tellico::Data::FieldPtr field_/*=0*/) { 0408 QString t = field_ ? field_->title() : groupTitle(); 0409 if(sortModel()->sortRole() == Qt::DisplayRole) { 0410 model()->setHeaderData(0, Qt::Horizontal, t); 0411 } else { 0412 model()->setHeaderData(0, Qt::Horizontal, i18n("%1 (Sort by Count)", t)); 0413 } 0414 } 0415 0416 QString GroupView::groupTitle() { 0417 QString title; 0418 if(!m_coll || m_groupBy.isEmpty()) { 0419 title = i18nc("Group Name Header", "Group"); 0420 } else { 0421 Data::FieldPtr f = m_coll->fieldByName(m_groupBy); 0422 if(f) { 0423 title = f->title(); 0424 } else if(m_groupBy == Data::Collection::s_peopleGroupName) { 0425 title = i18n("People"); 0426 } 0427 } 0428 return title; 0429 } 0430 0431 void GroupView::addGroup(Tellico::Data::EntryGroup* group_) { 0432 if(group_->isEmpty()) { 0433 return; 0434 } 0435 QModelIndex index = sourceModel()->addGroup(group_); 0436 if(group_->hasEmptyGroupName()) { 0437 sourceModel()->setData(index, QLatin1String("folder-red"), Qt::DecorationRole); 0438 } else { 0439 sourceModel()->setData(index, m_groupClosedIconName, Qt::DecorationRole); 0440 } 0441 }