File indexing completed on 2024-04-28 16:32:01

0001 /***************************************************************************
0002     Copyright (C) 2005-2009 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 "filterview.h"
0026 #include "controller.h"
0027 #include "entry.h"
0028 #include "collection.h"
0029 #include "tellico_kernel.h"
0030 #include "../tellico_debug.h"
0031 #include "models/filtermodel.h"
0032 #include "models/entrysortmodel.h"
0033 #include "models/models.h"
0034 #include "gui/countdelegate.h"
0035 
0036 #include <KLocalizedString>
0037 
0038 #include <QMenu>
0039 #include <QIcon>
0040 #include <QHeaderView>
0041 #include <QContextMenuEvent>
0042 
0043 using namespace Tellico;
0044 using Tellico::FilterView;
0045 
0046 FilterView::FilterView(QWidget* parent_)
0047     : GUI::TreeView(parent_), m_notSortedYet(true) {
0048   header()->setSectionResizeMode(QHeaderView::Stretch);
0049   setHeaderHidden(false);
0050   setSelectionMode(QAbstractItemView::ExtendedSelection);
0051 
0052   connect(this, &QAbstractItemView::doubleClicked,
0053           this, &FilterView::slotDoubleClicked);
0054 
0055   connect(header(), &QHeaderView::sortIndicatorChanged,
0056           this, &FilterView::slotSortingChanged);
0057 
0058   FilterModel* filterModel = new FilterModel(this);
0059   EntrySortModel* sortModel = new EntrySortModel(this);
0060   sortModel->setSourceModel(filterModel);
0061   setModel(sortModel);
0062   setItemDelegate(new GUI::CountDelegate(this));
0063 }
0064 
0065 Tellico::FilterModel* FilterView::sourceModel() const {
0066   return static_cast<FilterModel*>(sortModel()->sourceModel());
0067 }
0068 
0069 void FilterView::addCollection(Tellico::Data::CollPtr coll_) {
0070   m_coll = coll_;
0071   sourceModel()->clear();
0072   sourceModel()->addFilters(m_coll->filters());
0073 }
0074 
0075 void FilterView::addEntries(Tellico::Data::EntryList entries_) {
0076   invalidate(entries_);
0077 }
0078 
0079 void FilterView::modifyEntries(Tellico::Data::EntryList entries_) {
0080   invalidate(entries_);
0081 }
0082 
0083 void FilterView::removeEntries(Tellico::Data::EntryList entries_) {
0084   invalidate(entries_);
0085 }
0086 
0087 void FilterView::slotReset() {
0088   sourceModel()->clear();
0089 }
0090 
0091 void FilterView::addFilter(Tellico::FilterPtr filter_) {
0092   Q_ASSERT(filter_);
0093   sourceModel()->addFilter(filter_);
0094 }
0095 
0096 void FilterView::slotModifyFilter() {
0097   QModelIndex index = currentIndex();
0098   // parent means a top-level item
0099   if(!index.isValid() || index.parent().isValid()) {
0100     return;
0101   }
0102 
0103   QModelIndex realIndex = sortModel()->mapToSource(index);
0104   Kernel::self()->modifyFilter(sourceModel()->filter(realIndex));
0105 }
0106 
0107 void FilterView::slotDeleteFilter() {
0108   QModelIndex index = currentIndex();
0109   // parent means a top-level item
0110   if(!index.isValid() || index.parent().isValid()) {
0111     return;
0112   }
0113 
0114   QModelIndex realIndex = sortModel()->mapToSource(index);
0115   Kernel::self()->removeFilter(sourceModel()->filter(realIndex));
0116 }
0117 
0118 void FilterView::removeFilter(Tellico::FilterPtr filter_) {
0119   Q_ASSERT(filter_);
0120   sourceModel()->removeFilter(filter_);
0121 }
0122 
0123 void FilterView::contextMenuEvent(QContextMenuEvent* event_) {
0124   QModelIndex index = indexAt(event_->pos());
0125   if(!index.isValid()) {
0126     return;
0127   }
0128 
0129   QMenu menu(this);
0130   // no parent means it's a top-level item
0131   if(!index.parent().isValid()) {
0132     menu.addAction(QIcon::fromTheme(QStringLiteral("view-filter")),
0133                     i18n("Modify Filter"), this, &FilterView::slotModifyFilter);
0134     menu.addAction(QIcon::fromTheme(QStringLiteral("edit-delete")),
0135                     i18n("Delete Filter"), this, &FilterView::slotDeleteFilter);
0136   } else {
0137     Controller::self()->plugEntryActions(&menu);
0138   }
0139   menu.exec(event_->globalPos());
0140 }
0141 
0142 void FilterView::selectionChanged(const QItemSelection& selected_, const QItemSelection& deselected_) {
0143 //  DEBUG_BLOCK;
0144   // in Controller::slotUpdateFilter(), filterView->clearSelection() gets called when the filter is empty
0145   // which happens when only entries are selected here
0146   GUI::TreeView::selectionChanged(selected_, deselected_);
0147   FilterPtr filter;
0148   foreach(const QModelIndex& index, selectionModel()->selectedIndexes()) {
0149     QModelIndex realIndex = sortModel()->mapToSource(index);
0150     Data::EntryPtr entry = sourceModel()->entry(realIndex);
0151     if(!entry && !filter) {
0152       filter = sourceModel()->filter(realIndex);
0153       break;
0154     }
0155   }
0156   // emitting signal with a null filter is ok and accounts for clearing the selection
0157   emit signalUpdateFilter(filter);
0158 }
0159 
0160 void FilterView::slotDoubleClicked(const QModelIndex& index_) {
0161   QModelIndex realIndex = sortModel()->mapToSource(index_);
0162   Data::EntryPtr entry = sourceModel()->entry(realIndex);
0163   if(entry) {
0164     Controller::self()->editEntry(entry);
0165   } else {
0166     FilterPtr filter = sourceModel()->filter(realIndex);
0167     if(filter) {
0168       Kernel::self()->modifyFilter(filter);
0169     }
0170   }
0171 }
0172 
0173 // this gets called when header() is clicked, so cycle through
0174 void FilterView::slotSortingChanged(int col_, Qt::SortOrder order_) {
0175   Q_UNUSED(col_);
0176   if(order_ == Qt::AscendingOrder && !m_notSortedYet) { // cycle through after ascending
0177     if(sortModel()->sortRole() == RowCountRole) {
0178       sortModel()->setSortRole(Qt::DisplayRole);
0179     } else {
0180       sortModel()->setSortRole(RowCountRole);
0181     }
0182   }
0183 
0184   updateHeader();
0185   m_notSortedYet = false;
0186 }
0187 
0188 void FilterView::updateHeader() {
0189   if(sortModel()->sortRole() == Qt::DisplayRole) {
0190     model()->setHeaderData(0, Qt::Horizontal, i18n("Filter"));
0191   } else {
0192     model()->setHeaderData(0, Qt::Horizontal, i18n("Filter (Sort by Count)"));
0193   }
0194 }
0195 
0196 void FilterView::invalidate(Tellico::Data::EntryList entries_) {
0197   const int rows = model()->rowCount();
0198   for(int row = 0; row < rows; ++row) {
0199     QModelIndex index = sourceModel()->index(row, 0);
0200     FilterPtr filter = sourceModel()->filter(index);
0201     if(!filter) {
0202       continue;
0203     }
0204     // two cases: if the filter used to match the entry and no longer does, then check the children indexes
0205     // if the filter matches now, check the actual match
0206     foreach(Data::EntryPtr entry, entries_) {
0207       if(sourceModel()->indexContainsEntry(index, entry) || filter->matches(entry)) {
0208         sourceModel()->invalidate(index);
0209         break;
0210       }
0211     }
0212   }
0213 }