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

0001 /***************************************************************************
0002     Copyright (C) 2001-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 "detailedlistview.h"
0026 #include "collection.h"
0027 #include "collectionfactory.h"
0028 #include "controller.h"
0029 #include "field.h"
0030 #include "entry.h"
0031 #include "tellico_debug.h"
0032 #include "tellico_kernel.h"
0033 #include "config/tellico_config.h"
0034 #include "models/entrymodel.h"
0035 #include "models/entrysortmodel.h"
0036 #include "models/modelmanager.h"
0037 #include "gui/detailedentryitemdelegate.h"
0038 #include "gui/ratingdelegate.h"
0039 #include "utils/string_utils.h"
0040 
0041 #include <KLocalizedString>
0042 #include <KSharedConfig>
0043 
0044 #include <QMenu>
0045 #include <QMouseEvent>
0046 #include <QHeaderView>
0047 #include <QContextMenuEvent>
0048 
0049 using namespace Tellico;
0050 using Tellico::DetailedListView;
0051 
0052 DetailedListView::DetailedListView(QWidget* parent_) : GUI::TreeView(parent_)
0053     , m_loadingCollection(false), m_currentContextColumn(-1) {
0054   setHeaderHidden(false);
0055   setSelectionMode(QAbstractItemView::ExtendedSelection);
0056   setAlternatingRowColors(true);
0057   setRootIsDecorated(false);
0058   setUniformRowHeights(true);
0059 
0060   connect(this, &QAbstractItemView::doubleClicked, this, &DetailedListView::slotDoubleClicked);
0061 
0062   // header menu
0063   header()->installEventFilter(this);
0064   header()->setMinimumSectionSize(20);
0065 
0066   m_headerMenu = new QMenu(this);
0067   m_columnMenu = new QMenu(this);
0068   connect(m_columnMenu, &QMenu::triggered,
0069           this, &DetailedListView::slotColumnMenuActivated);
0070 
0071   EntryModel* entryModel = new EntryModel(this);
0072   EntrySortModel* sortModel = new EntrySortModel(this);
0073   sortModel->setSortRole(EntryPtrRole);
0074   sortModel->setSourceModel(entryModel);
0075   setModel(sortModel);
0076   setItemDelegate(new DetailedEntryItemDelegate(this));
0077 
0078   ModelManager::self()->setEntryModel(sortModel);
0079 
0080   connect(model(), &QAbstractItemModel::headerDataChanged, this, &DetailedListView::updateHeaderMenu);
0081   connect(model(), &QAbstractItemModel::headerDataChanged, this, &DetailedListView::updateColumnDelegates);
0082   connect(model(), &QAbstractItemModel::columnsInserted, this, &DetailedListView::hideNewColumn);
0083   connect(header(), &QHeaderView::sectionCountChanged, this, &DetailedListView::updateHeaderMenu);
0084 }
0085 
0086 DetailedListView::~DetailedListView() {
0087 }
0088 
0089 Tellico::EntryModel* DetailedListView::sourceModel() const {
0090   return static_cast<EntryModel*>(sortModel()->sourceModel());
0091 }
0092 
0093 void DetailedListView::addCollection(Tellico::Data::CollPtr coll_) {
0094   if(!coll_) {
0095     return;
0096   }
0097 
0098   const QString configGroup = QStringLiteral("Options - %1").arg(CollectionFactory::typeName(coll_));
0099   KConfigGroup config(KSharedConfig::openConfig(), configGroup);
0100 
0101   QString configN;
0102   if(coll_->type() == Data::Collection::Base) {
0103     QUrl url = Kernel::self()->URL();
0104     for(int i = 0; i < Config::maxCustomURLSettings(); ++i) {
0105       QUrl u = config.readEntry(QStringLiteral("URL_%1").arg(i), QUrl());
0106       if(u == url) {
0107         configN = QStringLiteral("_%1").arg(i);
0108         break;
0109       }
0110     }
0111   }
0112 
0113   // we don't want to immediately hide all these columns when adding fields
0114   disconnect(model(), &QAbstractItemModel::columnsInserted,
0115              this, &DetailedListView::hideNewColumn);
0116   sourceModel()->setImagesAreAvailable(false);
0117   sourceModel()->setFields(coll_->fields());
0118   connect(model(), &QAbstractItemModel::columnsInserted,
0119           this, &DetailedListView::hideNewColumn);
0120 
0121   // we're not using saveState() and restoreState() since our columns are variable
0122   QStringList columnNames = config.readEntry(QLatin1String("ColumnNames") + configN, QStringList());
0123   QList<int> columnWidths = config.readEntry(QLatin1String("ColumnWidths") + configN, QList<int>());
0124   QList<int> columnOrder = config.readEntry(QLatin1String("ColumnOrder") + configN, QList<int>());
0125 
0126   // just a broken-world check
0127   while(columnWidths.size() < columnNames.size()) {
0128     columnWidths << 0;
0129   }
0130   while(columnOrder.size() < columnNames.size()) {
0131     columnOrder << columnOrder.size();
0132   }
0133   QList<int> currentColumnOrder;
0134   // now restore widths and order
0135   for(int ncol = 0; ncol < header()->count(); ++ncol) {
0136     int idx = columnNames.indexOf(columnFieldName(ncol));
0137     // column width of 0 means hidden
0138     if(idx < 0 || columnWidths.at(idx) <= 0) {
0139       hideColumn(ncol);
0140       if(idx > -1) {
0141         currentColumnOrder << ncol;
0142       }
0143     } else {
0144       setColumnWidth(ncol, columnWidths.at(idx));
0145       currentColumnOrder << ncol;
0146     }
0147   }
0148   const int maxCount = qMin(currentColumnOrder.size(), columnOrder.size());
0149   for(int i = 0; i < maxCount; ++i) {
0150     header()->moveSection(header()->visualIndex(currentColumnOrder.at(i)), columnOrder.at(i));
0151   }
0152 
0153   // always hide tables and paragraphs
0154   for(int ncol = 0; ncol < coll_->fields().count(); ++ncol) {
0155     Data::FieldPtr field = model()->headerData(ncol, Qt::Horizontal, FieldPtrRole).value<Data::FieldPtr>();
0156     if(field) {
0157       if(field->type() == Data::Field::Table || field->type() == Data::Field::Para) {
0158         hideColumn(ncol);
0159       }
0160     } else {
0161       myDebug() << "no field for col" << ncol;
0162     }
0163   }
0164 
0165   // because some of the fields got hidden...
0166   updateColumnDelegates();
0167   updateHeaderMenu();
0168   checkHeader();
0169 
0170   sortModel()->setSortColumn(config.readEntry(QLatin1String("SortColumn") + configN, -1));
0171   sortModel()->setSecondarySortColumn(config.readEntry(QLatin1String("PrevSortColumn") + configN, -1));
0172   sortModel()->setTertiarySortColumn(config.readEntry(QLatin1String("Prev2SortColumn") + configN, -1));
0173   const int order = config.readEntry(QLatin1String("SortOrder") + configN, static_cast<int>(Qt::AscendingOrder));
0174   sortModel()->setSortOrder(static_cast<Qt::SortOrder>(order));
0175 
0176   setUpdatesEnabled(false);
0177   m_loadingCollection = true;
0178   addEntries(coll_->entries());
0179   m_loadingCollection = false;
0180   setUpdatesEnabled(true);
0181 
0182   header()->setSortIndicator(sortModel()->sortColumn(), sortModel()->sortOrder());
0183 }
0184 
0185 void DetailedListView::slotReset() {
0186   // clear() does not remove columns
0187   sourceModel()->clear();
0188 }
0189 
0190 void DetailedListView::addEntries(Tellico::Data::EntryList entries_) {
0191   if(entries_.isEmpty()) {
0192     return;
0193   }
0194   sourceModel()->addEntries(entries_);
0195   if(!m_loadingCollection) {
0196     setState(entries_, NewState);
0197     // TODO: this only scrolls to new entries in the list view, not the other widgets
0198     // who use this selection model
0199     setEntriesSelected(entries_);
0200   }
0201 }
0202 
0203 void DetailedListView::modifyEntries(Tellico::Data::EntryList entries_) {
0204   if(entries_.isEmpty()) {
0205     return;
0206   }
0207   sourceModel()->modifyEntries(entries_);
0208   setState(entries_, ModifiedState);
0209 }
0210 
0211 void DetailedListView::removeEntries(Tellico::Data::EntryList entries_) {
0212   if(entries_.isEmpty()) {
0213     return;
0214   }
0215   sourceModel()->removeEntries(entries_);
0216 }
0217 
0218 void DetailedListView::setState(Tellico::Data::EntryList entries_, int state) {
0219   foreach(Data::EntryPtr entry, entries_) {
0220     QModelIndex index = sourceModel()->indexFromEntry(entry);
0221     if(index.isValid()) {
0222       sourceModel()->setData(index, state, SaveStateRole);
0223     } else {
0224       myWarning() << "no index found for" << entry->id() << entry->title();
0225     }
0226   }
0227 }
0228 
0229 void DetailedListView::removeCollection(Tellico::Data::CollPtr coll_) {
0230   if(!coll_) {
0231     myWarning() << "null coll pointer!";
0232     return;
0233   }
0234 
0235   sourceModel()->clear();
0236 }
0237 
0238 void DetailedListView::contextMenuEvent(QContextMenuEvent* event_) {
0239   QModelIndex index = indexAt(event_->pos());
0240   if(!index.isValid()) {
0241     return;
0242   }
0243 
0244   QMenu menu(this);
0245   Controller::self()->plugEntryActions(&menu);
0246   menu.exec(event_->globalPos());
0247 }
0248 
0249 // don't shadow QListView::setSelected
0250 void DetailedListView::setEntriesSelected(Data::EntryList entries_) {
0251   if(entries_.isEmpty()) {
0252     // don't move this one outside the block since it calls setCurrentItem(0)
0253     clearSelection();
0254     return;
0255   }
0256 
0257   clearSelection();
0258   EntrySortModel* proxyModel = static_cast<EntrySortModel*>(model());
0259   foreach(Data::EntryPtr entry, entries_) {
0260     QModelIndex index = sourceModel()->indexFromEntry(entry);
0261     if(!proxyModel->mapFromSource(index).isValid()) {
0262       // clear the filter if we're trying to select an entry that is currently filtered out
0263       Controller::self()->clearFilter();
0264       break;
0265     }
0266   }
0267   blockSignals(true);
0268   QItemSelection itemSel;
0269   foreach(Data::EntryPtr entry, entries_) {
0270     QModelIndex index = proxyModel->mapFromSource(sourceModel()->indexFromEntry(entry));
0271     itemSel.select(index, index);
0272   }
0273   selectionModel()->select(itemSel, QItemSelectionModel::Select | QItemSelectionModel::Rows);
0274   blockSignals(false);
0275 
0276   QModelIndex index = sourceModel()->indexFromEntry(entries_.first());
0277   scrollTo(proxyModel->mapFromSource(index));
0278 }
0279 
0280 bool DetailedListView::eventFilter(QObject* obj_, QEvent* event_) {
0281   if(event_->type() == QEvent::ContextMenu
0282      && obj_ == header()) {
0283     m_currentContextColumn = header()->logicalIndexAt(static_cast<QContextMenuEvent*>(event_)->pos());
0284     m_headerMenu->exec(static_cast<QContextMenuEvent*>(event_)->globalPos());
0285     return true;
0286   }
0287   return GUI::TreeView::eventFilter(obj_, event_);
0288 }
0289 
0290 void DetailedListView::slotDoubleClicked(const QModelIndex& index_) {
0291   Data::EntryPtr entry = index_.data(EntryPtrRole).value<Data::EntryPtr>();
0292   if(entry) {
0293     Controller::self()->editEntry(entry);
0294   }
0295 }
0296 
0297 void DetailedListView::slotColumnMenuActivated(QAction* action_) {
0298   const int col = action_->data().toInt();
0299   if(col > -1) { // only column actions have data
0300     const bool isChecked = action_->isChecked();
0301     setColumnHidden(col, !isChecked);
0302     // if we're showing a column, resize all sections
0303     if(isChecked) {
0304       resizeColumnToContents(col);
0305       adjustColumnWidths();
0306     }
0307   }
0308   checkHeader();
0309 }
0310 
0311 void DetailedListView::showAllColumns() {
0312   foreach(QAction* action, m_columnMenu->actions()) {
0313     if(action->isCheckable() && !action->isChecked()) {
0314       action->trigger();
0315     }
0316   }
0317 }
0318 
0319 void DetailedListView::hideAllColumns() {
0320   for(int ncol = 0; ncol < header()->count(); ++ncol) {
0321     hideColumn(ncol);
0322   }
0323   foreach(QAction* action, m_columnMenu->actions()) {
0324     if(action->isCheckable()) {
0325       action->setChecked(false);
0326     }
0327   }
0328   checkHeader();
0329 }
0330 
0331 void DetailedListView::hideCurrentColumn() {
0332   setColumnHidden(m_currentContextColumn, true);
0333   checkHeader();
0334 }
0335 
0336 void DetailedListView::slotRefresh() {
0337   sortModel()->invalidate();
0338 }
0339 
0340 void DetailedListView::setFilter(Tellico::FilterPtr filter_) {
0341   static_cast<EntrySortModel*>(sortModel())->setFilter(filter_);
0342 }
0343 
0344 Tellico::FilterPtr DetailedListView::filter() const {
0345   return static_cast<EntrySortModel*>(sortModel())->filter();
0346 }
0347 
0348 void DetailedListView::addField(Tellico::Data::CollPtr, Tellico::Data::FieldPtr field) {
0349   sourceModel()->addFields(Data::FieldList() << field);
0350 }
0351 
0352 void DetailedListView::modifyField(Tellico::Data::CollPtr, Tellico::Data::FieldPtr oldField_, Tellico::Data::FieldPtr newField_) {
0353   Q_UNUSED(oldField_)
0354   sourceModel()->modifyField(oldField_, newField_);
0355 }
0356 
0357 void DetailedListView::removeField(Tellico::Data::CollPtr, Tellico::Data::FieldPtr field_) {
0358   sourceModel()->removeFields(Data::FieldList() << field_);
0359 }
0360 
0361 void DetailedListView::reorderFields(const Tellico::Data::FieldList& fields_) {
0362   QStringList columnNames;
0363   QList<int> columnWidths, columnOrder;
0364   for(int ncol = 0; ncol < header()->count(); ++ncol) {
0365     // ignore hidden columns
0366     if(!isColumnHidden(ncol)) {
0367       columnNames << columnFieldName(ncol);
0368       columnWidths << columnWidth(ncol);
0369       columnOrder << header()->visualIndex(ncol);
0370     }
0371   }
0372 
0373   sourceModel()->reorderFields(fields_);
0374 
0375   QList<int> currentColumnOrder;
0376   // now restore widths and order
0377   for(int ncol = 0; ncol < header()->count(); ++ncol) {
0378     int idx = columnNames.indexOf(columnFieldName(ncol));
0379     // column width of 0 means hidden
0380     if(idx < 0 || columnWidths.at(idx) <= 0) {
0381       hideColumn(ncol);
0382       if(idx > -1) {
0383         currentColumnOrder << ncol;
0384       }
0385     } else {
0386       setColumnHidden(ncol, false);
0387       setColumnWidth(ncol, columnWidths.at(idx));
0388       currentColumnOrder << ncol;
0389     }
0390   }
0391   const int maxCount = qMin(currentColumnOrder.size(), columnOrder.size());
0392   for(int i = 0; i < maxCount; ++i) {
0393     header()->moveSection(header()->visualIndex(currentColumnOrder.at(i)), columnOrder.at(i));
0394   }
0395   updateHeaderMenu();
0396 }
0397 
0398 void DetailedListView::saveConfig(Tellico::Data::CollPtr coll_, int configIndex_) {
0399   const QString configGroup = QStringLiteral("Options - %1").arg(CollectionFactory::typeName(coll_));
0400   KConfigGroup config(KSharedConfig::openConfig(), configGroup);
0401 
0402   // all of this is to have custom settings on a per file basis
0403   QString configN;
0404   if(coll_->type() == Data::Collection::Base) {
0405     QList<ConfigInfo> info;
0406     for(int i = 0; i < Config::maxCustomURLSettings(); ++i) {
0407       QUrl u(config.readEntry(QStringLiteral("URL_%1").arg(i)));
0408       if(!u.isEmpty() && i != configIndex_) {
0409         configN = QStringLiteral("_%1").arg(i);
0410         ConfigInfo ci;
0411         ci.cols      = config.readEntry(QLatin1String("ColumnNames") + configN, QStringList());
0412         ci.widths    = config.readEntry(QLatin1String("ColumnWidths") + configN, QList<int>());
0413         ci.order     = config.readEntry(QLatin1String("ColumnOrder") + configN, QList<int>());
0414         ci.prevSort  = config.readEntry(QLatin1String("PrevSortColumn") + configN, 0);
0415         ci.prev2Sort = config.readEntry(QLatin1String("Prev2SortColumn") + configN, 0);
0416         ci.sortOrder = config.readEntry(QLatin1String("SortOrder") + configN, static_cast<int>(Qt::AscendingOrder));
0417         info.append(ci);
0418       }
0419     }
0420     // subtract one since we're writing the current settings, too
0421     int limit = qMin(info.count(), Config::maxCustomURLSettings()-1);
0422     for(int i = 0; i < limit; ++i) {
0423       // starts at one since the current config will be written below
0424       configN = QStringLiteral("_%1").arg(i+1);
0425       config.writeEntry(QLatin1String("ColumnNames")     + configN, info[i].cols);
0426       config.writeEntry(QLatin1String("ColumnWidths")    + configN, info[i].widths);
0427       config.writeEntry(QLatin1String("ColumnOrder")     + configN, info[i].order);
0428       config.writeEntry(QLatin1String("PrevSortColumn")  + configN, info[i].prevSort);
0429       config.writeEntry(QLatin1String("Prev2SortColumn") + configN, info[i].prev2Sort);
0430       config.writeEntry(QLatin1String("SortOrder")       + configN, info[i].sortOrder);
0431       // legacy entry item
0432       config.deleteEntry(QLatin1String("ColumnState")    + configN);
0433     }
0434     configN = QStringLiteral("_0");
0435   }
0436 
0437   QStringList colNames;
0438   QList<int> widths, order;
0439   for(int ncol = 0; ncol < header()->count(); ++ncol) {
0440     // ignore hidden columns
0441     if(!isColumnHidden(ncol)) {
0442       colNames << columnFieldName(ncol);
0443       widths << columnWidth(ncol);
0444       order << header()->visualIndex(ncol);
0445     }
0446   }
0447 
0448   config.writeEntry(QLatin1String("ColumnNames") + configN, colNames);
0449   config.writeEntry(QLatin1String("ColumnWidths") + configN, widths);
0450   config.writeEntry(QLatin1String("ColumnOrder") + configN, order);
0451 
0452   const int sortCol1 = sortModel()->sortColumn();
0453   const int sortCol2 = sortModel()->secondarySortColumn();
0454   const int sortCol3 = sortModel()->tertiarySortColumn();
0455   const int sortOrder = static_cast<int>(sortModel()->sortOrder());
0456   config.writeEntry(QLatin1String("SortColumn")      + configN, sortCol1);
0457   config.writeEntry(QLatin1String("PrevSortColumn")  + configN, sortCol2);
0458   config.writeEntry(QLatin1String("Prev2SortColumn") + configN, sortCol3);
0459   config.writeEntry(QLatin1String("SortOrder")       + configN, sortOrder);
0460   // remove old entry item
0461   config.deleteEntry(QLatin1String("ColumnState")    + configN);
0462 }
0463 
0464 QString DetailedListView::sortColumnTitle1() const {
0465   return model()->headerData(header()->sortIndicatorSection(), Qt::Horizontal).toString();
0466 }
0467 
0468 QString DetailedListView::sortColumnTitle2() const {
0469   return model()->headerData(sortModel()->secondarySortColumn(), Qt::Horizontal).toString();
0470 }
0471 
0472 QString DetailedListView::sortColumnTitle3() const {
0473   return model()->headerData(sortModel()->tertiarySortColumn(), Qt::Horizontal).toString();
0474 }
0475 
0476 QStringList DetailedListView::visibleColumns() const {
0477   // we want the visual order, so use a QMap and sort by visualIndex
0478   QMap<int, QString> titleMap;
0479   for(int i = 0; i < header()->count(); ++i) {
0480     if(!isColumnHidden(i)) {
0481       titleMap.insert(header()->visualIndex(i), model()->headerData(i, Qt::Horizontal).toString());
0482     }
0483   }
0484   return titleMap.values();
0485 }
0486 
0487 // can't be const
0488 Tellico::Data::EntryList DetailedListView::visibleEntries() {
0489   // We could just return the full collection entry list if the filter is 0
0490   // but printing depends on the sorted order
0491   Data::EntryList entries;
0492   for(int i = 0; i < model()->rowCount(); ++i) {
0493     Data::EntryPtr tmp = model()->data(model()->index(i, 0), EntryPtrRole).value<Data::EntryPtr>();
0494     if(tmp) {
0495       entries += tmp;
0496     }
0497   }
0498   return entries;
0499 }
0500 
0501 void DetailedListView::selectAllVisible() {
0502   QModelIndex topLeft = model()->index(0, 0);
0503   QModelIndex bottomRight = model()->index(model()->rowCount()-1, model()->columnCount()-1);
0504   QItemSelection selection(topLeft, bottomRight);
0505   selectionModel()->select(selection, QItemSelectionModel::Select);
0506 }
0507 
0508 int DetailedListView::visibleItems() const {
0509   return model()->rowCount();
0510 }
0511 
0512 void DetailedListView::resetEntryStatus() {
0513   sourceModel()->clearSaveState();
0514 }
0515 
0516 void DetailedListView::updateHeaderMenu() {
0517   // we only want to update the menu when the header count and model count agree
0518   if(model()->columnCount() != header()->count()) {
0519     myDebug() << "column counts disagree";
0520     return;
0521   }
0522   m_headerMenu->clear();
0523   m_headerMenu->addSection(i18n("View Columns"));
0524 
0525   m_columnMenu->clear();
0526 
0527   for(int ncol = 0; ncol < header()->count(); ++ncol) {
0528     Data::FieldPtr field = model()->headerData(ncol, Qt::Horizontal, FieldPtrRole).value<Data::FieldPtr>();
0529     if(field && (field->type() == Data::Field::Table || field->type() == Data::Field::Para)) {
0530       continue;
0531     }
0532     QAction* act = m_columnMenu->addAction(model()->headerData(ncol, Qt::Horizontal).toString());
0533     act->setData(ncol);
0534     act->setCheckable(true);
0535     act->setChecked(!isColumnHidden(ncol));
0536   }
0537   QAction* columnAction = m_headerMenu->addMenu(m_columnMenu);
0538   columnAction->setText(i18nc("Noun, Menu name", "Columns"));
0539   columnAction->setIcon(QIcon::fromTheme(QStringLiteral("view-file-columns")));
0540 
0541   QAction* actHideThis = m_headerMenu->addAction(i18n("Hide This Column"));
0542   connect(actHideThis, &QAction::triggered, this, &DetailedListView::hideCurrentColumn);
0543   QAction* actResize = m_headerMenu->addAction(QIcon::fromTheme(QStringLiteral("zoom-fit-width")), i18n("Resize to Content"));
0544   connect(actResize, &QAction::triggered, this, &DetailedListView::resizeColumnsToContents);
0545 
0546   m_headerMenu->addSeparator();
0547 
0548   QAction* actShowAll = m_headerMenu->addAction(i18n("Show All Columns"));
0549   connect(actShowAll, &QAction::triggered, this, &DetailedListView::showAllColumns);
0550   QAction* actHideAll = m_headerMenu->addAction(i18n("Hide All Columns"));
0551   connect(actHideAll, &QAction::triggered, this, &DetailedListView::hideAllColumns);
0552 }
0553 
0554 void DetailedListView::updateColumnDelegates() {
0555   for(int ncol = 0; ncol < header()->count(); ++ncol) {
0556     Data::FieldPtr field = model()->headerData(ncol, Qt::Horizontal, FieldPtrRole).value<Data::FieldPtr>();
0557     if(field && field->type() == Data::Field::Rating) {
0558       /// if we're not using the overall delegate, delete the delegate since we're setting a new on
0559       if(itemDelegateForColumn(ncol) != itemDelegate()) {
0560         delete itemDelegateForColumn(ncol);
0561       }
0562       RatingDelegate* delegate = new RatingDelegate(this);
0563       bool ok; // not used
0564       delegate->setMaxRating(Tellico::toUInt(field->property(QStringLiteral("maximum")), &ok));
0565       setItemDelegateForColumn(ncol, delegate);
0566     } else {
0567       // reset column delegate to overall delegate
0568       setItemDelegateForColumn(ncol, itemDelegate());
0569     }
0570   }
0571 }
0572 
0573 void DetailedListView::slotRefreshImages() {
0574   sourceModel()->setImagesAreAvailable(true);
0575 }
0576 
0577 void DetailedListView::adjustColumnWidths() {
0578   // this function is called when a column is shown
0579   // reduce all visible columns to their size hint, if they are wider than that
0580   for(int ncol = 0; ncol < header()->count(); ++ncol) {
0581     if(!isColumnHidden(ncol)) {
0582       const int width = sizeHintForColumn(ncol);
0583       if(columnWidth(ncol) > width) {
0584         resizeColumnToContents(ncol);
0585       }
0586     }
0587   }
0588 }
0589 
0590 void DetailedListView::resizeColumnsToContents() {
0591   for(int ncol = 0; ncol < header()->count(); ++ncol) {
0592     if(!isColumnHidden(ncol)) {
0593       resizeColumnToContents(ncol);
0594     }
0595   }
0596 }
0597 
0598 void DetailedListView::hideNewColumn(const QModelIndex& index_, int start_, int end_) {
0599   Q_UNUSED(index_);
0600   for(int ncol = start_; ncol <= end_; ++ncol) {
0601     hideColumn(ncol);
0602   }
0603   updateHeaderMenu(); // make sure to update checkable actions
0604 }
0605 
0606 void DetailedListView::checkHeader() {
0607   // the header disappears if all columns are hidden, so if the user hides all
0608   // columns, we turn around and show the title
0609   //
0610   // normally, I would expect a check like header()->count() == header()->hiddenSectionCount()
0611   // to tell me if all sections are hidden, but it often doesn't work, with the hiddenSectionCount()
0612   // being greater than count()! From testing, if the sizeHint() width is 0, then the header is hidden
0613   if(!header()->sizeHint().isEmpty()) {
0614     return;
0615   }
0616   // find title action in menu and activate it
0617   QAction* action = nullptr;
0618   QAction* fallbackAction = nullptr;
0619   foreach(QAction* tryAction, m_columnMenu->actions()) {
0620     const int ncol = tryAction->data().toInt();
0621     if(ncol > -1 && columnFieldName(ncol) == QLatin1String("title")) {
0622       action = tryAction;
0623       break;
0624     } else if(ncol > -1 && !fallbackAction) {
0625       fallbackAction = tryAction;
0626     }
0627   }
0628   if(!action) {
0629     action = fallbackAction;
0630   }
0631   if(action) {
0632     action->setChecked(true);
0633     const int col = action->data().toInt();
0634     // calling slotColumnMenuActivated() would be infinite loop
0635     setColumnHidden(col, false);
0636     resizeColumnToContents(col);
0637   } else {
0638     myDebug() << "found no action to show, still empty header!";
0639   }
0640 }
0641 
0642 QString DetailedListView::columnFieldName(int ncol_) const {
0643   Data::FieldPtr field = model()->headerData(ncol_, Qt::Horizontal, FieldPtrRole).value<Data::FieldPtr>();
0644   return field ? field->name() : QString();
0645 }