Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into refactor-instanceview
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
This commit is contained in:
781
launcher/ui/instanceview/AccessibleInstanceView.cpp
Normal file
781
launcher/ui/instanceview/AccessibleInstanceView.cpp
Normal file
@ -0,0 +1,781 @@
|
||||
#include "AccessibleInstanceView.h"
|
||||
#include "AccessibleInstanceView_p.h"
|
||||
#include "InstanceView.h"
|
||||
|
||||
#include <qaccessible.h>
|
||||
#include <qheaderview.h>
|
||||
#include <qvariant.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 */
|
||||
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 */
|
7
launcher/ui/instanceview/AccessibleInstanceView.h
Normal file
7
launcher/ui/instanceview/AccessibleInstanceView.h
Normal file
@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
class QAccessibleInterface;
|
||||
|
||||
QAccessibleInterface* groupViewAccessibleFactory(const QString& classname, QObject* object);
|
116
launcher/ui/instanceview/AccessibleInstanceView_p.h
Normal file
116
launcher/ui/instanceview/AccessibleInstanceView_p.h
Normal file
@ -0,0 +1,116 @@
|
||||
#pragma once
|
||||
|
||||
#include <QtGui/qaccessible.h>
|
||||
#include <QAbstractItemView>
|
||||
#include <QAccessibleWidget>
|
||||
#include "QtCore/qpointer.h"
|
||||
#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 */
|
@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@ -19,9 +19,7 @@
|
||||
#include "InstanceDelegate.h"
|
||||
#include "InstanceList.h"
|
||||
|
||||
InstanceDelegate::InstanceDelegate(QObject* parent, int iconSize)
|
||||
: QStyledItemDelegate(parent), m_iconSize(iconSize)
|
||||
{}
|
||||
InstanceDelegate::InstanceDelegate(QObject* parent, int iconSize) : QStyledItemDelegate(parent), m_iconSize(iconSize) {}
|
||||
|
||||
void InstanceDelegate::initStyleOption(QStyleOptionViewItem* option, const QModelIndex& index) const
|
||||
{
|
||||
|
68
launcher/ui/instanceview/InstanceProxyModel.cpp
Normal file
68
launcher/ui/instanceview/InstanceProxyModel.cpp
Normal file
@ -0,0 +1,68 @@
|
||||
/* 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 "InstanceProxyModel.h"
|
||||
|
||||
#include <BaseInstance.h>
|
||||
#include <icons/IconList.h>
|
||||
#include "Application.h"
|
||||
#include "InstanceView.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
InstanceProxyModel::InstanceProxyModel(QObject* parent) : QSortFilterProxyModel(parent)
|
||||
{
|
||||
m_naturalSort.setNumericMode(true);
|
||||
m_naturalSort.setCaseSensitivity(Qt::CaseSensitivity::CaseInsensitive);
|
||||
// FIXME: use loaded translation as source of locale instead, hook this up to translation changes
|
||||
m_naturalSort.setLocale(QLocale::system());
|
||||
}
|
||||
|
||||
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()));
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
34
launcher/ui/instanceview/InstanceProxyModel.h
Normal file
34
launcher/ui/instanceview/InstanceProxyModel.h
Normal file
@ -0,0 +1,34 @@
|
||||
/* 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 <QCollator>
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
class InstanceProxyModel : public QSortFilterProxyModel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
InstanceProxyModel(QObject* parent = 0);
|
||||
|
||||
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;
|
||||
};
|
956
launcher/ui/instanceview/InstanceView.cpp
Normal file
956
launcher/ui/instanceview/InstanceView.cpp
Normal file
@ -0,0 +1,956 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - 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 "InstanceView.h"
|
||||
|
||||
#include <QAccessible>
|
||||
#include <QApplication>
|
||||
#include <QCache>
|
||||
#include <QDrag>
|
||||
#include <QListView>
|
||||
#include <QMimeData>
|
||||
#include <QMouseEvent>
|
||||
#include <QPainter>
|
||||
#include <QPersistentModelIndex>
|
||||
#include <QScrollBar>
|
||||
#include <QtMath>
|
||||
|
||||
#include "VisualGroup.h"
|
||||
#include "ui/themes/ThemeManager.h"
|
||||
|
||||
#include <Application.h>
|
||||
#include <InstanceList.h>
|
||||
|
||||
template <typename T>
|
||||
bool listsIntersect(const QList<T>& l1, const QList<T> t2)
|
||||
{
|
||||
for (auto& item : l1) {
|
||||
if (t2.contains(item)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
InstanceView::InstanceView(QWidget* parent) : QAbstractItemView(parent)
|
||||
{
|
||||
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||
setAcceptDrops(true);
|
||||
setAutoScroll(true);
|
||||
setPaintCat(APPLICATION->settings()->get("TheCat").toBool());
|
||||
}
|
||||
|
||||
InstanceView::~InstanceView()
|
||||
{
|
||||
qDeleteAll(m_groups);
|
||||
m_groups.clear();
|
||||
}
|
||||
|
||||
void InstanceView::setModel(QAbstractItemModel* model)
|
||||
{
|
||||
QAbstractItemView::setModel(model);
|
||||
connect(model, &QAbstractItemModel::modelReset, this, &InstanceView::modelReset);
|
||||
connect(model, &QAbstractItemModel::rowsRemoved, this, &InstanceView::rowsRemoved);
|
||||
}
|
||||
|
||||
void InstanceView::dataChanged([[maybe_unused]] const QModelIndex& topLeft,
|
||||
[[maybe_unused]] const QModelIndex& bottomRight,
|
||||
[[maybe_unused]] const QVector<int>& roles)
|
||||
{
|
||||
scheduleDelayedItemsLayout();
|
||||
}
|
||||
void InstanceView::rowsInserted([[maybe_unused]] const QModelIndex& parent, [[maybe_unused]] int start, [[maybe_unused]] int end)
|
||||
{
|
||||
scheduleDelayedItemsLayout();
|
||||
}
|
||||
|
||||
void InstanceView::rowsAboutToBeRemoved([[maybe_unused]] const QModelIndex& parent, [[maybe_unused]] int start, [[maybe_unused]] int end)
|
||||
{
|
||||
scheduleDelayedItemsLayout();
|
||||
}
|
||||
|
||||
void InstanceView::modelReset()
|
||||
{
|
||||
scheduleDelayedItemsLayout();
|
||||
}
|
||||
|
||||
void InstanceView::rowsRemoved()
|
||||
{
|
||||
scheduleDelayedItemsLayout();
|
||||
}
|
||||
|
||||
void InstanceView::currentChanged(const QModelIndex& current, const QModelIndex& previous)
|
||||
{
|
||||
QAbstractItemView::currentChanged(current, previous);
|
||||
// TODO: for accessibility support, implement+register a factory, steal QAccessibleTable from Qt and return an instance of it for
|
||||
// InstanceView.
|
||||
#ifndef QT_NO_ACCESSIBILITY
|
||||
if (QAccessible::isActive() && current.isValid()) {
|
||||
QAccessibleEvent event(this, QAccessible::Focus);
|
||||
event.setChild(current.row());
|
||||
QAccessible::updateAccessibility(&event);
|
||||
}
|
||||
#endif /* !QT_NO_ACCESSIBILITY */
|
||||
}
|
||||
|
||||
class LocaleString : public QString {
|
||||
public:
|
||||
LocaleString(const char* s) : QString(s) {}
|
||||
LocaleString(const QString& s) : QString(s) {}
|
||||
};
|
||||
|
||||
inline bool operator<(const LocaleString& lhs, const LocaleString& rhs)
|
||||
{
|
||||
return (QString::localeAwareCompare(lhs, rhs) < 0);
|
||||
}
|
||||
|
||||
void InstanceView::updateScrollbar()
|
||||
{
|
||||
int previousScroll = verticalScrollBar()->value();
|
||||
if (m_groups.isEmpty()) {
|
||||
verticalScrollBar()->setRange(0, 0);
|
||||
} else {
|
||||
int totalHeight = 0;
|
||||
// top margin
|
||||
totalHeight += m_categoryMargin;
|
||||
int itemScroll = 0;
|
||||
for (auto category : m_groups) {
|
||||
category->m_verticalPosition = totalHeight;
|
||||
totalHeight += category->totalHeight() + m_categoryMargin;
|
||||
if (!itemScroll && category->totalHeight() != 0) {
|
||||
itemScroll = category->contentHeight() / category->numRows();
|
||||
}
|
||||
}
|
||||
// do not divide by zero
|
||||
if (itemScroll == 0)
|
||||
itemScroll = 64;
|
||||
|
||||
totalHeight += m_bottomMargin;
|
||||
verticalScrollBar()->setSingleStep(itemScroll);
|
||||
const int rowsPerPage = qMax(viewport()->height() / itemScroll, 1);
|
||||
verticalScrollBar()->setPageStep(rowsPerPage * itemScroll);
|
||||
|
||||
verticalScrollBar()->setRange(0, totalHeight - height());
|
||||
}
|
||||
|
||||
verticalScrollBar()->setValue(qMin(previousScroll, verticalScrollBar()->maximum()));
|
||||
}
|
||||
|
||||
void InstanceView::updateGeometries()
|
||||
{
|
||||
geometryCache.clear();
|
||||
|
||||
QMap<LocaleString, VisualGroup*> cats;
|
||||
|
||||
for (int i = 0; i < model()->rowCount(); ++i) {
|
||||
const QString groupName = model()->index(i, 0).data(InstanceViewRoles::GroupRole).toString();
|
||||
if (!cats.contains(groupName)) {
|
||||
VisualGroup* old = this->category(groupName);
|
||||
if (old) {
|
||||
auto cat = new VisualGroup(old);
|
||||
cats.insert(groupName, cat);
|
||||
cat->update();
|
||||
} else {
|
||||
auto cat = new VisualGroup(groupName, this);
|
||||
if (fVisibility) {
|
||||
cat->collapsed = fVisibility(groupName);
|
||||
}
|
||||
cats.insert(groupName, cat);
|
||||
cat->update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
qDeleteAll(m_groups);
|
||||
m_groups = cats.values();
|
||||
updateScrollbar();
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
bool InstanceView::isIndexHidden(const QModelIndex& index) const
|
||||
{
|
||||
VisualGroup* cat = category(index);
|
||||
if (cat) {
|
||||
return cat->collapsed;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
VisualGroup* InstanceView::category(const QModelIndex& index) const
|
||||
{
|
||||
return category(index.data(InstanceViewRoles::GroupRole).toString());
|
||||
}
|
||||
|
||||
VisualGroup* InstanceView::category(const QString& cat) const
|
||||
{
|
||||
for (auto group : m_groups) {
|
||||
if (group->text == cat) {
|
||||
return group;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
VisualGroup* InstanceView::categoryAt(const QPoint& pos, VisualGroup::HitResults& result) const
|
||||
{
|
||||
for (auto group : m_groups) {
|
||||
result = group->hitScan(pos);
|
||||
if (result != VisualGroup::NoHit) {
|
||||
return group;
|
||||
}
|
||||
}
|
||||
result = VisualGroup::NoHit;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QString InstanceView::groupNameAt(const QPoint& point)
|
||||
{
|
||||
executeDelayedItemsLayout();
|
||||
|
||||
VisualGroup::HitResults hitResult;
|
||||
auto group = categoryAt(point + offset(), hitResult);
|
||||
if (group && (hitResult & (VisualGroup::HeaderHit | VisualGroup::BodyHit))) {
|
||||
return group->text;
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
int InstanceView::calculateItemsPerRow() const
|
||||
{
|
||||
return qFloor((qreal)(contentWidth()) / (qreal)(itemWidth() + m_spacing));
|
||||
}
|
||||
|
||||
int InstanceView::contentWidth() const
|
||||
{
|
||||
return width() - m_leftMargin - m_rightMargin;
|
||||
}
|
||||
|
||||
int InstanceView::itemWidth() const
|
||||
{
|
||||
return m_itemWidth;
|
||||
}
|
||||
|
||||
void InstanceView::mousePressEvent(QMouseEvent* event)
|
||||
{
|
||||
executeDelayedItemsLayout();
|
||||
|
||||
QPoint visualPos = event->pos();
|
||||
QPoint geometryPos = event->pos() + offset();
|
||||
|
||||
QPersistentModelIndex index = indexAt(visualPos);
|
||||
|
||||
m_pressedIndex = index;
|
||||
m_pressedAlreadySelected = selectionModel()->isSelected(m_pressedIndex);
|
||||
m_pressedPosition = geometryPos;
|
||||
|
||||
VisualGroup::HitResults hitResult;
|
||||
m_pressedCategory = categoryAt(geometryPos, hitResult);
|
||||
if (m_pressedCategory && hitResult & VisualGroup::CheckboxHit) {
|
||||
setState(m_pressedCategory->collapsed ? ExpandingState : CollapsingState);
|
||||
event->accept();
|
||||
return;
|
||||
}
|
||||
|
||||
if (index.isValid() && (index.flags() & Qt::ItemIsEnabled)) {
|
||||
if (index != currentIndex()) {
|
||||
// FIXME: better!
|
||||
m_currentCursorColumn = -1;
|
||||
}
|
||||
// we disable scrollTo for mouse press so the item doesn't change position
|
||||
// when the user is interacting with it (ie. clicking on it)
|
||||
bool autoScroll = hasAutoScroll();
|
||||
setAutoScroll(false);
|
||||
selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
|
||||
|
||||
setAutoScroll(autoScroll);
|
||||
QRect rect(visualPos, visualPos);
|
||||
setSelection(rect, QItemSelectionModel::ClearAndSelect);
|
||||
|
||||
// signal handlers may change the model
|
||||
emit pressed(index);
|
||||
} else {
|
||||
// Forces a finalize() even if mouse is pressed, but not on a item
|
||||
selectionModel()->select(QModelIndex(), QItemSelectionModel::Select);
|
||||
}
|
||||
}
|
||||
|
||||
void InstanceView::mouseMoveEvent(QMouseEvent* event)
|
||||
{
|
||||
executeDelayedItemsLayout();
|
||||
|
||||
QPoint topLeft;
|
||||
QPoint visualPos = event->pos();
|
||||
QPoint geometryPos = event->pos() + offset();
|
||||
|
||||
if (state() == ExpandingState || state() == CollapsingState) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (state() == DraggingState) {
|
||||
topLeft = m_pressedPosition - offset();
|
||||
if ((topLeft - event->pos()).manhattanLength() > QApplication::startDragDistance()) {
|
||||
m_pressedIndex = QModelIndex();
|
||||
startDrag(model()->supportedDragActions());
|
||||
setState(NoState);
|
||||
stopAutoScroll();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectionMode() != SingleSelection) {
|
||||
topLeft = m_pressedPosition - offset();
|
||||
} else {
|
||||
topLeft = geometryPos;
|
||||
}
|
||||
|
||||
if (m_pressedIndex.isValid() && (state() != DragSelectingState) && (event->buttons() != Qt::NoButton) && !selectedIndexes().isEmpty()) {
|
||||
setState(DraggingState);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((event->buttons() & Qt::LeftButton) && selectionModel()) {
|
||||
setState(DragSelectingState);
|
||||
|
||||
setSelection(QRect(visualPos, visualPos), QItemSelectionModel::ClearAndSelect);
|
||||
QModelIndex index = indexAt(visualPos);
|
||||
|
||||
// set at the end because it might scroll the view
|
||||
if (index.isValid() && (index != selectionModel()->currentIndex()) && (index.flags() & Qt::ItemIsEnabled)) {
|
||||
selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InstanceView::mouseReleaseEvent(QMouseEvent* event)
|
||||
{
|
||||
executeDelayedItemsLayout();
|
||||
|
||||
QPoint visualPos = event->pos();
|
||||
QPoint geometryPos = event->pos() + offset();
|
||||
QPersistentModelIndex index = indexAt(visualPos);
|
||||
|
||||
VisualGroup::HitResults hitResult;
|
||||
|
||||
bool click =
|
||||
(index == m_pressedIndex && index.isValid()) || (m_pressedCategory && m_pressedCategory == categoryAt(geometryPos, hitResult));
|
||||
|
||||
if (click && m_pressedCategory) {
|
||||
if (state() == ExpandingState) {
|
||||
m_pressedCategory->collapsed = false;
|
||||
emit groupStateChanged(m_pressedCategory->text, false);
|
||||
|
||||
updateGeometries();
|
||||
viewport()->update();
|
||||
event->accept();
|
||||
m_pressedCategory = nullptr;
|
||||
setState(NoState);
|
||||
return;
|
||||
} else if (state() == CollapsingState) {
|
||||
m_pressedCategory->collapsed = true;
|
||||
emit groupStateChanged(m_pressedCategory->text, true);
|
||||
|
||||
updateGeometries();
|
||||
viewport()->update();
|
||||
event->accept();
|
||||
m_pressedCategory = nullptr;
|
||||
setState(NoState);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_ctrlDragSelectionFlag = QItemSelectionModel::NoUpdate;
|
||||
|
||||
setState(NoState);
|
||||
|
||||
if (click) {
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
emit clicked(index);
|
||||
}
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
QStyleOptionViewItem option;
|
||||
initViewItemOption(&option);
|
||||
#else
|
||||
QStyleOptionViewItem option = viewOptions();
|
||||
#endif
|
||||
if (m_pressedAlreadySelected) {
|
||||
option.state |= QStyle::State_Selected;
|
||||
}
|
||||
if ((model()->flags(index) & Qt::ItemIsEnabled) &&
|
||||
style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, &option, this)) {
|
||||
emit activated(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InstanceView::mouseDoubleClickEvent(QMouseEvent* event)
|
||||
{
|
||||
executeDelayedItemsLayout();
|
||||
|
||||
QModelIndex index = indexAt(event->pos());
|
||||
if (!index.isValid() || !(index.flags() & Qt::ItemIsEnabled) || (m_pressedIndex != index)) {
|
||||
QMouseEvent me(QEvent::MouseButtonPress, event->localPos(), event->windowPos(), event->screenPos(), event->button(),
|
||||
event->buttons(), event->modifiers());
|
||||
mousePressEvent(&me);
|
||||
return;
|
||||
}
|
||||
// signal handlers may change the model
|
||||
QPersistentModelIndex persistent = index;
|
||||
emit doubleClicked(persistent);
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
QStyleOptionViewItem option;
|
||||
initViewItemOption(&option);
|
||||
#else
|
||||
QStyleOptionViewItem option = viewOptions();
|
||||
#endif
|
||||
if ((model()->flags(index) & Qt::ItemIsEnabled) && !style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, &option, this)) {
|
||||
emit activated(index);
|
||||
}
|
||||
}
|
||||
|
||||
void InstanceView::setPaintCat(bool visible)
|
||||
{
|
||||
m_catVisible = visible;
|
||||
if (visible)
|
||||
m_catPixmap.load(APPLICATION->themeManager()->getCatPack());
|
||||
else
|
||||
m_catPixmap = QPixmap();
|
||||
}
|
||||
|
||||
void InstanceView::paintEvent([[maybe_unused]] QPaintEvent* event)
|
||||
{
|
||||
executeDelayedItemsLayout();
|
||||
|
||||
QPainter painter(this->viewport());
|
||||
|
||||
if (m_catVisible) {
|
||||
int widWidth = this->viewport()->width();
|
||||
int widHeight = this->viewport()->height();
|
||||
if (m_catPixmap.width() < widWidth)
|
||||
widWidth = m_catPixmap.width();
|
||||
if (m_catPixmap.height() < widHeight)
|
||||
widHeight = m_catPixmap.height();
|
||||
auto pixmap = m_catPixmap.scaled(widWidth, widHeight, Qt::KeepAspectRatio);
|
||||
QRect rectOfPixmap = pixmap.rect();
|
||||
rectOfPixmap.moveBottomRight(this->viewport()->rect().bottomRight());
|
||||
painter.drawPixmap(rectOfPixmap.topLeft(), pixmap);
|
||||
}
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
QStyleOptionViewItem option;
|
||||
initViewItemOption(&option);
|
||||
#else
|
||||
QStyleOptionViewItem option = viewOptions();
|
||||
#endif
|
||||
option.widget = this;
|
||||
|
||||
int wpWidth = viewport()->width();
|
||||
option.rect.setWidth(wpWidth);
|
||||
for (int i = 0; i < m_groups.size(); ++i) {
|
||||
VisualGroup* category = m_groups.at(i);
|
||||
int y = category->verticalPosition();
|
||||
y -= verticalOffset();
|
||||
QRect backup = option.rect;
|
||||
int height = category->totalHeight();
|
||||
option.rect.setTop(y);
|
||||
option.rect.setHeight(height);
|
||||
option.rect.setLeft(m_leftMargin);
|
||||
option.rect.setRight(wpWidth - m_rightMargin);
|
||||
category->drawHeader(&painter, option);
|
||||
y += category->totalHeight() + m_categoryMargin;
|
||||
option.rect = backup;
|
||||
}
|
||||
|
||||
for (int i = 0; i < model()->rowCount(); ++i) {
|
||||
const QModelIndex index = model()->index(i, 0);
|
||||
if (isIndexHidden(index)) {
|
||||
continue;
|
||||
}
|
||||
Qt::ItemFlags flags = index.flags();
|
||||
option.rect = visualRect(index);
|
||||
option.features |= QStyleOptionViewItem::WrapText;
|
||||
if (flags & Qt::ItemIsSelectable && selectionModel()->isSelected(index)) {
|
||||
option.state |= selectionModel()->isSelected(index) ? QStyle::State_Selected : QStyle::State_None;
|
||||
} else {
|
||||
option.state &= ~QStyle::State_Selected;
|
||||
}
|
||||
option.state |= (index == currentIndex()) ? QStyle::State_HasFocus : QStyle::State_None;
|
||||
if (!(flags & Qt::ItemIsEnabled)) {
|
||||
option.state &= ~QStyle::State_Enabled;
|
||||
}
|
||||
itemDelegate()->paint(&painter, option, index);
|
||||
}
|
||||
|
||||
/*
|
||||
* Drop indicators for manual reordering...
|
||||
*/
|
||||
#if 0
|
||||
if (!m_lastDragPosition.isNull())
|
||||
{
|
||||
std::pair<VisualGroup *, VisualGroup::HitResults> pair = rowDropPos(m_lastDragPosition);
|
||||
VisualGroup *category = pair.first;
|
||||
VisualGroup::HitResults row = pair.second;
|
||||
if (category)
|
||||
{
|
||||
int internalRow = row - category->firstItemIndex;
|
||||
QLine line;
|
||||
if (internalRow >= category->numItems())
|
||||
{
|
||||
QRect toTheRightOfRect = visualRect(category->lastItem());
|
||||
line = QLine(toTheRightOfRect.topRight(), toTheRightOfRect.bottomRight());
|
||||
}
|
||||
else
|
||||
{
|
||||
QRect toTheLeftOfRect = visualRect(model()->index(row, 0));
|
||||
line = QLine(toTheLeftOfRect.topLeft(), toTheLeftOfRect.bottomLeft());
|
||||
}
|
||||
painter.save();
|
||||
painter.setPen(QPen(Qt::black, 3));
|
||||
painter.drawLine(line);
|
||||
painter.restore();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void InstanceView::resizeEvent([[maybe_unused]] QResizeEvent* event)
|
||||
{
|
||||
int newItemsPerRow = calculateItemsPerRow();
|
||||
if (newItemsPerRow != m_currentItemsPerRow) {
|
||||
m_currentCursorColumn = -1;
|
||||
m_currentItemsPerRow = newItemsPerRow;
|
||||
updateGeometries();
|
||||
} else {
|
||||
updateScrollbar();
|
||||
}
|
||||
}
|
||||
|
||||
void InstanceView::dragEnterEvent(QDragEnterEvent* event)
|
||||
{
|
||||
executeDelayedItemsLayout();
|
||||
|
||||
if (!isDragEventAccepted(event)) {
|
||||
return;
|
||||
}
|
||||
m_lastDragPosition = event->pos() + offset();
|
||||
viewport()->update();
|
||||
event->accept();
|
||||
}
|
||||
|
||||
void InstanceView::dragMoveEvent(QDragMoveEvent* event)
|
||||
{
|
||||
executeDelayedItemsLayout();
|
||||
|
||||
if (!isDragEventAccepted(event)) {
|
||||
return;
|
||||
}
|
||||
m_lastDragPosition = event->pos() + offset();
|
||||
viewport()->update();
|
||||
event->accept();
|
||||
}
|
||||
|
||||
void InstanceView::dragLeaveEvent([[maybe_unused]] QDragLeaveEvent* event)
|
||||
{
|
||||
executeDelayedItemsLayout();
|
||||
|
||||
m_lastDragPosition = QPoint();
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
void InstanceView::dropEvent(QDropEvent* event)
|
||||
{
|
||||
executeDelayedItemsLayout();
|
||||
|
||||
m_lastDragPosition = QPoint();
|
||||
|
||||
stopAutoScroll();
|
||||
setState(NoState);
|
||||
|
||||
auto mimedata = event->mimeData();
|
||||
|
||||
if (event->source() == this) {
|
||||
if (event->possibleActions() & Qt::MoveAction) {
|
||||
std::pair<VisualGroup*, VisualGroup::HitResults> dropPos = rowDropPos(event->pos());
|
||||
const VisualGroup* group = dropPos.first;
|
||||
auto hitResult = dropPos.second;
|
||||
|
||||
if (hitResult == VisualGroup::HitResult::NoHit) {
|
||||
viewport()->update();
|
||||
return;
|
||||
}
|
||||
auto instanceId = QString::fromUtf8(mimedata->data("application/x-instanceid"));
|
||||
auto instanceList = APPLICATION->instances().get();
|
||||
instanceList->setInstanceGroup(instanceId, group->text);
|
||||
event->setDropAction(Qt::MoveAction);
|
||||
event->accept();
|
||||
|
||||
updateGeometries();
|
||||
viewport()->update();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// check if the action is supported
|
||||
if (!mimedata) {
|
||||
return;
|
||||
}
|
||||
|
||||
// files dropped from outside?
|
||||
if (mimedata->hasUrls()) {
|
||||
auto urls = mimedata->urls();
|
||||
event->accept();
|
||||
emit droppedURLs(urls);
|
||||
}
|
||||
}
|
||||
|
||||
void InstanceView::startDrag(Qt::DropActions supportedActions)
|
||||
{
|
||||
executeDelayedItemsLayout();
|
||||
|
||||
QModelIndexList indexes = selectionModel()->selectedIndexes();
|
||||
if (indexes.count() == 0)
|
||||
return;
|
||||
|
||||
QMimeData* mimeData = model()->mimeData(indexes);
|
||||
if (!mimeData) {
|
||||
return;
|
||||
}
|
||||
QRect rect;
|
||||
QPixmap pixmap = renderToPixmap(indexes, &rect);
|
||||
QDrag* drag = new QDrag(this);
|
||||
drag->setPixmap(pixmap);
|
||||
drag->setMimeData(mimeData);
|
||||
drag->setHotSpot(m_pressedPosition - rect.topLeft());
|
||||
Qt::DropAction defaultDropAction = Qt::IgnoreAction;
|
||||
if (this->defaultDropAction() != Qt::IgnoreAction && (supportedActions & this->defaultDropAction())) {
|
||||
defaultDropAction = this->defaultDropAction();
|
||||
}
|
||||
/*auto action = */
|
||||
drag->exec(supportedActions, defaultDropAction);
|
||||
}
|
||||
|
||||
QRect InstanceView::visualRect(const QModelIndex& index) const
|
||||
{
|
||||
const_cast<InstanceView*>(this)->executeDelayedItemsLayout();
|
||||
|
||||
return geometryRect(index).translated(-offset());
|
||||
}
|
||||
|
||||
QRect InstanceView::geometryRect(const QModelIndex& index) const
|
||||
{
|
||||
const_cast<InstanceView*>(this)->executeDelayedItemsLayout();
|
||||
|
||||
if (!index.isValid() || isIndexHidden(index) || index.column() > 0) {
|
||||
return QRect();
|
||||
}
|
||||
|
||||
int row = index.row();
|
||||
if (geometryCache.contains(row)) {
|
||||
return *geometryCache[row];
|
||||
}
|
||||
|
||||
const VisualGroup* cat = category(index);
|
||||
QPair<int, int> pos = cat->positionOf(index);
|
||||
int x = pos.first;
|
||||
// int y = pos.second;
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
QStyleOptionViewItem option;
|
||||
initViewItemOption(&option);
|
||||
#else
|
||||
QStyleOptionViewItem option = viewOptions();
|
||||
#endif
|
||||
|
||||
QRect out;
|
||||
out.setTop(cat->verticalPosition() + cat->headerHeight() + 5 + cat->rowTopOf(index));
|
||||
out.setLeft(m_spacing + x * (itemWidth() + m_spacing));
|
||||
out.setSize(itemDelegate()->sizeHint(option, index));
|
||||
geometryCache.insert(row, new QRect(out));
|
||||
return out;
|
||||
}
|
||||
|
||||
QModelIndex InstanceView::indexAt(const QPoint& point) const
|
||||
{
|
||||
const_cast<InstanceView*>(this)->executeDelayedItemsLayout();
|
||||
|
||||
for (int i = 0; i < model()->rowCount(); ++i) {
|
||||
QModelIndex index = model()->index(i, 0);
|
||||
if (visualRect(index).contains(point)) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
void InstanceView::setSelection(const QRect& rect, const QItemSelectionModel::SelectionFlags commands)
|
||||
{
|
||||
executeDelayedItemsLayout();
|
||||
|
||||
for (int i = 0; i < model()->rowCount(); ++i) {
|
||||
QModelIndex index = model()->index(i, 0);
|
||||
QRect itemRect = visualRect(index);
|
||||
if (itemRect.intersects(rect)) {
|
||||
selectionModel()->select(index, commands);
|
||||
update(itemRect.translated(-offset()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QPixmap InstanceView::renderToPixmap(const QModelIndexList& indices, QRect* r) const
|
||||
{
|
||||
Q_ASSERT(r);
|
||||
auto paintPairs = draggablePaintPairs(indices, r);
|
||||
if (paintPairs.isEmpty()) {
|
||||
return QPixmap();
|
||||
}
|
||||
QPixmap pixmap(r->size());
|
||||
pixmap.fill(Qt::transparent);
|
||||
QPainter painter(&pixmap);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
QStyleOptionViewItem option;
|
||||
initViewItemOption(&option);
|
||||
#else
|
||||
QStyleOptionViewItem option = viewOptions();
|
||||
#endif
|
||||
option.state |= QStyle::State_Selected;
|
||||
for (int j = 0; j < paintPairs.count(); ++j) {
|
||||
option.rect = paintPairs.at(j).first.translated(-r->topLeft());
|
||||
const QModelIndex& current = paintPairs.at(j).second;
|
||||
itemDelegate()->paint(&painter, option, current);
|
||||
}
|
||||
return pixmap;
|
||||
}
|
||||
|
||||
QList<std::pair<QRect, QModelIndex>> InstanceView::draggablePaintPairs(const QModelIndexList& indices, QRect* r) const
|
||||
{
|
||||
Q_ASSERT(r);
|
||||
QRect& rect = *r;
|
||||
QList<std::pair<QRect, QModelIndex>> ret;
|
||||
for (int i = 0; i < indices.count(); ++i) {
|
||||
const QModelIndex& index = indices.at(i);
|
||||
const QRect current = geometryRect(index);
|
||||
ret += std::make_pair(current, index);
|
||||
rect |= current;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool InstanceView::isDragEventAccepted([[maybe_unused]] QDropEvent* event)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
std::pair<VisualGroup*, VisualGroup::HitResults> InstanceView::rowDropPos(const QPoint& pos)
|
||||
{
|
||||
VisualGroup::HitResults hitResult;
|
||||
auto group = categoryAt(pos + offset(), hitResult);
|
||||
return std::make_pair(group, hitResult);
|
||||
}
|
||||
|
||||
QPoint InstanceView::offset() const
|
||||
{
|
||||
return QPoint(horizontalOffset(), verticalOffset());
|
||||
}
|
||||
|
||||
QRegion InstanceView::visualRegionForSelection(const QItemSelection& selection) const
|
||||
{
|
||||
QRegion region;
|
||||
for (auto& range : selection) {
|
||||
int start_row = range.top();
|
||||
int end_row = range.bottom();
|
||||
for (int row = start_row; row <= end_row; ++row) {
|
||||
int start_column = range.left();
|
||||
int end_column = range.right();
|
||||
for (int column = start_column; column <= end_column; ++column) {
|
||||
QModelIndex index = model()->index(row, column, rootIndex());
|
||||
region += visualRect(index); // OK
|
||||
}
|
||||
}
|
||||
}
|
||||
return region;
|
||||
}
|
||||
|
||||
QModelIndex InstanceView::moveCursor(QAbstractItemView::CursorAction cursorAction, [[maybe_unused]] Qt::KeyboardModifiers modifiers)
|
||||
{
|
||||
auto current = currentIndex();
|
||||
if (!current.isValid()) {
|
||||
return current;
|
||||
}
|
||||
auto cat = category(current);
|
||||
int group_index = m_groups.indexOf(cat);
|
||||
if (group_index < 0)
|
||||
return current;
|
||||
|
||||
QPair<int, int> pos = cat->positionOf(current);
|
||||
int column = pos.first;
|
||||
int row = pos.second;
|
||||
if (m_currentCursorColumn < 0) {
|
||||
m_currentCursorColumn = column;
|
||||
}
|
||||
switch (cursorAction) {
|
||||
case MoveUp: {
|
||||
if (row == 0) {
|
||||
int prevGroupIndex = group_index - 1;
|
||||
while (prevGroupIndex >= 0) {
|
||||
auto prevGroup = m_groups[prevGroupIndex];
|
||||
if (prevGroup->collapsed) {
|
||||
prevGroupIndex--;
|
||||
continue;
|
||||
}
|
||||
int newRow = prevGroup->numRows() - 1;
|
||||
int newRowSize = prevGroup->rows[newRow].size();
|
||||
int newColumn = m_currentCursorColumn;
|
||||
if (m_currentCursorColumn >= newRowSize) {
|
||||
newColumn = newRowSize - 1;
|
||||
}
|
||||
return prevGroup->rows[newRow][newColumn];
|
||||
}
|
||||
} else {
|
||||
int newRow = row - 1;
|
||||
int newRowSize = cat->rows[newRow].size();
|
||||
int newColumn = m_currentCursorColumn;
|
||||
if (m_currentCursorColumn >= newRowSize) {
|
||||
newColumn = newRowSize - 1;
|
||||
}
|
||||
return cat->rows[newRow][newColumn];
|
||||
}
|
||||
return current;
|
||||
}
|
||||
case MoveDown: {
|
||||
if (row == cat->rows.size() - 1) {
|
||||
int nextGroupIndex = group_index + 1;
|
||||
while (nextGroupIndex < m_groups.size()) {
|
||||
auto nextGroup = m_groups[nextGroupIndex];
|
||||
if (nextGroup->collapsed) {
|
||||
nextGroupIndex++;
|
||||
continue;
|
||||
}
|
||||
int newRowSize = nextGroup->rows[0].size();
|
||||
int newColumn = m_currentCursorColumn;
|
||||
if (m_currentCursorColumn >= newRowSize) {
|
||||
newColumn = newRowSize - 1;
|
||||
}
|
||||
return nextGroup->rows[0][newColumn];
|
||||
}
|
||||
} else {
|
||||
int newRow = row + 1;
|
||||
int newRowSize = cat->rows[newRow].size();
|
||||
int newColumn = m_currentCursorColumn;
|
||||
if (m_currentCursorColumn >= newRowSize) {
|
||||
newColumn = newRowSize - 1;
|
||||
}
|
||||
return cat->rows[newRow][newColumn];
|
||||
}
|
||||
return current;
|
||||
}
|
||||
case MoveLeft: {
|
||||
if (column > 0) {
|
||||
m_currentCursorColumn = column - 1;
|
||||
return cat->rows[row][column - 1];
|
||||
}
|
||||
// TODO: moving to previous line
|
||||
return current;
|
||||
}
|
||||
case MoveRight: {
|
||||
if (column < cat->rows[row].size() - 1) {
|
||||
m_currentCursorColumn = column + 1;
|
||||
return cat->rows[row][column + 1];
|
||||
}
|
||||
// TODO: moving to next line
|
||||
return current;
|
||||
}
|
||||
case MoveHome: {
|
||||
m_currentCursorColumn = 0;
|
||||
return cat->rows[row][0];
|
||||
}
|
||||
case MoveEnd: {
|
||||
auto last = cat->rows[row].size() - 1;
|
||||
m_currentCursorColumn = last;
|
||||
return cat->rows[row][last];
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
int InstanceView::horizontalOffset() const
|
||||
{
|
||||
return horizontalScrollBar()->value();
|
||||
}
|
||||
|
||||
int InstanceView::verticalOffset() const
|
||||
{
|
||||
return verticalScrollBar()->value();
|
||||
}
|
||||
|
||||
void InstanceView::scrollContentsBy(int dx, int dy)
|
||||
{
|
||||
scrollDirtyRegion(dx, dy);
|
||||
viewport()->scroll(dx, dy);
|
||||
}
|
||||
|
||||
void InstanceView::scrollTo(const QModelIndex& index, ScrollHint hint)
|
||||
{
|
||||
if (!index.isValid())
|
||||
return;
|
||||
|
||||
const QRect rect = visualRect(index);
|
||||
if (hint == EnsureVisible && viewport()->rect().contains(rect)) {
|
||||
viewport()->update(rect);
|
||||
return;
|
||||
}
|
||||
|
||||
verticalScrollBar()->setValue(verticalScrollToValue(index, rect, hint));
|
||||
}
|
||||
|
||||
int InstanceView::verticalScrollToValue([[maybe_unused]] const QModelIndex& index, const QRect& rect, QListView::ScrollHint hint) const
|
||||
{
|
||||
const QRect area = viewport()->rect();
|
||||
const bool above = (hint == QListView::EnsureVisible && rect.top() < area.top());
|
||||
const bool below = (hint == QListView::EnsureVisible && rect.bottom() > area.bottom());
|
||||
|
||||
int verticalValue = verticalScrollBar()->value();
|
||||
QRect adjusted = rect.adjusted(-spacing(), -spacing(), spacing(), spacing());
|
||||
if (hint == QListView::PositionAtTop || above)
|
||||
verticalValue += adjusted.top();
|
||||
else if (hint == QListView::PositionAtBottom || below)
|
||||
verticalValue += qMin(adjusted.top(), adjusted.bottom() - area.height() + 1);
|
||||
else if (hint == QListView::PositionAtCenter)
|
||||
verticalValue += adjusted.top() - ((area.height() - adjusted.height()) / 2);
|
||||
return verticalValue;
|
||||
}
|
161
launcher/ui/instanceview/InstanceView.h
Normal file
161
launcher/ui/instanceview/InstanceView.h
Normal file
@ -0,0 +1,161 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QCache>
|
||||
#include <QLineEdit>
|
||||
#include <QListView>
|
||||
#include <QScrollBar>
|
||||
#include <functional>
|
||||
#include "VisualGroup.h"
|
||||
|
||||
struct InstanceViewRoles {
|
||||
enum { GroupRole = Qt::UserRole, ProgressValueRole, ProgressMaximumRole };
|
||||
};
|
||||
|
||||
class InstanceView : public QAbstractItemView {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
InstanceView(QWidget* parent = 0);
|
||||
~InstanceView();
|
||||
|
||||
void setModel(QAbstractItemModel* model) override;
|
||||
|
||||
using visibilityFunction = std::function<bool(const QString&)>;
|
||||
void setSourceOfGroupCollapseStatus(visibilityFunction f) { fVisibility = f; }
|
||||
|
||||
/// 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; };
|
||||
void setPaintCat(bool visible);
|
||||
|
||||
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;
|
||||
|
||||
signals:
|
||||
void droppedURLs(QList<QUrl> urls);
|
||||
void groupStateChanged(QString group, bool collapsed);
|
||||
|
||||
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:
|
||||
friend struct VisualGroup;
|
||||
QList<VisualGroup*> m_groups;
|
||||
|
||||
visibilityFunction fVisibility;
|
||||
|
||||
// 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;
|
||||
bool m_catVisible = false;
|
||||
QPixmap m_catPixmap;
|
||||
|
||||
// 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;
|
||||
};
|
248
launcher/ui/instanceview/VisualGroup.cpp
Normal file
248
launcher/ui/instanceview/VisualGroup.cpp
Normal file
@ -0,0 +1,248 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2023 Tayou <git@tayou.org>
|
||||
*
|
||||
* 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 <QApplication>
|
||||
#include <QDebug>
|
||||
#include <QModelIndex>
|
||||
#include <QPainter>
|
||||
#include <QtMath>
|
||||
#include <utility>
|
||||
|
||||
#include "InstanceView.h"
|
||||
|
||||
VisualGroup::VisualGroup(QString text, InstanceView* view) : view(view), text(std::move(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();
|
||||
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, view->width() - 4, 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) const
|
||||
{
|
||||
QRect optRect = option.rect;
|
||||
optRect.setTop(optRect.top() + 7);
|
||||
QFont font(QApplication::font());
|
||||
font.setBold(true);
|
||||
const QFontMetrics fontMetrics = QFontMetrics(font);
|
||||
painter->setFont(font);
|
||||
|
||||
QPen pen;
|
||||
pen.setWidth(2);
|
||||
QColor penColor = option.palette.text().color();
|
||||
penColor.setAlphaF(0.6);
|
||||
pen.setColor(penColor);
|
||||
painter->setPen(pen);
|
||||
painter->setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
// sizes and offsets, to keep things consistent below
|
||||
int arrowOffsetLeft = fontMetrics.height() / 2 + 7;
|
||||
int textOffsetLeft = arrowOffsetLeft * 2;
|
||||
int arrowSize = 6;
|
||||
int centerHeight = optRect.top() + fontMetrics.height() / 2;
|
||||
|
||||
// BEGIN: arrow
|
||||
{
|
||||
QPolygon arrowPolygon;
|
||||
if (collapsed) {
|
||||
arrowPolygon << QPoint(arrowOffsetLeft - arrowSize / 2, centerHeight - arrowSize)
|
||||
<< QPoint(arrowOffsetLeft + arrowSize / 2, centerHeight)
|
||||
<< QPoint(arrowOffsetLeft - arrowSize / 2, centerHeight + arrowSize);
|
||||
painter->drawPolyline(arrowPolygon);
|
||||
} else {
|
||||
arrowPolygon << QPoint(arrowOffsetLeft - arrowSize, centerHeight - arrowSize / 2)
|
||||
<< QPoint(arrowOffsetLeft, centerHeight + arrowSize / 2)
|
||||
<< QPoint(arrowOffsetLeft + arrowSize, centerHeight - arrowSize / 2);
|
||||
painter->drawPolyline(arrowPolygon);
|
||||
}
|
||||
}
|
||||
// END: arrow
|
||||
|
||||
// BEGIN: text
|
||||
{
|
||||
QRect textRect(optRect);
|
||||
textRect.setTop(textRect.top());
|
||||
textRect.setLeft(textOffsetLeft);
|
||||
textRect.setHeight(fontMetrics.height());
|
||||
textRect.setRight(textRect.right() - 7);
|
||||
|
||||
painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, !text.isEmpty() ? text : QObject::tr("Ungrouped"));
|
||||
}
|
||||
// END: text
|
||||
}
|
||||
|
||||
int VisualGroup::totalHeight() const
|
||||
{
|
||||
return headerHeight() + contentHeight();
|
||||
}
|
||||
|
||||
int VisualGroup::headerHeight()
|
||||
{
|
||||
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 (int)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;
|
||||
}
|
111
launcher/ui/instanceview/VisualGroup.h
Normal file
111
launcher/ui/instanceview/VisualGroup.h
Normal file
@ -0,0 +1,111 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2023 Tayou <git@tayou.org>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QRect>
|
||||
#include <QString>
|
||||
#include <QStyleOption>
|
||||
#include <QVector>
|
||||
|
||||
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(QString text, InstanceView* view);
|
||||
explicit 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) const;
|
||||
|
||||
/// height of the group, in total. includes a small bit of padding.
|
||||
int totalHeight() const;
|
||||
|
||||
/// height of the group header, in pixels
|
||||
static int headerHeight();
|
||||
|
||||
/// 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