Make the MultiMC delegate fully usable. Dynamic row heights.
This commit is contained in:
parent
0109220678
commit
4662fbd298
@ -29,6 +29,8 @@ set(SOURCES
|
||||
CategorizedView.cpp
|
||||
CategorizedProxyModel.h
|
||||
CategorizedProxyModel.cpp
|
||||
InstanceDelegate.h
|
||||
InstanceDelegate.cpp
|
||||
)
|
||||
|
||||
add_executable(GroupView ${SOURCES})
|
||||
|
@ -80,7 +80,22 @@ int CategorizedView::Category::contentHeight() const
|
||||
return 0;
|
||||
}
|
||||
const int rows = qMax(1, qCeil((qreal)view->numItemsForCategory(this) / (qreal)view->itemsPerRow()));
|
||||
return view->itemSize().height() * rows;
|
||||
QMap<int, int> rowToHeightMapping;
|
||||
foreach (const QModelIndex &index, view->itemsForCategory(this))
|
||||
{
|
||||
int row = view->categoryInternalPosition(index).second;
|
||||
if (!rowToHeightMapping.contains(row))
|
||||
{
|
||||
rowToHeightMapping.insert(row, view->itemSize(index).height());
|
||||
}
|
||||
}
|
||||
int result = 0;
|
||||
for (int i = 0; i < rows; ++i)
|
||||
{
|
||||
Q_ASSERT(rowToHeightMapping.contains(i));
|
||||
result += rowToHeightMapping[i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
QSize CategorizedView::Category::categoryTotalSize() const
|
||||
{
|
||||
@ -153,9 +168,10 @@ void CategorizedView::updateGeometries()
|
||||
{
|
||||
QListView::updateGeometries();
|
||||
|
||||
m_cachedItemSize = QSize();
|
||||
m_cachedItemWidth = -1;
|
||||
m_cachedCategoryToIndexMapping.clear();
|
||||
m_cachedVisualRects.clear();
|
||||
m_cachedItemSizes.clear();
|
||||
|
||||
QMap<QString, Category *> cats;
|
||||
|
||||
@ -323,24 +339,59 @@ QList<CategorizedView::Category *> CategorizedView::sortedCategories() const
|
||||
return out;
|
||||
}
|
||||
|
||||
QSize CategorizedView::itemSize(const QStyleOptionViewItem &option) const
|
||||
int CategorizedView::itemWidth() const
|
||||
{
|
||||
if (!m_cachedItemSize.isValid())
|
||||
if (m_cachedItemWidth == -1)
|
||||
{
|
||||
QModelIndex sample = model()->index(model()->rowCount() -1, 0);
|
||||
const QAbstractItemDelegate *delegate = itemDelegate();
|
||||
if (delegate)
|
||||
{
|
||||
m_cachedItemSize = delegate->sizeHint(option, sample);
|
||||
m_cachedItemSize.setWidth(m_cachedItemSize.width() + 20);
|
||||
m_cachedItemSize.setHeight(m_cachedItemSize.height() + 20);
|
||||
m_cachedItemWidth = itemDelegate()->sizeHint(viewOptions(), model()->index(model()->rowCount() -1, 0)).width();
|
||||
}
|
||||
else
|
||||
return m_cachedItemWidth;
|
||||
}
|
||||
|
||||
QSize CategorizedView::itemSize(const QModelIndex &index) const
|
||||
{
|
||||
if (!m_cachedItemSizes.contains(index))
|
||||
{
|
||||
m_cachedItemSize = QSize();
|
||||
QModelIndexList indices;
|
||||
int internalRow = categoryInternalPosition(index).second;
|
||||
foreach (const QModelIndex &i, itemsForCategory(category(index)))
|
||||
{
|
||||
if (categoryInternalPosition(i).second == internalRow)
|
||||
{
|
||||
indices.append(i);
|
||||
}
|
||||
}
|
||||
return m_cachedItemSize;
|
||||
|
||||
int largestHeight = 0;
|
||||
foreach (const QModelIndex &i, indices)
|
||||
{
|
||||
largestHeight = qMax(largestHeight, itemDelegate()->sizeHint(viewOptions(), i).height());
|
||||
}
|
||||
m_cachedItemSizes.insert(index, new QSize(itemWidth(), largestHeight));
|
||||
}
|
||||
return *m_cachedItemSizes.object(index);
|
||||
}
|
||||
|
||||
QPair<int, int> CategorizedView::categoryInternalPosition(const QModelIndex &index) const
|
||||
{
|
||||
QList<QModelIndex> indices = itemsForCategory(category(index));
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
const int perRow = itemsPerRow();
|
||||
for (int i = 0; i < indices.size(); ++i)
|
||||
{
|
||||
if (indices.at(i) == index)
|
||||
{
|
||||
break;
|
||||
}
|
||||
++x;
|
||||
if (x == perRow)
|
||||
{
|
||||
x = 0;
|
||||
++y;
|
||||
}
|
||||
}
|
||||
return qMakePair(x, y);
|
||||
}
|
||||
|
||||
void CategorizedView::mousePressEvent(QMouseEvent *event)
|
||||
@ -719,25 +770,11 @@ QRect CategorizedView::visualRect(const QModelIndex &index) const
|
||||
if (!m_cachedVisualRects.contains(index))
|
||||
{
|
||||
const Category *cat = category(index);
|
||||
QList<QModelIndex> indices = itemsForCategory(cat);
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
const int perRow = itemsPerRow();
|
||||
for (int i = 0; i < indices.size(); ++i)
|
||||
{
|
||||
if (indices.at(i) == index)
|
||||
{
|
||||
break;
|
||||
}
|
||||
++x;
|
||||
if (x == perRow)
|
||||
{
|
||||
x = 0;
|
||||
++y;
|
||||
}
|
||||
}
|
||||
QPair<int, int> pos = categoryInternalPosition(index);
|
||||
int x = pos.first;
|
||||
int y = pos.second;
|
||||
|
||||
QSize size = itemSize();
|
||||
QSize size = itemSize(index);
|
||||
|
||||
QRect *out = new QRect;
|
||||
out->setTop(categoryTop(cat) + cat->headerHeight() + 5 + y * size.height());
|
||||
@ -897,7 +934,7 @@ QPair<CategorizedView::Category *, int> CategorizedView::rowDropPos(const QPoint
|
||||
// calculate the internal column
|
||||
int internalColumn = -1;
|
||||
{
|
||||
const int itemWidth = itemSize().width();
|
||||
const int itemWidth = this->itemWidth();
|
||||
for (int i = 0, c = 0;
|
||||
i < contentWidth();
|
||||
i += itemWidth, ++c)
|
||||
@ -918,7 +955,8 @@ QPair<CategorizedView::Category *, int> CategorizedView::rowDropPos(const QPoint
|
||||
// calculate the internal row
|
||||
int internalRow = -1;
|
||||
{
|
||||
const int itemHeight = itemSize().height();
|
||||
// FIXME rework the drag and drop code
|
||||
const int itemHeight = 0; //itemSize().height();
|
||||
const int top = categoryTop(category);
|
||||
for (int i = top + category->headerHeight(), r = 0;
|
||||
i < top + category->totalHeight();
|
||||
|
@ -91,9 +91,10 @@ private:
|
||||
QList<Category *> sortedCategories() const;
|
||||
|
||||
private:
|
||||
mutable QSize m_cachedItemSize;
|
||||
QSize itemSize(const QStyleOptionViewItem &option) const;
|
||||
QSize itemSize() const { return itemSize(viewOptions()); }
|
||||
mutable int m_cachedItemWidth;
|
||||
mutable QCache<QModelIndex, QSize> m_cachedItemSizes;
|
||||
int itemWidth() const;
|
||||
QSize itemSize(const QModelIndex &index) const;
|
||||
|
||||
/*QLineEdit *m_categoryEditor;
|
||||
Category *m_editedCategory;
|
||||
@ -110,6 +111,8 @@ private:
|
||||
QItemSelectionModel::SelectionFlag m_ctrlDragSelectionFlag;
|
||||
QPoint m_lastDragPosition;
|
||||
|
||||
QPair<int, int> categoryInternalPosition(const QModelIndex &index) const;
|
||||
|
||||
QPixmap renderToPixmap(const QModelIndexList &indices, QRect *r) const;
|
||||
QList<QPair<QRect, QModelIndex> > draggablePaintPairs(const QModelIndexList &indices, QRect *r) const;
|
||||
|
||||
|
254
InstanceDelegate.cpp
Normal file
254
InstanceDelegate.cpp
Normal file
@ -0,0 +1,254 @@
|
||||
/* Copyright 2013 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "InstanceDelegate.h"
|
||||
#include <QPainter>
|
||||
#include <QTextOption>
|
||||
#include <QTextLayout>
|
||||
#include <QApplication>
|
||||
#include <QtCore/qmath.h>
|
||||
|
||||
// Origin: Qt
|
||||
static void viewItemTextLayout(QTextLayout &textLayout, int lineWidth, qreal &height,
|
||||
qreal &widthUsed)
|
||||
{
|
||||
height = 0;
|
||||
widthUsed = 0;
|
||||
textLayout.beginLayout();
|
||||
QString str = textLayout.text();
|
||||
while (true)
|
||||
{
|
||||
QTextLine line = textLayout.createLine();
|
||||
if (!line.isValid())
|
||||
break;
|
||||
if (line.textLength() == 0)
|
||||
break;
|
||||
line.setLineWidth(lineWidth);
|
||||
line.setPosition(QPointF(0, height));
|
||||
height += line.height();
|
||||
widthUsed = qMax(widthUsed, line.naturalTextWidth());
|
||||
}
|
||||
textLayout.endLayout();
|
||||
}
|
||||
|
||||
#define QFIXED_MAX (INT_MAX / 256)
|
||||
|
||||
ListViewDelegate::ListViewDelegate(QObject *parent) : QStyledItemDelegate(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void drawSelectionRect(QPainter *painter, const QStyleOptionViewItemV4 &option,
|
||||
const QRect &rect)
|
||||
{
|
||||
if ((option.state & QStyle::State_Selected))
|
||||
painter->fillRect(rect, option.palette.brush(QPalette::Highlight));
|
||||
else
|
||||
{
|
||||
QColor backgroundColor = option.palette.color(QPalette::Background);
|
||||
backgroundColor.setAlpha(160);
|
||||
painter->fillRect(rect, QBrush(backgroundColor));
|
||||
}
|
||||
}
|
||||
|
||||
void drawFocusRect(QPainter *painter, const QStyleOptionViewItemV4 &option, const QRect &rect)
|
||||
{
|
||||
if (!(option.state & QStyle::State_HasFocus))
|
||||
return;
|
||||
QStyleOptionFocusRect opt;
|
||||
opt.direction = option.direction;
|
||||
opt.fontMetrics = option.fontMetrics;
|
||||
opt.palette = option.palette;
|
||||
opt.rect = rect;
|
||||
// opt.state = option.state | QStyle::State_KeyboardFocusChange |
|
||||
// QStyle::State_Item;
|
||||
auto col = option.state & QStyle::State_Selected ? QPalette::Highlight : QPalette::Base;
|
||||
opt.backgroundColor = option.palette.color(col);
|
||||
// Apparently some widget styles expect this hint to not be set
|
||||
painter->setRenderHint(QPainter::Antialiasing, false);
|
||||
|
||||
QStyle *style = option.widget ? option.widget->style() : QApplication::style();
|
||||
|
||||
style->drawPrimitive(QStyle::PE_FrameFocusRect, &opt, painter, option.widget);
|
||||
|
||||
painter->setRenderHint(QPainter::Antialiasing);
|
||||
}
|
||||
|
||||
static QSize viewItemTextSize(const QStyleOptionViewItemV4 *option)
|
||||
{
|
||||
QStyle *style = option->widget ? option->widget->style() : QApplication::style();
|
||||
QTextOption textOption;
|
||||
textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
|
||||
QTextLayout textLayout;
|
||||
textLayout.setTextOption(textOption);
|
||||
textLayout.setFont(option->font);
|
||||
textLayout.setText(option->text);
|
||||
const int textMargin =
|
||||
style->pixelMetric(QStyle::PM_FocusFrameHMargin, option, option->widget) + 1;
|
||||
QRect bounds(0, 0, 100 - 2 * textMargin, 600);
|
||||
qreal height = 0, widthUsed = 0;
|
||||
viewItemTextLayout(textLayout, bounds.width(), height, widthUsed);
|
||||
const QSize size(qCeil(widthUsed), qCeil(height));
|
||||
return QSize(size.width() + 2 * textMargin, size.height());
|
||||
}
|
||||
|
||||
void ListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
|
||||
const QModelIndex &index) const
|
||||
{
|
||||
QStyleOptionViewItemV4 opt = option;
|
||||
initStyleOption(&opt, index);
|
||||
painter->save();
|
||||
painter->setClipRect(opt.rect);
|
||||
|
||||
opt.features |= QStyleOptionViewItem::WrapText;
|
||||
opt.text = index.data().toString();
|
||||
opt.textElideMode = Qt::ElideRight;
|
||||
opt.displayAlignment = Qt::AlignTop | Qt::AlignHCenter;
|
||||
|
||||
QStyle *style = opt.widget ? opt.widget->style() : QApplication::style();
|
||||
|
||||
// const int iconSize = style->pixelMetric(QStyle::PM_IconViewIconSize);
|
||||
const int iconSize = 48;
|
||||
QRect iconbox = opt.rect;
|
||||
const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, opt.widget) + 1;
|
||||
QRect textRect = opt.rect;
|
||||
QRect textHighlightRect = textRect;
|
||||
// clip the decoration on top, remove width padding
|
||||
textRect.adjust(textMargin, iconSize + textMargin + 5, -textMargin, 0);
|
||||
|
||||
textHighlightRect.adjust(0, iconSize + 5, 0, 0);
|
||||
|
||||
// draw background
|
||||
{
|
||||
// FIXME: unused
|
||||
// QSize textSize = viewItemTextSize ( &opt );
|
||||
QPalette::ColorGroup cg;
|
||||
QStyleOptionViewItemV4 opt2(opt);
|
||||
|
||||
if ((opt.widget && opt.widget->isEnabled()) || (opt.state & QStyle::State_Enabled))
|
||||
{
|
||||
if (!(opt.state & QStyle::State_Active))
|
||||
cg = QPalette::Inactive;
|
||||
else
|
||||
cg = QPalette::Normal;
|
||||
}
|
||||
else
|
||||
{
|
||||
cg = QPalette::Disabled;
|
||||
}
|
||||
opt2.palette.setCurrentColorGroup(cg);
|
||||
|
||||
// fill in background, if any
|
||||
if (opt.backgroundBrush.style() != Qt::NoBrush)
|
||||
{
|
||||
QPointF oldBO = painter->brushOrigin();
|
||||
painter->setBrushOrigin(opt.rect.topLeft());
|
||||
painter->fillRect(opt.rect, opt.backgroundBrush);
|
||||
painter->setBrushOrigin(oldBO);
|
||||
}
|
||||
|
||||
if (opt.showDecorationSelected)
|
||||
{
|
||||
drawSelectionRect(painter, opt2, opt.rect);
|
||||
drawFocusRect(painter, opt2, opt.rect);
|
||||
// painter->fillRect ( opt.rect, opt.palette.brush ( cg, QPalette::Highlight ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
// if ( opt.state & QStyle::State_Selected )
|
||||
{
|
||||
// QRect textRect = subElementRect ( QStyle::SE_ItemViewItemText, opt,
|
||||
// opt.widget );
|
||||
// painter->fillRect ( textHighlightRect, opt.palette.brush ( cg,
|
||||
// QPalette::Highlight ) );
|
||||
drawSelectionRect(painter, opt2, textHighlightRect);
|
||||
drawFocusRect(painter, opt2, textHighlightRect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// draw the icon
|
||||
{
|
||||
QIcon::Mode mode = QIcon::Normal;
|
||||
if (!(opt.state & QStyle::State_Enabled))
|
||||
mode = QIcon::Disabled;
|
||||
else if (opt.state & QStyle::State_Selected)
|
||||
mode = QIcon::Selected;
|
||||
QIcon::State state = opt.state & QStyle::State_Open ? QIcon::On : QIcon::Off;
|
||||
|
||||
iconbox.setHeight(iconSize);
|
||||
opt.icon.paint(painter, iconbox, Qt::AlignCenter, mode, state);
|
||||
}
|
||||
// set the text colors
|
||||
QPalette::ColorGroup cg =
|
||||
opt.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled;
|
||||
if (cg == QPalette::Normal && !(opt.state & QStyle::State_Active))
|
||||
cg = QPalette::Inactive;
|
||||
if (opt.state & QStyle::State_Selected)
|
||||
{
|
||||
painter->setPen(opt.palette.color(cg, QPalette::HighlightedText));
|
||||
}
|
||||
else
|
||||
{
|
||||
painter->setPen(opt.palette.color(cg, QPalette::Text));
|
||||
}
|
||||
|
||||
// draw the text
|
||||
QTextOption textOption;
|
||||
textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
|
||||
textOption.setTextDirection(opt.direction);
|
||||
textOption.setAlignment(QStyle::visualAlignment(opt.direction, opt.displayAlignment));
|
||||
QTextLayout textLayout;
|
||||
textLayout.setTextOption(textOption);
|
||||
textLayout.setFont(opt.font);
|
||||
textLayout.setText(opt.text);
|
||||
|
||||
qreal width, height;
|
||||
viewItemTextLayout(textLayout, textRect.width(), height, width);
|
||||
|
||||
const int lineCount = textLayout.lineCount();
|
||||
|
||||
const QRect layoutRect = QStyle::alignedRect(
|
||||
opt.direction, opt.displayAlignment, QSize(textRect.width(), int(height)), textRect);
|
||||
const QPointF position = layoutRect.topLeft();
|
||||
for (int i = 0; i < lineCount; ++i)
|
||||
{
|
||||
const QTextLine line = textLayout.lineAt(i);
|
||||
line.draw(painter, position);
|
||||
}
|
||||
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
QSize ListViewDelegate::sizeHint(const QStyleOptionViewItem &option,
|
||||
const QModelIndex &index) const
|
||||
{
|
||||
QStyleOptionViewItemV4 opt = option;
|
||||
initStyleOption(&opt, index);
|
||||
opt.features |= QStyleOptionViewItem::WrapText;
|
||||
opt.text = index.data().toString();
|
||||
opt.textElideMode = Qt::ElideRight;
|
||||
opt.displayAlignment = Qt::AlignTop | Qt::AlignHCenter;
|
||||
|
||||
QStyle *style = opt.widget ? opt.widget->style() : QApplication::style();
|
||||
const int textMargin =
|
||||
style->pixelMetric(QStyle::PM_FocusFrameHMargin, &option, opt.widget) + 1;
|
||||
int height = 48 + textMargin * 2 + 5; // TODO: turn constants into variables
|
||||
QSize szz = viewItemTextSize(&opt);
|
||||
height += szz.height();
|
||||
// FIXME: maybe the icon items could scale and keep proportions?
|
||||
QSize sz(100, height);
|
||||
return sz;
|
||||
}
|
27
InstanceDelegate.h
Normal file
27
InstanceDelegate.h
Normal file
@ -0,0 +1,27 @@
|
||||
/* Copyright 2013 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QStyledItemDelegate>
|
||||
|
||||
class ListViewDelegate : public QStyledItemDelegate
|
||||
{
|
||||
public:
|
||||
explicit ListViewDelegate ( QObject* parent = 0 );
|
||||
protected:
|
||||
void paint ( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const;
|
||||
QSize sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const;
|
||||
};
|
4
main.cpp
4
main.cpp
@ -4,6 +4,7 @@
|
||||
#include <QPainter>
|
||||
|
||||
#include "CategorizedProxyModel.h"
|
||||
#include "InstanceDelegate.h"
|
||||
|
||||
QPixmap icon(const Qt::GlobalColor color)
|
||||
{
|
||||
@ -50,7 +51,7 @@ int main(int argc, char *argv[])
|
||||
model.setRowCount(10);
|
||||
model.setColumnCount(1);
|
||||
|
||||
model.setItem(0, createItem(Qt::red, "Red", "Colorful"));
|
||||
model.setItem(0, createItem(Qt::red, "Red is a color. Some more text. I'm out of ideas. 42. What's your name?", "Colorful"));
|
||||
model.setItem(1, createItem(Qt::blue, "Blue", "Colorful"));
|
||||
model.setItem(2, createItem(Qt::yellow, "Yellow", "Colorful"));
|
||||
|
||||
@ -72,6 +73,7 @@ int main(int argc, char *argv[])
|
||||
pModel.setSourceModel(&model);
|
||||
|
||||
CategorizedView w;
|
||||
w.setItemDelegate(new ListViewDelegate);
|
||||
w.setModel(&pModel);
|
||||
w.resize(640, 480);
|
||||
w.show();
|
||||
|
Loading…
Reference in New Issue
Block a user