Initial commit. Basics work. Next: Drag and Drop
This commit is contained in:
commit
ccbf341dc8
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
build/
|
||||||
|
*.user*
|
35
CMakeLists.txt
Normal file
35
CMakeLists.txt
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
cmake_minimum_required(VERSION 2.8.9)
|
||||||
|
|
||||||
|
project(GroupView)
|
||||||
|
|
||||||
|
set(CMAKE_AUTOMOC ON)
|
||||||
|
|
||||||
|
IF(APPLE)
|
||||||
|
message(STATUS "Using APPLE CMAKE_CXX_FLAGS")
|
||||||
|
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall")
|
||||||
|
ELSEIF(UNIX)
|
||||||
|
# assume GCC, add C++0x/C++11 stuff
|
||||||
|
MESSAGE(STATUS "Using UNIX CMAKE_CXX_FLAGS")
|
||||||
|
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall")
|
||||||
|
ELSEIF(MINGW)
|
||||||
|
MESSAGE(STATUS "Using MINGW CMAKE_CXX_FLAGS")
|
||||||
|
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11 -Wall")
|
||||||
|
ENDIF()
|
||||||
|
|
||||||
|
find_package(Qt5Core REQUIRED)
|
||||||
|
find_package(Qt5Gui REQUIRED)
|
||||||
|
find_package(Qt5Widgets REQUIRED)
|
||||||
|
|
||||||
|
include_directories(${Qt5Core_INCLUDE_DIRS} ${Qt5Gui_INCLUDE_DIRS} ${Qt5Widgets_INCLUDE_DIRS})
|
||||||
|
|
||||||
|
set(SOURCES
|
||||||
|
main.cpp
|
||||||
|
|
||||||
|
CategorizedView.h
|
||||||
|
CategorizedView.cpp
|
||||||
|
CategorizedProxyModel.h
|
||||||
|
CategorizedProxyModel.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
add_executable(GroupView ${SOURCES})
|
||||||
|
qt5_use_modules(GroupView Core Gui Widgets)
|
12
CategorizedProxyModel.cpp
Normal file
12
CategorizedProxyModel.cpp
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#include "CategorizedProxyModel.h"
|
||||||
|
|
||||||
|
#include "CategorizedView.h"
|
||||||
|
|
||||||
|
CategorizedProxyModel::CategorizedProxyModel(QObject *parent)
|
||||||
|
: QSortFilterProxyModel(parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
bool CategorizedProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
|
||||||
|
{
|
||||||
|
return left.data(CategorizedView::CategoryRole).toString() < right.data(CategorizedView::CategoryRole).toString();
|
||||||
|
}
|
18
CategorizedProxyModel.h
Normal file
18
CategorizedProxyModel.h
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
#ifndef CATEGORIZEDPROXYMODEL_H
|
||||||
|
#define CATEGORIZEDPROXYMODEL_H
|
||||||
|
|
||||||
|
#include <QSortFilterProxyModel>
|
||||||
|
|
||||||
|
class CategorizedProxyModel : public QSortFilterProxyModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
CategorizedProxyModel(QObject *parent = 0);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool lessThan(const QModelIndex &left, const QModelIndex &right) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif // CATEGORIZEDPROXYMODEL_H
|
587
CategorizedView.cpp
Normal file
587
CategorizedView.cpp
Normal file
@ -0,0 +1,587 @@
|
|||||||
|
#include "CategorizedView.h"
|
||||||
|
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QtMath>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QMouseEvent>
|
||||||
|
|
||||||
|
CategorizedView::Category::Category(const QString &text, CategorizedView *view)
|
||||||
|
: view(view), text(text), collapsed(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
CategorizedView::Category::Category(const CategorizedView::Category *other) :
|
||||||
|
view(other->view), text(other->text), collapsed(other->collapsed), iconRect(other->iconRect), textRect(other->textRect)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void CategorizedView::Category::drawHeader(QPainter *painter, const int y)
|
||||||
|
{
|
||||||
|
painter->save();
|
||||||
|
|
||||||
|
int height = headerHeight() - 4;
|
||||||
|
int collapseSize = height;
|
||||||
|
|
||||||
|
// the icon
|
||||||
|
iconRect = QRect(view->m_rightMargin + 2, 2 + y, collapseSize, collapseSize);
|
||||||
|
painter->setPen(QPen(Qt::black, 1));
|
||||||
|
painter->drawRect(iconRect);
|
||||||
|
static const int margin = 2;
|
||||||
|
QRect iconSubrect = iconRect.adjusted(margin, margin, -margin, -margin);
|
||||||
|
int midX = iconSubrect.center().x();
|
||||||
|
int midY = iconSubrect.center().y();
|
||||||
|
if (collapsed)
|
||||||
|
{
|
||||||
|
painter->drawLine(midX, iconSubrect.top(), midX, iconSubrect.bottom());
|
||||||
|
}
|
||||||
|
painter->drawLine(iconSubrect.left(), midY, iconSubrect.right(), midY);
|
||||||
|
|
||||||
|
// the text
|
||||||
|
int textWidth = painter->fontMetrics().width(text);
|
||||||
|
textRect = QRect(iconRect.right() + 4, y, textWidth, headerHeight());
|
||||||
|
painter->drawText(textRect, text, QTextOption(Qt::AlignHCenter | Qt::AlignVCenter));
|
||||||
|
|
||||||
|
// the line
|
||||||
|
painter->drawLine(textRect.right() + 4, y + headerHeight() / 2, view->contentWidth() - view->m_rightMargin, y + headerHeight() / 2);
|
||||||
|
|
||||||
|
painter->restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
int CategorizedView::Category::totalHeight() const
|
||||||
|
{
|
||||||
|
return headerHeight() + 5 + contentHeight();
|
||||||
|
}
|
||||||
|
int CategorizedView::Category::headerHeight() const
|
||||||
|
{
|
||||||
|
return qApp->fontMetrics().height() + 4;
|
||||||
|
}
|
||||||
|
int CategorizedView::Category::contentHeight() const
|
||||||
|
{
|
||||||
|
if (collapsed)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const int rows = qMax(1, qCeil((qreal)view->numItemsForCategory(this) / (qreal)view->itemsPerRow()));
|
||||||
|
return view->itemSize().height() * rows;
|
||||||
|
}
|
||||||
|
QSize CategorizedView::Category::categoryTotalSize() const
|
||||||
|
{
|
||||||
|
return QSize(view->contentWidth(), contentHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
CategorizedView::CategorizedView(QWidget *parent)
|
||||||
|
: QListView(parent), m_leftMargin(5), m_rightMargin(5), m_categoryMargin(5)//, m_updatesDisabled(false), m_categoryEditor(0), m_editedCategory(0)
|
||||||
|
{
|
||||||
|
setViewMode(IconMode);
|
||||||
|
setMovement(Snap);
|
||||||
|
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||||
|
setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||||
|
setWordWrap(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
CategorizedView::~CategorizedView()
|
||||||
|
{
|
||||||
|
qDeleteAll(m_categories);
|
||||||
|
m_categories.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CategorizedView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
|
||||||
|
{
|
||||||
|
// if (m_updatesDisabled)
|
||||||
|
// {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
QListView::dataChanged(topLeft, bottomRight, roles);
|
||||||
|
|
||||||
|
if (roles.contains(CategoryRole))
|
||||||
|
{
|
||||||
|
updateGeometries();
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void CategorizedView::rowsInserted(const QModelIndex &parent, int start, int end)
|
||||||
|
{
|
||||||
|
// if (m_updatesDisabled)
|
||||||
|
// {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
QListView::rowsInserted(parent, start, end);
|
||||||
|
|
||||||
|
updateGeometries();
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
void CategorizedView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
|
||||||
|
{
|
||||||
|
// if (m_updatesDisabled)
|
||||||
|
// {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
QListView::rowsAboutToBeRemoved(parent, start, end);
|
||||||
|
|
||||||
|
updateGeometries();
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CategorizedView::updateGeometries()
|
||||||
|
{
|
||||||
|
QListView::updateGeometries();
|
||||||
|
|
||||||
|
m_cachedItemSize = QSize();
|
||||||
|
|
||||||
|
QMap<QString, Category *> cats;
|
||||||
|
|
||||||
|
for (int i = 0; i < model()->rowCount(); ++i)
|
||||||
|
{
|
||||||
|
const QString category = model()->index(i, 0).data(CategoryRole).toString();
|
||||||
|
if (!cats.contains(category))
|
||||||
|
{
|
||||||
|
Category *old = this->category(category);
|
||||||
|
if (old)
|
||||||
|
{
|
||||||
|
cats.insert(category, new Category(old));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cats.insert(category, new Category(category, this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*if (m_editedCategory)
|
||||||
|
{
|
||||||
|
m_editedCategory = cats[m_editedCategory->text];
|
||||||
|
}*/
|
||||||
|
|
||||||
|
qDeleteAll(m_categories);
|
||||||
|
m_categories = cats.values();
|
||||||
|
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CategorizedView::isIndexHidden(const QModelIndex &index) const
|
||||||
|
{
|
||||||
|
Category *cat = category(index);
|
||||||
|
if (cat)
|
||||||
|
{
|
||||||
|
return cat->collapsed;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CategorizedView::Category *CategorizedView::category(const QModelIndex &index) const
|
||||||
|
{
|
||||||
|
return category(index.data(CategoryRole).toString());
|
||||||
|
}
|
||||||
|
CategorizedView::Category *CategorizedView::category(const QString &cat) const
|
||||||
|
{
|
||||||
|
for (int i = 0; i < m_categories.size(); ++i)
|
||||||
|
{
|
||||||
|
if (m_categories.at(i)->text == cat)
|
||||||
|
{
|
||||||
|
return m_categories.at(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CategorizedView::numItemsForCategory(const CategorizedView::Category *category) const
|
||||||
|
{
|
||||||
|
return itemsForCategory(category).size();
|
||||||
|
}
|
||||||
|
QList<QModelIndex> CategorizedView::itemsForCategory(const CategorizedView::Category *category) const
|
||||||
|
{
|
||||||
|
QList<QModelIndex> indices;
|
||||||
|
for (int i = 0; i < model()->rowCount(); ++i)
|
||||||
|
{
|
||||||
|
if (model()->index(i, 0).data(CategoryRole).toString() == category->text)
|
||||||
|
{
|
||||||
|
indices += model()->index(i, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return indices;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CategorizedView::categoryTop(const CategorizedView::Category *category) const
|
||||||
|
{
|
||||||
|
int res = 0;
|
||||||
|
const QList<Category *> cats = sortedCategories();
|
||||||
|
for (int i = 0; i < cats.size(); ++i)
|
||||||
|
{
|
||||||
|
if (cats.at(i) == category)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
res += cats.at(i)->totalHeight() + m_categoryMargin;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CategorizedView::itemsPerRow() const
|
||||||
|
{
|
||||||
|
return qFloor((qreal)contentWidth() / (qreal)itemSize().width());
|
||||||
|
}
|
||||||
|
int CategorizedView::contentWidth() const
|
||||||
|
{
|
||||||
|
return width() - m_leftMargin - m_rightMargin;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CategorizedView::lessThanCategoryPointer(const CategorizedView::Category *c1, const CategorizedView::Category *c2)
|
||||||
|
{
|
||||||
|
return c1->text < c2->text;
|
||||||
|
}
|
||||||
|
QList<CategorizedView::Category *> CategorizedView::sortedCategories() const
|
||||||
|
{
|
||||||
|
QList<Category *> out = m_categories;
|
||||||
|
qSort(out.begin(), out.end(), &CategorizedView::lessThanCategoryPointer);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSize CategorizedView::itemSize(const QStyleOptionViewItem &option) const
|
||||||
|
{
|
||||||
|
if (!m_cachedItemSize.isValid())
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_cachedItemSize = QSize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m_cachedItemSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CategorizedView::mousePressEvent(QMouseEvent *event)
|
||||||
|
{
|
||||||
|
//endCategoryEditor();
|
||||||
|
|
||||||
|
if (event->buttons() & Qt::LeftButton)
|
||||||
|
{
|
||||||
|
foreach (Category *category, m_categories)
|
||||||
|
{
|
||||||
|
if (category->iconRect.contains(event->pos()))
|
||||||
|
{
|
||||||
|
category->collapsed = !category->collapsed;
|
||||||
|
updateGeometries();
|
||||||
|
viewport()->update();
|
||||||
|
event->accept();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < model()->rowCount(); ++i)
|
||||||
|
{
|
||||||
|
QModelIndex index = model()->index(i, 0);
|
||||||
|
if (visualRect(index).contains(event->pos()))
|
||||||
|
{
|
||||||
|
selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect);
|
||||||
|
event->accept();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QListView::mousePressEvent(event);
|
||||||
|
}
|
||||||
|
void CategorizedView::mouseMoveEvent(QMouseEvent *event)
|
||||||
|
{
|
||||||
|
if (event->buttons() & Qt::LeftButton)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < model()->rowCount(); ++i)
|
||||||
|
{
|
||||||
|
QModelIndex index = model()->index(i, 0);
|
||||||
|
if (visualRect(index).contains(event->pos()))
|
||||||
|
{
|
||||||
|
selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect);
|
||||||
|
event->accept();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QListView::mouseMoveEvent(event);
|
||||||
|
}
|
||||||
|
void CategorizedView::mouseReleaseEvent(QMouseEvent *event)
|
||||||
|
{
|
||||||
|
if (event->buttons() & Qt::LeftButton)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < model()->rowCount(); ++i)
|
||||||
|
{
|
||||||
|
QModelIndex index = model()->index(i, 0);
|
||||||
|
if (visualRect(index).contains(event->pos()))
|
||||||
|
{
|
||||||
|
selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect);
|
||||||
|
event->accept();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QListView::mouseReleaseEvent(event);
|
||||||
|
}
|
||||||
|
void CategorizedView::mouseDoubleClickEvent(QMouseEvent *event)
|
||||||
|
{
|
||||||
|
/*endCategoryEditor();
|
||||||
|
|
||||||
|
foreach (Category *category, m_categories)
|
||||||
|
{
|
||||||
|
if (category->textRect.contains(event->pos()) && m_categoryEditor == 0)
|
||||||
|
{
|
||||||
|
startCategoryEditor(category);
|
||||||
|
event->accept();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
QListView::mouseDoubleClickEvent(event);
|
||||||
|
}
|
||||||
|
void CategorizedView::paintEvent(QPaintEvent *event)
|
||||||
|
{
|
||||||
|
QPainter painter(this->viewport());
|
||||||
|
|
||||||
|
int y = 0;
|
||||||
|
for (int i = 0; i < m_categories.size(); ++i)
|
||||||
|
{
|
||||||
|
Category *category = m_categories.at(i);
|
||||||
|
category->drawHeader(&painter, y);
|
||||||
|
y += category->totalHeight() + m_categoryMargin;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < model()->rowCount(); ++i)
|
||||||
|
{
|
||||||
|
const QModelIndex index = model()->index(i, 0);
|
||||||
|
if (isIndexHidden(index))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Qt::ItemFlags flags = index.flags();
|
||||||
|
QStyleOptionViewItemV4 option(viewOptions());
|
||||||
|
option.rect = visualRect(index);
|
||||||
|
option.widget = this;
|
||||||
|
option.features |= wordWrap() ? QStyleOptionViewItemV2::WrapText : QStyleOptionViewItemV2::None;
|
||||||
|
if (flags & Qt::ItemIsSelectable)
|
||||||
|
{
|
||||||
|
option.state |= selectionModel()->isSelected(index) ? QStyle::State_Selected : QStyle::State_None;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
option.state &= ~QStyle::State_Selected;
|
||||||
|
}
|
||||||
|
option.state |= (index == currentIndex()) ? QStyle::State_HasFocus : QStyle::State_None;
|
||||||
|
if (!(flags & Qt::ItemIsEnabled))
|
||||||
|
{
|
||||||
|
option.state &= ~QStyle::State_Enabled;
|
||||||
|
}
|
||||||
|
itemDelegate()->paint(&painter, option, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void CategorizedView::resizeEvent(QResizeEvent *event)
|
||||||
|
{
|
||||||
|
QListView::resizeEvent(event);
|
||||||
|
|
||||||
|
// if (m_categoryEditor)
|
||||||
|
// {
|
||||||
|
// m_categoryEditor->resize(qMax(contentWidth() / 2, m_editedCategory->textRect.width()), m_categoryEditor->height());
|
||||||
|
// }
|
||||||
|
|
||||||
|
updateGeometries();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CategorizedView::dragEnterEvent(QDragEnterEvent *event)
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
void CategorizedView::dragMoveEvent(QDragMoveEvent *event)
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
void CategorizedView::dragLeaveEvent(QDragLeaveEvent *event)
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
void CategorizedView::dropEvent(QDropEvent *event)
|
||||||
|
{
|
||||||
|
stopAutoScroll();
|
||||||
|
setState(NoState);
|
||||||
|
|
||||||
|
if (event->source() != this || !(event->possibleActions() & Qt::MoveAction))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that we aren't on a category header and calculate which category we're in
|
||||||
|
Category *category = 0;
|
||||||
|
{
|
||||||
|
int y = 0;
|
||||||
|
foreach (Category *cat, m_categories)
|
||||||
|
{
|
||||||
|
if (event->pos().y() > y && event->pos().y() < (y + cat->headerHeight()))
|
||||||
|
{
|
||||||
|
viewport()->update();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
y += cat->totalHeight() + m_categoryMargin;
|
||||||
|
if (event->pos().y() < y)
|
||||||
|
{
|
||||||
|
category = cat;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate the internal column
|
||||||
|
int internalColumn = -1;
|
||||||
|
{
|
||||||
|
const int itemWidth = itemSize().width();
|
||||||
|
for (int i = 0, c = 0;
|
||||||
|
i < contentWidth();
|
||||||
|
i += itemWidth, ++c)
|
||||||
|
{
|
||||||
|
if (event->pos().x() > (i - itemWidth / 2) &&
|
||||||
|
event->pos().x() < (i + itemWidth / 2))
|
||||||
|
{
|
||||||
|
internalColumn = c;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (internalColumn == -1)
|
||||||
|
{
|
||||||
|
viewport()->update();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate the internal row
|
||||||
|
int internalRow = -1;
|
||||||
|
{
|
||||||
|
const int itemHeight = itemSize().height();
|
||||||
|
const int top = categoryTop(category);
|
||||||
|
for (int i = top + category->headerHeight(), r = 0;
|
||||||
|
i < top + category->totalHeight();
|
||||||
|
i += itemHeight, ++r)
|
||||||
|
{
|
||||||
|
if (event->pos().y() > i && event->pos().y() < (i + itemHeight))
|
||||||
|
{
|
||||||
|
internalRow = r;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (internalRow == -1)
|
||||||
|
{
|
||||||
|
viewport()->update();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QModelIndex> indices = itemsForCategory(category);
|
||||||
|
|
||||||
|
// flaten the internalColumn/internalRow to one row
|
||||||
|
int categoryRow;
|
||||||
|
{
|
||||||
|
for (int i = 0; i < internalRow; ++i)
|
||||||
|
{
|
||||||
|
if (i == internalRow)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
categoryRow += itemsPerRow();
|
||||||
|
}
|
||||||
|
categoryRow += internalColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
int row = indices.at(categoryRow).row();
|
||||||
|
if (model()->dropMimeData(event->mimeData(), Qt::MoveAction, row, 0, QModelIndex()))
|
||||||
|
{
|
||||||
|
event->setDropAction(Qt::MoveAction);
|
||||||
|
event->accept();
|
||||||
|
}
|
||||||
|
updateGeometries();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool lessThanQModelIndex(const QModelIndex &i1, const QModelIndex &i2)
|
||||||
|
{
|
||||||
|
return i1.data() < i2.data();
|
||||||
|
}
|
||||||
|
QRect CategorizedView::visualRect(const QModelIndex &index) const
|
||||||
|
{
|
||||||
|
if (!index.isValid() || isIndexHidden(index) || index.column() > 0)
|
||||||
|
{
|
||||||
|
return QRect();
|
||||||
|
}
|
||||||
|
|
||||||
|
const Category *cat = category(index);
|
||||||
|
QList<QModelIndex> indices = itemsForCategory(cat);
|
||||||
|
qSort(indices.begin(), indices.end(), &lessThanQModelIndex);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QSize size = itemSize();
|
||||||
|
|
||||||
|
QRect out;
|
||||||
|
out.setTop(categoryTop(cat) + cat->headerHeight() + 5 + y * size.height());
|
||||||
|
out.setLeft(x * size.width());
|
||||||
|
out.setSize(size);
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
void CategorizedView::startCategoryEditor(Category *category)
|
||||||
|
{
|
||||||
|
if (m_categoryEditor != 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_editedCategory = category;
|
||||||
|
m_categoryEditor = new QLineEdit(m_editedCategory->text, this);
|
||||||
|
QRect rect = m_editedCategory->textRect;
|
||||||
|
rect.setWidth(qMax(contentWidth() / 2, rect.width()));
|
||||||
|
m_categoryEditor->setGeometry(rect);
|
||||||
|
m_categoryEditor->show();
|
||||||
|
m_categoryEditor->setFocus();
|
||||||
|
connect(m_categoryEditor, &QLineEdit::returnPressed, this, &CategorizedView::endCategoryEditor);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CategorizedView::endCategoryEditor()
|
||||||
|
{
|
||||||
|
if (m_categoryEditor == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_editedCategory->text = m_categoryEditor->text();
|
||||||
|
m_updatesDisabled = true;
|
||||||
|
foreach (const QModelIndex &index, itemsForCategory(m_editedCategory))
|
||||||
|
{
|
||||||
|
const_cast<QAbstractItemModel *>(index.model())->setData(index, m_categoryEditor->text(), CategoryRole);
|
||||||
|
}
|
||||||
|
m_updatesDisabled = false;
|
||||||
|
delete m_categoryEditor;
|
||||||
|
m_categoryEditor = 0;
|
||||||
|
m_editedCategory = 0;
|
||||||
|
updateGeometries();
|
||||||
|
}
|
||||||
|
*/
|
96
CategorizedView.h
Normal file
96
CategorizedView.h
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
#ifndef WIDGET_H
|
||||||
|
#define WIDGET_H
|
||||||
|
|
||||||
|
#include <QListView>
|
||||||
|
#include <QLineEdit>
|
||||||
|
|
||||||
|
class CategorizedView : public QListView
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
CategorizedView(QWidget *parent = 0);
|
||||||
|
~CategorizedView();
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
CategoryRole = Qt::UserRole
|
||||||
|
};
|
||||||
|
|
||||||
|
virtual QRect visualRect(const QModelIndex &index) const;
|
||||||
|
|
||||||
|
protected slots:
|
||||||
|
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles);
|
||||||
|
virtual void rowsInserted(const QModelIndex &parent, int start, int end);
|
||||||
|
virtual void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end);
|
||||||
|
virtual void updateGeometries();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual bool isIndexHidden(const QModelIndex &index) const;
|
||||||
|
void mousePressEvent(QMouseEvent *event) override;
|
||||||
|
void mouseMoveEvent(QMouseEvent *event) override;
|
||||||
|
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||||
|
void mouseDoubleClickEvent(QMouseEvent *event) override;
|
||||||
|
void paintEvent(QPaintEvent *event) override;
|
||||||
|
void resizeEvent(QResizeEvent *event) override;
|
||||||
|
|
||||||
|
void dragEnterEvent(QDragEnterEvent *event) override;
|
||||||
|
void dragMoveEvent(QDragMoveEvent *event) override;
|
||||||
|
void dragLeaveEvent(QDragLeaveEvent *event) override;
|
||||||
|
void dropEvent(QDropEvent *event) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Category
|
||||||
|
{
|
||||||
|
Category(const QString &text, CategorizedView *view);
|
||||||
|
Category(const Category *other);
|
||||||
|
CategorizedView *view;
|
||||||
|
QString text;
|
||||||
|
bool collapsed;
|
||||||
|
QRect iconRect;
|
||||||
|
QRect textRect;
|
||||||
|
|
||||||
|
void drawHeader(QPainter *painter, const int y);
|
||||||
|
int totalHeight() const;
|
||||||
|
int headerHeight() const;
|
||||||
|
int contentHeight() const;
|
||||||
|
QSize categoryTotalSize() const;
|
||||||
|
};
|
||||||
|
friend struct Category;
|
||||||
|
|
||||||
|
QList<Category *> m_categories;
|
||||||
|
|
||||||
|
int m_leftMargin;
|
||||||
|
int m_rightMargin;
|
||||||
|
int m_categoryMargin;
|
||||||
|
int m_itemSpacing;
|
||||||
|
|
||||||
|
//bool m_updatesDisabled;
|
||||||
|
|
||||||
|
Category *category(const QModelIndex &index) const;
|
||||||
|
Category *category(const QString &cat) const;
|
||||||
|
int numItemsForCategory(const Category *category) const;
|
||||||
|
QList<QModelIndex> itemsForCategory(const Category *category) const;
|
||||||
|
|
||||||
|
int categoryTop(const Category *category) const;
|
||||||
|
|
||||||
|
int itemsPerRow() const;
|
||||||
|
int contentWidth() const;
|
||||||
|
|
||||||
|
static bool lessThanCategoryPointer(const Category *c1, const Category *c2);
|
||||||
|
QList<Category *> sortedCategories() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
mutable QSize m_cachedItemSize;
|
||||||
|
QSize itemSize(const QStyleOptionViewItem &option) const;
|
||||||
|
QSize itemSize() const { return itemSize(viewOptions()); }
|
||||||
|
|
||||||
|
/*QLineEdit *m_categoryEditor;
|
||||||
|
Category *m_editedCategory;
|
||||||
|
void startCategoryEditor(Category *category);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void endCategoryEditor();*/
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // WIDGET_H
|
53
main.cpp
Normal file
53
main.cpp
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
#include "CategorizedView.h"
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QStandardItemModel>
|
||||||
|
|
||||||
|
#include "CategorizedProxyModel.h"
|
||||||
|
|
||||||
|
QPixmap icon(const Qt::GlobalColor color)
|
||||||
|
{
|
||||||
|
QPixmap p = QPixmap(32, 32);
|
||||||
|
p.fill(QColor(color));
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
QStandardItem *createItem(const Qt::GlobalColor color, const QString &text, const QString &category)
|
||||||
|
{
|
||||||
|
QStandardItem *item = new QStandardItem;
|
||||||
|
item->setText(text);
|
||||||
|
item->setData(icon(color), Qt::DecorationRole);
|
||||||
|
item->setData(category, CategorizedView::CategoryRole);
|
||||||
|
item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
QApplication a(argc, argv);
|
||||||
|
|
||||||
|
QStandardItemModel model;
|
||||||
|
model.setRowCount(10);
|
||||||
|
model.setColumnCount(1);
|
||||||
|
|
||||||
|
model.setItem(0, createItem(Qt::red, "Red", "Colorful"));
|
||||||
|
model.setItem(1, createItem(Qt::blue, "Blue", "Colorful"));
|
||||||
|
model.setItem(2, createItem(Qt::yellow, "Yellow", "Colorful"));
|
||||||
|
|
||||||
|
model.setItem(3, createItem(Qt::black, "Black", "Not Colorful"));
|
||||||
|
model.setItem(4, createItem(Qt::darkGray, "Dark Gray", "Not Colorful"));
|
||||||
|
model.setItem(5, createItem(Qt::gray, "Gray", "Not Colorful"));
|
||||||
|
model.setItem(6, createItem(Qt::lightGray, "Light Gray", "Not Colorful"));
|
||||||
|
model.setItem(7, createItem(Qt::white, "White", "Not Colorful"));
|
||||||
|
|
||||||
|
model.setItem(8, createItem(Qt::darkGreen, "Dark Green", ""));
|
||||||
|
model.setItem(9, createItem(Qt::green, "Green", ""));
|
||||||
|
|
||||||
|
CategorizedProxyModel pModel;
|
||||||
|
pModel.setSourceModel(&model);
|
||||||
|
|
||||||
|
CategorizedView w;
|
||||||
|
w.setModel(&pModel);
|
||||||
|
w.resize(640, 480);
|
||||||
|
w.show();
|
||||||
|
|
||||||
|
return a.exec();
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user