/* BEGIN_COMMON_COPYRIGHT_HEADER * (c)LGPL2+ * * LXQt - a lightweight, Qt based, desktop toolset * http://lxqt.org/ * * Copyright: 2017 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 "listwidget.h" #include #include #include /*! * 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(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 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(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); }