Add 'depends/groupview/' from commit '946d49675cb4725c31ab49a51f3bcae302f89a19'
git-subtree-dir: depends/groupview git-subtree-mainline:a17caba2c9
git-subtree-split:946d49675c
This commit is contained in:
commit
b82eb5873e
24
depends/groupview/.clang-format
Normal file
24
depends/groupview/.clang-format
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
UseTab: true
|
||||||
|
IndentWidth: 4
|
||||||
|
TabWidth: 4
|
||||||
|
ConstructorInitializerIndentWidth: 4
|
||||||
|
AccessModifierOffset: -4
|
||||||
|
IndentCaseLabels: false
|
||||||
|
IndentFunctionDeclarationAfterType: false
|
||||||
|
NamespaceIndentation: None
|
||||||
|
|
||||||
|
BreakBeforeBraces: Allman
|
||||||
|
AllowShortIfStatementsOnASingleLine: false
|
||||||
|
ColumnLimit: 96
|
||||||
|
MaxEmptyLinesToKeep: 1
|
||||||
|
|
||||||
|
Standard: Cpp11
|
||||||
|
Cpp11BracedListStyle: true
|
||||||
|
|
||||||
|
SpacesInParentheses: false
|
||||||
|
SpaceInEmptyParentheses: false
|
||||||
|
SpacesInCStyleCastParentheses: false
|
||||||
|
SpaceAfterControlStatementKeyword: true
|
||||||
|
|
||||||
|
AlignTrailingComments: true
|
||||||
|
SpacesBeforeTrailingComments: 1
|
2
depends/groupview/.gitignore
vendored
Normal file
2
depends/groupview/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
build/
|
||||||
|
*.user*
|
40
depends/groupview/CMakeLists.txt
Normal file
40
depends/groupview/CMakeLists.txt
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
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
|
||||||
|
main.h
|
||||||
|
|
||||||
|
GroupView.h
|
||||||
|
GroupView.cpp
|
||||||
|
Group.h
|
||||||
|
Group.cpp
|
||||||
|
GroupedProxyModel.h
|
||||||
|
GroupedProxyModel.cpp
|
||||||
|
InstanceDelegate.h
|
||||||
|
InstanceDelegate.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
add_executable(GroupView ${SOURCES})
|
||||||
|
qt5_use_modules(GroupView Core Gui Widgets)
|
169
depends/groupview/Group.cpp
Normal file
169
depends/groupview/Group.cpp
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
#include "Group.h"
|
||||||
|
|
||||||
|
#include <QModelIndex>
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QtMath>
|
||||||
|
|
||||||
|
#include "GroupView.h"
|
||||||
|
|
||||||
|
Group::Group(const QString &text, GroupView *view) : view(view), text(text), collapsed(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Group::Group(const Group *other)
|
||||||
|
: view(other->view), text(other->text), collapsed(other->collapsed)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void Group::update()
|
||||||
|
{
|
||||||
|
firstItemIndex = firstItem().row();
|
||||||
|
|
||||||
|
rowHeights = QVector<int>(numRows());
|
||||||
|
for (int i = 0; i < numRows(); ++i)
|
||||||
|
{
|
||||||
|
rowHeights[i] = view->categoryRowHeight(
|
||||||
|
view->model()->index(i * view->itemsPerRow() + firstItemIndex, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Group::HitResults Group::hitScan(const QPoint &pos) const
|
||||||
|
{
|
||||||
|
Group::HitResults results = Group::NoHit;
|
||||||
|
int y_start = verticalPosition();
|
||||||
|
int body_start = y_start + headerHeight();
|
||||||
|
int body_end = body_start + contentHeight() + 5; // FIXME: wtf is this 5?
|
||||||
|
int y = pos.y();
|
||||||
|
// int x = pos.x();
|
||||||
|
if(y < y_start)
|
||||||
|
{
|
||||||
|
results = Group::NoHit;
|
||||||
|
}
|
||||||
|
else if(y < body_start)
|
||||||
|
{
|
||||||
|
results = Group::HeaderHit;
|
||||||
|
int collapseSize = headerHeight() - 4;
|
||||||
|
|
||||||
|
// the icon
|
||||||
|
QRect iconRect = QRect(view->m_leftMargin + 2, 2 + y_start, collapseSize, collapseSize);
|
||||||
|
if(iconRect.contains(pos))
|
||||||
|
{
|
||||||
|
results |= Group::CheckboxHit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (y < body_end)
|
||||||
|
{
|
||||||
|
results |= Group::BodyHit;
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Group::drawHeader(QPainter *painter, const int y)
|
||||||
|
{
|
||||||
|
painter->save();
|
||||||
|
|
||||||
|
int height = headerHeight() - 4;
|
||||||
|
int collapseSize = height;
|
||||||
|
|
||||||
|
// the icon
|
||||||
|
QRect iconRect = QRect(view->m_leftMargin + 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);
|
||||||
|
QRect textRect = QRect(iconRect.right() + 4, y, textWidth, headerHeight());
|
||||||
|
painter->setBrush(view->viewOptions().palette.text());
|
||||||
|
view->style()->drawItemText(painter, textRect, Qt::AlignHCenter | Qt::AlignVCenter,
|
||||||
|
view->viewport()->palette(), true, text);
|
||||||
|
|
||||||
|
// the line
|
||||||
|
painter->drawLine(textRect.right() + 4, y + headerHeight() / 2,
|
||||||
|
view->contentWidth() - view->m_rightMargin, y + headerHeight() / 2);
|
||||||
|
|
||||||
|
painter->restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
int Group::totalHeight() const
|
||||||
|
{
|
||||||
|
return headerHeight() + 5 + contentHeight(); // FIXME: wtf is that '5'?
|
||||||
|
}
|
||||||
|
|
||||||
|
int Group::headerHeight() const
|
||||||
|
{
|
||||||
|
return view->viewport()->fontMetrics().height() + 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Group::contentHeight() const
|
||||||
|
{
|
||||||
|
if (collapsed)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int result = 0;
|
||||||
|
for (int i = 0; i < rowHeights.size(); ++i)
|
||||||
|
{
|
||||||
|
result += rowHeights[i];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Group::numRows() const
|
||||||
|
{
|
||||||
|
return qMax(1, qCeil((qreal)numItems() / (qreal)view->itemsPerRow()));
|
||||||
|
}
|
||||||
|
|
||||||
|
int Group::verticalPosition() const
|
||||||
|
{
|
||||||
|
int res = 0;
|
||||||
|
const QList<Group *> cats = view->m_groups;
|
||||||
|
for (int i = 0; i < cats.size(); ++i)
|
||||||
|
{
|
||||||
|
if (cats.at(i) == this)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
res += cats.at(i)->totalHeight() + view->m_categoryMargin;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QModelIndex> Group::items() const
|
||||||
|
{
|
||||||
|
QList<QModelIndex> indices;
|
||||||
|
for (int i = 0; i < view->model()->rowCount(); ++i)
|
||||||
|
{
|
||||||
|
const QModelIndex index = view->model()->index(i, 0);
|
||||||
|
if (index.data(GroupViewRoles::GroupRole).toString() == text)
|
||||||
|
{
|
||||||
|
indices.append(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return indices;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Group::numItems() const
|
||||||
|
{
|
||||||
|
return items().size();
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndex Group::firstItem() const
|
||||||
|
{
|
||||||
|
QList<QModelIndex> indices = items();
|
||||||
|
return indices.isEmpty() ? QModelIndex() : indices.first();
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndex Group::lastItem() const
|
||||||
|
{
|
||||||
|
QList<QModelIndex> indices = items();
|
||||||
|
return indices.isEmpty() ? QModelIndex() : indices.last();
|
||||||
|
}
|
67
depends/groupview/Group.h
Normal file
67
depends/groupview/Group.h
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <QRect>
|
||||||
|
#include <QVector>
|
||||||
|
|
||||||
|
class GroupView;
|
||||||
|
class QPainter;
|
||||||
|
class QModelIndex;
|
||||||
|
|
||||||
|
struct Group
|
||||||
|
{
|
||||||
|
/* constructors */
|
||||||
|
Group(const QString &text, GroupView *view);
|
||||||
|
Group(const Group *other);
|
||||||
|
|
||||||
|
/* data */
|
||||||
|
GroupView *view;
|
||||||
|
QString text;
|
||||||
|
bool collapsed;
|
||||||
|
QVector<int> rowHeights;
|
||||||
|
int firstItemIndex;
|
||||||
|
|
||||||
|
/* logic */
|
||||||
|
/// do stuff. and things. TODO: redo.
|
||||||
|
void update();
|
||||||
|
|
||||||
|
/// draw the header at y-position.
|
||||||
|
void drawHeader(QPainter *painter, const int y);
|
||||||
|
|
||||||
|
/// height of the group, in total. includes a small bit of padding.
|
||||||
|
int totalHeight() const;
|
||||||
|
|
||||||
|
/// height of the group header, in pixels
|
||||||
|
int headerHeight() const;
|
||||||
|
|
||||||
|
/// height of the group content, in pixels
|
||||||
|
int contentHeight() const;
|
||||||
|
|
||||||
|
/// the number of visual rows this group has
|
||||||
|
int numRows() const;
|
||||||
|
|
||||||
|
/// the height at which this group starts, in pixels
|
||||||
|
int verticalPosition() const;
|
||||||
|
|
||||||
|
enum HitResult
|
||||||
|
{
|
||||||
|
NoHit = 0x0,
|
||||||
|
TextHit = 0x1,
|
||||||
|
CheckboxHit = 0x2,
|
||||||
|
HeaderHit = 0x4,
|
||||||
|
BodyHit = 0x8
|
||||||
|
};
|
||||||
|
Q_DECLARE_FLAGS(HitResults, HitResult)
|
||||||
|
|
||||||
|
/// shoot! BANG! what did we hit?
|
||||||
|
HitResults hitScan (const QPoint &pos) const;
|
||||||
|
|
||||||
|
/// super derpy thing.
|
||||||
|
QList<QModelIndex> items() const;
|
||||||
|
/// I don't even
|
||||||
|
int numItems() const;
|
||||||
|
QModelIndex firstItem() const;
|
||||||
|
QModelIndex lastItem() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
Q_DECLARE_OPERATORS_FOR_FLAGS(Group::HitResults)
|
908
depends/groupview/GroupView.cpp
Normal file
908
depends/groupview/GroupView.cpp
Normal file
@ -0,0 +1,908 @@
|
|||||||
|
#include "GroupView.h"
|
||||||
|
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QtMath>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QMouseEvent>
|
||||||
|
#include <QListView>
|
||||||
|
#include <QPersistentModelIndex>
|
||||||
|
#include <QDrag>
|
||||||
|
#include <QMimeData>
|
||||||
|
#include <QScrollBar>
|
||||||
|
|
||||||
|
#include "Group.h"
|
||||||
|
|
||||||
|
template <typename T> bool listsIntersect(const QList<T> &l1, const QList<T> t2)
|
||||||
|
{
|
||||||
|
for (auto &item : l1)
|
||||||
|
{
|
||||||
|
if (t2.contains(item))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupView::GroupView(QWidget *parent)
|
||||||
|
: QAbstractItemView(parent), m_leftMargin(5), m_rightMargin(5), m_bottomMargin(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);
|
||||||
|
// setDragDropMode(QListView::InternalMove);
|
||||||
|
setAcceptDrops(true);
|
||||||
|
m_spacing = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupView::~GroupView()
|
||||||
|
{
|
||||||
|
qDeleteAll(m_groups);
|
||||||
|
m_groups.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroupView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight,
|
||||||
|
const QVector<int> &roles)
|
||||||
|
{
|
||||||
|
if (roles.contains(GroupViewRoles::GroupRole) || roles.contains(Qt::DisplayRole))
|
||||||
|
{
|
||||||
|
updateGeometries();
|
||||||
|
}
|
||||||
|
viewport()->update();
|
||||||
|
}
|
||||||
|
void GroupView::rowsInserted(const QModelIndex &parent, int start, int end)
|
||||||
|
{
|
||||||
|
updateGeometries();
|
||||||
|
viewport()->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroupView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
|
||||||
|
{
|
||||||
|
updateGeometries();
|
||||||
|
viewport()->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroupView::updateGeometries()
|
||||||
|
{
|
||||||
|
int previousScroll = verticalScrollBar()->value();
|
||||||
|
|
||||||
|
QMap<QString, Group *> cats;
|
||||||
|
|
||||||
|
for (int i = 0; i < model()->rowCount(); ++i)
|
||||||
|
{
|
||||||
|
const QString groupName =
|
||||||
|
model()->index(i, 0).data(GroupViewRoles::GroupRole).toString();
|
||||||
|
if (!cats.contains(groupName))
|
||||||
|
{
|
||||||
|
Group *old = this->category(groupName);
|
||||||
|
if (old)
|
||||||
|
{
|
||||||
|
cats.insert(groupName, new Group(old));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cats.insert(groupName, new Group(groupName, this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*if (m_editedCategory)
|
||||||
|
{
|
||||||
|
m_editedCategory = cats[m_editedCategory->text];
|
||||||
|
}*/
|
||||||
|
|
||||||
|
qDeleteAll(m_groups);
|
||||||
|
m_groups = cats.values();
|
||||||
|
|
||||||
|
for (auto cat : m_groups)
|
||||||
|
{
|
||||||
|
cat->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_groups.isEmpty())
|
||||||
|
{
|
||||||
|
verticalScrollBar()->setRange(0, 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int totalHeight = 0;
|
||||||
|
for (auto category : m_groups)
|
||||||
|
{
|
||||||
|
totalHeight += category->totalHeight() + m_categoryMargin;
|
||||||
|
}
|
||||||
|
// remove the last margin (we don't want it)
|
||||||
|
totalHeight -= m_categoryMargin;
|
||||||
|
totalHeight += m_bottomMargin;
|
||||||
|
verticalScrollBar()->setRange(0, totalHeight - height());
|
||||||
|
}
|
||||||
|
|
||||||
|
verticalScrollBar()->setValue(qMin(previousScroll, verticalScrollBar()->maximum()));
|
||||||
|
|
||||||
|
viewport()->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GroupView::isIndexHidden(const QModelIndex &index) const
|
||||||
|
{
|
||||||
|
Group *cat = category(index);
|
||||||
|
if (cat)
|
||||||
|
{
|
||||||
|
return cat->collapsed;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Group *GroupView::category(const QModelIndex &index) const
|
||||||
|
{
|
||||||
|
return category(index.data(GroupViewRoles::GroupRole).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
Group *GroupView::category(const QString &cat) const
|
||||||
|
{
|
||||||
|
for (auto group : m_groups)
|
||||||
|
{
|
||||||
|
if (group->text == cat)
|
||||||
|
{
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Group *GroupView::categoryAt(const QPoint &pos) const
|
||||||
|
{
|
||||||
|
for (auto group : m_groups)
|
||||||
|
{
|
||||||
|
if(group->hitScan(pos) & Group::CheckboxHit)
|
||||||
|
{
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
int GroupView::itemsPerRow() const
|
||||||
|
{
|
||||||
|
return qFloor((qreal)(contentWidth()) / (qreal)(itemWidth() + m_spacing));
|
||||||
|
}
|
||||||
|
|
||||||
|
int GroupView::contentWidth() const
|
||||||
|
{
|
||||||
|
return width() - m_leftMargin - m_rightMargin;
|
||||||
|
}
|
||||||
|
|
||||||
|
int GroupView::itemWidth() const
|
||||||
|
{
|
||||||
|
return itemDelegate()
|
||||||
|
->sizeHint(viewOptions(), model()->index(model()->rowCount() - 1, 0))
|
||||||
|
.width();
|
||||||
|
}
|
||||||
|
|
||||||
|
int GroupView::categoryRowHeight(const QModelIndex &index) const
|
||||||
|
{
|
||||||
|
QModelIndexList indices;
|
||||||
|
int internalRow = categoryInternalPosition(index).second;
|
||||||
|
for (auto &i : category(index)->items())
|
||||||
|
{
|
||||||
|
if (categoryInternalPosition(i).second == internalRow)
|
||||||
|
{
|
||||||
|
indices.append(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int largestHeight = 0;
|
||||||
|
for (auto &i : indices)
|
||||||
|
{
|
||||||
|
largestHeight =
|
||||||
|
qMax(largestHeight, itemDelegate()->sizeHint(viewOptions(), i).height());
|
||||||
|
}
|
||||||
|
return largestHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPair<int, int> GroupView::categoryInternalPosition(const QModelIndex &index) const
|
||||||
|
{
|
||||||
|
QList<QModelIndex> indices = category(index)->items();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
int GroupView::categoryInternalRowTop(const QModelIndex &index) const
|
||||||
|
{
|
||||||
|
Group *cat = category(index);
|
||||||
|
int categoryInternalRow = categoryInternalPosition(index).second;
|
||||||
|
int result = 0;
|
||||||
|
for (int i = 0; i < categoryInternalRow; ++i)
|
||||||
|
{
|
||||||
|
result += cat->rowHeights.at(i);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int GroupView::itemHeightForCategoryRow(const Group *category, const int internalRow) const
|
||||||
|
{
|
||||||
|
for (auto &i : category->items())
|
||||||
|
{
|
||||||
|
QPair<int, int> pos = categoryInternalPosition(i);
|
||||||
|
if (pos.second == internalRow)
|
||||||
|
{
|
||||||
|
return categoryRowHeight(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroupView::mousePressEvent(QMouseEvent *event)
|
||||||
|
{
|
||||||
|
// endCategoryEditor();
|
||||||
|
|
||||||
|
QPoint pos = event->pos() + offset();
|
||||||
|
QPersistentModelIndex index = indexAt(pos);
|
||||||
|
|
||||||
|
m_pressedIndex = index;
|
||||||
|
m_pressedAlreadySelected = selectionModel()->isSelected(m_pressedIndex);
|
||||||
|
QItemSelectionModel::SelectionFlags selection_flags = selectionCommand(index, event);
|
||||||
|
m_pressedPosition = pos;
|
||||||
|
|
||||||
|
m_pressedCategory = categoryAt(m_pressedPosition);
|
||||||
|
if (m_pressedCategory)
|
||||||
|
{
|
||||||
|
setState(m_pressedCategory->collapsed ? ExpandingState : CollapsingState);
|
||||||
|
event->accept();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index.isValid() && (index.flags() & Qt::ItemIsEnabled))
|
||||||
|
{
|
||||||
|
// we disable scrollTo for mouse press so the item doesn't change position
|
||||||
|
// when the user is interacting with it (ie. clicking on it)
|
||||||
|
bool autoScroll = hasAutoScroll();
|
||||||
|
setAutoScroll(false);
|
||||||
|
selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
|
||||||
|
setAutoScroll(autoScroll);
|
||||||
|
QRect rect(m_pressedPosition, pos);
|
||||||
|
setSelection(rect, QItemSelectionModel::ClearAndSelect);
|
||||||
|
|
||||||
|
// signal handlers may change the model
|
||||||
|
emit pressed(index);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Forces a finalize() even if mouse is pressed, but not on a item
|
||||||
|
selectionModel()->select(QModelIndex(), QItemSelectionModel::Select);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroupView::mouseMoveEvent(QMouseEvent *event)
|
||||||
|
{
|
||||||
|
QPoint topLeft;
|
||||||
|
QPoint pos = event->pos() + offset();
|
||||||
|
|
||||||
|
if (state() == ExpandingState || state() == CollapsingState)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state() == DraggingState)
|
||||||
|
{
|
||||||
|
topLeft = m_pressedPosition - offset();
|
||||||
|
if ((topLeft - event->pos()).manhattanLength() > QApplication::startDragDistance())
|
||||||
|
{
|
||||||
|
m_pressedIndex = QModelIndex();
|
||||||
|
startDrag(model()->supportedDragActions());
|
||||||
|
setState(NoState);
|
||||||
|
stopAutoScroll();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectionMode() != SingleSelection)
|
||||||
|
{
|
||||||
|
topLeft = m_pressedPosition - offset();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
topLeft = pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_pressedIndex.isValid() && (state() != DragSelectingState) &&
|
||||||
|
(event->buttons() != Qt::NoButton) && !selectedIndexes().isEmpty())
|
||||||
|
{
|
||||||
|
setState(DraggingState);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((event->buttons() & Qt::LeftButton) && selectionModel())
|
||||||
|
{
|
||||||
|
setState(DragSelectingState);
|
||||||
|
|
||||||
|
setSelection(QRect(pos, pos), QItemSelectionModel::ClearAndSelect);
|
||||||
|
QModelIndex index = indexAt(pos);
|
||||||
|
|
||||||
|
// set at the end because it might scroll the view
|
||||||
|
if (index.isValid() && (index != selectionModel()->currentIndex()) &&
|
||||||
|
(index.flags() & Qt::ItemIsEnabled))
|
||||||
|
{
|
||||||
|
selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroupView::mouseReleaseEvent(QMouseEvent *event)
|
||||||
|
{
|
||||||
|
QPoint pos = event->pos() + offset();
|
||||||
|
QPersistentModelIndex index = indexAt(pos);
|
||||||
|
|
||||||
|
bool click = (index == m_pressedIndex && index.isValid()) ||
|
||||||
|
(m_pressedCategory && m_pressedCategory == categoryAt(pos));
|
||||||
|
|
||||||
|
if (click && m_pressedCategory)
|
||||||
|
{
|
||||||
|
if (state() == ExpandingState)
|
||||||
|
{
|
||||||
|
m_pressedCategory->collapsed = false;
|
||||||
|
updateGeometries();
|
||||||
|
viewport()->update();
|
||||||
|
event->accept();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (state() == CollapsingState)
|
||||||
|
{
|
||||||
|
m_pressedCategory->collapsed = true;
|
||||||
|
updateGeometries();
|
||||||
|
viewport()->update();
|
||||||
|
event->accept();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_ctrlDragSelectionFlag = QItemSelectionModel::NoUpdate;
|
||||||
|
|
||||||
|
setState(NoState);
|
||||||
|
|
||||||
|
if (click)
|
||||||
|
{
|
||||||
|
if (event->button() == Qt::LeftButton)
|
||||||
|
{
|
||||||
|
emit clicked(index);
|
||||||
|
}
|
||||||
|
QStyleOptionViewItem option = viewOptions();
|
||||||
|
if (m_pressedAlreadySelected)
|
||||||
|
{
|
||||||
|
option.state |= QStyle::State_Selected;
|
||||||
|
}
|
||||||
|
if ((model()->flags(index) & Qt::ItemIsEnabled) &&
|
||||||
|
style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, &option, this))
|
||||||
|
{
|
||||||
|
emit activated(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroupView::mouseDoubleClickEvent(QMouseEvent *event)
|
||||||
|
{
|
||||||
|
QModelIndex index = indexAt(event->pos());
|
||||||
|
if (!index.isValid() || !(index.flags() & Qt::ItemIsEnabled) || (m_pressedIndex != index))
|
||||||
|
{
|
||||||
|
QMouseEvent me(QEvent::MouseButtonPress, event->localPos(), event->windowPos(),
|
||||||
|
event->screenPos(), event->button(), event->buttons(),
|
||||||
|
event->modifiers());
|
||||||
|
mousePressEvent(&me);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// signal handlers may change the model
|
||||||
|
QPersistentModelIndex persistent = index;
|
||||||
|
emit doubleClicked(persistent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroupView::paintEvent(QPaintEvent *event)
|
||||||
|
{
|
||||||
|
QPainter painter(this->viewport());
|
||||||
|
|
||||||
|
int y = -verticalOffset();
|
||||||
|
for (int i = 0; i < m_groups.size(); ++i)
|
||||||
|
{
|
||||||
|
Group *category = m_groups.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 |=
|
||||||
|
QStyleOptionViewItemV2::WrapText; // FIXME: what is the meaning of this anyway?
|
||||||
|
if (flags & Qt::ItemIsSelectable && selectionModel()->isSelected(index))
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Drop indicators for manual reordering...
|
||||||
|
*/
|
||||||
|
#if 0
|
||||||
|
if (!m_lastDragPosition.isNull())
|
||||||
|
{
|
||||||
|
QPair<Group *, int> pair = rowDropPos(m_lastDragPosition);
|
||||||
|
Group *category = pair.first;
|
||||||
|
int row = pair.second;
|
||||||
|
if (category)
|
||||||
|
{
|
||||||
|
int internalRow = row - category->firstItemIndex;
|
||||||
|
QLine line;
|
||||||
|
if (internalRow >= category->numItems())
|
||||||
|
{
|
||||||
|
QRect toTheRightOfRect = visualRect(category->lastItem());
|
||||||
|
line = QLine(toTheRightOfRect.topRight(), toTheRightOfRect.bottomRight());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QRect toTheLeftOfRect = visualRect(model()->index(row, 0));
|
||||||
|
line = QLine(toTheLeftOfRect.topLeft(), toTheLeftOfRect.bottomLeft());
|
||||||
|
}
|
||||||
|
painter.save();
|
||||||
|
painter.setPen(QPen(Qt::black, 3));
|
||||||
|
painter.drawLine(line);
|
||||||
|
painter.restore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroupView::resizeEvent(QResizeEvent *event)
|
||||||
|
{
|
||||||
|
// QListView::resizeEvent(event);
|
||||||
|
|
||||||
|
// if (m_categoryEditor)
|
||||||
|
// {
|
||||||
|
// m_categoryEditor->resize(qMax(contentWidth() / 2,
|
||||||
|
// m_editedCategory->textRect.width()),
|
||||||
|
// m_categoryEditor->height());
|
||||||
|
// }
|
||||||
|
|
||||||
|
updateGeometries();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroupView::dragEnterEvent(QDragEnterEvent *event)
|
||||||
|
{
|
||||||
|
if (!isDragEventAccepted(event))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_lastDragPosition = event->pos() + offset();
|
||||||
|
viewport()->update();
|
||||||
|
event->accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroupView::dragMoveEvent(QDragMoveEvent *event)
|
||||||
|
{
|
||||||
|
if (!isDragEventAccepted(event))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_lastDragPosition = event->pos() + offset();
|
||||||
|
viewport()->update();
|
||||||
|
event->accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroupView::dragLeaveEvent(QDragLeaveEvent *event)
|
||||||
|
{
|
||||||
|
m_lastDragPosition = QPoint();
|
||||||
|
viewport()->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroupView::dropEvent(QDropEvent *event)
|
||||||
|
{
|
||||||
|
m_lastDragPosition = QPoint();
|
||||||
|
|
||||||
|
stopAutoScroll();
|
||||||
|
setState(NoState);
|
||||||
|
|
||||||
|
if (event->source() != this || !(event->possibleActions() & Qt::MoveAction))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPair<Group *, int> dropPos = rowDropPos(event->pos() + offset());
|
||||||
|
const Group *category = dropPos.first;
|
||||||
|
const int row = dropPos.second;
|
||||||
|
|
||||||
|
if (row == -1)
|
||||||
|
{
|
||||||
|
viewport()->update();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString categoryText = category->text;
|
||||||
|
if (model()->dropMimeData(event->mimeData(), Qt::MoveAction, row, 0, QModelIndex()))
|
||||||
|
{
|
||||||
|
model()->setData(model()->index(row, 0), categoryText,
|
||||||
|
GroupViewRoles::GroupRole);
|
||||||
|
event->setDropAction(Qt::MoveAction);
|
||||||
|
event->accept();
|
||||||
|
}
|
||||||
|
updateGeometries();
|
||||||
|
viewport()->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroupView::startDrag(Qt::DropActions supportedActions)
|
||||||
|
{
|
||||||
|
QModelIndexList indexes = selectionModel()->selectedIndexes();
|
||||||
|
if (indexes.count() > 0)
|
||||||
|
{
|
||||||
|
QMimeData *data = model()->mimeData(indexes);
|
||||||
|
if (!data)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QRect rect;
|
||||||
|
QPixmap pixmap = renderToPixmap(indexes, &rect);
|
||||||
|
//rect.translate(offset());
|
||||||
|
// rect.adjust(horizontalOffset(), verticalOffset(), 0, 0);
|
||||||
|
QDrag *drag = new QDrag(this);
|
||||||
|
drag->setPixmap(pixmap);
|
||||||
|
drag->setMimeData(data);
|
||||||
|
drag->setHotSpot(m_pressedPosition - rect.topLeft());
|
||||||
|
Qt::DropAction defaultDropAction = Qt::IgnoreAction;
|
||||||
|
if (this->defaultDropAction() != Qt::IgnoreAction &&
|
||||||
|
(supportedActions & this->defaultDropAction()))
|
||||||
|
{
|
||||||
|
defaultDropAction = this->defaultDropAction();
|
||||||
|
}
|
||||||
|
if (drag->exec(supportedActions, defaultDropAction) == Qt::MoveAction)
|
||||||
|
{
|
||||||
|
const QItemSelection selection = selectionModel()->selection();
|
||||||
|
|
||||||
|
for (auto it = selection.constBegin(); it != selection.constEnd(); ++it)
|
||||||
|
{
|
||||||
|
QModelIndex parent = (*it).parent();
|
||||||
|
if ((*it).left() != 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ((*it).right() != (model()->columnCount(parent) - 1))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int count = (*it).bottom() - (*it).top() + 1;
|
||||||
|
model()->removeRows((*it).top(), count, parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QRect GroupView::visualRect(const QModelIndex &index) const
|
||||||
|
{
|
||||||
|
return geometryRect(index).translated(-offset());
|
||||||
|
}
|
||||||
|
|
||||||
|
QRect GroupView::geometryRect(const QModelIndex &index) const
|
||||||
|
{
|
||||||
|
if (!index.isValid() || isIndexHidden(index) || index.column() > 0)
|
||||||
|
{
|
||||||
|
return QRect();
|
||||||
|
}
|
||||||
|
|
||||||
|
const Group *cat = category(index);
|
||||||
|
QPair<int, int> pos = categoryInternalPosition(index);
|
||||||
|
int x = pos.first;
|
||||||
|
// int y = pos.second;
|
||||||
|
|
||||||
|
QRect out;
|
||||||
|
out.setTop(cat->verticalPosition() + cat->headerHeight() + 5 + categoryInternalRowTop(index));
|
||||||
|
out.setLeft(m_spacing + x * (itemWidth() + m_spacing));
|
||||||
|
out.setSize(itemDelegate()->sizeHint(viewOptions(), index));
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
QModelIndex GroupView::indexAt(const QPoint &point) const
|
||||||
|
{
|
||||||
|
for (int i = 0; i < model()->rowCount(); ++i)
|
||||||
|
{
|
||||||
|
QModelIndex index = model()->index(i, 0);
|
||||||
|
if (geometryRect(index).contains(point))
|
||||||
|
{
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return QModelIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroupView::setSelection(const QRect &rect,
|
||||||
|
const QItemSelectionModel::SelectionFlags commands)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < model()->rowCount(); ++i)
|
||||||
|
{
|
||||||
|
QModelIndex index = model()->index(i, 0);
|
||||||
|
QRect itemRect = geometryRect(index);
|
||||||
|
if (itemRect.intersects(rect))
|
||||||
|
{
|
||||||
|
selectionModel()->select(index, commands);
|
||||||
|
update(itemRect.translated(-offset()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QPixmap GroupView::renderToPixmap(const QModelIndexList &indices, QRect *r) const
|
||||||
|
{
|
||||||
|
Q_ASSERT(r);
|
||||||
|
auto paintPairs = draggablePaintPairs(indices, r);
|
||||||
|
if (paintPairs.isEmpty())
|
||||||
|
{
|
||||||
|
return QPixmap();
|
||||||
|
}
|
||||||
|
QPixmap pixmap(r->size());
|
||||||
|
pixmap.fill(Qt::transparent);
|
||||||
|
QPainter painter(&pixmap);
|
||||||
|
QStyleOptionViewItem option = viewOptions();
|
||||||
|
option.state |= QStyle::State_Selected;
|
||||||
|
for (int j = 0; j < paintPairs.count(); ++j)
|
||||||
|
{
|
||||||
|
option.rect = paintPairs.at(j).first.translated(-r->topLeft());
|
||||||
|
const QModelIndex ¤t = paintPairs.at(j).second;
|
||||||
|
itemDelegate()->paint(&painter, option, current);
|
||||||
|
}
|
||||||
|
return pixmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QPair<QRect, QModelIndex>> GroupView::draggablePaintPairs(const QModelIndexList &indices,
|
||||||
|
QRect *r) const
|
||||||
|
{
|
||||||
|
Q_ASSERT(r);
|
||||||
|
QRect &rect = *r;
|
||||||
|
QList<QPair<QRect, QModelIndex>> ret;
|
||||||
|
for (int i = 0; i < indices.count(); ++i)
|
||||||
|
{
|
||||||
|
const QModelIndex &index = indices.at(i);
|
||||||
|
const QRect current = geometryRect(index);
|
||||||
|
ret += qMakePair(current, index);
|
||||||
|
rect |= current;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GroupView::isDragEventAccepted(QDropEvent *event)
|
||||||
|
{
|
||||||
|
if (event->source() != this)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!listsIntersect(event->mimeData()->formats(), model()->mimeTypes()))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!model()->canDropMimeData(event->mimeData(), event->dropAction(),
|
||||||
|
rowDropPos(event->pos()).second, 0, QModelIndex()))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPair<Group *, int> GroupView::rowDropPos(const QPoint &pos)
|
||||||
|
{
|
||||||
|
// check that we aren't on a category header and calculate which category we're in
|
||||||
|
Group *category = 0;
|
||||||
|
{
|
||||||
|
int y = 0;
|
||||||
|
for (auto cat : m_groups)
|
||||||
|
{
|
||||||
|
if (pos.y() > y && pos.y() < (y + cat->headerHeight()))
|
||||||
|
{
|
||||||
|
return qMakePair(nullptr, -1);
|
||||||
|
}
|
||||||
|
y += cat->totalHeight() + m_categoryMargin;
|
||||||
|
if (pos.y() < y)
|
||||||
|
{
|
||||||
|
category = cat;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (category == 0)
|
||||||
|
{
|
||||||
|
return qMakePair(nullptr, -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QModelIndex> indices = category->items();
|
||||||
|
|
||||||
|
// calculate the internal column
|
||||||
|
int internalColumn = -1;
|
||||||
|
{
|
||||||
|
const int itemWidth = this->itemWidth();
|
||||||
|
if (pos.x() >= (itemWidth * itemsPerRow()))
|
||||||
|
{
|
||||||
|
internalColumn = itemsPerRow();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int i = 0, c = 0; i < contentWidth(); i += itemWidth + 10 /*spacing()*/, ++c)
|
||||||
|
{
|
||||||
|
if (pos.x() > (i - itemWidth / 2) && pos.x() <= (i + itemWidth / 2))
|
||||||
|
{
|
||||||
|
internalColumn = c;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (internalColumn == -1)
|
||||||
|
{
|
||||||
|
return qMakePair(nullptr, -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate the internal row
|
||||||
|
int internalRow = -1;
|
||||||
|
{
|
||||||
|
// FIXME rework the drag and drop code
|
||||||
|
const int top = category->verticalPosition();
|
||||||
|
for (int r = 0, h = top; r < category->numRows();
|
||||||
|
h += itemHeightForCategoryRow(category, r), ++r)
|
||||||
|
{
|
||||||
|
if (pos.y() > h && pos.y() < (h + itemHeightForCategoryRow(category, r)))
|
||||||
|
{
|
||||||
|
internalRow = r;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (internalRow == -1)
|
||||||
|
{
|
||||||
|
return qMakePair(nullptr, -1);
|
||||||
|
}
|
||||||
|
// this happens if we're in the margin between a one category and another
|
||||||
|
// categories header
|
||||||
|
if (internalRow > (indices.size() / itemsPerRow()))
|
||||||
|
{
|
||||||
|
return qMakePair(nullptr, -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// flaten the internalColumn/internalRow to one row
|
||||||
|
int categoryRow = internalRow * itemsPerRow() + internalColumn;
|
||||||
|
|
||||||
|
// this is used if we're past the last item
|
||||||
|
if (categoryRow >= indices.size())
|
||||||
|
{
|
||||||
|
return qMakePair(category, indices.last().row() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return qMakePair(category, indices.at(categoryRow).row());
|
||||||
|
}
|
||||||
|
|
||||||
|
QPoint GroupView::offset() const
|
||||||
|
{
|
||||||
|
return QPoint(horizontalOffset(), verticalOffset());
|
||||||
|
}
|
||||||
|
|
||||||
|
QRegion GroupView::visualRegionForSelection(const QItemSelection &selection) const
|
||||||
|
{
|
||||||
|
QRegion region;
|
||||||
|
for (auto &range : selection)
|
||||||
|
{
|
||||||
|
int start_row = range.top();
|
||||||
|
int end_row = range.bottom();
|
||||||
|
for (int row = start_row; row <= end_row; ++row)
|
||||||
|
{
|
||||||
|
int start_column = range.left();
|
||||||
|
int end_column = range.right();
|
||||||
|
for (int column = start_column; column <= end_column; ++column)
|
||||||
|
{
|
||||||
|
QModelIndex index = model()->index(row, column, rootIndex());
|
||||||
|
region += visualRect(index); // OK
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return region;
|
||||||
|
}
|
||||||
|
QModelIndex GroupView::moveCursor(QAbstractItemView::CursorAction cursorAction,
|
||||||
|
Qt::KeyboardModifiers modifiers)
|
||||||
|
{
|
||||||
|
auto current = currentIndex();
|
||||||
|
if(!current.isValid())
|
||||||
|
{
|
||||||
|
qDebug() << "model row: invalid";
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
qDebug() << "model row: " << current.row();
|
||||||
|
auto cat = category(current);
|
||||||
|
int i = m_groups.indexOf(cat);
|
||||||
|
if(i >= 0)
|
||||||
|
{
|
||||||
|
// this is a pile of something foul
|
||||||
|
auto real_group = m_groups[i];
|
||||||
|
int beginning_row = 0;
|
||||||
|
for(auto group: m_groups)
|
||||||
|
{
|
||||||
|
if(group == real_group)
|
||||||
|
break;
|
||||||
|
beginning_row += group->numRows();
|
||||||
|
}
|
||||||
|
qDebug() << "category: " << real_group->text;
|
||||||
|
QPair<int, int> pos = categoryInternalPosition(current);
|
||||||
|
int row = beginning_row + pos.second;
|
||||||
|
qDebug() << "row: " << row;
|
||||||
|
qDebug() << "column: " << pos.first;
|
||||||
|
}
|
||||||
|
return current;
|
||||||
|
}
|
139
depends/groupview/GroupView.h
Normal file
139
depends/groupview/GroupView.h
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QListView>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QScrollBar>
|
||||||
|
|
||||||
|
struct GroupViewRoles
|
||||||
|
{
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
GroupRole = Qt::UserRole,
|
||||||
|
ProgressValueRole,
|
||||||
|
ProgressMaximumRole
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Group;
|
||||||
|
|
||||||
|
class GroupView : public QAbstractItemView
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
GroupView(QWidget *parent = 0);
|
||||||
|
~GroupView();
|
||||||
|
|
||||||
|
QRect geometryRect(const QModelIndex &index) const;
|
||||||
|
virtual QRect visualRect(const QModelIndex &index) const override;
|
||||||
|
QModelIndex indexAt(const QPoint &point) const;
|
||||||
|
void setSelection(const QRect &rect,
|
||||||
|
const QItemSelectionModel::SelectionFlags commands) override;
|
||||||
|
|
||||||
|
virtual int horizontalOffset() const override
|
||||||
|
{
|
||||||
|
return horizontalScrollBar()->value();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual int verticalOffset() const override
|
||||||
|
{
|
||||||
|
return verticalScrollBar()->value();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void scrollContentsBy(int dx, int dy) override
|
||||||
|
{
|
||||||
|
scrollDirtyRegion(dx, dy);
|
||||||
|
viewport()->scroll(dx, dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO!
|
||||||
|
*/
|
||||||
|
virtual void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible) override
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual QModelIndex moveCursor(CursorAction cursorAction,
|
||||||
|
Qt::KeyboardModifiers modifiers) override;
|
||||||
|
|
||||||
|
virtual QRegion visualRegionForSelection(const QItemSelection &selection) const override;
|
||||||
|
|
||||||
|
protected
|
||||||
|
slots:
|
||||||
|
virtual void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight,
|
||||||
|
const QVector<int> &roles) override;
|
||||||
|
virtual void rowsInserted(const QModelIndex &parent, int start, int end) override;
|
||||||
|
virtual void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) override;
|
||||||
|
virtual void updateGeometries() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual bool isIndexHidden(const QModelIndex &index) const override;
|
||||||
|
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;
|
||||||
|
|
||||||
|
void startDrag(Qt::DropActions supportedActions) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend struct Group;
|
||||||
|
|
||||||
|
QList<Group *> m_groups;
|
||||||
|
|
||||||
|
int m_leftMargin;
|
||||||
|
int m_rightMargin;
|
||||||
|
int m_bottomMargin;
|
||||||
|
int m_categoryMargin;
|
||||||
|
|
||||||
|
// bool m_updatesDisabled;
|
||||||
|
|
||||||
|
Group *category(const QModelIndex &index) const;
|
||||||
|
Group *category(const QString &cat) const;
|
||||||
|
Group *categoryAt(const QPoint &pos) const;
|
||||||
|
|
||||||
|
int itemsPerRow() const;
|
||||||
|
int contentWidth() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
int itemWidth() const;
|
||||||
|
int categoryRowHeight(const QModelIndex &index) const;
|
||||||
|
|
||||||
|
/*QLineEdit *m_categoryEditor;
|
||||||
|
Category *m_editedCategory;
|
||||||
|
void startCategoryEditor(Category *category);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void endCategoryEditor();*/
|
||||||
|
|
||||||
|
private: /* variables */
|
||||||
|
QPoint m_pressedPosition;
|
||||||
|
QPersistentModelIndex m_pressedIndex;
|
||||||
|
bool m_pressedAlreadySelected;
|
||||||
|
Group *m_pressedCategory;
|
||||||
|
QItemSelectionModel::SelectionFlag m_ctrlDragSelectionFlag;
|
||||||
|
QPoint m_lastDragPosition;
|
||||||
|
int m_spacing = 5;
|
||||||
|
|
||||||
|
private: /* methods */
|
||||||
|
QPair<int, int> categoryInternalPosition(const QModelIndex &index) const;
|
||||||
|
int categoryInternalRowTop(const QModelIndex &index) const;
|
||||||
|
int itemHeightForCategoryRow(const Group *category, const int internalRow) const;
|
||||||
|
|
||||||
|
QPixmap renderToPixmap(const QModelIndexList &indices, QRect *r) const;
|
||||||
|
QList<QPair<QRect, QModelIndex>> draggablePaintPairs(const QModelIndexList &indices,
|
||||||
|
QRect *r) const;
|
||||||
|
|
||||||
|
bool isDragEventAccepted(QDropEvent *event);
|
||||||
|
|
||||||
|
QPair<Group *, int> rowDropPos(const QPoint &pos);
|
||||||
|
|
||||||
|
QPoint offset() const;
|
||||||
|
};
|
21
depends/groupview/GroupedProxyModel.cpp
Normal file
21
depends/groupview/GroupedProxyModel.cpp
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#include "GroupedProxyModel.h"
|
||||||
|
|
||||||
|
#include "GroupView.h"
|
||||||
|
|
||||||
|
GroupedProxyModel::GroupedProxyModel(QObject *parent) : QSortFilterProxyModel(parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GroupedProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
|
||||||
|
{
|
||||||
|
const QString leftCategory = left.data(GroupViewRoles::GroupRole).toString();
|
||||||
|
const QString rightCategory = right.data(GroupViewRoles::GroupRole).toString();
|
||||||
|
if (leftCategory == rightCategory)
|
||||||
|
{
|
||||||
|
return left.row() < right.row();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return leftCategory < rightCategory;
|
||||||
|
}
|
||||||
|
}
|
14
depends/groupview/GroupedProxyModel.h
Normal file
14
depends/groupview/GroupedProxyModel.h
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QSortFilterProxyModel>
|
||||||
|
|
||||||
|
class GroupedProxyModel : public QSortFilterProxyModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
GroupedProxyModel(QObject *parent = 0);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool lessThan(const QModelIndex &left, const QModelIndex &right) const;
|
||||||
|
};
|
281
depends/groupview/InstanceDelegate.cpp
Normal file
281
depends/groupview/InstanceDelegate.cpp
Normal file
@ -0,0 +1,281 @@
|
|||||||
|
/* 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>
|
||||||
|
|
||||||
|
#include "GroupView.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO this can be made a lot prettier
|
||||||
|
void drawProgressOverlay(QPainter *painter, const QStyleOptionViewItemV4 &option,
|
||||||
|
const int value, const int maximum)
|
||||||
|
{
|
||||||
|
if (maximum == 0 || value == maximum)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
painter->save();
|
||||||
|
|
||||||
|
qreal percent = (qreal)value / (qreal)maximum;
|
||||||
|
QColor color = option.palette.color(QPalette::Dark);
|
||||||
|
color.setAlphaF(0.70f);
|
||||||
|
painter->setBrush(color);
|
||||||
|
painter->setPen(QPen(QBrush(), 0));
|
||||||
|
painter->drawPie(option.rect, 90 * 16, -percent * 360 * 16);
|
||||||
|
|
||||||
|
painter->restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
drawProgressOverlay(painter, opt,
|
||||||
|
index.data(GroupViewRoles::ProgressValueRole).toInt(),
|
||||||
|
index.data(GroupViewRoles::ProgressMaximumRole).toInt());
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
29
depends/groupview/InstanceDelegate.h
Normal file
29
depends/groupview/InstanceDelegate.h
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/* 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;
|
||||||
|
};
|
98
depends/groupview/main.cpp
Normal file
98
depends/groupview/main.cpp
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
#include "main.h"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QStandardItemModel>
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QTime>
|
||||||
|
|
||||||
|
#include "GroupView.h"
|
||||||
|
#include "GroupedProxyModel.h"
|
||||||
|
#include "InstanceDelegate.h"
|
||||||
|
|
||||||
|
Progresser *progresser;
|
||||||
|
|
||||||
|
QPixmap icon(const Qt::GlobalColor color)
|
||||||
|
{
|
||||||
|
QPixmap p = QPixmap(32, 32);
|
||||||
|
p.fill(QColor(color));
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
QPixmap icon(const int number)
|
||||||
|
{
|
||||||
|
QPixmap p = icon(Qt::white);
|
||||||
|
QPainter painter(&p);
|
||||||
|
QFont font = painter.font();
|
||||||
|
font.setBold(true);
|
||||||
|
font.setPixelSize(28);
|
||||||
|
painter.setFont(font);
|
||||||
|
painter.drawText(QRect(QPoint(0, 0), p.size()), Qt::AlignVCenter | Qt::AlignHCenter,
|
||||||
|
QString::number(number));
|
||||||
|
painter.end();
|
||||||
|
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, GroupViewRoles::GroupRole);
|
||||||
|
item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
|
||||||
|
// progresser->addTrackedIndex(item);
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
QStandardItem *createItem(const int index, const QString &category)
|
||||||
|
{
|
||||||
|
QStandardItem *item = new QStandardItem;
|
||||||
|
item->setText(QString("Item #%1").arg(index));
|
||||||
|
item->setData(icon(index), Qt::DecorationRole);
|
||||||
|
item->setData(category, GroupViewRoles::GroupRole);
|
||||||
|
item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
|
||||||
|
// progresser->addTrackedIndex(item);
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
QApplication a(argc, argv);
|
||||||
|
|
||||||
|
qsrand(QTime::currentTime().msec());
|
||||||
|
|
||||||
|
progresser = new Progresser();
|
||||||
|
|
||||||
|
QStandardItemModel model;
|
||||||
|
model.setRowCount(10);
|
||||||
|
model.setColumnCount(1);
|
||||||
|
|
||||||
|
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"));
|
||||||
|
|
||||||
|
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, progresser->addTrackedIndex(createItem(Qt::green, "Green", "")));
|
||||||
|
|
||||||
|
for (int i = 0; i < 20; ++i)
|
||||||
|
{
|
||||||
|
model.setItem(i + 10, createItem(i + 1, "Items 1-20"));
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupedProxyModel pModel;
|
||||||
|
pModel.setSourceModel(&model);
|
||||||
|
|
||||||
|
GroupView w;
|
||||||
|
w.setItemDelegate(new ListViewDelegate);
|
||||||
|
w.setModel(&pModel);
|
||||||
|
w.resize(640, 480);
|
||||||
|
w.show();
|
||||||
|
|
||||||
|
return a.exec();
|
||||||
|
}
|
54
depends/groupview/main.h
Normal file
54
depends/groupview/main.h
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QList>
|
||||||
|
#include <QStandardItem>
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
#include "GroupView.h"
|
||||||
|
|
||||||
|
class Progresser : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit Progresser(QObject *parent = 0) : QObject(parent)
|
||||||
|
{
|
||||||
|
QTimer *timer = new QTimer(this);
|
||||||
|
connect(timer, SIGNAL(timeout()), this, SLOT(timeout()));
|
||||||
|
timer->start(50);
|
||||||
|
}
|
||||||
|
|
||||||
|
QStandardItem *addTrackedIndex(QStandardItem *item)
|
||||||
|
{
|
||||||
|
item->setData(1000, GroupViewRoles::ProgressMaximumRole);
|
||||||
|
m_items.append(item);
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
slots:
|
||||||
|
void timeout()
|
||||||
|
{
|
||||||
|
QList<QStandardItem *> toRemove;
|
||||||
|
for (auto item : m_items)
|
||||||
|
{
|
||||||
|
int maximum = item->data(GroupViewRoles::ProgressMaximumRole).toInt();
|
||||||
|
int value = item->data(GroupViewRoles::ProgressValueRole).toInt();
|
||||||
|
int newvalue = std::min(value + 3, maximum);
|
||||||
|
item->setData(newvalue, GroupViewRoles::ProgressValueRole);
|
||||||
|
|
||||||
|
if(newvalue >= maximum)
|
||||||
|
{
|
||||||
|
toRemove.append(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(auto remove : toRemove)
|
||||||
|
{
|
||||||
|
m_items.removeAll(remove);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
QList<QStandardItem *> m_items;
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user