summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJan Dalheimer <jan@dalheimer.de>2013-12-30 18:45:40 +0100
committerJan Dalheimer <jan@dalheimer.de>2013-12-30 18:45:40 +0100
commit4662fbd29891ccb9120df82d17a34a7619242827 (patch)
tree91332b6388f547a0c641edab163e216a58cc79b7
parent01092206783f74ce14f31d328cdac025fd90fe16 (diff)
downloadMultiMC-4662fbd29891ccb9120df82d17a34a7619242827.tar
MultiMC-4662fbd29891ccb9120df82d17a34a7619242827.tar.gz
MultiMC-4662fbd29891ccb9120df82d17a34a7619242827.tar.lz
MultiMC-4662fbd29891ccb9120df82d17a34a7619242827.tar.xz
MultiMC-4662fbd29891ccb9120df82d17a34a7619242827.zip
Make the MultiMC delegate fully usable. Dynamic row heights.
-rw-r--r--CMakeLists.txt2
-rw-r--r--CategorizedView.cpp104
-rw-r--r--CategorizedView.h9
-rw-r--r--InstanceDelegate.cpp254
-rw-r--r--InstanceDelegate.h27
-rw-r--r--main.cpp4
6 files changed, 363 insertions, 37 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8a246bcf..b94cf53e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -29,6 +29,8 @@ set(SOURCES
CategorizedView.cpp
CategorizedProxyModel.h
CategorizedProxyModel.cpp
+ InstanceDelegate.h
+ InstanceDelegate.cpp
)
add_executable(GroupView ${SOURCES})
diff --git a/CategorizedView.cpp b/CategorizedView.cpp
index c134220f..bdb0b222 100644
--- a/CategorizedView.cpp
+++ b/CategorizedView.cpp
@@ -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_cachedItemWidth = itemDelegate()->sizeHint(viewOptions(), model()->index(model()->rowCount() -1, 0)).width();
+ }
+ return m_cachedItemWidth;
+}
+
+QSize CategorizedView::itemSize(const QModelIndex &index) const
+{
+ if (!m_cachedItemSizes.contains(index))
+ {
+ QModelIndexList indices;
+ int internalRow = categoryInternalPosition(index).second;
+ foreach (const QModelIndex &i, itemsForCategory(category(index)))
+ {
+ if (categoryInternalPosition(i).second == internalRow)
+ {
+ indices.append(i);
+ }
+ }
+
+ int largestHeight = 0;
+ foreach (const QModelIndex &i, indices)
{
- m_cachedItemSize = delegate->sizeHint(option, sample);
- m_cachedItemSize.setWidth(m_cachedItemSize.width() + 20);
- m_cachedItemSize.setHeight(m_cachedItemSize.height() + 20);
+ largestHeight = qMax(largestHeight, itemDelegate()->sizeHint(viewOptions(), i).height());
}
- else
+ 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)
{
- m_cachedItemSize = QSize();
+ x = 0;
+ ++y;
}
}
- return m_cachedItemSize;
+ 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();
diff --git a/CategorizedView.h b/CategorizedView.h
index 0756629a..e98e7c5e 100644
--- a/CategorizedView.h
+++ b/CategorizedView.h
@@ -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;
diff --git a/InstanceDelegate.cpp b/InstanceDelegate.cpp
new file mode 100644
index 00000000..5020b8b6
--- /dev/null
+++ b/InstanceDelegate.cpp
@@ -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;
+}
diff --git a/InstanceDelegate.h b/InstanceDelegate.h
new file mode 100644
index 00000000..6f924405
--- /dev/null
+++ b/InstanceDelegate.h
@@ -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;
+};
diff --git a/main.cpp b/main.cpp
index 427ae17a..b164b85d 100644
--- a/main.cpp
+++ b/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();