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-session-packaging/lxqt-leave/listwidget.cpp

242 lines
8.1 KiB

/* BEGIN_COMMON_COPYRIGHT_HEADER
* (c)LGPL2+
*
* LXQt - a lightweight, Qt based, desktop toolset
* http://lxqt.org/
*
* Copyright: 2017 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 "listwidget.h"
#include <QItemDelegate>
#include <QApplication>
#include <QKeyEvent>
/*!
* This private delegate does:
* - returns unified sizeHint() -> maximum of all items in (list) model
* - cahes the sizeHint() to not iterate over all items and checking their size
* - overrides decoration position to Qt::Top
* - gives the items margins (increasing sizeHint()) ~~and mimics Button visual~~
* - overrides painting the focus around the whole item (with the decoration)
*
* \note It is a single purpose delegate and expects, that the model
* never changes (cached sizeHint() is never invalidated).
*/
class ItemDelegate : public QItemDelegate
{
public:
static constexpr QMargins MARGINS{5, 5, 5, 5};
public:
using QItemDelegate::QItemDelegate;
virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override
{
if (mItemSize.isValid())
return mItemSize;
// compute maximum item size
QStyleOptionViewItem opt = option;
opt.decorationPosition = QStyleOptionViewItem::Top;
QAbstractListModel const * model = qobject_cast<QAbstractListModel const *>(index.model());
for (QModelIndex i = model->index(0); i.isValid(); i = model->index(i.row() + 1))
{
mItemSize = mItemSize.expandedTo(QItemDelegate::sizeHint(opt, i));
}
mItemSize += {MARGINS.left() + MARGINS.right(), MARGINS.top() + MARGINS.bottom()}; // add some margins
return mItemSize;
}
virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
{
/*
// mimic the button visual
QStyleOption button_option;
button_option.initFrom(option.widget);
button_option.rect = option.rect;
if (!(option.state & QStyle::State_HasFocus))
button_option.state &= ~QStyle::State_HasFocus;
QStyle * style = option.widget->style() ? option.widget->style() : QApplication::style();
style->drawPrimitive(QStyle::PE_PanelButtonTool, &button_option, painter, option.widget);
*/
QStyleOptionViewItem opt = option;
opt.decorationPosition = QStyleOptionViewItem::Top;
opt.displayAlignment = Qt::AlignHCenter | Qt::AlignTop;
return QItemDelegate::paint(painter, opt, index);
}
protected:
// Note: We want to paint the focus rectangle around the whole (text+icon)
// (default in QItemDelegate is to draw the focus only in text rectangle)
virtual void drawFocus(QPainter *painter
, const QStyleOptionViewItem &option
, const QRect &/*rect*/) const override
{
// don't override the rectangle to the text-only
return QItemDelegate::drawFocus(painter, option, option.rect);
}
virtual void drawDisplay(QPainter *painter
, const QStyleOptionViewItem &option
, const QRect &rect
, const QString &text) const override
{
// shrink (and move to bottom) the text rectangle
QRect r = rect.adjusted(0, MARGINS.top(), 0, 0);
return QItemDelegate::drawDisplay(painter, option, r, text);
}
virtual void drawDecoration(QPainter *painter
, const QStyleOptionViewItem &option
, const QRect &rect
, const QPixmap &pixmap) const override
{
// move to bottom the pixmap rectangle
QRect r = rect.translated(0, MARGINS.top());
return QItemDelegate::drawDecoration(painter, option, r, pixmap);
}
private:
mutable QSize mItemSize; //!< the cached (unified/max) item size
};
constexpr QMargins ItemDelegate::MARGINS;
ListWidget::ListWidget(QWidget * parent/* = nullptr*/)
: QListWidget{parent}
, mRows(3)
, mColumns(3)
{
ItemDelegate * delegate = new ItemDelegate{this};
{
QScopedPointer<QAbstractItemDelegate> old_del{itemDelegate()};
setItemDelegate(delegate);
}
}
void ListWidget::setRows(int rows)
{
mRows = rows;
}
void ListWidget::setColumns(int columns)
{
mColumns = columns;
}
QSize ListWidget::viewportSizeHint() const
{
QSize size = sizeHintForIndex(model()->index(0, 0));
size.rwidth() = size.width() * mColumns + spacing() * mColumns * 2 + 1;
size.rheight() = size.height() * mRows + spacing() * mRows * 2 + 1;
return size;
}
QModelIndex ListWidget::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers/* modifiers*/)
{
QModelIndex index = currentIndex();
int count = model()->rowCount(rootIndex());
int current = 0;
if (index.isValid())
{
current = index.row();
index = QModelIndex{}; // setting to invalid to get inside the for loop (move the position)
} else
{
current = cursorAction == MovePrevious ? count - 1 : 0;
index = model()->index(current, 0, rootIndex());
}
// if not enabled, try to find any next enabled
for (int tries = 1; tries < count && 0 == (model()->flags(index) & Qt::ItemIsEnabled); ++tries)
{
int next;
switch (cursorAction)
{
case MoveUp:
case MovePageUp:
next = (current - mColumns) % count;
break;
case MoveDown:
case MovePageDown:
next = (current + mColumns) % count;
break;
case MoveLeft:
if (0 == (current % mColumns))
current += mColumns;
// fall through
case MovePrevious:
if (current == 0)
return QModelIndex{};
next = (current - 1) % count;
break;
case MoveRight:
if ((mColumns - 1) == (current % mColumns))
current -= mColumns;
// fall through
case MoveNext:
if (current == count - 1)
return QModelIndex{};
next = (current + 1) % count;
break;
case MoveHome:
next = 0;
break;
case MoveEnd:
next = count - 1;
break;
}
if (next < 0)
next += count;
index = model()->index(next, 0, rootIndex());
current = next;
}
return index;
}
void ListWidget::keyPressEvent(QKeyEvent * event)
{
if (event->key() == Qt::Key_Space)
{
// mimic the "enter" to fire activated
QKeyEvent k{event->type(), Qt::Key_Enter, event->modifiers(), event->text(), event->isAutoRepeat(), static_cast<ushort>(event->count())};
QListWidget::keyPressEvent(&k);
event->setAccepted(k.isAccepted());
return;
}
return QListWidget::keyPressEvent(event);
}
void ListWidget::focusInEvent(QFocusEvent * event)
{
switch (event->reason())
{
case Qt::TabFocusReason:
setCurrentIndex(model()->index(0, 0, rootIndex()));
break;
case Qt::BacktabFocusReason:
setCurrentIndex(model()->index(model()->rowCount(rootIndex()) - 1, 0, rootIndex()));
break;
default:
break;
}
return QListWidget::focusInEvent(event);
}