File indexing completed on 2024-05-19 05:21:23
0001 /* 0002 This file is part of KDE Kontact. 0003 0004 SPDX-FileCopyrightText: 2003 Cornelius Schumacher <schumacher@kde.org> 0005 SPDX-FileCopyrightText: 2008 Rafael Fernández López <ereslibre@kde.org> 0006 0007 SPDX-License-Identifier: GPL-2.0-or-later 0008 */ 0009 0010 #include "iconsidepane.h" 0011 #include "mainwindow.h" 0012 #include "prefs.h" 0013 using namespace Kontact; 0014 0015 #include <KontactInterface/Core> 0016 #include <KontactInterface/Plugin> 0017 0018 #include <KIconLoader> 0019 #include <KLocalizedString> 0020 #include <QAction> 0021 #include <QActionGroup> 0022 #include <QApplication> 0023 #include <QIcon> 0024 0025 #include <QCollator> 0026 #include <QDragEnterEvent> 0027 #include <QDragMoveEvent> 0028 #include <QDropEvent> 0029 #include <QLayout> 0030 #include <QPainter> 0031 #include <QSortFilterProxyModel> 0032 #include <QStringListModel> 0033 #include <QStyledItemDelegate> 0034 #include <QTimer> 0035 0036 namespace Kontact 0037 { 0038 class SelectionModel : public QItemSelectionModel 0039 { 0040 Q_OBJECT 0041 public: 0042 SelectionModel(QAbstractItemModel *model, QObject *parent) 0043 : QItemSelectionModel(model, parent) 0044 { 0045 } 0046 0047 public Q_SLOTS: 0048 void clear() override 0049 { 0050 // Don't allow the current selection to be cleared. QListView doesn't call to this method 0051 // nowadays, but just to cover of future change of implementation, since QTreeView does call 0052 // to this one when clearing the selection. 0053 } 0054 0055 void select(const QModelIndex &index, QItemSelectionModel::SelectionFlags command) override 0056 { 0057 // Don't allow the current selection to be cleared 0058 if (!index.isValid() && (command & QItemSelectionModel::Clear)) { 0059 return; 0060 } 0061 QItemSelectionModel::select(index, command); 0062 } 0063 0064 void select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command) override 0065 { 0066 // Don't allow the current selection to be cleared 0067 if (selection.isEmpty() && (command & QItemSelectionModel::Clear)) { 0068 return; 0069 } 0070 QItemSelectionModel::select(selection, command); 0071 } 0072 }; 0073 0074 class Model : public QStringListModel 0075 { 0076 Q_OBJECT 0077 public: 0078 enum SpecialRoles { PluginName = Qt::UserRole }; 0079 0080 explicit Model(Navigator *parentNavigator = nullptr) 0081 : QStringListModel(parentNavigator) 0082 , mNavigator(parentNavigator) 0083 { 0084 } 0085 0086 void setPluginList(const QList<KontactInterface::Plugin *> &list) 0087 { 0088 pluginList = list; 0089 } 0090 0091 [[nodiscard]] Qt::ItemFlags flags(const QModelIndex &index) const override 0092 { 0093 Qt::ItemFlags flags = QStringListModel::flags(index); 0094 0095 flags &= ~Qt::ItemIsEditable; 0096 0097 if (index.isValid()) { 0098 if (static_cast<KontactInterface::Plugin *>(index.internalPointer())->disabled()) { 0099 flags &= ~Qt::ItemIsEnabled; 0100 flags &= ~Qt::ItemIsSelectable; 0101 flags &= ~Qt::ItemIsDropEnabled; 0102 } else { 0103 flags |= Qt::ItemIsDropEnabled; 0104 } 0105 } else { 0106 flags &= ~Qt::ItemIsDropEnabled; 0107 } 0108 0109 return flags; 0110 } 0111 0112 [[nodiscard]] QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override 0113 { 0114 Q_UNUSED(parent) 0115 if (row < 0 || row >= pluginList.count()) { 0116 return {}; 0117 } 0118 return createIndex(row, column, pluginList[row]); 0119 } 0120 0121 [[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override 0122 { 0123 if (!index.isValid() || !index.internalPointer()) { 0124 return {}; 0125 } 0126 0127 if (role == Qt::DisplayRole) { 0128 if (!mNavigator->showText()) { 0129 return {}; 0130 } 0131 return static_cast<KontactInterface::Plugin *>(index.internalPointer())->title(); 0132 } else if (role == Qt::DecorationRole) { 0133 if (!mNavigator->showIcons()) { 0134 return {}; 0135 } 0136 return QIcon::fromTheme(static_cast<KontactInterface::Plugin *>(index.internalPointer())->icon()); 0137 } else if (role == Qt::TextAlignmentRole) { 0138 return Qt::AlignCenter; 0139 } else if (role == Qt::ToolTipRole) { 0140 if (!mNavigator->showText()) { 0141 return static_cast<KontactInterface::Plugin *>(index.internalPointer())->title(); 0142 } 0143 return {}; 0144 } else if (role == PluginName) { 0145 return static_cast<KontactInterface::Plugin *>(index.internalPointer())->identifier(); 0146 } 0147 return QStringListModel::data(index, role); 0148 } 0149 0150 private: 0151 QList<KontactInterface::Plugin *> pluginList; 0152 Navigator *const mNavigator; 0153 }; 0154 0155 class SortFilterProxyModel : public QSortFilterProxyModel 0156 { 0157 Q_OBJECT 0158 public: 0159 explicit SortFilterProxyModel(QObject *parent = nullptr) 0160 : QSortFilterProxyModel(parent) 0161 { 0162 setDynamicSortFilter(true); 0163 sort(0); 0164 } 0165 0166 protected: 0167 [[nodiscard]] bool lessThan(const QModelIndex &left, const QModelIndex &right) const override 0168 { 0169 auto leftPlugin = static_cast<KontactInterface::Plugin *>(left.internalPointer()); 0170 auto rightPlugin = static_cast<KontactInterface::Plugin *>(right.internalPointer()); 0171 0172 if (leftPlugin->weight() == rightPlugin->weight()) { 0173 // Optimize it 0174 QCollator col; 0175 return col.compare(leftPlugin->title(), rightPlugin->title()) < 0; 0176 } 0177 0178 return leftPlugin->weight() < rightPlugin->weight(); 0179 } 0180 }; 0181 0182 class Delegate : public QStyledItemDelegate 0183 { 0184 Q_OBJECT 0185 public: 0186 explicit Delegate(Navigator *parentNavigator = nullptr) 0187 : QStyledItemDelegate(parentNavigator) 0188 , mNavigator(parentNavigator) 0189 { 0190 } 0191 0192 void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override 0193 { 0194 if (!index.isValid() || !index.internalPointer()) { 0195 return; 0196 } 0197 0198 QStyleOptionViewItem opt(*static_cast<const QStyleOptionViewItem *>(&option)); 0199 // optionCopy.state |= QStyle::State_Active; 0200 opt.decorationPosition = QStyleOptionViewItem::Top; 0201 const int height = 0; 0202 painter->save(); 0203 0204 mNavigator->style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter); 0205 0206 if (mNavigator->showIcons() && mNavigator->showText()) { 0207 opt.icon = index.data(Qt::DecorationRole).value<QIcon>(); 0208 const int size = mNavigator->iconSize(); 0209 const auto spacing = mNavigator->style()->pixelMetric(QStyle::PM_FocusFrameHMargin); 0210 const int textHeight = painter->fontMetrics().height(); 0211 0212 const int y = opt.rect.y() + (opt.rect.height() - size - spacing - textHeight) / 2; 0213 0214 opt.icon.paint(painter, QRect(opt.rect.x(), y, opt.rect.width(), size), Qt::AlignCenter, QIcon::Normal, QIcon::On); 0215 painter->drawText(QRect(opt.rect.x(), y + size + spacing, opt.rect.width(), textHeight), index.data(Qt::DisplayRole).toString(), {Qt::AlignCenter}); 0216 } else if (mNavigator->showIcons()) { 0217 opt.icon = index.data(Qt::DecorationRole).value<QIcon>(); 0218 const int size = mNavigator->iconSize() + height; 0219 opt.decorationSize = QSize(size, size); 0220 opt.icon.paint(painter, opt.rect, Qt::AlignCenter, QIcon::Normal, QIcon::On); 0221 } else if (mNavigator->showText()) { 0222 painter->drawText(opt.rect, index.data(Qt::DisplayRole).toString(), {Qt::AlignCenter}); 0223 } 0224 painter->restore(); 0225 } 0226 0227 [[nodiscard]] QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override 0228 { 0229 if (!index.isValid() || !index.internalPointer()) { 0230 return {}; 0231 } 0232 0233 QStyleOptionViewItem optionCopy(*static_cast<const QStyleOptionViewItem *>(&option)); 0234 optionCopy.decorationPosition = QStyleOptionViewItem::Top; 0235 0236 optionCopy.decorationSize = mNavigator->showIcons() ? QSize(mNavigator->iconSize(), mNavigator->iconSize()) : QSize(); 0237 optionCopy.textElideMode = Qt::ElideNone; 0238 return QStyledItemDelegate::sizeHint(optionCopy, index); 0239 } 0240 0241 private: 0242 Navigator *const mNavigator; 0243 }; 0244 } 0245 0246 Navigator::Navigator(SidePaneBase *parent) 0247 : QListView(parent) 0248 , mSidePane(parent) 0249 { 0250 setViewport(new QWidget(this)); 0251 0252 setVerticalScrollMode(ScrollPerPixel); 0253 setHorizontalScrollMode(ScrollPerPixel); 0254 setFrameShape(QFrame::NoFrame); 0255 0256 mIconSize = Prefs::self()->sidePaneIconSize(); 0257 mShowIcons = Prefs::self()->sidePaneShowIcons(); 0258 mShowText = Prefs::self()->sidePaneShowText(); 0259 0260 auto viewMode = new QActionGroup(this); 0261 connect(viewMode, &QActionGroup::triggered, this, &Navigator::slotActionTriggered); 0262 0263 mShowIconsAction = new QAction(i18nc("@action:inmenu", "Show Icons Only"), this); 0264 mShowIconsAction->setCheckable(true); 0265 mShowIconsAction->setActionGroup(viewMode); 0266 mShowIconsAction->setChecked(!mShowText && mShowIcons); 0267 setHelpText(mShowIconsAction, i18nc("@info:status", "Show sidebar items with icons and without text")); 0268 mShowIconsAction->setWhatsThis(i18nc("@info:whatsthis", "Choose this option if you want the sidebar items to have icons without text.")); 0269 0270 mShowTextAction = new QAction(i18nc("@action:inmenu", "Show Text Only"), this); 0271 mShowTextAction->setCheckable(true); 0272 mShowTextAction->setActionGroup(viewMode); 0273 mShowTextAction->setChecked(mShowText && !mShowIcons); 0274 setHelpText(mShowTextAction, i18nc("@info:status", "Show sidebar items with text and without icons")); 0275 mShowTextAction->setWhatsThis(i18nc("@info:whatsthis", "Choose this option if you want the sidebar items to have text without icons.")); 0276 0277 mShowBothAction = new QAction(i18nc("@action:inmenu", "Show Icons && Text"), this); 0278 mShowBothAction->setCheckable(true); 0279 mShowBothAction->setActionGroup(viewMode); 0280 mShowBothAction->setChecked(mShowText && mShowIcons); 0281 setHelpText(mShowBothAction, i18nc("@info:status", "Show sidebar items with icons and text")); 0282 mShowBothAction->setWhatsThis(i18nc("@info:whatsthis", "Choose this option if you want the sidebar items to have icons and text.")); 0283 0284 auto iconSizeActionGroup = new QActionGroup(this); 0285 connect(iconSizeActionGroup, &QActionGroup::triggered, this, &Navigator::slotActionTriggered); 0286 0287 mBigIconsAction = new QAction(i18nc("@action:inmenu", "Big Icons"), this); 0288 mBigIconsAction->setCheckable(true); 0289 mBigIconsAction->setActionGroup(iconSizeActionGroup); 0290 mBigIconsAction->setChecked(mIconSize == KIconLoader::SizeLarge); 0291 setHelpText(mBigIconsAction, i18nc("@info:status", "Show large size sidebar icons")); 0292 mBigIconsAction->setWhatsThis(i18nc("@info:whatsthis", "Choose this option if you want the sidebar icons to be extra big.")); 0293 0294 mNormalIconsAction = new QAction(i18nc("@action:inmenu", "Normal Icons"), this); 0295 mNormalIconsAction->setCheckable(true); 0296 mNormalIconsAction->setActionGroup(iconSizeActionGroup); 0297 mNormalIconsAction->setChecked(mIconSize == KIconLoader::SizeMedium); 0298 setHelpText(mNormalIconsAction, i18nc("@info:status", "Show normal size sidebar icons")); 0299 mNormalIconsAction->setWhatsThis(i18nc("@info:whatsthis", "Choose this option if you want the sidebar icons to be normal size.")); 0300 0301 mSmallIconsAction = new QAction(i18nc("@action:inmenu", "Small Icons"), this); 0302 mSmallIconsAction->setCheckable(true); 0303 mSmallIconsAction->setActionGroup(iconSizeActionGroup); 0304 mSmallIconsAction->setChecked(mIconSize == KIconLoader::SizeSmallMedium); 0305 setHelpText(mSmallIconsAction, i18nc("@info:status", "Show small size sidebar icons")); 0306 mSmallIconsAction->setWhatsThis(i18nc("@info:whatsthis", "Choose this option if you want the sidebar icons to be extra small.")); 0307 0308 mHideSideBarAction = new QAction(i18nc("@action:inmenu", "Hide Sidebar"), this); 0309 mHideSideBarAction->setCheckable(true); 0310 mHideSideBarAction->setChecked(false); 0311 setHelpText(mHideSideBarAction, i18nc("@info:status", "Hide the icon sidebar")); 0312 mHideSideBarAction->setWhatsThis(i18nc("@info:whatsthis", "Choose this option if you want to hide the icon sidebar. Press F9 to unhide.")); 0313 connect(mHideSideBarAction, &QAction::triggered, this, &Navigator::slotHideSideBarTriggered); 0314 0315 auto sep = new QAction(this); 0316 sep->setSeparator(true); 0317 auto sep2 = new QAction(this); 0318 sep2->setSeparator(true); 0319 0320 QList<QAction *> actionList; 0321 actionList << mShowIconsAction << mShowTextAction << mShowBothAction << sep << mBigIconsAction << mNormalIconsAction << mSmallIconsAction << sep2 0322 << mHideSideBarAction; 0323 0324 insertActions(nullptr, actionList); 0325 0326 setContextMenuPolicy(Qt::ActionsContextMenu); 0327 setViewMode(ListMode); 0328 setItemDelegate(new Delegate(this)); 0329 mModel = new Model(this); 0330 auto sortFilterProxyModel = new SortFilterProxyModel(this); 0331 sortFilterProxyModel->setSourceModel(mModel); 0332 setModel(sortFilterProxyModel); 0333 setSelectionModel(new SelectionModel(sortFilterProxyModel, this)); 0334 0335 setDragDropMode(DropOnly); 0336 viewport()->setAcceptDrops(true); 0337 setDropIndicatorShown(true); 0338 0339 connect(selectionModel(), &QItemSelectionModel::currentChanged, this, &Navigator::slotCurrentChanged); 0340 } 0341 0342 void Navigator::updatePlugins(const QList<KontactInterface::Plugin *> &plugins_) 0343 { 0344 QString currentPlugin; 0345 if (currentIndex().isValid()) { 0346 currentPlugin = currentIndex().model()->data(currentIndex(), Model::PluginName).toString(); 0347 } 0348 0349 QList<KontactInterface::Plugin *> pluginsToShow; 0350 for (KontactInterface::Plugin *plugin : plugins_) { 0351 if (plugin->showInSideBar()) { 0352 pluginsToShow << plugin; 0353 } 0354 } 0355 0356 mModel->setPluginList(pluginsToShow); 0357 0358 mModel->removeRows(0, mModel->rowCount()); 0359 mModel->insertRows(0, pluginsToShow.count()); 0360 0361 // Restore the previous selected index, if any 0362 if (!currentPlugin.isEmpty()) { 0363 setCurrentPlugin(currentPlugin); 0364 } 0365 } 0366 0367 void Navigator::setCurrentPlugin(const QString &plugin) 0368 { 0369 const int numberOfRows(model()->rowCount()); 0370 for (int i = 0; i < numberOfRows; ++i) { 0371 const QModelIndex index = model()->index(i, 0); 0372 const QString pluginName = model()->data(index, Model::PluginName).toString(); 0373 0374 if (plugin == pluginName) { 0375 selectionModel()->setCurrentIndex(index, QItemSelectionModel::SelectCurrent); 0376 break; 0377 } 0378 } 0379 } 0380 0381 QSize Navigator::sizeHint() const 0382 { 0383 //### TODO: We can cache this value, so this reply is faster. Since here we won't 0384 // have too many elements, it is not that important. When caching this value 0385 // make sure it is updated correctly when new rows have been added or 0386 // removed. (ereslibre) 0387 0388 int maxWidth = 0; 0389 const int numberOfRows(model()->rowCount()); 0390 for (int i = 0; i < numberOfRows; ++i) { 0391 const QModelIndex index = model()->index(i, 0); 0392 maxWidth = qMax(maxWidth, sizeHintForIndex(index).width()); 0393 } 0394 0395 // Take vertical scrollbar into account 0396 maxWidth += qApp->style()->pixelMetric(QStyle::PM_ScrollBarExtent); 0397 0398 const int viewHeight = QListView::sizeHint().height(); 0399 0400 const QSize size(maxWidth + rect().width() - contentsRect().width(), viewHeight); 0401 return size; 0402 } 0403 0404 void Navigator::dragEnterEvent(QDragEnterEvent *event) 0405 { 0406 if (event->proposedAction() == Qt::IgnoreAction) { 0407 return; 0408 } 0409 event->acceptProposedAction(); 0410 } 0411 0412 void Navigator::dragMoveEvent(QDragMoveEvent *event) 0413 { 0414 if (event->proposedAction() == Qt::IgnoreAction) { 0415 return; 0416 } 0417 const QModelIndex dropIndex = indexAt(event->position().toPoint()); 0418 0419 if (!dropIndex.isValid() || !(dropIndex.model()->flags(dropIndex) & Qt::ItemIsEnabled)) { 0420 event->setAccepted(false); 0421 return; 0422 } else { 0423 const QModelIndex sourceIndex = static_cast<const QSortFilterProxyModel *>(model())->mapToSource(dropIndex); 0424 auto plugin = static_cast<KontactInterface::Plugin *>(sourceIndex.internalPointer()); 0425 if (!plugin->canDecodeMimeData(event->mimeData())) { 0426 event->setAccepted(false); 0427 return; 0428 } 0429 } 0430 0431 event->acceptProposedAction(); 0432 } 0433 0434 void Navigator::dropEvent(QDropEvent *event) 0435 { 0436 if (event->proposedAction() == Qt::IgnoreAction) { 0437 return; 0438 } 0439 0440 const QModelIndex dropIndex = indexAt(event->position().toPoint()); 0441 0442 if (!dropIndex.isValid()) { 0443 return; 0444 } else { 0445 const QModelIndex sourceIndex = static_cast<const QSortFilterProxyModel *>(model())->mapToSource(dropIndex); 0446 auto plugin = static_cast<KontactInterface::Plugin *>(sourceIndex.internalPointer()); 0447 plugin->processDropEvent(event); 0448 } 0449 } 0450 0451 void Navigator::setHelpText(QAction *act, const QString &text) 0452 { 0453 act->setStatusTip(text); 0454 act->setToolTip(text); 0455 if (act->whatsThis().isEmpty()) { 0456 act->setWhatsThis(text); 0457 } 0458 } 0459 0460 void Navigator::showEvent(QShowEvent *event) 0461 { 0462 parentWidget()->setMaximumWidth(sizeHint().width()); 0463 parentWidget()->setMinimumWidth(sizeHint().width()); 0464 0465 QListView::showEvent(event); 0466 } 0467 0468 void Navigator::slotCurrentChanged(const QModelIndex ¤t) 0469 { 0470 if (!current.isValid() || !current.internalPointer() || !(current.model()->flags(current) & Qt::ItemIsEnabled)) { 0471 return; 0472 } 0473 0474 QModelIndex source = static_cast<const QSortFilterProxyModel *>(current.model())->mapToSource(current); 0475 0476 Q_EMIT pluginActivated(static_cast<KontactInterface::Plugin *>(source.internalPointer())); 0477 } 0478 0479 void Navigator::slotActionTriggered(QAction *object) 0480 { 0481 bool checked = object->isChecked(); 0482 if (object == mShowIconsAction) { 0483 mShowIcons = checked; 0484 mShowText = !checked; 0485 } else if (object == mShowTextAction) { 0486 mShowIcons = !checked; 0487 mShowText = checked; 0488 } else if (object == mShowBothAction) { 0489 mShowIcons = checked; 0490 mShowText = checked; 0491 } else if (object == mBigIconsAction) { 0492 mIconSize = KIconLoader::SizeLarge; 0493 } else if (object == mNormalIconsAction) { 0494 mIconSize = KIconLoader::SizeMedium; 0495 } else if (object == mSmallIconsAction) { 0496 mIconSize = KIconLoader::SizeSmallMedium; 0497 } 0498 0499 Prefs::self()->setSidePaneIconSize(mIconSize); 0500 Prefs::self()->setSidePaneShowIcons(mShowIcons); 0501 Prefs::self()->setSidePaneShowText(mShowText); 0502 0503 reset(); 0504 0505 QTimer::singleShot(0, this, &Navigator::updateNavigatorSize); 0506 } 0507 0508 void Navigator::slotHideSideBarTriggered() 0509 { 0510 if (mainWindow()) { 0511 mainWindow()->showHideSideBar(false); 0512 mHideSideBarAction->setChecked(false); 0513 } 0514 } 0515 0516 void Navigator::updateNavigatorSize() 0517 { 0518 parentWidget()->setMaximumWidth(sizeHint().width()); 0519 parentWidget()->setMinimumWidth(sizeHint().width()); 0520 update(); 0521 } 0522 0523 IconSidePane::IconSidePane(KontactInterface::Core *core, QWidget *parent) 0524 : SidePaneBase(core, parent) 0525 { 0526 mNavigator = new Navigator(this); 0527 layout()->addWidget(mNavigator); 0528 mNavigator->setFocusPolicy(Qt::NoFocus); 0529 mNavigator->setMainWindow(qobject_cast<MainWindow *>(core)); 0530 connect(mNavigator, &Navigator::pluginActivated, this, &IconSidePane::pluginSelected); 0531 } 0532 0533 IconSidePane::~IconSidePane() = default; 0534 0535 void IconSidePane::setCurrentPlugin(const QString &plugin) 0536 { 0537 mNavigator->setCurrentPlugin(plugin); 0538 } 0539 0540 void IconSidePane::updatePlugins() 0541 { 0542 mNavigator->updatePlugins(core()->pluginList()); 0543 } 0544 0545 void IconSidePane::resizeEvent(QResizeEvent *event) 0546 { 0547 Q_UNUSED(event) 0548 const int newWidth(mNavigator->sizeHint().width()); 0549 setFixedWidth(newWidth); 0550 mNavigator->setFixedWidth(newWidth); 0551 } 0552 0553 #include "iconsidepane.moc" 0554 0555 #include "moc_iconsidepane.cpp"