/* BEGIN_COMMON_COPYRIGHT_HEADER * (c)LGPL2+ * * LXQt - a lightweight, Qt based, desktop toolset * http://lxqt.org * * Copyright: 2016 LXQt team * Authors: * Palo Kisa * * This program or library is free software; you can redistribute it * and/or modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA * * END_COMMON_COPYRIGHT_HEADER */ #include "actionview.h" #ifdef HAVE_MENU_CACHE #include "xdgcachedmenu.h" #endif #include #include #include #include #include #include #include #include namespace { class SingleActivateStyle : public QProxyStyle { public: using QProxyStyle::QProxyStyle; virtual int styleHint(StyleHint hint, const QStyleOption * option = 0, const QWidget * widget = 0, QStyleHintReturn * returnData = 0) const override { if(hint == QStyle::SH_ItemView_ActivateItemOnSingleClick) return 1; return QProxyStyle::styleHint(hint, option, widget, returnData); } }; class DelayedIconDelegate : public QStyledItemDelegate { public: DelayedIconDelegate(QObject * parent = nullptr) : QStyledItemDelegate(parent) , mMaxItemWidth(300) { } void setMaxItemWidth(int max) { mMaxItemWidth = max; } virtual QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override { //the XdgCachedMenuAction does load the icon upon showing its menu #ifdef HAVE_MENU_CACHE QIcon icon = index.data(Qt::DecorationRole).value(); if (icon.isNull()) { XdgCachedMenuAction * cached_action = qobject_cast(qvariant_cast(index.data(ActionView::ActionRole))); Q_ASSERT(nullptr != cached_action); cached_action->updateIcon(); const_cast(index.model())->setData(index, cached_action->icon(), Qt::DecorationRole); } #endif QSize s = QStyledItemDelegate::sizeHint(option, index); s.setWidth(qMin(mMaxItemWidth, s.width())); return s; } private: int mMaxItemWidth; }; } ActionView::ActionView(QWidget * parent /*= nullptr*/) : QListView(parent) , mModel{new QStandardItemModel{this}} , mProxy{new QSortFilterProxyModel{this}} , mMaxItemsToShow(10) { setEditTriggers(QAbstractItemView::NoEditTriggers); setSizeAdjustPolicy(AdjustToContents); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setSelectionBehavior(SelectRows); setSelectionMode(SingleSelection); SingleActivateStyle * s = new SingleActivateStyle; s->setParent(this); setStyle(s); mProxy->setSourceModel(mModel); mProxy->setDynamicSortFilter(true); mProxy->setFilterRole(FilterRole); mProxy->setFilterCaseSensitivity(Qt::CaseInsensitive); mProxy->sort(0); { QScopedPointer guard{selectionModel()}; setModel(mProxy); } { QScopedPointer guard{itemDelegate()}; setItemDelegate(new DelayedIconDelegate{this}); } connect(this, &QAbstractItemView::activated, this, &ActionView::onActivated); } void ActionView::ActionView::clear() { for (int i = mModel->rowCount() - 1; i >= 0; --i) { mModel->removeRow(i); } } void ActionView::addAction(QAction * action) { QStandardItem * item = new QStandardItem; item->setData(QVariant::fromValue(action), ActionRole); item->setFont(action->font()); //Note: XdgCachedMenuAction has delayed icon loading... we are loading the icon //in QStyledItemDelegate:sizeHint if necessary item->setIcon(action->icon()); item->setText(action->text()); item->setToolTip(action->toolTip()); QString all = action->text(); all += '\n'; all += action->toolTip(); item->setData(all, FilterRole); mModel->appendRow(item); connect(action, &QObject::destroyed, this, &ActionView::onActionDestroyed); } bool ActionView::existsAction(QAction const * action) const { bool exists = false; for (int row = mModel->rowCount() - 1; 0 <= row; --row) { const QModelIndex index = mModel->index(row, 0); if (action->text() == mModel->data(index, Qt::DisplayRole) && action->toolTip() == mModel->data(index, Qt::ToolTipRole) ) { exists = true; break; } } return exists; } void ActionView::fillActions(QMenu * menu) { clear(); fillActionsRecursive(menu); } void ActionView::setFilter(QString const & filter) { mProxy->setFilterFixedString(filter); const int count = mProxy->rowCount(); if (0 < count) { if (count > mMaxItemsToShow) { setCurrentIndex(mProxy->index(mMaxItemsToShow - 1, 0)); verticalScrollBar()->triggerAction(QScrollBar::SliderToMinimum); } else { setCurrentIndex(mProxy->index(count - 1, 0)); } } } void ActionView::setMaxItemsToShow(int max) { mMaxItemsToShow = max; } void ActionView::setMaxItemWidth(int max) { dynamic_cast(itemDelegate())->setMaxItemWidth(max); } void ActionView::activateCurrent() { QModelIndex const index = currentIndex(); if (index.isValid()) emit activated(index); } QSize ActionView::viewportSizeHint() const { const int count = mProxy->rowCount(); QSize s{0, 0}; if (0 < count) { const bool scrollable = mMaxItemsToShow < count; s.setWidth(sizeHintForColumn(0) + (scrollable ? verticalScrollBar()->sizeHint().width() : 0)); s.setHeight(sizeHintForRow(0) * (scrollable ? mMaxItemsToShow : count)); } return s; } QSize ActionView::minimumSizeHint() const { return QSize{0, 0}; } void ActionView::onActivated(QModelIndex const & index) { QAction * action = qvariant_cast(model()->data(index, ActionRole)); Q_ASSERT(nullptr != action); action->trigger(); } void ActionView::onActionDestroyed() { QObject * const action = sender(); Q_ASSERT(nullptr != action); for (int i = mModel->rowCount() - 1; 0 <= i; --i) { QStandardItem * item = mModel->item(i); if (action == item->data(ActionRole).value()) { mModel->removeRow(i); break; } } } void ActionView::fillActionsRecursive(QMenu * menu) { for (auto const & action : menu->actions()) { if (QMenu * sub_menu = action->menu()) { fillActionsRecursive(sub_menu); //recursion } else if (nullptr == qobject_cast(action) && !action->isSeparator()) { //real menu action -> app if (!existsAction(action)) addAction(action); } } }