File indexing completed on 2024-04-28 05:08:23

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 "models/filtermodel.h"
0031 #include "models/entrysortmodel.h"
0032 #include "models/models.h"
0033 #include "gui/countdelegate.h"
0034 
0035 #include <KLocalizedString>
0036 
0037 #include <QMenu>
0038 #include <QIcon>
0039 #include <QHeaderView>
0040 #include <QContextMenuEvent>
0041 
0042 using namespace Tellico;
0043 using Tellico::FilterView;
0044 
0045 FilterView::FilterView(QWidget* parent_)
0046     : GUI::TreeView(parent_), m_notSortedYet(true) {
0047   header()->setSectionResizeMode(QHeaderView::Stretch);
0048   setHeaderHidden(false);
0049   setSelectionMode(QAbstractItemView::ExtendedSelection);
0050 
0051   connect(this, &QAbstractItemView::doubleClicked,
0052           this, &FilterView::slotDoubleClicked);
0053 
0054   connect(header(), &QHeaderView::sortIndicatorChanged,
0055           this, &FilterView::slotSortingChanged);
0056 
0057   FilterModel* filterModel = new FilterModel(this);
0058   EntrySortModel* sortModel = new EntrySortModel(this);
0059   sortModel->setSourceModel(filterModel);
0060   setModel(sortModel);
0061   setItemDelegate(new GUI::CountDelegate(this));
0062 }
0063 
0064 Tellico::FilterModel* FilterView::sourceModel() const {
0065   return static_cast<FilterModel*>(sortModel()->sourceModel());
0066 }
0067 
0068 void FilterView::addCollection(Tellico::Data::CollPtr coll_) {
0069   m_coll = coll_;
0070   sourceModel()->clear();
0071   sourceModel()->addFilters(m_coll->filters());
0072 }
0073 
0074 void FilterView::addEntries(Tellico::Data::EntryList entries_) {
0075   invalidate(entries_);
0076 }
0077 
0078 void FilterView::modifyEntries(Tellico::Data::EntryList entries_) {
0079   invalidate(entries_);
0080 }
0081 
0082 void FilterView::removeEntries(Tellico::Data::EntryList entries_) {
0083   invalidate(entries_);
0084 }
0085 
0086 void FilterView::slotReset() {
0087   sourceModel()->clear();
0088 }
0089 
0090 void FilterView::addFilter(Tellico::FilterPtr filter_) {
0091   Q_ASSERT(filter_);
0092   sourceModel()->addFilter(filter_);
0093 }
0094 
0095 void FilterView::slotModifyFilter() {
0096   QModelIndex index = currentIndex();
0097   // parent means a top-level item
0098   if(!index.isValid() || index.parent().isValid()) {
0099     return;
0100   }
0101 
0102   QModelIndex realIndex = sortModel()->mapToSource(index);
0103   Kernel::self()->modifyFilter(sourceModel()->filter(realIndex));
0104 }
0105 
0106 void FilterView::slotDeleteFilter() {
0107   QModelIndex index = currentIndex();
0108   // parent means a top-level item
0109   if(!index.isValid() || index.parent().isValid()) {
0110     return;
0111   }
0112 
0113   QModelIndex realIndex = sortModel()->mapToSource(index);
0114   Kernel::self()->removeFilter(sourceModel()->filter(realIndex));
0115 }
0116 
0117 void FilterView::removeFilter(Tellico::FilterPtr filter_) {
0118   Q_ASSERT(filter_);
0119   sourceModel()->removeFilter(filter_);
0120 }
0121 
0122 void FilterView::contextMenuEvent(QContextMenuEvent* event_) {
0123   QModelIndex index = indexAt(event_->pos());
0124   if(!index.isValid()) {
0125     return;
0126   }
0127 
0128   QMenu menu(this);
0129   // no parent means it's a top-level item
0130   if(!index.parent().isValid()) {
0131     menu.addAction(QIcon::fromTheme(QStringLiteral("view-filter")),
0132                     i18n("Modify Filter"), this, &FilterView::slotModifyFilter);
0133     menu.addAction(QIcon::fromTheme(QStringLiteral("edit-delete")),
0134                     i18n("Delete Filter"), this, &FilterView::slotDeleteFilter);
0135   } else {
0136     Controller::self()->plugEntryActions(&menu);
0137   }
0138   menu.exec(event_->globalPos());
0139 }
0140 
0141 void FilterView::selectionChanged(const QItemSelection& selected_, const QItemSelection& deselected_) {
0142 //  DEBUG_BLOCK;
0143   // in Controller::slotUpdateFilter(), filterView->clearSelection() gets called when the filter is empty
0144   GUI::TreeView::selectionChanged(selected_, deselected_);
0145   FilterPtr filter;
0146   foreach(const QModelIndex& index, selectionModel()->selectedIndexes()) {
0147     QModelIndex realIndex = sortModel()->mapToSource(index);
0148     Data::EntryPtr entry = sourceModel()->entry(realIndex);
0149     if(!entry && !filter) {
0150       filter = sourceModel()->filter(realIndex);
0151       break;
0152     }
0153   }
0154   // emit the signal with a null filter if there is nothing selected, also clearing any active filter as well
0155   if(filter || selectionModel()->selectedIndexes().isEmpty()) {
0156     emit signalUpdateFilter(filter);
0157   }
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 }