diff --git a/src/iconsidepane.cpp b/src/iconsidepane.cpp index 80bad5a2..37a4ed10 100644 --- a/src/iconsidepane.cpp +++ b/src/iconsidepane.cpp @@ -1,590 +1,612 @@ /* This file is part of KDE Kontact. Copyright (C) 2003 Cornelius Schumacher Copyright (C) 2008 Rafael Fernández López This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "iconsidepane.h" #include "mainwindow.h" #include "prefs.h" using namespace Kontact; #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Kontact { class SelectionModel : public QItemSelectionModel { Q_OBJECT public: SelectionModel(QAbstractItemModel *model, QObject *parent) : QItemSelectionModel(model, parent) { } public Q_SLOTS: void clear() override { // Don't allow the current selection to be cleared. QListView doesn't call to this method // nowadays, but just to cover of future change of implementation, since QTreeView does call // to this one when clearing the selection. } void select(const QModelIndex &index, QItemSelectionModel::SelectionFlags command) override { // Don't allow the current selection to be cleared if (!index.isValid() && (command & QItemSelectionModel::Clear)) { return; } QItemSelectionModel::select(index, command); } void select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command) override { // Don't allow the current selection to be cleared if (!selection.count() && (command & QItemSelectionModel::Clear)) { return; } QItemSelectionModel::select(selection, command); } }; class Model : public QStringListModel { Q_OBJECT public: enum SpecialRoles { PluginName = Qt::UserRole }; explicit Model(Navigator *parentNavigator = nullptr) : QStringListModel(parentNavigator) , mNavigator(parentNavigator) { } void setPluginList(const QList &list) { pluginList = list; } Qt::ItemFlags flags(const QModelIndex &index) const override { Qt::ItemFlags flags = QStringListModel::flags(index); flags &= ~Qt::ItemIsEditable; if (index.isValid()) { if (static_cast(index.internalPointer())->disabled()) { flags &= ~Qt::ItemIsEnabled; flags &= ~Qt::ItemIsSelectable; flags &= ~Qt::ItemIsDropEnabled; } else { flags |= Qt::ItemIsDropEnabled; } } else { flags &= ~Qt::ItemIsDropEnabled; } return flags; } QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override { Q_UNUSED(parent); if (row < 0 || row >= pluginList.count()) { return QModelIndex(); } return createIndex(row, column, pluginList[row]); } QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override { if (!index.isValid() || !index.internalPointer()) { return QVariant(); } if (role == Qt::DisplayRole) { if (!mNavigator->showText()) { return QVariant(); } return static_cast(index.internalPointer())->title(); } else if (role == Qt::DecorationRole) { if (!mNavigator->showIcons()) { return QVariant(); } return QIcon::fromTheme(static_cast(index.internalPointer())->icon()); } else if (role == Qt::TextAlignmentRole) { return Qt::AlignCenter; } else if (role == Qt::ToolTipRole) { if (!mNavigator->showText()) { return static_cast(index.internalPointer())->title(); } return QVariant(); } else if (role == PluginName) { return static_cast(index.internalPointer())->identifier(); } return QStringListModel::data(index, role); } private: QList pluginList; Navigator *mNavigator = nullptr; }; class SortFilterProxyModel : public QSortFilterProxyModel { Q_OBJECT public: explicit SortFilterProxyModel(QObject *parent = nullptr) : QSortFilterProxyModel(parent) { setDynamicSortFilter(true); sort(0); } protected: bool lessThan(const QModelIndex &left, const QModelIndex &right) const override { KontactInterface::Plugin *leftPlugin = static_cast(left.internalPointer()); KontactInterface::Plugin *rightPlugin = static_cast(right.internalPointer()); if (leftPlugin->weight() == rightPlugin->weight()) { //Optimize it QCollator col; return col.compare(leftPlugin->title(), rightPlugin->title()) < 0; } return leftPlugin->weight() < rightPlugin->weight(); } }; class Delegate : public QStyledItemDelegate { Q_OBJECT public: explicit Delegate(Navigator *parentNavigator = nullptr) : QStyledItemDelegate(parentNavigator) , mNavigator(parentNavigator) { } void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override { if (!index.isValid() || !index.internalPointer()) { return; } - QStyleOptionViewItem optionCopy(*static_cast(&option)); - optionCopy.decorationPosition = QStyleOptionViewItem::Top; + QStyleOptionViewItem opt(*static_cast(&option)); + //optionCopy.state |= QStyle::State_Active; + opt.decorationPosition = QStyleOptionViewItem::Top; #if QT_VERSION < QT_VERSION_CHECK(5,9,0) const int height = QFontMetrics(painter->font()).height(); #else const int height = 0; #endif - optionCopy.decorationSize - = mNavigator->showIcons() ? QSize(mNavigator->iconSize(), mNavigator->iconSize() + height) : QSize(); - optionCopy.textElideMode = Qt::ElideNone; - QStyledItemDelegate::paint(painter, optionCopy, index); + painter->save(); + + mNavigator->style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter); + + if (mNavigator->showIcons() && mNavigator->showText()) { + opt.icon = index.data(Qt::DecorationRole).value(); + const int size = mNavigator->iconSize(); + const auto spacing = mNavigator->style()->pixelMetric(QStyle::PM_FocusFrameHMargin); + const int textHeight = painter->fontMetrics().height(); + + const int y = opt.rect.y() + (opt.rect.height() - size - spacing - textHeight) / 2; + + opt.icon.paint(painter, QRect(opt.rect.x(), y, opt.rect.width(), size), Qt::AlignCenter, QIcon::Normal, QIcon::On); + painter->drawText(QRect(opt.rect.x(), y + size + spacing, opt.rect.width(), textHeight), + index.data(Qt::DisplayRole).toString(), { Qt::AlignCenter }); + + } else if (mNavigator->showIcons()) { + opt.icon = index.data(Qt::DecorationRole).value(); + const int size = mNavigator->iconSize() + height; + opt.decorationSize = QSize(size, size); + opt.icon.paint(painter, opt.rect, Qt::AlignCenter, QIcon::Normal, QIcon::On); + } else if (mNavigator->showText()) { + painter->drawText(opt.rect, index.data(Qt::DisplayRole).toString(), { Qt::AlignCenter }); + } + painter->restore(); } QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override { if (!index.isValid() || !index.internalPointer()) { return QSize(); } QStyleOptionViewItem optionCopy(*static_cast(&option)); optionCopy.decorationPosition = QStyleOptionViewItem::Top; optionCopy.decorationSize = mNavigator->showIcons() ? QSize(mNavigator->iconSize(), mNavigator->iconSize()) : QSize(); optionCopy.textElideMode = Qt::ElideNone; return QStyledItemDelegate::sizeHint(optionCopy, index); } private: Navigator *mNavigator = nullptr; }; } Navigator::Navigator(SidePaneBase *parent) : QListView(parent) , mSidePane(parent) , mMainWindow(nullptr) { setViewport(new QWidget(this)); setVerticalScrollMode(ScrollPerPixel); setHorizontalScrollMode(ScrollPerPixel); mIconSize = Prefs::self()->sidePaneIconSize(); mShowIcons = Prefs::self()->sidePaneShowIcons(); mShowText = Prefs::self()->sidePaneShowText(); QActionGroup *viewMode = new QActionGroup(this); connect(viewMode, &QActionGroup::triggered, this, &Navigator::slotActionTriggered); mShowIconsAction = new QAction(i18nc("@action:inmenu", "Show Icons Only"), this); mShowIconsAction->setCheckable(true); mShowIconsAction->setActionGroup(viewMode); mShowIconsAction->setChecked(!mShowText && mShowIcons); setHelpText(mShowIconsAction, i18nc("@info:status", "Show sidebar items with icons and without text")); mShowIconsAction->setWhatsThis( i18nc("@info:whatsthis", "Choose this option if you want the sidebar items to have icons without text.")); mShowTextAction = new QAction(i18nc("@action:inmenu", "Show Text Only"), this); mShowTextAction->setCheckable(true); mShowTextAction->setActionGroup(viewMode); mShowTextAction->setChecked(mShowText && !mShowIcons); setHelpText(mShowTextAction, i18nc("@info:status", "Show sidebar items with text and without icons")); mShowTextAction->setWhatsThis( i18nc("@info:whatsthis", "Choose this option if you want the sidebar items to have text without icons.")); mShowBothAction = new QAction(i18nc("@action:inmenu", "Show Icons && Text"), this); mShowBothAction->setCheckable(true); mShowBothAction->setActionGroup(viewMode); mShowBothAction->setChecked(mShowText && mShowIcons); setHelpText(mShowBothAction, i18nc("@info:status", "Show sidebar items with icons and text")); mShowBothAction->setWhatsThis( i18nc("@info:whatsthis", "Choose this option if you want the sidebar items to have icons and text.")); QActionGroup *iconSize = new QActionGroup(this); connect(iconSize, &QActionGroup::triggered, this, &Navigator::slotActionTriggered); mBigIconsAction = new QAction(i18nc("@action:inmenu", "Big Icons"), this); mBigIconsAction->setCheckable(true); mBigIconsAction->setActionGroup(iconSize); mBigIconsAction->setChecked(mIconSize == KIconLoader::SizeLarge); setHelpText(mBigIconsAction, i18nc("@info:status", "Show large size sidebar icons")); mBigIconsAction->setWhatsThis( i18nc("@info:whatsthis", "Choose this option if you want the sidebar icons to be extra big.")); mNormalIconsAction = new QAction(i18nc("@action:inmenu", "Normal Icons"), this); mNormalIconsAction->setCheckable(true); mNormalIconsAction->setActionGroup(iconSize); mNormalIconsAction->setChecked(mIconSize == KIconLoader::SizeMedium); setHelpText(mNormalIconsAction, i18nc("@info:status", "Show normal size sidebar icons")); mNormalIconsAction->setWhatsThis( i18nc("@info:whatsthis", "Choose this option if you want the sidebar icons to be normal size.")); mSmallIconsAction = new QAction(i18nc("@action:inmenu", "Small Icons"), this); mSmallIconsAction->setCheckable(true); mSmallIconsAction->setActionGroup(iconSize); mSmallIconsAction->setChecked(mIconSize == KIconLoader::SizeSmallMedium); setHelpText(mSmallIconsAction, i18nc("@info:status", "Show small size sidebar icons")); mSmallIconsAction->setWhatsThis( i18nc("@info:whatsthis", "Choose this option if you want the sidebar icons to be extra small.")); mHideSideBarAction = new QAction(i18nc("@action:inmenu", "Hide Sidebar"), this); mHideSideBarAction->setCheckable(true); mHideSideBarAction->setChecked(false); setHelpText(mHideSideBarAction, i18nc("@info:status", "Hide the icon sidebar")); mHideSideBarAction->setWhatsThis( i18nc("@info:whatsthis", "Choose this option if you to hide the icon sidebar. Press F9 to unhide.")); connect(mHideSideBarAction, &QAction::triggered, this, &Navigator::slotHideSideBarTriggered); QAction *sep = new QAction(this); sep->setSeparator(true); QAction *sep2 = new QAction(this); sep2->setSeparator(true); QList actionList; actionList << mShowIconsAction << mShowTextAction << mShowBothAction << sep << mBigIconsAction << mNormalIconsAction << mSmallIconsAction << sep2 << mHideSideBarAction; insertActions(nullptr, actionList); setContextMenuPolicy(Qt::ActionsContextMenu); setViewMode(ListMode); setItemDelegate(new Delegate(this)); mModel = new Model(this); SortFilterProxyModel *sortFilterProxyModel = new SortFilterProxyModel; sortFilterProxyModel->setSourceModel(mModel); setModel(sortFilterProxyModel); setSelectionModel(new SelectionModel(sortFilterProxyModel, this)); setDragDropMode(DropOnly); viewport()->setAcceptDrops(true); setDropIndicatorShown(true); connect(selectionModel(), &QItemSelectionModel::currentChanged, this, &Navigator::slotCurrentChanged); } void Navigator::updatePlugins(const QList &plugins_) { QString currentPlugin; if (currentIndex().isValid()) { currentPlugin = currentIndex().model()->data(currentIndex(), Model::PluginName).toString(); } QList pluginsToShow; for (KontactInterface::Plugin *plugin : plugins_) { if (plugin->showInSideBar()) { pluginsToShow << plugin; } } mModel->setPluginList(pluginsToShow); mModel->removeRows(0, mModel->rowCount()); mModel->insertRows(0, pluginsToShow.count()); // Restore the previous selected index, if any if (!currentPlugin.isEmpty()) { setCurrentPlugin(currentPlugin); } } void Navigator::setCurrentPlugin(const QString &plugin) { const int numberOfRows(model()->rowCount()); for (int i = 0; i < numberOfRows; ++i) { const QModelIndex index = model()->index(i, 0); const QString pluginName = model()->data(index, Model::PluginName).toString(); if (plugin == pluginName) { selectionModel()->setCurrentIndex(index, QItemSelectionModel::SelectCurrent); break; } } } QSize Navigator::sizeHint() const { //### TODO: We can cache this value, so this reply is faster. Since here we won't // have too many elements, it is not that important. When caching this value // make sure it is updated correctly when new rows have been added or // removed. (ereslibre) int maxWidth = 0; const int numberOfRows(model()->rowCount()); for (int i = 0; i < numberOfRows; ++i) { const QModelIndex index = model()->index(i, 0); maxWidth = qMax(maxWidth, sizeHintForIndex(index).width()); } // Take vertical scrollbar into account maxWidth += qApp->style()->pixelMetric(QStyle::PM_ScrollBarExtent); int viewHeight = QListView::sizeHint().height(); QSize size(maxWidth + rect().width() - contentsRect().width(), viewHeight); return size; } void Navigator::dragEnterEvent(QDragEnterEvent *event) { if (event->proposedAction() == Qt::IgnoreAction) { return; } event->acceptProposedAction(); } void Navigator::dragMoveEvent(QDragMoveEvent *event) { if (event->proposedAction() == Qt::IgnoreAction) { return; } const QModelIndex dropIndex = indexAt(event->pos()); if (!dropIndex.isValid() || !(dropIndex.model()->flags(dropIndex) & Qt::ItemIsEnabled)) { event->setAccepted(false); return; } else { const QModelIndex sourceIndex = static_cast(model())->mapToSource(dropIndex); KontactInterface::Plugin *plugin = static_cast(sourceIndex.internalPointer()); if (!plugin->canDecodeMimeData(event->mimeData())) { event->setAccepted(false); return; } } event->acceptProposedAction(); } void Navigator::dropEvent(QDropEvent *event) { if (event->proposedAction() == Qt::IgnoreAction) { return; } const QModelIndex dropIndex = indexAt(event->pos()); if (!dropIndex.isValid()) { return; } else { const QModelIndex sourceIndex = static_cast(model())->mapToSource(dropIndex); KontactInterface::Plugin *plugin = static_cast(sourceIndex.internalPointer()); plugin->processDropEvent(event); } } void Navigator::setHelpText(QAction *act, const QString &text) { act->setStatusTip(text); act->setToolTip(text); if (act->whatsThis().isEmpty()) { act->setWhatsThis(text); } } void Navigator::showEvent(QShowEvent *event) { parentWidget()->setMaximumWidth(sizeHint().width()); parentWidget()->setMinimumWidth(sizeHint().width()); QListView::showEvent(event); } void Navigator::slotCurrentChanged(const QModelIndex ¤t) { if (!current.isValid() || !current.internalPointer() || !(current.model()->flags(current) & Qt::ItemIsEnabled)) { return; } QModelIndex source = static_cast(current.model())->mapToSource(current); Q_EMIT pluginActivated(static_cast(source.internalPointer())); } void Navigator::slotActionTriggered(QAction *object) { bool checked = object->isChecked(); if (object == mShowIconsAction) { mShowIcons = checked; mShowText = !checked; } else if (object == mShowTextAction) { mShowIcons = !checked; mShowText = checked; } else if (object == mShowBothAction) { mShowIcons = checked; mShowText = checked; } else if (object == mBigIconsAction) { mIconSize = KIconLoader::SizeLarge; } else if (object == mNormalIconsAction) { mIconSize = KIconLoader::SizeMedium; } else if (object == mSmallIconsAction) { mIconSize = KIconLoader::SizeSmallMedium; } Prefs::self()->setSidePaneIconSize(mIconSize); Prefs::self()->setSidePaneShowIcons(mShowIcons); Prefs::self()->setSidePaneShowText(mShowText); reset(); QTimer::singleShot(0, this, &Navigator::updateNavigatorSize); } void Navigator::slotHideSideBarTriggered() { if (mainWindow()) { mainWindow()->showHideSideBar(false); mHideSideBarAction->setChecked(false); } } void Navigator::updateNavigatorSize() { parentWidget()->setMaximumWidth(sizeHint().width()); parentWidget()->setMinimumWidth(sizeHint().width()); update(); } IconSidePane::IconSidePane(KontactInterface::Core *core, QWidget *parent) : SidePaneBase(core, parent) { mNavigator = new Navigator(this); layout()->addWidget(mNavigator); mNavigator->setFocusPolicy(Qt::NoFocus); mNavigator->setMainWindow(dynamic_cast(core)); connect(mNavigator, &Navigator::pluginActivated, this, &IconSidePane::pluginSelected); } IconSidePane::~IconSidePane() { } void IconSidePane::setCurrentPlugin(const QString &plugin) { mNavigator->setCurrentPlugin(plugin); } void IconSidePane::updatePlugins() { mNavigator->updatePlugins(core()->pluginList()); } void IconSidePane::resizeEvent(QResizeEvent *event) { Q_UNUSED(event); const int newWidth(mNavigator->sizeHint().width()); setFixedWidth(newWidth); mNavigator->setFixedWidth(newWidth); } #include "iconsidepane.moc"