NOISSUE move everything.
This commit is contained in:
1003
application/groupview/GroupView.cpp
Normal file
1003
application/groupview/GroupView.cpp
Normal file
File diff suppressed because it is too large
Load Diff
125
application/groupview/GroupView.h
Normal file
125
application/groupview/GroupView.h
Normal file
@ -0,0 +1,125 @@
|
||||
#pragma once
|
||||
|
||||
#include <QListView>
|
||||
#include <QLineEdit>
|
||||
#include <QScrollBar>
|
||||
#include <QCache>
|
||||
|
||||
struct GroupViewRoles
|
||||
{
|
||||
enum
|
||||
{
|
||||
GroupRole = Qt::UserRole,
|
||||
ProgressValueRole,
|
||||
ProgressMaximumRole
|
||||
};
|
||||
};
|
||||
|
||||
struct VisualGroup;
|
||||
|
||||
class GroupView : public QAbstractItemView
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GroupView(QWidget *parent = 0);
|
||||
~GroupView();
|
||||
|
||||
void setModel(QAbstractItemModel *model) override;
|
||||
|
||||
/// return geometry rectangle occupied by the specified model item
|
||||
QRect geometryRect(const QModelIndex &index) const;
|
||||
/// return visual rectangle occupied by the specified model item
|
||||
virtual QRect visualRect(const QModelIndex &index) const override;
|
||||
/// get the model index at the specified visual point
|
||||
virtual QModelIndex indexAt(const QPoint &point) const override;
|
||||
void setSelection(const QRect &rect,
|
||||
const QItemSelectionModel::SelectionFlags commands) override;
|
||||
|
||||
virtual int horizontalOffset() const override;
|
||||
virtual int verticalOffset() const override;
|
||||
virtual void scrollContentsBy(int dx, int dy) override;
|
||||
virtual void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible) override;
|
||||
|
||||
virtual QModelIndex moveCursor(CursorAction cursorAction,
|
||||
Qt::KeyboardModifiers modifiers) override;
|
||||
|
||||
virtual QRegion visualRegionForSelection(const QItemSelection &selection) const override;
|
||||
|
||||
int spacing() const
|
||||
{
|
||||
return m_spacing;
|
||||
};
|
||||
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;
|
||||
void modelReset();
|
||||
|
||||
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 VisualGroup;
|
||||
QList<VisualGroup *> m_groups;
|
||||
|
||||
// geometry
|
||||
int m_leftMargin = 5;
|
||||
int m_rightMargin = 5;
|
||||
int m_bottomMargin = 5;
|
||||
int m_categoryMargin = 5;
|
||||
int m_spacing = 5;
|
||||
int m_itemWidth = 100;
|
||||
int m_currentItemsPerRow = -1;
|
||||
int m_currentCursorColumn= -1;
|
||||
mutable QCache<int, QRect> geometryCache;
|
||||
|
||||
// point where the currently active mouse action started in geometry coordinates
|
||||
QPoint m_pressedPosition;
|
||||
QPersistentModelIndex m_pressedIndex;
|
||||
bool m_pressedAlreadySelected;
|
||||
VisualGroup *m_pressedCategory;
|
||||
QItemSelectionModel::SelectionFlag m_ctrlDragSelectionFlag;
|
||||
QPoint m_lastDragPosition;
|
||||
|
||||
VisualGroup *category(const QModelIndex &index) const;
|
||||
VisualGroup *category(const QString &cat) const;
|
||||
VisualGroup *categoryAt(const QPoint &pos) const;
|
||||
|
||||
int itemsPerRow() const
|
||||
{
|
||||
return m_currentItemsPerRow;
|
||||
};
|
||||
int contentWidth() const;
|
||||
|
||||
private: /* methods */
|
||||
int itemWidth() const;
|
||||
int calculateItemsPerRow() const;
|
||||
int verticalScrollToValue(const QModelIndex &index, const QRect &rect,
|
||||
QListView::ScrollHint hint) 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<VisualGroup *, int> rowDropPos(const QPoint &pos);
|
||||
|
||||
QPoint offset() const;
|
||||
};
|
42
application/groupview/GroupedProxyModel.cpp
Normal file
42
application/groupview/GroupedProxyModel.cpp
Normal file
@ -0,0 +1,42 @@
|
||||
#include "GroupedProxyModel.h"
|
||||
|
||||
#include "GroupView.h"
|
||||
#include <QDebug>
|
||||
|
||||
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 subSortLessThan(left, right);
|
||||
}
|
||||
else
|
||||
{
|
||||
// FIXME: real group sorting happens in GroupView::updateGeometries(), see LocaleString
|
||||
auto result = leftCategory.localeAwareCompare(rightCategory);
|
||||
if(result < 0)
|
||||
{
|
||||
qDebug() << leftCategory << "<" << rightCategory;
|
||||
}
|
||||
if(result == 0)
|
||||
{
|
||||
qDebug() << leftCategory << "=" << rightCategory;
|
||||
return subSortLessThan(left, right);
|
||||
}
|
||||
if(result > 0)
|
||||
{
|
||||
qDebug() << leftCategory << ">" << rightCategory;
|
||||
}
|
||||
return result < 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool GroupedProxyModel::subSortLessThan(const QModelIndex &left, const QModelIndex &right) const
|
||||
{
|
||||
return left.row() < right.row();
|
||||
}
|
15
application/groupview/GroupedProxyModel.h
Normal file
15
application/groupview/GroupedProxyModel.h
Normal file
@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
class GroupedProxyModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GroupedProxyModel(QObject *parent = 0);
|
||||
|
||||
protected:
|
||||
virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
|
||||
virtual bool subSortLessThan(const QModelIndex &left, const QModelIndex &right) const;
|
||||
};
|
362
application/groupview/InstanceDelegate.cpp
Normal file
362
application/groupview/InstanceDelegate.cpp
Normal file
@ -0,0 +1,362 @@
|
||||
/* Copyright 2013-2015 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 <QtMath>
|
||||
|
||||
#include "GroupView.h"
|
||||
#include "BaseInstance.h"
|
||||
#include "InstanceList.h"
|
||||
|
||||
QCache<QString, QPixmap> ListViewDelegate::m_pixmapCache;
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
void drawBadges(QPainter *painter, const QStyleOptionViewItemV4 &option, BaseInstance *instance)
|
||||
{
|
||||
QList<QString> pixmaps;
|
||||
const BaseInstance::InstanceFlags flags = instance->flags();
|
||||
if (flags & BaseInstance::VersionBrokenFlag)
|
||||
{
|
||||
pixmaps.append("broken");
|
||||
}
|
||||
if (flags & BaseInstance::UpdateAvailable)
|
||||
{
|
||||
pixmaps.append("updateavailable");
|
||||
}
|
||||
|
||||
// begin easter eggs
|
||||
if (instance->name().contains("btw", Qt::CaseInsensitive) ||
|
||||
instance->name().contains("better then wolves", Qt::CaseInsensitive) ||
|
||||
instance->name().contains("better than wolves", Qt::CaseInsensitive))
|
||||
{
|
||||
pixmaps.append("herobrine");
|
||||
}
|
||||
if (instance->name().contains("direwolf", Qt::CaseInsensitive))
|
||||
{
|
||||
pixmaps.append("enderman");
|
||||
}
|
||||
if (instance->name().contains("kitten", Qt::CaseInsensitive))
|
||||
{
|
||||
pixmaps.append("kitten");
|
||||
}
|
||||
if (instance->name().contains("derp", Qt::CaseInsensitive))
|
||||
{
|
||||
pixmaps.append("derp");
|
||||
}
|
||||
// end easter eggs
|
||||
|
||||
static const int itemSide = 24;
|
||||
static const int spacing = 1;
|
||||
const int itemsPerRow = qMax(1, qFloor(double(option.rect.width() + spacing) / double(itemSide + spacing)));
|
||||
const int rows = qCeil((double)pixmaps.size() / (double)itemsPerRow);
|
||||
QListIterator<QString> it(pixmaps);
|
||||
painter->translate(option.rect.topLeft());
|
||||
for (int y = 0; y < rows; ++y)
|
||||
{
|
||||
for (int x = 0; x < itemsPerRow; ++x)
|
||||
{
|
||||
if (!it.hasNext())
|
||||
{
|
||||
return;
|
||||
}
|
||||
const QPixmap pixmap = ListViewDelegate::requestBadgePixmap(it.next()).scaled(
|
||||
itemSide, itemSide, Qt::KeepAspectRatio, Qt::FastTransformation);
|
||||
painter->drawPixmap(option.rect.width() - x * itemSide + qMax(x - 1, 0) * spacing - itemSide,
|
||||
y * itemSide + qMax(y - 1, 0) * spacing, itemSide, itemSide,
|
||||
pixmap);
|
||||
}
|
||||
}
|
||||
painter->translate(-option.rect.topLeft());
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
drawSelectionRect(painter, opt2, textHighlightRect);
|
||||
|
||||
/*
|
||||
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);
|
||||
}
|
||||
|
||||
// FIXME: this really has no business of being here. Make generic.
|
||||
auto instance = (BaseInstance*)index.data(InstanceList::InstancePointerRole)
|
||||
.value<void *>();
|
||||
if (instance)
|
||||
{
|
||||
drawBadges(painter, opt, instance);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
QPixmap ListViewDelegate::requestBadgePixmap(const QString &key)
|
||||
{
|
||||
if (!m_pixmapCache.contains(key))
|
||||
{
|
||||
m_pixmapCache.insert(key, new QPixmap(":/icons/badges/" + key + ".png"));
|
||||
}
|
||||
return *m_pixmapCache.object(key);
|
||||
}
|
35
application/groupview/InstanceDelegate.h
Normal file
35
application/groupview/InstanceDelegate.h
Normal file
@ -0,0 +1,35 @@
|
||||
/* Copyright 2013-2015 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>
|
||||
#include <QCache>
|
||||
|
||||
class ListViewDelegate : public QStyledItemDelegate
|
||||
{
|
||||
public:
|
||||
explicit ListViewDelegate(QObject *parent = 0);
|
||||
|
||||
static QPixmap requestBadgePixmap(const QString &key);
|
||||
|
||||
protected:
|
||||
void paint(QPainter *painter, const QStyleOptionViewItem &option,
|
||||
const QModelIndex &index) const;
|
||||
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const;
|
||||
|
||||
private:
|
||||
static QCache<QString, QPixmap> m_pixmapCache;
|
||||
};
|
301
application/groupview/VisualGroup.cpp
Normal file
301
application/groupview/VisualGroup.cpp
Normal file
@ -0,0 +1,301 @@
|
||||
#include "VisualGroup.h"
|
||||
|
||||
#include <QModelIndex>
|
||||
#include <QPainter>
|
||||
#include <QtMath>
|
||||
#include <QApplication>
|
||||
|
||||
#include "GroupView.h"
|
||||
|
||||
VisualGroup::VisualGroup(const QString &text, GroupView *view) : view(view), text(text), collapsed(false)
|
||||
{
|
||||
}
|
||||
|
||||
VisualGroup::VisualGroup(const VisualGroup *other)
|
||||
: view(other->view), text(other->text), collapsed(other->collapsed)
|
||||
{
|
||||
}
|
||||
|
||||
void VisualGroup::update()
|
||||
{
|
||||
auto temp_items = items();
|
||||
auto itemsPerRow = view->itemsPerRow();
|
||||
|
||||
int numRows = qMax(1, qCeil((qreal)temp_items.size() / (qreal)itemsPerRow));
|
||||
rows = QVector<VisualRow>(numRows);
|
||||
|
||||
int maxRowHeight = 0;
|
||||
int positionInRow = 0;
|
||||
int currentRow = 0;
|
||||
int offsetFromTop = 0;
|
||||
for (auto item: temp_items)
|
||||
{
|
||||
if(positionInRow == itemsPerRow)
|
||||
{
|
||||
rows[currentRow].height = maxRowHeight;
|
||||
rows[currentRow].top = offsetFromTop;
|
||||
currentRow ++;
|
||||
offsetFromTop += maxRowHeight + 5;
|
||||
positionInRow = 0;
|
||||
maxRowHeight = 0;
|
||||
}
|
||||
auto itemHeight = view->itemDelegate()->sizeHint(view->viewOptions(), item).height();
|
||||
if(itemHeight > maxRowHeight)
|
||||
{
|
||||
maxRowHeight = itemHeight;
|
||||
}
|
||||
rows[currentRow].items.append(item);
|
||||
positionInRow++;
|
||||
}
|
||||
rows[currentRow].height = maxRowHeight;
|
||||
rows[currentRow].top = offsetFromTop;
|
||||
}
|
||||
|
||||
QPair<int, int> VisualGroup::positionOf(const QModelIndex &index) const
|
||||
{
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
for (auto & row: rows)
|
||||
{
|
||||
for(auto x = 0; x < row.items.size(); x++)
|
||||
{
|
||||
if(row.items[x] == index)
|
||||
{
|
||||
return qMakePair(x,y);
|
||||
}
|
||||
}
|
||||
y++;
|
||||
}
|
||||
return qMakePair(x, y);
|
||||
}
|
||||
|
||||
int VisualGroup::rowTopOf(const QModelIndex &index) const
|
||||
{
|
||||
auto position = positionOf(index);
|
||||
return rows[position.second].top;
|
||||
}
|
||||
|
||||
int VisualGroup::rowHeightOf(const QModelIndex &index) const
|
||||
{
|
||||
auto position = positionOf(index);
|
||||
return rows[position.second].height;
|
||||
}
|
||||
|
||||
VisualGroup::HitResults VisualGroup::hitScan(const QPoint &pos) const
|
||||
{
|
||||
VisualGroup::HitResults results = VisualGroup::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 = VisualGroup::NoHit;
|
||||
}
|
||||
else if (y < body_start)
|
||||
{
|
||||
results = VisualGroup::HeaderHit;
|
||||
int collapseSize = headerHeight() - 4;
|
||||
|
||||
// the icon
|
||||
QRect iconRect = QRect(view->m_leftMargin + 2, 2 + y_start, collapseSize, collapseSize);
|
||||
if (iconRect.contains(pos))
|
||||
{
|
||||
results |= VisualGroup::CheckboxHit;
|
||||
}
|
||||
}
|
||||
else if (y < body_end)
|
||||
{
|
||||
results |= VisualGroup::BodyHit;
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
void VisualGroup::drawHeader(QPainter *painter, const QStyleOptionViewItem &option)
|
||||
{
|
||||
painter->setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
const QRect optRect = option.rect;
|
||||
QFont font(QApplication::font());
|
||||
font.setBold(true);
|
||||
const QFontMetrics fontMetrics = QFontMetrics(font);
|
||||
|
||||
QColor outlineColor = option.palette.text().color();
|
||||
outlineColor.setAlphaF(0.35);
|
||||
|
||||
//BEGIN: top left corner
|
||||
{
|
||||
painter->save();
|
||||
painter->setPen(outlineColor);
|
||||
const QPointF topLeft(optRect.topLeft());
|
||||
QRectF arc(topLeft, QSizeF(4, 4));
|
||||
arc.translate(0.5, 0.5);
|
||||
painter->drawArc(arc, 1440, 1440);
|
||||
painter->restore();
|
||||
}
|
||||
//END: top left corner
|
||||
|
||||
//BEGIN: left vertical line
|
||||
{
|
||||
QPoint start(optRect.topLeft());
|
||||
start.ry() += 3;
|
||||
QPoint verticalGradBottom(optRect.topLeft());
|
||||
verticalGradBottom.ry() += fontMetrics.height() + 5;
|
||||
QLinearGradient gradient(start, verticalGradBottom);
|
||||
gradient.setColorAt(0, outlineColor);
|
||||
gradient.setColorAt(1, Qt::transparent);
|
||||
painter->fillRect(QRect(start, QSize(1, fontMetrics.height() + 5)), gradient);
|
||||
}
|
||||
//END: left vertical line
|
||||
|
||||
//BEGIN: horizontal line
|
||||
{
|
||||
QPoint start(optRect.topLeft());
|
||||
start.rx() += 3;
|
||||
QPoint horizontalGradTop(optRect.topLeft());
|
||||
horizontalGradTop.rx() += optRect.width() - 6;
|
||||
painter->fillRect(QRect(start, QSize(optRect.width() - 6, 1)), outlineColor);
|
||||
}
|
||||
//END: horizontal line
|
||||
|
||||
//BEGIN: top right corner
|
||||
{
|
||||
painter->save();
|
||||
painter->setPen(outlineColor);
|
||||
QPointF topRight(optRect.topRight());
|
||||
topRight.rx() -= 4;
|
||||
QRectF arc(topRight, QSizeF(4, 4));
|
||||
arc.translate(0.5, 0.5);
|
||||
painter->drawArc(arc, 0, 1440);
|
||||
painter->restore();
|
||||
}
|
||||
//END: top right corner
|
||||
|
||||
//BEGIN: right vertical line
|
||||
{
|
||||
QPoint start(optRect.topRight());
|
||||
start.ry() += 3;
|
||||
QPoint verticalGradBottom(optRect.topRight());
|
||||
verticalGradBottom.ry() += fontMetrics.height() + 5;
|
||||
QLinearGradient gradient(start, verticalGradBottom);
|
||||
gradient.setColorAt(0, outlineColor);
|
||||
gradient.setColorAt(1, Qt::transparent);
|
||||
painter->fillRect(QRect(start, QSize(1, fontMetrics.height() + 5)), gradient);
|
||||
}
|
||||
//END: right vertical line
|
||||
|
||||
//BEGIN: checkboxy thing
|
||||
{
|
||||
painter->save();
|
||||
painter->setRenderHint(QPainter::Antialiasing, false);
|
||||
painter->setFont(font);
|
||||
QColor penColor(option.palette.text().color());
|
||||
penColor.setAlphaF(0.6);
|
||||
painter->setPen(penColor);
|
||||
QRect iconSubRect(option.rect);
|
||||
iconSubRect.setTop(iconSubRect.top() + 7);
|
||||
iconSubRect.setLeft(iconSubRect.left() + 7);
|
||||
|
||||
int sizing = fontMetrics.height();
|
||||
int even = ( (sizing - 1) % 2 );
|
||||
|
||||
iconSubRect.setHeight(sizing - even);
|
||||
iconSubRect.setWidth(sizing - even);
|
||||
painter->drawRect(iconSubRect);
|
||||
|
||||
|
||||
/*
|
||||
if(collapsed)
|
||||
painter->drawText(iconSubRect, Qt::AlignHCenter | Qt::AlignVCenter, "+");
|
||||
else
|
||||
painter->drawText(iconSubRect, Qt::AlignHCenter | Qt::AlignVCenter, "-");
|
||||
*/
|
||||
painter->setBrush(option.palette.text());
|
||||
painter->fillRect(iconSubRect.x(), iconSubRect.y() + iconSubRect.height() / 2,
|
||||
iconSubRect.width(), 2, penColor);
|
||||
if (collapsed)
|
||||
{
|
||||
painter->fillRect(iconSubRect.x() + iconSubRect.width() / 2, iconSubRect.y(), 2,
|
||||
iconSubRect.height(), penColor);
|
||||
}
|
||||
|
||||
painter->restore();
|
||||
}
|
||||
//END: checkboxy thing
|
||||
|
||||
//BEGIN: text
|
||||
{
|
||||
QRect textRect(option.rect);
|
||||
textRect.setTop(textRect.top() + 7);
|
||||
textRect.setLeft(textRect.left() + 7 + fontMetrics.height() + 7);
|
||||
textRect.setHeight(fontMetrics.height());
|
||||
textRect.setRight(textRect.right() - 7);
|
||||
|
||||
painter->save();
|
||||
painter->setFont(font);
|
||||
QColor penColor(option.palette.text().color());
|
||||
penColor.setAlphaF(0.6);
|
||||
painter->setPen(penColor);
|
||||
painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, text);
|
||||
painter->restore();
|
||||
}
|
||||
//END: text
|
||||
}
|
||||
|
||||
int VisualGroup::totalHeight() const
|
||||
{
|
||||
return headerHeight() + 5 + contentHeight(); // FIXME: wtf is that '5'?
|
||||
}
|
||||
|
||||
int VisualGroup::headerHeight() const
|
||||
{
|
||||
QFont font(QApplication::font());
|
||||
font.setBold(true);
|
||||
QFontMetrics fontMetrics(font);
|
||||
|
||||
const int height = fontMetrics.height() + 1 /* 1 pixel-width gradient */
|
||||
+ 11 /* top and bottom separation */;
|
||||
return height;
|
||||
/*
|
||||
int raw = view->viewport()->fontMetrics().height() + 4;
|
||||
// add english. maybe. depends on font height.
|
||||
if (raw % 2 == 0)
|
||||
raw++;
|
||||
return std::min(raw, 25);
|
||||
*/
|
||||
}
|
||||
|
||||
int VisualGroup::contentHeight() const
|
||||
{
|
||||
if (collapsed)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
auto last = rows[numRows() - 1];
|
||||
return last.top + last.height;
|
||||
}
|
||||
|
||||
int VisualGroup::numRows() const
|
||||
{
|
||||
return rows.size();
|
||||
}
|
||||
|
||||
int VisualGroup::verticalPosition() const
|
||||
{
|
||||
return m_verticalPosition;
|
||||
}
|
||||
|
||||
QList<QModelIndex> VisualGroup::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;
|
||||
}
|
91
application/groupview/VisualGroup.h
Normal file
91
application/groupview/VisualGroup.h
Normal file
@ -0,0 +1,91 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QRect>
|
||||
#include <QVector>
|
||||
#include <QStyleOption>
|
||||
|
||||
class GroupView;
|
||||
class QPainter;
|
||||
class QModelIndex;
|
||||
|
||||
struct VisualRow
|
||||
{
|
||||
QList<QModelIndex> items;
|
||||
int height = 0;
|
||||
int top = 0;
|
||||
inline int size() const
|
||||
{
|
||||
return items.size();
|
||||
}
|
||||
inline QModelIndex &operator[](int i)
|
||||
{
|
||||
return items[i];
|
||||
}
|
||||
};
|
||||
|
||||
struct VisualGroup
|
||||
{
|
||||
/* constructors */
|
||||
VisualGroup(const QString &text, GroupView *view);
|
||||
VisualGroup(const VisualGroup *other);
|
||||
|
||||
/* data */
|
||||
GroupView *view = nullptr;
|
||||
QString text;
|
||||
bool collapsed = false;
|
||||
QVector<VisualRow> rows;
|
||||
int firstItemIndex = 0;
|
||||
int m_verticalPosition = 0;
|
||||
|
||||
/* logic */
|
||||
/// update the internal list of items and flow them into the rows.
|
||||
void update();
|
||||
|
||||
/// draw the header at y-position.
|
||||
void drawHeader(QPainter *painter, const QStyleOptionViewItem &option);
|
||||
|
||||
/// 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;
|
||||
|
||||
/// actually calculate the above value
|
||||
int calculateNumRows() const;
|
||||
|
||||
/// the height at which this group starts, in pixels
|
||||
int verticalPosition() const;
|
||||
|
||||
/// relative geometry - top of the row of the given item
|
||||
int rowTopOf(const QModelIndex &index) const;
|
||||
|
||||
/// height of the row of the given item
|
||||
int rowHeightOf(const QModelIndex &index) const;
|
||||
|
||||
/// x/y position of the given item inside the group (in items!)
|
||||
QPair<int, int> positionOf(const QModelIndex &index) 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;
|
||||
|
||||
QList<QModelIndex> items() const;
|
||||
};
|
||||
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(VisualGroup::HitResults)
|
Reference in New Issue
Block a user