You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
lxqt-panel-packaging/plugin-mainmenu/actionview.cpp

267 lines
7.6 KiB

/* BEGIN_COMMON_COPYRIGHT_HEADER
* (c)LGPL2+
*
* LXQt - a lightweight, Qt based, desktop toolset
* http://lxqt.org
*
* Copyright: 2016 LXQt team
* Authors:
* Palo Kisa <palo.kisa@gmail.com>
*
* 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 <QAction>
#include <QWidgetAction>
#include <QMenu>
#include <QStandardItemModel>
#include <QSortFilterProxyModel>
#include <QScrollBar>
#include <QProxyStyle>
#include <QStyledItemDelegate>
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<QIcon>();
if (icon.isNull())
{
XdgCachedMenuAction * cached_action = qobject_cast<XdgCachedMenuAction *>(qvariant_cast<QAction *>(index.data(ActionView::ActionRole)));
Q_ASSERT(nullptr != cached_action);
cached_action->updateIcon();
const_cast<QAbstractItemModel *>(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<QItemSelectionModel> guard{selectionModel()};
setModel(mProxy);
}
{
QScopedPointer<QAbstractItemDelegate> 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<QAction *>(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<DelayedIconDelegate *>(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<QAction *>(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<QObject *>())
{
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<QWidgetAction* >(action)
&& !action->isSeparator())
{
//real menu action -> app
if (!existsAction(action))
addAction(action);
}
}
}