File indexing completed on 2024-05-12 05:14:38

0001 /*
0002  *  alarmlistview.cpp  -  widget showing list of alarms
0003  *  Program:  kalarm
0004  *  SPDX-FileCopyrightText: 2007-2023 David Jarvie <djarvie@kde.org>
0005  *
0006  *  SPDX-License-Identifier: GPL-2.0-or-later
0007  */
0008 
0009 #include "alarmlistview.h"
0010 
0011 #include "resources/resourcedatamodelbase.h"
0012 #include "resources/eventmodel.h"
0013 
0014 #include <KSharedConfig>
0015 #include <KConfigGroup>
0016 
0017 #include <QHeaderView>
0018 #include <QMenu>
0019 #include <QAction>
0020 #include <QApplication>
0021 #include <QStyledItemDelegate>
0022 #include <QPainter>
0023 
0024 /*=============================================================================
0025 * Item delegate to draw an icon centered in the column.
0026 =============================================================================*/
0027 class CentreDelegate : public QStyledItemDelegate
0028 {
0029     Q_OBJECT
0030 public:
0031     CentreDelegate(QWidget* parent = nullptr) : QStyledItemDelegate(parent) {}
0032     void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
0033 };
0034 
0035 
0036 AlarmListView::AlarmListView(const QString& configGroup, QWidget* parent)
0037     : EventListView(parent)
0038     , mConfigGroup(configGroup)
0039 {
0040     setEditOnSingleClick(true);
0041     setItemDelegateForColumn(AlarmListModel::TypeColumn, new CentreDelegate(this));
0042     connect(header(), &QHeaderView::sectionMoved, this, &AlarmListView::saveColumnsState);
0043     header()->setContextMenuPolicy(Qt::CustomContextMenu);
0044     connect(header(), &QWidget::customContextMenuRequested, this, &AlarmListView::headerContextMenuRequested);
0045     Preferences::connect(&Preferences::useAlarmNameChanged, this, &AlarmListView::useAlarmNameChanged);
0046 }
0047 
0048 /******************************************************************************
0049 * Return which of the optional columns are currently shown.
0050 * Note that the column order must be the same as in setColumnsVisible().
0051 * Reply = array of 5 columns if not using alarm names;
0052 *       = array of 6 columns if not using alarm names.
0053 */
0054 QList<bool> AlarmListView::columnsVisible() const
0055 {
0056     if (!model())
0057         return {};
0058     QList<bool> vis{ !header()->isSectionHidden(AlarmListModel::TimeColumn),
0059                      !header()->isSectionHidden(AlarmListModel::TimeToColumn),
0060                      !header()->isSectionHidden(AlarmListModel::RepeatColumn),
0061                      !header()->isSectionHidden(AlarmListModel::ColourColumn),
0062                      !header()->isSectionHidden(AlarmListModel::TypeColumn) };
0063     if (Preferences::useAlarmName())
0064         vis += !header()->isSectionHidden(AlarmListModel::TextColumn);
0065     return vis;
0066 }
0067 
0068 /******************************************************************************
0069 * Set which of the optional columns are to be shown.
0070 * 'show' = array of 5 columns if not using alarm names;
0071 *        = array of 6 columns if not using alarm names.
0072 * Note that the column order must be the same as in columnsVisible().
0073 */
0074 void AlarmListView::setColumnsVisible(const QList<bool>& show)
0075 {
0076     if (!model())
0077         return;
0078     const bool useName = Preferences::useAlarmName();
0079     QList<bool> vis{ true, false, true, true, true, !useName };
0080     const int colCount = useName ? 6 : 5;
0081     for (int i = 0, count = std::min<int>(colCount, show.count());  i < count;  ++i)
0082         vis[i] = show[i];
0083     header()->setSectionHidden(AlarmListModel::TimeColumn,   !vis[0]);
0084     header()->setSectionHidden(AlarmListModel::TimeToColumn, !vis[1]);
0085     header()->setSectionHidden(AlarmListModel::RepeatColumn, !vis[2]);
0086     header()->setSectionHidden(AlarmListModel::ColourColumn, !vis[3]);
0087     header()->setSectionHidden(AlarmListModel::TypeColumn,   !vis[4]);
0088     header()->setSectionHidden(AlarmListModel::NameColumn,   !useName);
0089     header()->setSectionHidden(AlarmListModel::TextColumn,   !vis[5]);
0090     setReplaceBlankName();
0091     setSortingEnabled(false);  // sortByColumn() won't work if sorting is already enabled!
0092     sortByColumn(vis[0] ? AlarmListModel::TimeColumn : AlarmListModel::TimeToColumn, Qt::AscendingOrder);
0093 }
0094 
0095 /******************************************************************************
0096 * Initialize column settings and sizing.
0097 */
0098 void AlarmListView::initSections()
0099 {
0100     KConfigGroup config(KSharedConfig::openConfig(), mConfigGroup);
0101     const QByteArray settings = config.readEntry("ListHead", QByteArray());
0102     if (!settings.isEmpty())
0103     {
0104         header()->restoreState(settings);
0105         const bool useName = Preferences::useAlarmName();
0106         header()->setSectionHidden(AlarmListModel::NameColumn, !useName);
0107         if (!useName)
0108         {
0109             header()->setSectionHidden(AlarmListModel::TextColumn, false);
0110             setReplaceBlankName();
0111         }
0112     }
0113     header()->setSectionsMovable(true);
0114     header()->setSectionResizeMode(AlarmListModel::TimeColumn, QHeaderView::ResizeToContents);
0115     header()->setSectionResizeMode(AlarmListModel::TimeToColumn, QHeaderView::ResizeToContents);
0116     header()->setSectionResizeMode(AlarmListModel::RepeatColumn, QHeaderView::ResizeToContents);
0117     header()->setSectionResizeMode(AlarmListModel::ColourColumn, QHeaderView::Fixed);
0118     header()->setSectionResizeMode(AlarmListModel::TypeColumn, QHeaderView::Fixed);
0119     header()->setSectionResizeMode(AlarmListModel::NameColumn, QHeaderView::ResizeToContents);
0120     header()->setSectionResizeMode(AlarmListModel::TextColumn, QHeaderView::Stretch);
0121     header()->setStretchLastSection(true);   // necessary to ensure ResizeToContents columns do resize to contents!
0122     const int minWidth = listViewOptions().fontMetrics.lineSpacing() * 3 / 4;
0123     header()->setMinimumSectionSize(minWidth);
0124     const int margin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin);
0125     header()->resizeSection(AlarmListModel::ColourColumn, minWidth);
0126     header()->resizeSection(AlarmListModel::TypeColumn, AlarmListModel::iconWidth() + 2*margin + 2);
0127 }
0128 
0129 /******************************************************************************
0130 * Called when the column order is changed.
0131 * Save the new order for restoration on program restart.
0132 */
0133 void AlarmListView::saveColumnsState()
0134 {
0135     KConfigGroup config(KSharedConfig::openConfig(), mConfigGroup);
0136     config.writeEntry("ListHead", header()->saveState());
0137     config.sync();
0138 }
0139 
0140 /******************************************************************************
0141 * Called when a context menu is requested for the header.
0142 * Allow the user to choose which columns to display.
0143 */
0144 void AlarmListView::headerContextMenuRequested(const QPoint& pt)
0145 {
0146     QAbstractItemModel* almodel = model();
0147     QMenu menu;
0148     const bool useName = Preferences::useAlarmName();
0149     for (int col = 0, count = header()->count();  col < count;  ++col)
0150     {
0151         const QString title = almodel->headerData(col, Qt::Horizontal, ResourceDataModelBase::ColumnTitleRole).toString();
0152         if (!title.isEmpty())
0153         {
0154             QAction* act = menu.addAction(title);
0155             act->setData(col);
0156             act->setCheckable(true);
0157             act->setChecked(!header()->isSectionHidden(col));
0158             // Always show Name column, but disabled.
0159             // If the alarm name feature is not enabled, this serves as a small
0160             // hint to the user that the name feature exists.
0161             if (col == AlarmListModel::NameColumn)
0162                 act->setEnabled(false);    // don't allow name column to be hidden if name is used
0163             else if (col == AlarmListModel::TextColumn  &&  !useName)
0164                 act->setEnabled(false);    // don't allow text column to be hidden if name not used
0165             else
0166                 QObject::connect(act, &QAction::triggered,
0167                                  this, [this, &menu, act] { showHideColumn(menu, act); });   //clazy:exclude=lambda-in-connect
0168         }
0169     }
0170     enableTimeColumns(&menu);
0171     menu.exec(header()->mapToGlobal(pt));
0172 }
0173 
0174 /******************************************************************************
0175 * Called when the 'use alarm name' setting has changed.
0176 */
0177 void AlarmListView::useAlarmNameChanged(bool use)
0178 {
0179     header()->setSectionHidden(AlarmListModel::NameColumn, !use);
0180     header()->setSectionHidden(AlarmListModel::TextColumn, use);
0181     setReplaceBlankName();
0182 }
0183 
0184 /******************************************************************************
0185 * Show or hide a column according to the header context menu.
0186 */
0187 void AlarmListView::showHideColumn(QMenu& menu, QAction* act)
0188 {
0189     int col = act->data().toInt();
0190     if (col < 0  ||  col >= header()->count())
0191         return;
0192     bool show = act->isChecked();
0193     header()->setSectionHidden(col, !show);
0194     if (col == AlarmListModel::TimeColumn  ||  col == AlarmListModel::TimeToColumn)
0195         enableTimeColumns(&menu);
0196     if (col == AlarmListModel::TextColumn)
0197         setReplaceBlankName();
0198     saveColumnsState();
0199     Q_EMIT columnsVisibleChanged();
0200 }
0201 
0202 /******************************************************************************
0203 * Set whether to replace a blank alarm name with the alarm text.
0204 */
0205 void AlarmListView::setReplaceBlankName()
0206 {
0207     bool textHidden = header()->isSectionHidden(AlarmListModel::TextColumn);
0208     auto almodel = qobject_cast<AlarmListModel*>(model());
0209     if (almodel)
0210         almodel->setReplaceBlankName(textHidden);
0211 }
0212 
0213 /******************************************************************************
0214 * Disable Time or Time To in the context menu if the other one is not
0215 * selected to be displayed, to ensure that at least one is always shown.
0216 */
0217 void AlarmListView::enableTimeColumns(QMenu* menu)
0218 {
0219     bool timeShown   = !header()->isSectionHidden(AlarmListModel::TimeColumn);
0220     bool timeToShown = !header()->isSectionHidden(AlarmListModel::TimeToColumn);
0221     const QList<QAction*> actions = menu->actions();
0222     if (!timeToShown)
0223     {
0224         header()->setSectionHidden(AlarmListModel::TimeColumn, false);
0225         for (QAction* act : actions)
0226         {
0227             if (act->data().toInt() == AlarmListModel::TimeColumn)
0228             {
0229                 act->setEnabled(false);
0230                 break;
0231             }
0232         }
0233     }
0234     else if (!timeShown)
0235     {
0236         header()->setSectionHidden(AlarmListModel::TimeToColumn, false);
0237         for (QAction* act : actions)
0238         {
0239             if (act->data().toInt() == AlarmListModel::TimeToColumn)
0240             {
0241                 act->setEnabled(false);
0242                 break;
0243             }
0244         }
0245     }
0246 }
0247 
0248 
0249 /******************************************************************************
0250 * Draw the alarm's icon centered in the column.
0251 */
0252 void CentreDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
0253 {
0254     // Draw the item background without any icon
0255     QStyleOptionViewItem opt = option;
0256     initStyleOption(&opt, index);
0257     opt.icon = QIcon();
0258     QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter, nullptr);
0259 
0260     // Draw the icon centered within item
0261     const QPixmap icon = qvariant_cast<QPixmap>(index.data(Qt::DecorationRole));
0262     const QRect r = option.rect;
0263     const QPoint p = QPoint((r.width() - icon.width())/2, (r.height() - icon.height())/2);
0264     painter->drawPixmap(r.topLeft() + p, icon);
0265 }
0266 
0267 #include "alarmlistview.moc"
0268 #include "moc_alarmlistview.cpp"
0269 
0270 // vim: et sw=4: