refactor: switch instance view to QTableView

Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
This commit is contained in:
Sefa Eyeoglu 2022-09-30 23:37:00 +02:00
parent d0e668e1d8
commit 493a951ecb
No known key found for this signature in database
GPG Key ID: C10411294912A422
18 changed files with 228 additions and 3219 deletions

View File

@ -41,8 +41,6 @@
#include "ui/MainWindow.h"
#include "ui/InstanceWindow.h"
#include "ui/instanceview/AccessibleInstanceView.h"
#include "ui/pages/BasePageProvider.h"
#include "ui/pages/global/LauncherPage.h"
#include "ui/pages/global/MinecraftPage.h"
@ -695,10 +693,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
qDebug() << "<> Settings loaded.";
}
#ifndef QT_NO_ACCESSIBILITY
QAccessible::installFactory(groupViewAccessibleFactory);
#endif /* !QT_NO_ACCESSIBILITY */
// initialize network access and proxy setup
{
m_network = new QNetworkAccessManager();

View File

@ -872,16 +872,7 @@ SET(LAUNCHER_SOURCES
# GUI - instance group view
ui/instanceview/InstanceProxyModel.cpp
ui/instanceview/InstanceProxyModel.h
ui/instanceview/AccessibleInstanceView.cpp
ui/instanceview/AccessibleInstanceView.h
ui/instanceview/AccessibleInstanceView_p.h
ui/instanceview/InstanceView.cpp
ui/instanceview/InstanceView.h
ui/instanceview/InstanceDelegate.cpp
ui/instanceview/InstanceDelegate.h
ui/instanceview/VisualGroup.cpp
ui/instanceview/VisualGroup.h
)
ui/instanceview/InstanceView.cpp ui/instanceview/InstanceView.h)
if(WIN32)
set(LAUNCHER_SOURCES

View File

@ -33,7 +33,6 @@
* limitations under the License.
*/
#include <QDebug>
#include <QDir>
#include <QDirIterator>
#include <QFile>
@ -42,13 +41,10 @@
#include <QJsonDocument>
#include <QMimeData>
#include <QSet>
#include <QStack>
#include <QPair>
#include <QTextStream>
#include <QThread>
#include <QTimer>
#include <QUuid>
#include <QXmlStreamReader>
#include "BaseInstance.h"
#include "ExponentialSeries.h"
@ -59,6 +55,7 @@
#include "WatchLock.h"
#include "minecraft/MinecraftInstance.h"
#include "settings/INISettingsObject.h"
#include "MMCTime.h"
#ifdef Q_OS_WIN32
#include <Windows.h>
@ -67,7 +64,7 @@
const static int GROUP_FILE_FORMAT_VERSION = 1;
InstanceList::InstanceList(SettingsObjectPtr settings, const QString& instDir, QObject* parent)
: QAbstractListModel(parent), m_globalSettings(settings)
: QAbstractTableModel(parent), m_globalSettings(settings)
{
resumeWatch();
// Create aand normalize path
@ -114,14 +111,14 @@ bool InstanceList::dropMimeData(const QMimeData* data, Qt::DropAction action, in
QStringList InstanceList::mimeTypes() const
{
auto types = QAbstractListModel::mimeTypes();
auto types = QAbstractTableModel::mimeTypes();
types.push_back("application/x-instanceid");
return types;
}
QMimeData* InstanceList::mimeData(const QModelIndexList& indexes) const
{
auto mimeData = QAbstractListModel::mimeData(indexes);
auto mimeData = QAbstractTableModel::mimeData(indexes);
if (indexes.size() == 1) {
auto instanceId = data(indexes[0], InstanceIDRole).toString();
mimeData->setData("application/x-instanceid", instanceId.toUtf8());
@ -135,55 +132,63 @@ int InstanceList::rowCount(const QModelIndex& parent) const
return m_instances.count();
}
QModelIndex InstanceList::index(int row, int column, const QModelIndex& parent) const
{
Q_UNUSED(parent);
if (row < 0 || row >= m_instances.size())
return QModelIndex();
return createIndex(row, column, (void*)m_instances.at(row).get());
QVariant InstanceList::headerData(int section, Qt::Orientation orientation, int role) const {
if (role != Qt::DisplayRole) {
return QVariant();
}
switch(static_cast<Column>(section)) {
case Icon: return tr("Icon");
case Name: return tr("Name");
case GameVersion: return tr("Game Version");
case LastPlayed: return tr("Last played");
case PlayTime: return tr("Play time");
default: return QVariant();
}
}
QVariant InstanceList::data(const QModelIndex& index, int role) const
{
QVariant InstanceList::data(const QModelIndex& index, int role) const {
if (!index.isValid()) {
return QVariant();
}
BaseInstance *pdata = static_cast<BaseInstance *>(index.internalPointer());
switch (role)
{
case InstancePointerRole:
{
QVariant v = QVariant::fromValue((void *)pdata);
return v;
}
case InstanceIDRole:
{
return pdata->id();
}
case Qt::EditRole:
case Qt::DisplayRole:
{
return pdata->name();
}
case Qt::AccessibleTextRole:
{
return tr("%1 Instance").arg(pdata->name());
}
case Qt::ToolTipRole:
{
return pdata->instanceRoot();
}
case Qt::DecorationRole:
{
return pdata->iconKey();
}
// HACK: see InstanceView.h in gui!
case GroupRole:
{
return getInstanceGroup(pdata->id());
}
default:
break;
const InstancePtr inst = m_instances[index.row()];
switch (static_cast<Column>(index.column())) {
case Icon:
if (role == Qt::DecorationRole) {
return inst->iconKey();
}
break;
case Name:
if (role == Qt::DisplayRole || role == Qt::EditRole)
return inst->name();
if (role == Qt::AccessibleTextRole)
return tr("%1 Instance").arg(inst->name());
if (role == Qt::ToolTipRole)
return inst->instanceRoot();
break;
case GameVersion: {
if (role == Qt::DisplayRole)
return "Minecraft <something>";
}
case LastPlayed: {
QString foo = Time::prettifyDuration(inst->lastTimePlayed());
QDateTime bar = QDateTime::fromMSecsSinceEpoch(inst->lastLaunch());
if (role == Qt::DisplayRole)
return bar;
if (role == Qt::ToolTipRole)
return tr("Last played for %1").arg(foo);
break;
}
case PlayTime: {
QString foo = Time::prettifyDuration(inst->totalTimePlayed());
if (role == Qt::DisplayRole)
return foo;
if (role == Qt::ToolTipRole)
return tr("Total played for %1").arg(foo);
break;
}
default:
break;
}
return QVariant();
}
@ -196,20 +201,28 @@ bool InstanceList::setData(const QModelIndex& index, const QVariant& value, int
if (role != Qt::EditRole) {
return false;
}
BaseInstance* pdata = static_cast<BaseInstance*>(index.internalPointer());
InstancePtr inst = m_instances.at(index.row());
auto newName = value.toString();
if (pdata->name() == newName) {
if (inst->name() == newName) {
return true;
}
pdata->setName(newName);
return true;
inst->setName(newName);
return true;
}
int InstanceList::columnCount(const QModelIndex &parent) const
{
return ColumnCount;
}
Qt::ItemFlags InstanceList::flags(const QModelIndex& index) const
{
Qt::ItemFlags f;
if (index.isValid()) {
f |= (Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
f |= (Qt::ItemIsEnabled | Qt::ItemIsSelectable);
if (index.column() == Name) {
f |= Qt::ItemIsEditable; // FIXME: bad UX! User can only use rename, if they selected the name column
}
}
return f;
}
@ -250,7 +263,7 @@ void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name)
if (changed) {
m_groupNameCache.insert(name);
auto idx = getInstIndex(inst.get());
emit dataChanged(index(idx), index(idx), { GroupRole });
emit dataChanged(index(idx, Name), index(idx, Name), { GroupRole });
saveGroupList();
}
}
@ -273,7 +286,7 @@ void InstanceList::deleteGroup(const QString& name)
removed = true;
auto idx = getInstIndex(instance.get());
if (idx > 0) {
emit dataChanged(index(idx), index(idx), { GroupRole });
emit dataChanged(index(idx, Name), index(idx, Name), { GroupRole });
}
}
}
@ -311,7 +324,7 @@ bool InstanceList::trashInstance(const InstanceId& id)
qDebug() << "Instance" << id << "has been trashed by the launcher.";
m_trashHistory.push({id, inst->instanceRoot(), trashedLoc, cachedGroupId});
return true;
}
@ -550,7 +563,7 @@ InstancePtr InstanceList::getInstanceByManagedName(const QString& managed_name)
QModelIndex InstanceList::getInstanceIndexById(const QString &id) const
{
return index(getInstIndex(getInstanceById(id).get()));
return index(getInstIndex(getInstanceById(id).get()), Name);
}
int InstanceList::getInstIndex(BaseInstance* inst) const
@ -568,7 +581,7 @@ void InstanceList::propertiesChanged(BaseInstance* inst)
{
int i = getInstIndex(inst);
if (i != -1) {
emit dataChanged(index(i), index(i));
emit dataChanged(index(i, Icon), index(i, ColumnCount - 1));
updateTotalPlayTime();
}
}

View File

@ -16,7 +16,7 @@
#pragma once
#include <QObject>
#include <QAbstractListModel>
#include <QAbstractTableModel>
#include <QSet>
#include <QList>
#include <QStack>
@ -55,7 +55,7 @@ struct TrashHistoryItem {
QString groupName;
};
class InstanceList : public QAbstractListModel
class InstanceList : public QAbstractTableModel
{
Q_OBJECT
@ -64,12 +64,25 @@ public:
virtual ~InstanceList();
public:
QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
QVariant data(const QModelIndex &index, int role) const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
bool setData(const QModelIndex & index, const QVariant & value, int role) override;
bool setData(const QModelIndex & index, const QVariant & value, int role) override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
enum Column
{
Icon = 0,
Name,
GameVersion,
LastPlayed,
PlayTime,
ColumnCount
};
enum AdditionalRoles
{
@ -152,8 +165,7 @@ public:
bool dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent) override;
QStringList mimeTypes() const override;
QMimeData *mimeData(const QModelIndexList &indexes) const override;
QMimeData *mimeData(const QModelIndexList &indexes) const override;
signals:
void dataIsInvalid();
void instancesChanged();

View File

@ -90,9 +90,7 @@
#include "JavaCommon.h"
#include "LaunchController.h"
#include "ui/instanceview/InstanceProxyModel.h"
#include "ui/instanceview/InstanceView.h"
#include "ui/instanceview/InstanceDelegate.h"
#include "ui/widgets/LabeledToolButton.h"
#include "ui/dialogs/NewInstanceDialog.h"
#include "ui/dialogs/NewsDialog.h"
@ -872,30 +870,12 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow
// Create the instance list widget
{
view = new InstanceView(ui->centralWidget);
view->setSelectionMode(QAbstractItemView::SingleSelection);
// FIXME: leaks ListViewDelegate
view->setItemDelegate(new ListViewDelegate(this));
view->setFrameShape(QFrame::NoFrame);
// do not show ugly blue border on the mac
view->setAttribute(Qt::WA_MacShowFocusRect, false);
view = new InstanceView(ui->centralWidget, APPLICATION->instances().get());
view->installEventFilter(this);
view->setContextMenuPolicy(Qt::CustomContextMenu);
connect(view, &QWidget::customContextMenuRequested, this, &MainWindow::showInstanceContextMenu);
connect(view, &InstanceView::droppedURLs, this, &MainWindow::droppedURLs, Qt::QueuedConnection);
proxymodel = new InstanceProxyModel(this);
proxymodel->setSourceModel(APPLICATION->instances().get());
proxymodel->sort(0);
connect(proxymodel, &InstanceProxyModel::dataChanged, this, &MainWindow::instanceDataChanged);
view->setModel(proxymodel);
view->setSourceOfGroupCollapseStatus([](const QString & groupName)->bool {
return APPLICATION->instances()->isGroupCollapsed(groupName);
});
connect(view, &InstanceView::groupStateChanged, APPLICATION->instances().get(), &InstanceList::on_GroupStateChanged);
ui->horizontalLayout->addWidget(view);
}
// The cat background
@ -907,10 +887,10 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow
setCatBackground(cat_enable);
}
// start instance when double-clicked
connect(view, &InstanceView::activated, this, &MainWindow::instanceActivated);
connect(view, &InstanceView::instanceActivated, this, &MainWindow::instanceActivated);
// track the selection -- update the instance toolbar
connect(view->selectionModel(), &QItemSelectionModel::currentChanged, this, &MainWindow::instanceChanged);
connect(view, &InstanceView::currentInstanceChanged, this, &MainWindow::instanceChanged);
// track icon changes and update the toolbar!
connect(APPLICATION->icons().get(), &IconList::iconUpdated, this, &MainWindow::iconUpdated);
@ -1074,7 +1054,7 @@ void MainWindow::showInstanceContextMenu(const QPoint &pos)
QAction *actionSep = new QAction("", this);
actionSep->setSeparator(true);
bool onInstance = view->indexAt(pos).isValid();
bool onInstance = view->currentView()->indexAt(pos).isValid();
if (onInstance)
{
actions = ui->instanceToolBar->actions();
@ -1091,42 +1071,6 @@ void MainWindow::showInstanceContextMenu(const QPoint &pos)
actionVoid->setEnabled(false);
actions.prepend(actionVoid);
}
else
{
auto group = view->groupNameAt(pos);
QAction *actionVoid = new QAction(BuildConfig.LAUNCHER_DISPLAYNAME, this);
actionVoid->setEnabled(false);
QAction *actionCreateInstance = new QAction(tr("Create instance"), this);
actionCreateInstance->setToolTip(ui->actionAddInstance->toolTip());
if(!group.isNull())
{
QVariantMap data;
data["group"] = group;
actionCreateInstance->setData(data);
}
connect(actionCreateInstance, SIGNAL(triggered(bool)), SLOT(on_actionAddInstance_triggered()));
actions.prepend(actionSep);
actions.prepend(actionVoid);
actions.append(actionCreateInstance);
if(!group.isNull())
{
QAction *actionDeleteGroup = new QAction(tr("Delete group '%1'").arg(group), this);
QVariantMap data;
data["group"] = group;
actionDeleteGroup->setData(data);
connect(actionDeleteGroup, SIGNAL(triggered(bool)), SLOT(deleteGroup()));
actions.append(actionDeleteGroup);
}
QAction *actionUndoTrashInstance = new QAction("Undo last trash instance", this);
connect(actionUndoTrashInstance, SIGNAL(triggered(bool)), SLOT(undoTrashInstance()));
actionUndoTrashInstance->setEnabled(APPLICATION->instances()->trashedSomething());
actions.append(actionUndoTrashInstance);
}
QMenu myMenu;
myMenu.addActions(actions);
/*
@ -1626,7 +1570,6 @@ void MainWindow::on_actionCopyInstance_triggered()
void MainWindow::finalizeInstance(InstancePtr inst)
{
view->updateGeometries();
setSelectedInstanceById(inst->id());
if (APPLICATION->accounts()->anyAccountIsValid())
{
@ -1763,15 +1706,7 @@ void MainWindow::updateInstanceToolIcon(QString new_icon)
void MainWindow::setSelectedInstanceById(const QString &id)
{
if (id.isNull())
return;
const QModelIndex index = APPLICATION->instances()->getInstanceIndexById(id);
if (index.isValid())
{
QModelIndex selectionIndex = proxymodel->mapFromSource(index);
view->selectionModel()->setCurrentIndex(selectionIndex, QItemSelectionModel::ClearAndSelect);
updateStatusCenter();
}
//TODO
}
void MainWindow::on_actionChangeInstGroup_triggered()
@ -1861,8 +1796,8 @@ void MainWindow::globalSettingsClosed()
{
// FIXME: quick HACK to make this work. improve, optimize.
APPLICATION->instances()->loadList();
proxymodel->invalidate();
proxymodel->sort(0);
//proxymodel->invalidate(); // TODO!
//proxymodel->sort(0);
updateMainToolBar();
updateToolsMenu();
updateStatusCenter();
@ -1957,7 +1892,7 @@ void MainWindow::on_actionRenameInstance_triggered()
{
if (m_selectedInstance)
{
view->edit(view->currentIndex());
// FIXME
}
}
@ -1988,12 +1923,8 @@ void MainWindow::changeEvent(QEvent* event)
QMainWindow::changeEvent(event);
}
void MainWindow::instanceActivated(QModelIndex index)
void MainWindow::instanceActivated(InstancePtr inst)
{
if (!index.isValid())
return;
QString id = index.data(InstanceList::InstanceIDRole).toString();
InstancePtr inst = APPLICATION->instances()->getInstanceById(id);
if (!inst)
return;
@ -2053,9 +1984,9 @@ void MainWindow::startTask(Task *task)
task->start();
}
void MainWindow::instanceChanged(const QModelIndex &current, const QModelIndex &previous)
void MainWindow::instanceChanged(InstancePtr current, InstancePtr previous)
{
if (!current.isValid())
if (!current)
{
APPLICATION->settings()->set("SelectedInstance", QString());
selectionBad();
@ -2064,8 +1995,7 @@ void MainWindow::instanceChanged(const QModelIndex &current, const QModelIndex &
if (m_selectedInstance) {
disconnect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::refreshCurrentInstance);
}
QString id = current.data(InstanceList::InstanceIDRole).toString();
m_selectedInstance = APPLICATION->instances()->getInstanceById(id);
m_selectedInstance = current;
if (m_selectedInstance)
{
ui->instanceToolBar->setEnabled(true);
@ -2112,16 +2042,6 @@ void MainWindow::instanceSelectRequest(QString id)
setSelectedInstanceById(id);
}
void MainWindow::instanceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
{
auto current = view->selectionModel()->currentIndex();
QItemSelection test(topLeft, bottomRight);
if (test.contains(current))
{
instanceChanged(current, current);
}
}
void MainWindow::selectionBad()
{
// start by reseting everything...
@ -2189,6 +2109,6 @@ void MainWindow::updateStatusCenter()
void MainWindow::refreshCurrentInstance(bool running)
{
auto current = view->selectionModel()->currentIndex();
auto current = view->currentInstance();
instanceChanged(current, current);
}

View File

@ -56,9 +56,9 @@ class QToolButton;
class InstanceProxyModel;
class LabeledToolButton;
class QLabel;
class InstanceView;
class MinecraftLauncher;
class BaseProfilerFactory;
class InstanceView;
class KonamiCode;
class InstanceTask;
@ -170,14 +170,12 @@ private slots:
void updateToolsMenu();
void instanceActivated(QModelIndex);
void instanceActivated(InstancePtr inst);
void instanceChanged(const QModelIndex &current, const QModelIndex &previous);
void instanceChanged(InstancePtr current, InstancePtr previous);
void instanceSelectRequest(QString id);
void instanceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
void selectionBad();
void startTask(Task *task);
@ -228,7 +226,6 @@ private:
// these are managed by Qt's memory management model!
InstanceView *view = nullptr;
InstanceProxyModel *proxymodel = nullptr;
QToolButton *newsLabel = nullptr;
QLabel *m_statusLeft = nullptr;
QLabel *m_statusCenter = nullptr;

View File

@ -22,8 +22,6 @@
#include "IconPickerDialog.h"
#include "ui_IconPickerDialog.h"
#include "ui/instanceview/InstanceDelegate.h"
#include "icons/IconList.h"
#include "icons/IconUtils.h"
#include <DesktopServices.h>
@ -48,8 +46,7 @@ IconPickerDialog::IconPickerDialog(QWidget *parent)
contentsWidget->setTextElideMode(Qt::ElideRight);
contentsWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
contentsWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
contentsWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
contentsWidget->setItemDelegate(new ListViewDelegate());
contentsWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
// contentsWidget->setAcceptDrops(true);
contentsWidget->setDropIndicatorShown(true);

View File

@ -1,778 +0,0 @@
#include "InstanceView.h"
#include "AccessibleInstanceView.h"
#include "AccessibleInstanceView_p.h"
#include <qvariant.h>
#include <qaccessible.h>
#include <qheaderview.h>
#ifndef QT_NO_ACCESSIBILITY
QAccessibleInterface *groupViewAccessibleFactory(const QString &classname, QObject *object)
{
QAccessibleInterface *iface = 0;
if (!object || !object->isWidgetType())
return iface;
QWidget *widget = static_cast<QWidget*>(object);
if (classname == QLatin1String("InstanceView")) {
iface = new AccessibleInstanceView((InstanceView *)widget);
}
return iface;
}
QAbstractItemView *AccessibleInstanceView::view() const
{
return qobject_cast<QAbstractItemView*>(object());
}
int AccessibleInstanceView::logicalIndex(const QModelIndex &index) const
{
if (!view()->model() || !index.isValid())
return -1;
return index.row() * (index.model()->columnCount()) + index.column();
}
AccessibleInstanceView::AccessibleInstanceView(QWidget *w)
: QAccessibleObject(w)
{
Q_ASSERT(view());
}
bool AccessibleInstanceView::isValid() const
{
return view();
}
AccessibleInstanceView::~AccessibleInstanceView()
{
for (QAccessible::Id id : childToId) {
QAccessible::deleteAccessibleInterface(id);
}
}
QAccessibleInterface *AccessibleInstanceView::cellAt(int row, int column) const
{
if (!view()->model()) {
return 0;
}
QModelIndex index = view()->model()->index(row, column, view()->rootIndex());
if (Q_UNLIKELY(!index.isValid())) {
qWarning() << "AccessibleInstanceView::cellAt: invalid index: " << index << " for " << view();
return 0;
}
return child(logicalIndex(index));
}
QAccessibleInterface *AccessibleInstanceView::caption() const
{
return 0;
}
QString AccessibleInstanceView::columnDescription(int column) const
{
if (!view()->model())
return QString();
return view()->model()->headerData(column, Qt::Horizontal).toString();
}
int AccessibleInstanceView::columnCount() const
{
if (!view()->model())
return 0;
return 1;
}
int AccessibleInstanceView::rowCount() const
{
if (!view()->model())
return 0;
return view()->model()->rowCount();
}
int AccessibleInstanceView::selectedCellCount() const
{
if (!view()->selectionModel())
return 0;
return view()->selectionModel()->selectedIndexes().count();
}
int AccessibleInstanceView::selectedColumnCount() const
{
if (!view()->selectionModel())
return 0;
return view()->selectionModel()->selectedColumns().count();
}
int AccessibleInstanceView::selectedRowCount() const
{
if (!view()->selectionModel())
return 0;
return view()->selectionModel()->selectedRows().count();
}
QString AccessibleInstanceView::rowDescription(int row) const
{
if (!view()->model())
return QString();
return view()->model()->headerData(row, Qt::Vertical).toString();
}
QList<QAccessibleInterface *> AccessibleInstanceView::selectedCells() const
{
QList<QAccessibleInterface*> cells;
if (!view()->selectionModel())
return cells;
const QModelIndexList selectedIndexes = view()->selectionModel()->selectedIndexes();
cells.reserve(selectedIndexes.size());
for (const QModelIndex &index : selectedIndexes)
cells.append(child(logicalIndex(index)));
return cells;
}
QList<int> AccessibleInstanceView::selectedColumns() const
{
if (!view()->selectionModel()) {
return QList<int>();
}
const QModelIndexList selectedColumns = view()->selectionModel()->selectedColumns();
QList<int> columns;
columns.reserve(selectedColumns.size());
for (const QModelIndex &index : selectedColumns) {
columns.append(index.column());
}
return columns;
}
QList<int> AccessibleInstanceView::selectedRows() const
{
if (!view()->selectionModel()) {
return QList<int>();
}
QList<int> rows;
const QModelIndexList selectedRows = view()->selectionModel()->selectedRows();
rows.reserve(selectedRows.size());
for (const QModelIndex &index : selectedRows) {
rows.append(index.row());
}
return rows;
}
QAccessibleInterface *AccessibleInstanceView::summary() const
{
return 0;
}
bool AccessibleInstanceView::isColumnSelected(int column) const
{
if (!view()->selectionModel()) {
return false;
}
return view()->selectionModel()->isColumnSelected(column, QModelIndex());
}
bool AccessibleInstanceView::isRowSelected(int row) const
{
if (!view()->selectionModel()) {
return false;
}
return view()->selectionModel()->isRowSelected(row, QModelIndex());
}
bool AccessibleInstanceView::selectRow(int row)
{
if (!view()->model() || !view()->selectionModel()) {
return false;
}
QModelIndex index = view()->model()->index(row, 0, view()->rootIndex());
if (!index.isValid() || view()->selectionBehavior() == QAbstractItemView::SelectColumns) {
return false;
}
switch (view()->selectionMode()) {
case QAbstractItemView::NoSelection: {
return false;
}
case QAbstractItemView::SingleSelection: {
if (view()->selectionBehavior() != QAbstractItemView::SelectRows && columnCount() > 1 )
return false;
view()->clearSelection();
break;
}
case QAbstractItemView::ContiguousSelection: {
if ((!row || !view()->selectionModel()->isRowSelected(row - 1, view()->rootIndex())) && !view()->selectionModel()->isRowSelected(row + 1, view()->rootIndex())) {
view()->clearSelection();
}
break;
}
default: {
break;
}
}
view()->selectionModel()->select(index, QItemSelectionModel::Select | QItemSelectionModel::Rows);
return true;
}
bool AccessibleInstanceView::selectColumn(int column)
{
if (!view()->model() || !view()->selectionModel()) {
return false;
}
QModelIndex index = view()->model()->index(0, column, view()->rootIndex());
if (!index.isValid() || view()->selectionBehavior() == QAbstractItemView::SelectRows) {
return false;
}
switch (view()->selectionMode()) {
case QAbstractItemView::NoSelection: {
return false;
}
case QAbstractItemView::SingleSelection: {
if (view()->selectionBehavior() != QAbstractItemView::SelectColumns && rowCount() > 1) {
return false;
}
// fallthrough intentional
}
case QAbstractItemView::ContiguousSelection: {
if ((!column || !view()->selectionModel()->isColumnSelected(column - 1, view()->rootIndex())) && !view()->selectionModel()->isColumnSelected(column + 1, view()->rootIndex())) {
view()->clearSelection();
}
break;
}
default: {
break;
}
}
view()->selectionModel()->select(index, QItemSelectionModel::Select | QItemSelectionModel::Columns);
return true;
}
bool AccessibleInstanceView::unselectRow(int row)
{
if (!view()->model() || !view()->selectionModel()) {
return false;
}
QModelIndex index = view()->model()->index(row, 0, view()->rootIndex());
if (!index.isValid()) {
return false;
}
QItemSelection selection(index, index);
auto selectionModel = view()->selectionModel();
switch (view()->selectionMode()) {
case QAbstractItemView::SingleSelection:
// no unselect
if (selectedRowCount() == 1) {
return false;
}
break;
case QAbstractItemView::ContiguousSelection: {
// no unselect
if (selectedRowCount() == 1) {
return false;
}
if ((!row || selectionModel->isRowSelected(row - 1, view()->rootIndex())) && selectionModel->isRowSelected(row + 1, view()->rootIndex())) {
//If there are rows selected both up the current row and down the current rown,
//the ones which are down the current row will be deselected
selection = QItemSelection(index, view()->model()->index(rowCount() - 1, 0, view()->rootIndex()));
}
}
default: {
break;
}
}
selectionModel->select(selection, QItemSelectionModel::Deselect | QItemSelectionModel::Rows);
return true;
}
bool AccessibleInstanceView::unselectColumn(int column)
{
auto model = view()->model();
if (!model || !view()->selectionModel()) {
return false;
}
QModelIndex index = model->index(0, column, view()->rootIndex());
if (!index.isValid()) {
return false;
}
QItemSelection selection(index, index);
switch (view()->selectionMode()) {
case QAbstractItemView::SingleSelection: {
//In SingleSelection and ContiguousSelection once an item
//is selected, there's no way for the user to unselect all items
if (selectedColumnCount() == 1) {
return false;
}
break;
}
case QAbstractItemView::ContiguousSelection:
if (selectedColumnCount() == 1) {
return false;
}
if ((!column || view()->selectionModel()->isColumnSelected(column - 1, view()->rootIndex()))
&& view()->selectionModel()->isColumnSelected(column + 1, view()->rootIndex())) {
//If there are columns selected both at the left of the current row and at the right
//of the current row, the ones which are at the right will be deselected
selection = QItemSelection(index, model->index(0, columnCount() - 1, view()->rootIndex()));
}
default:
break;
}
view()->selectionModel()->select(selection, QItemSelectionModel::Deselect | QItemSelectionModel::Columns);
return true;
}
QAccessible::Role AccessibleInstanceView::role() const
{
return QAccessible::List;
}
QAccessible::State AccessibleInstanceView::state() const
{
return QAccessible::State();
}
QAccessibleInterface *AccessibleInstanceView::childAt(int x, int y) const
{
QPoint viewportOffset = view()->viewport()->mapTo(view(), QPoint(0,0));
QPoint indexPosition = view()->mapFromGlobal(QPoint(x, y) - viewportOffset);
// FIXME: if indexPosition < 0 in one coordinate, return header
QModelIndex index = view()->indexAt(indexPosition);
if (index.isValid()) {
return child(logicalIndex(index));
}
return 0;
}
int AccessibleInstanceView::childCount() const
{
if (!view()->model()) {
return 0;
}
return (view()->model()->rowCount()) * (view()->model()->columnCount());
}
int AccessibleInstanceView::indexOfChild(const QAccessibleInterface *iface) const
{
if (!view()->model())
return -1;
QAccessibleInterface *parent = iface->parent();
if (parent->object() != view())
return -1;
Q_ASSERT(iface->role() != QAccessible::TreeItem); // should be handled by tree class
if (iface->role() == QAccessible::Cell || iface->role() == QAccessible::ListItem) {
const AccessibleInstanceViewItem* cell = static_cast<const AccessibleInstanceViewItem*>(iface);
return logicalIndex(cell->m_index);
} else if (iface->role() == QAccessible::Pane) {
return 0; // corner button
} else {
qWarning() << "AccessibleInstanceView::indexOfChild has a child with unknown role..." << iface->role() << iface->text(QAccessible::Name);
}
// FIXME: we are in denial of our children. this should stop.
return -1;
}
QString AccessibleInstanceView::text(QAccessible::Text t) const
{
if (t == QAccessible::Description)
return view()->accessibleDescription();
return view()->accessibleName();
}
QRect AccessibleInstanceView::rect() const
{
if (!view()->isVisible())
return QRect();
QPoint pos = view()->mapToGlobal(QPoint(0, 0));
return QRect(pos.x(), pos.y(), view()->width(), view()->height());
}
QAccessibleInterface *AccessibleInstanceView::parent() const
{
if (view() && view()->parent()) {
if (qstrcmp("QComboBoxPrivateContainer", view()->parent()->metaObject()->className()) == 0) {
return QAccessible::queryAccessibleInterface(view()->parent()->parent());
}
return QAccessible::queryAccessibleInterface(view()->parent());
}
return 0;
}
QAccessibleInterface *AccessibleInstanceView::child(int logicalIndex) const
{
if (!view()->model())
return 0;
auto id = childToId.constFind(logicalIndex);
if (id != childToId.constEnd())
return QAccessible::accessibleInterface(id.value());
int columns = view()->model()->columnCount();
int row = logicalIndex / columns;
int column = logicalIndex % columns;
QAccessibleInterface *iface = 0;
QModelIndex index = view()->model()->index(row, column, view()->rootIndex());
if (Q_UNLIKELY(!index.isValid())) {
qWarning("AccessibleInstanceView::child: Invalid index at: %d %d", row, column);
return 0;
}
iface = new AccessibleInstanceViewItem(view(), index);
QAccessible::registerAccessibleInterface(iface);
childToId.insert(logicalIndex, QAccessible::uniqueId(iface));
return iface;
}
void *AccessibleInstanceView::interface_cast(QAccessible::InterfaceType t)
{
if (t == QAccessible::TableInterface)
return static_cast<QAccessibleTableInterface*>(this);
return 0;
}
void AccessibleInstanceView::modelChange(QAccessibleTableModelChangeEvent *event)
{
// if there is no cache yet, we don't update anything
if (childToId.isEmpty())
return;
switch (event->modelChangeType()) {
case QAccessibleTableModelChangeEvent::ModelReset:
for (QAccessible::Id id : childToId)
QAccessible::deleteAccessibleInterface(id);
childToId.clear();
break;
// rows are inserted: move every row after that
case QAccessibleTableModelChangeEvent::RowsInserted:
case QAccessibleTableModelChangeEvent::ColumnsInserted: {
ChildCache newCache;
ChildCache::ConstIterator iter = childToId.constBegin();
while (iter != childToId.constEnd()) {
QAccessible::Id id = iter.value();
QAccessibleInterface *iface = QAccessible::accessibleInterface(id);
Q_ASSERT(iface);
if (indexOfChild(iface) >= 0) {
newCache.insert(indexOfChild(iface), id);
} else {
// ### This should really not happen,
// but it might if the view has a root index set.
// This needs to be fixed.
QAccessible::deleteAccessibleInterface(id);
}
++iter;
}
childToId = newCache;
break;
}
case QAccessibleTableModelChangeEvent::ColumnsRemoved:
case QAccessibleTableModelChangeEvent::RowsRemoved: {
ChildCache newCache;
ChildCache::ConstIterator iter = childToId.constBegin();
while (iter != childToId.constEnd()) {
QAccessible::Id id = iter.value();
QAccessibleInterface *iface = QAccessible::accessibleInterface(id);
Q_ASSERT(iface);
if (iface->role() == QAccessible::Cell || iface->role() == QAccessible::ListItem) {
Q_ASSERT(iface->tableCellInterface());
AccessibleInstanceViewItem *cell = static_cast<AccessibleInstanceViewItem*>(iface->tableCellInterface());
// Since it is a QPersistentModelIndex, we only need to check if it is valid
if (cell->m_index.isValid())
newCache.insert(indexOfChild(cell), id);
else
QAccessible::deleteAccessibleInterface(id);
}
++iter;
}
childToId = newCache;
break;
}
case QAccessibleTableModelChangeEvent::DataChanged:
// nothing to do in this case
break;
}
}
// TABLE CELL
AccessibleInstanceViewItem::AccessibleInstanceViewItem(QAbstractItemView *view_, const QModelIndex &index_)
: view(view_), m_index(index_)
{
if (Q_UNLIKELY(!index_.isValid()))
qWarning() << "AccessibleInstanceViewItem::AccessibleInstanceViewItem with invalid index: " << index_;
}
void *AccessibleInstanceViewItem::interface_cast(QAccessible::InterfaceType t)
{
if (t == QAccessible::TableCellInterface)
return static_cast<QAccessibleTableCellInterface*>(this);
if (t == QAccessible::ActionInterface)
return static_cast<QAccessibleActionInterface*>(this);
return 0;
}
int AccessibleInstanceViewItem::columnExtent() const { return 1; }
int AccessibleInstanceViewItem::rowExtent() const { return 1; }
QList<QAccessibleInterface*> AccessibleInstanceViewItem::rowHeaderCells() const
{
return {};
}
QList<QAccessibleInterface*> AccessibleInstanceViewItem::columnHeaderCells() const
{
return {};
}
int AccessibleInstanceViewItem::columnIndex() const
{
if (!isValid()) {
return -1;
}
return m_index.column();
}
int AccessibleInstanceViewItem::rowIndex() const
{
if (!isValid()) {
return -1;
}
return m_index.row();
}
bool AccessibleInstanceViewItem::isSelected() const
{
if (!isValid()) {
return false;
}
return view->selectionModel()->isSelected(m_index);
}
QStringList AccessibleInstanceViewItem::actionNames() const
{
QStringList names;
names << toggleAction();
return names;
}
void AccessibleInstanceViewItem::doAction(const QString& actionName)
{
if (actionName == toggleAction()) {
if (isSelected()) {
unselectCell();
}
else {
selectCell();
}
}
}
QStringList AccessibleInstanceViewItem::keyBindingsForAction(const QString &) const
{
return QStringList();
}
void AccessibleInstanceViewItem::selectCell()
{
if (!isValid()) {
return;
}
QAbstractItemView::SelectionMode selectionMode = view->selectionMode();
if (selectionMode == QAbstractItemView::NoSelection) {
return;
}
Q_ASSERT(table());
QAccessibleTableInterface *cellTable = table()->tableInterface();
switch (view->selectionBehavior()) {
case QAbstractItemView::SelectItems:
break;
case QAbstractItemView::SelectColumns:
if (cellTable)
cellTable->selectColumn(m_index.column());
return;
case QAbstractItemView::SelectRows:
if (cellTable)
cellTable->selectRow(m_index.row());
return;
}
if (selectionMode == QAbstractItemView::SingleSelection) {
view->clearSelection();
}
view->selectionModel()->select(m_index, QItemSelectionModel::Select);
}
void AccessibleInstanceViewItem::unselectCell()
{
if (!isValid())
return;
QAbstractItemView::SelectionMode selectionMode = view->selectionMode();
if (selectionMode == QAbstractItemView::NoSelection)
return;
QAccessibleTableInterface *cellTable = table()->tableInterface();
switch (view->selectionBehavior()) {
case QAbstractItemView::SelectItems:
break;
case QAbstractItemView::SelectColumns:
if (cellTable)
cellTable->unselectColumn(m_index.column());
return;
case QAbstractItemView::SelectRows:
if (cellTable)
cellTable->unselectRow(m_index.row());
return;
}
//If the mode is not MultiSelection or ExtendedSelection and only
//one cell is selected it cannot be unselected by the user
if ((selectionMode != QAbstractItemView::MultiSelection) && (selectionMode != QAbstractItemView::ExtendedSelection) && (view->selectionModel()->selectedIndexes().count() <= 1))
return;
view->selectionModel()->select(m_index, QItemSelectionModel::Deselect);
}
QAccessibleInterface *AccessibleInstanceViewItem::table() const
{
return QAccessible::queryAccessibleInterface(view);
}
QAccessible::Role AccessibleInstanceViewItem::role() const
{
return QAccessible::ListItem;
}
QAccessible::State AccessibleInstanceViewItem::state() const
{
QAccessible::State st;
if (!isValid())
return st;
QRect globalRect = view->rect();
globalRect.translate(view->mapToGlobal(QPoint(0,0)));
if (!globalRect.intersects(rect()))
st.invisible = true;
if (view->selectionModel()->isSelected(m_index))
st.selected = true;
if (view->selectionModel()->currentIndex() == m_index)
st.focused = true;
if (m_index.model()->data(m_index, Qt::CheckStateRole).toInt() == Qt::Checked)
st.checked = true;
Qt::ItemFlags flags = m_index.flags();
if (flags & Qt::ItemIsSelectable) {
st.selectable = true;
st.focusable = true;
if (view->selectionMode() == QAbstractItemView::MultiSelection)
st.multiSelectable = true;
if (view->selectionMode() == QAbstractItemView::ExtendedSelection)
st.extSelectable = true;
}
return st;
}
QRect AccessibleInstanceViewItem::rect() const
{
QRect r;
if (!isValid())
return r;
r = view->visualRect(m_index);
if (!r.isNull()) {
r.translate(view->viewport()->mapTo(view, QPoint(0,0)));
r.translate(view->mapToGlobal(QPoint(0, 0)));
}
return r;
}
QString AccessibleInstanceViewItem::text(QAccessible::Text t) const
{
QString value;
if (!isValid())
return value;
QAbstractItemModel *model = view->model();
switch (t) {
case QAccessible::Name:
value = model->data(m_index, Qt::AccessibleTextRole).toString();
if (value.isEmpty())
value = model->data(m_index, Qt::DisplayRole).toString();
break;
case QAccessible::Description:
value = model->data(m_index, Qt::AccessibleDescriptionRole).toString();
break;
default:
break;
}
return value;
}
void AccessibleInstanceViewItem::setText(QAccessible::Text /*t*/, const QString &text)
{
if (!isValid() || !(m_index.flags() & Qt::ItemIsEditable))
return;
view->model()->setData(m_index, text);
}
bool AccessibleInstanceViewItem::isValid() const
{
return view && view->model() && m_index.isValid();
}
QAccessibleInterface *AccessibleInstanceViewItem::parent() const
{
return QAccessible::queryAccessibleInterface(view);
}
QAccessibleInterface *AccessibleInstanceViewItem::child(int) const
{
return 0;
}
#endif /* !QT_NO_ACCESSIBILITY */

View File

@ -1,6 +0,0 @@
#pragma once
#include <QString>
class QAccessibleInterface;
QAccessibleInterface *groupViewAccessibleFactory(const QString &classname, QObject *object);

View File

@ -1,118 +0,0 @@
#pragma once
#include "QtCore/qpointer.h"
#include <QtGui/qaccessible.h>
#include <QAccessibleWidget>
#include <QAbstractItemView>
#ifndef QT_NO_ACCESSIBILITY
#include "InstanceView.h"
// #include <QHeaderView>
class QAccessibleTableCell;
class QAccessibleTableHeaderCell;
class AccessibleInstanceView :public QAccessibleTableInterface, public QAccessibleObject
{
public:
explicit AccessibleInstanceView(QWidget *w);
bool isValid() const override;
QAccessible::Role role() const override;
QAccessible::State state() const override;
QString text(QAccessible::Text t) const override;
QRect rect() const override;
QAccessibleInterface *childAt(int x, int y) const override;
int childCount() const override;
int indexOfChild(const QAccessibleInterface *) const override;
QAccessibleInterface *parent() const override;
QAccessibleInterface *child(int index) const override;
void *interface_cast(QAccessible::InterfaceType t) override;
// table interface
QAccessibleInterface *cellAt(int row, int column) const override;
QAccessibleInterface *caption() const override;
QAccessibleInterface *summary() const override;
QString columnDescription(int column) const override;
QString rowDescription(int row) const override;
int columnCount() const override;
int rowCount() const override;
// selection
int selectedCellCount() const override;
int selectedColumnCount() const override;
int selectedRowCount() const override;
QList<QAccessibleInterface*> selectedCells() const override;
QList<int> selectedColumns() const override;
QList<int> selectedRows() const override;
bool isColumnSelected(int column) const override;
bool isRowSelected(int row) const override;
bool selectRow(int row) override;
bool selectColumn(int column) override;
bool unselectRow(int row) override;
bool unselectColumn(int column) override;
QAbstractItemView *view() const;
void modelChange(QAccessibleTableModelChangeEvent *event) override;
protected:
// maybe vector
typedef QHash<int, QAccessible::Id> ChildCache;
mutable ChildCache childToId;
virtual ~AccessibleInstanceView();
private:
inline int logicalIndex(const QModelIndex &index) const;
};
class AccessibleInstanceViewItem: public QAccessibleInterface, public QAccessibleTableCellInterface, public QAccessibleActionInterface
{
public:
AccessibleInstanceViewItem(QAbstractItemView *view, const QModelIndex &m_index);
void *interface_cast(QAccessible::InterfaceType t) override;
QObject *object() const override { return nullptr; }
QAccessible::Role role() const override;
QAccessible::State state() const override;
QRect rect() const override;
bool isValid() const override;
QAccessibleInterface *childAt(int, int) const override { return nullptr; }
int childCount() const override { return 0; }
int indexOfChild(const QAccessibleInterface *) const override { return -1; }
QString text(QAccessible::Text t) const override;
void setText(QAccessible::Text t, const QString &text) override;
QAccessibleInterface *parent() const override;
QAccessibleInterface *child(int) const override;
// cell interface
int columnExtent() const override;
QList<QAccessibleInterface*> columnHeaderCells() const override;
int columnIndex() const override;
int rowExtent() const override;
QList<QAccessibleInterface*> rowHeaderCells() const override;
int rowIndex() const override;
bool isSelected() const override;
QAccessibleInterface* table() const override;
//action interface
QStringList actionNames() const override;
void doAction(const QString &actionName) override;
QStringList keyBindingsForAction(const QString &actionName) const override;
private:
QPointer<QAbstractItemView > view;
QPersistentModelIndex m_index;
void selectCell();
void unselectCell();
friend class AccessibleInstanceView;
};
#endif /* !QT_NO_ACCESSIBILITY */

View File

@ -1,450 +0,0 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 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 <QDebug>
#include "InstanceView.h"
#include "BaseInstance.h"
#include "InstanceList.h"
#include <QIcon>
#include <QTextEdit>
// 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 QStyleOptionViewItem &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::Window);
backgroundColor.setAlpha(160);
painter->fillRect(rect, QBrush(backgroundColor));
}
}
void drawFocusRect(QPainter *painter, const QStyleOptionViewItem &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 QStyleOptionViewItem &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 QStyleOptionViewItem &option, BaseInstance *instance, QIcon::Mode mode, QIcon::State state)
{
QList<QString> pixmaps;
if (instance->isRunning())
{
pixmaps.append("status-running");
}
else if (instance->hasCrashed() || instance->hasVersionBroken())
{
pixmaps.append("status-bad");
}
if (instance->hasUpdateAvailable())
{
pixmaps.append("checkupdate");
}
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;
}
// FIXME: inject this.
auto icon = QIcon::fromTheme(it.next());
// opt.icon.paint(painter, iconbox, Qt::AlignCenter, mode, state);
const QPixmap pixmap;
// itemSide
QRect badgeRect(
option.rect.width() - x * itemSide + qMax(x - 1, 0) * spacing - itemSide,
y * itemSide + qMax(y - 1, 0) * spacing,
itemSide,
itemSide
);
icon.paint(painter, badgeRect, Qt::AlignCenter, mode, state);
}
}
painter->translate(-option.rect.topLeft());
}
static QSize viewItemTextSize(const QStyleOptionViewItem *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
{
QStyleOptionViewItem 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 );
drawSelectionRect(painter, opt, textHighlightRect);
/*
QPalette::ColorGroup cg;
QStyleOptionViewItem 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);
}
}
*/
}
// icon mode and state, also used for badges
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;
// draw the icon
{
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, mode, state);
}
drawProgressOverlay(painter, opt, index.data(InstanceViewRoles::ProgressValueRole).toInt(),
index.data(InstanceViewRoles::ProgressMaximumRole).toInt());
painter->restore();
}
QSize ListViewDelegate::sizeHint(const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
QStyleOptionViewItem 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;
}
class NoReturnTextEdit: public QTextEdit
{
Q_OBJECT
public:
explicit NoReturnTextEdit(QWidget *parent) : QTextEdit(parent)
{
setTextInteractionFlags(Qt::TextEditorInteraction);
setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff);
setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff);
}
bool event(QEvent * event) override
{
auto eventType = event->type();
if(eventType == QEvent::KeyPress || eventType == QEvent::KeyRelease)
{
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
auto key = keyEvent->key();
if ((key == Qt::Key_Return || key == Qt::Key_Enter) && eventType == QEvent::KeyPress)
{
emit editingDone();
return true;
}
if(key == Qt::Key_Tab)
{
return true;
}
}
return QTextEdit::event(event);
}
signals:
void editingDone();
};
void ListViewDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
const int iconSize = 48;
QRect textRect = option.rect;
// QStyle *style = option.widget ? option.widget->style() : QApplication::style();
textRect.adjust(0, iconSize + 5, 0, 0);
editor->setGeometry(textRect);
}
void ListViewDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const
{
auto text = index.data(Qt::EditRole).toString();
QTextEdit * realeditor = qobject_cast<NoReturnTextEdit *>(editor);
realeditor->setAlignment(Qt::AlignHCenter | Qt::AlignTop);
realeditor->append(text);
realeditor->selectAll();
realeditor->document()->clearUndoRedoStacks();
}
void ListViewDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const
{
QTextEdit * realeditor = qobject_cast<NoReturnTextEdit *>(editor);
QString text = realeditor->toPlainText();
text.replace(QChar('\n'), QChar(' '));
text = text.trimmed();
// Prevent instance names longer than 128 chars
text.truncate(128);
if(text.size() != 0)
{
model->setData(index, text);
}
}
QWidget * ListViewDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
auto editor = new NoReturnTextEdit(parent);
connect(editor, &NoReturnTextEdit::editingDone, this, &ListViewDelegate::editingDone);
return editor;
}
void ListViewDelegate::editingDone()
{
NoReturnTextEdit *editor = qobject_cast<NoReturnTextEdit *>(sender());
emit commitData(editor);
emit closeEditor(editor);
}
#include "InstanceDelegate.moc"

View File

@ -1,39 +0,0 @@
/* Copyright 2013-2021 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
{
Q_OBJECT
public:
explicit ListViewDelegate(QObject *parent = 0);
virtual ~ListViewDelegate() {}
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
void updateEditorGeometry(QWidget * editor, const QStyleOptionViewItem & option, const QModelIndex & index) const override;
QWidget * createEditor(QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index) const override;
void setEditorData(QWidget * editor, const QModelIndex & index) const override;
void setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const override;
private slots:
void editingDone();
};

View File

@ -17,11 +17,8 @@
#include "InstanceView.h"
#include "Application.h"
#include <BaseInstance.h>
#include <icons/IconList.h>
#include <QDebug>
InstanceProxyModel::InstanceProxyModel(QObject *parent) : QSortFilterProxyModel(parent) {
m_naturalSort.setNumericMode(true);
m_naturalSort.setCaseSensitivity(Qt::CaseSensitivity::CaseInsensitive);
@ -34,38 +31,8 @@ QVariant InstanceProxyModel::data(const QModelIndex & index, int role) const
QVariant data = QSortFilterProxyModel::data(index, role);
if(role == Qt::DecorationRole)
{
return QVariant(APPLICATION->icons()->getIcon(data.toString()));
if (!data.toString().isEmpty())
return APPLICATION->icons()->getIcon(data.toString()); //FIXME: Needs QStyledItemDelegate
}
return data;
}
bool InstanceProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const {
const QString leftCategory = left.data(InstanceViewRoles::GroupRole).toString();
const QString rightCategory = right.data(InstanceViewRoles::GroupRole).toString();
if (leftCategory == rightCategory) {
return subSortLessThan(left, right);
}
else {
// FIXME: real group sorting happens in InstanceView::updateGeometries(), see LocaleString
auto result = leftCategory.localeAwareCompare(rightCategory);
if(result == 0) {
return subSortLessThan(left, right);
}
return result < 0;
}
}
bool InstanceProxyModel::subSortLessThan(const QModelIndex &left, const QModelIndex &right) const
{
BaseInstance *pdataLeft = static_cast<BaseInstance *>(left.internalPointer());
BaseInstance *pdataRight = static_cast<BaseInstance *>(right.internalPointer());
QString sortMode = APPLICATION->settings()->get("InstSortMode").toString();
if (sortMode == "LastLaunch")
{
return pdataLeft->lastLaunch() > pdataRight->lastLaunch();
}
else
{
return m_naturalSort.compare(pdataLeft->name(), pdataRight->name()) < 0;
}
}
}

View File

@ -27,8 +27,6 @@ public:
protected:
QVariant data(const QModelIndex & index, int role) const override;
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
bool subSortLessThan(const QModelIndex &left, const QModelIndex &right) const;
private:
QCollator m_naturalSort;

File diff suppressed because it is too large Load Diff

View File

@ -14,160 +14,47 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 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 <QListView>
#include <QLineEdit>
#include <QScrollBar>
#include <QCache>
#include "VisualGroup.h"
#include <functional>
#include <QStackedWidget>
#include <QAbstractItemView>
#include <QTableView>
struct InstanceViewRoles
{
enum
{
GroupRole = Qt::UserRole,
ProgressValueRole,
ProgressMaximumRole
};
};
#include "BaseInstance.h"
class InstanceView : public QAbstractItemView
{
Q_OBJECT
class InstanceProxyModel;
class InstanceList;
class InstanceView : public QStackedWidget {
Q_OBJECT
public:
InstanceView(QWidget *parent = 0);
~InstanceView();
explicit InstanceView(QWidget *parent = nullptr, InstanceList* instances = nullptr);
void setModel(QAbstractItemModel *model) override;
using visibilityFunction = std::function<bool(const QString &)>;
void setSourceOfGroupCollapseStatus(visibilityFunction f) {
fVisibility = f;
QAbstractItemView* currentView() {
return m_table;
}
/// 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;
QString groupNameAt(const QPoint &point);
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;
};
public slots:
virtual void updateGeometries() 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;
void modelReset();
void rowsRemoved();
void currentChanged(const QModelIndex &current, const QModelIndex &previous) override;
InstancePtr currentInstance();
signals:
void droppedURLs(QList<QUrl> urls);
void groupStateChanged(QString group, bool collapsed);
void instanceActivated(InstancePtr inst);
void currentInstanceChanged(InstancePtr current, InstancePtr previous);
protected:
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;
void updateScrollbar();
private slots:
void currentRowChanged(const QModelIndex& current, const QModelIndex& previous);
void selectNameColumn(const QModelIndex& current, const QModelIndex& previous);
// emits currentRowChanged if a data update affected the current instance
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
private:
friend struct VisualGroup;
QList<VisualGroup *> m_groups;
void createTable();
void prepareModel();
visibilityFunction fVisibility;
int m_rowHeight = 48;
// 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, VisualGroup::HitResults & result) 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<std::pair<QRect, QModelIndex>> draggablePaintPairs(const QModelIndexList &indices, QRect *r) const;
bool isDragEventAccepted(QDropEvent *event);
std::pair<VisualGroup *, VisualGroup::HitResults> rowDropPos(const QPoint &pos);
QPoint offset() const;
QTableView* m_table;
InstanceProxyModel* m_proxy;
InstanceList* m_instances;
};

View File

@ -1,344 +0,0 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 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 "VisualGroup.h"
#include <QModelIndex>
#include <QPainter>
#include <QtMath>
#include <QApplication>
#include <QDebug>
#include "InstanceView.h"
VisualGroup::VisualGroup(const QString &text, InstanceView *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;
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QStyleOptionViewItem viewItemOption;
view->initViewItemOption(&viewItemOption);
#else
QStyleOptionViewItem viewItemOption = view->viewOptions();
#endif
auto itemHeight = view->itemDelegate()->sizeHint(viewItemOption, 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 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++;
}
qWarning() << "Item" << index.row() << index.data(Qt::DisplayRole).toString() << "not found in visual group" << text;
return qMakePair(0, 0);
}
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(InstanceViewRoles::GroupRole).toString() == text)
{
indices.append(index);
}
}
return indices;
}

View File

@ -1,106 +0,0 @@
/* Copyright 2013-2021 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 <QString>
#include <QRect>
#include <QVector>
#include <QStyleOption>
class InstanceView;
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, InstanceView *view);
VisualGroup(const VisualGroup *other);
/* data */
InstanceView *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)