Keep part names visible and highlight selected rows

Freeze the Part column with a width cap and full-name tooltips. Synchronize row selection across views, forward wheel scrolling, and avoid the initial cell highlight.

Closes #565
This commit is contained in:
Leonardo Zide committed 2026-10-08 23:33:09 -07:00
1 parent 6d8f778e3d
commit 50aa42f195
3 files changed
+236 -2

No files matched your search

+198
View File
@@ -6,6 +6,202 @@
#include "lc_application.h"
#include "pieceinf.h"
class lcFrozenPartsView : public QTableView
{
public:
explicit lcFrozenPartsView(lcPartsTableWidget* PartsTable)
: QTableView(PartsTable), mPartsTable(PartsTable)
{
}
protected:
QItemSelectionModel::SelectionFlags selectionCommand(const QModelIndex& Index, const QEvent* Event = nullptr) const override
{
return mPartsTable->TrackSelectionCommand(Index, Event, QTableView::selectionCommand(Index, Event));
}
void setSelection(const QRect& Rect, QItemSelectionModel::SelectionFlags Command) override
{
if (!mPartsTable->SelectSharedRange(Command))
QTableView::setSelection(Rect, Command);
}
private:
lcPartsTableWidget* mPartsTable;
};
lcPartsTableWidget::lcPartsTableWidget(QWidget* Parent)
: QTableWidget(Parent)
{
}
void lcPartsTableWidget::FreezePartColumn()
{
mPartColumnWidth = qMax(sizeHintForColumn(0), horizontalHeader()->sectionSizeHint(0));
horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed);
setTextElideMode(Qt::ElideRight);
for (int Row = 0; Row < rowCount(); Row++)
if (QTableWidgetItem* Item = item(Row, 0))
Item->setToolTip(Item->text());
// Both views share the data and selection, so sorting and row highlights stay in sync.
mFrozenColumn = new lcFrozenPartsView(this);
mFrozenColumn->setModel(model());
mFrozenColumn->setSelectionModel(selectionModel());
mFrozenColumn->setSelectionBehavior(QAbstractItemView::SelectRows);
mFrozenColumn->setSelectionMode(selectionMode());
mFrozenColumn->setEditTriggers(QAbstractItemView::NoEditTriggers);
mFrozenColumn->setTextElideMode(Qt::ElideRight);
mFrozenColumn->setFocusPolicy(Qt::NoFocus);
mFrozenColumn->setFocusProxy(this);
mFrozenColumn->setFrameShape(QFrame::NoFrame);
mFrozenColumn->verticalHeader()->hide();
mFrozenColumn->horizontalHeader()->setSectionResizeMode(QHeaderView::Fixed);
mFrozenColumn->horizontalHeader()->setSectionsClickable(true);
mFrozenColumn->horizontalHeader()->setSortIndicatorShown(true);
mFrozenColumn->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
mFrozenColumn->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
mFrozenColumn->viewport()->installEventFilter(this);
mFrozenColumn->horizontalHeader()->viewport()->installEventFilter(this);
setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
mFrozenColumn->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
for (int Column = 1; Column < columnCount(); Column++)
mFrozenColumn->hideColumn(Column);
for (int Row = 0; Row < rowCount(); Row++)
mFrozenColumn->setRowHeight(Row, rowHeight(Row));
connect(verticalScrollBar(), &QScrollBar::valueChanged, mFrozenColumn->verticalScrollBar(), &QScrollBar::setValue);
connect(mFrozenColumn->verticalScrollBar(), &QScrollBar::valueChanged, verticalScrollBar(), &QScrollBar::setValue);
connect(verticalHeader(), &QHeaderView::sectionResized, this, [this](int Row, int, int Height)
{
mFrozenColumn->setRowHeight(Row, Height);
});
connect(horizontalHeader(), &QHeaderView::sectionResized, this, [this](int Column, int, int)
{
if (Column == 0)
UpdateFrozenGeometry();
});
connect(horizontalHeader(), &QHeaderView::sortIndicatorChanged, mFrozenColumn->horizontalHeader(), &QHeaderView::setSortIndicator);
connect(mFrozenColumn->horizontalHeader(), &QHeaderView::sectionClicked, this, [this](int)
{
const Qt::SortOrder Order = horizontalHeader()->sortIndicatorSection() == 0 && horizontalHeader()->sortIndicatorOrder() == Qt::AscendingOrder
? Qt::DescendingOrder : Qt::AscendingOrder;
horizontalHeader()->setSortIndicator(0, Order);
});
mFrozenColumn->horizontalHeader()->setSortIndicator(horizontalHeader()->sortIndicatorSection(), horizontalHeader()->sortIndicatorOrder());
viewport()->stackUnder(mFrozenColumn);
UpdateFrozenGeometry();
mFrozenColumn->show();
}
bool lcPartsTableWidget::eventFilter(QObject* Object, QEvent* Event)
{
if (mFrozenColumn && Event->type() == QEvent::Wheel &&
(Object == mFrozenColumn->viewport() || Object == mFrozenColumn->horizontalHeader()->viewport()))
{
QCoreApplication::sendEvent(viewport(), Event);
return true;
}
return QTableWidget::eventFilter(Object, Event);
}
QItemSelectionModel::SelectionFlags lcPartsTableWidget::TrackSelectionCommand(const QModelIndex& Index, const QEvent* Event, QItemSelectionModel::SelectionFlags Command) const
{
// Qt keeps a separate range anchor in each view, even with a shared selection model.
mSelectionEnd = Index;
if (Command.testFlag(QItemSelectionModel::Current))
{
if (!mSelectionAnchor.isValid())
mSelectionAnchor = currentIndex().isValid() ? currentIndex() : Index;
}
else if (Command != QItemSelectionModel::NoUpdate || (Event && Event->type() == QEvent::MouseButtonPress))
mSelectionAnchor = Index;
return Command;
}
QItemSelectionModel::SelectionFlags lcPartsTableWidget::selectionCommand(const QModelIndex& Index, const QEvent* Event) const
{
return TrackSelectionCommand(Index, Event, QTableWidget::selectionCommand(Index, Event));
}
bool lcPartsTableWidget::SelectSharedRange(QItemSelectionModel::SelectionFlags Command)
{
if (!Command.testFlag(QItemSelectionModel::Current) || !mSelectionAnchor.isValid() || !mSelectionEnd.isValid())
return false;
const int FirstRow = qMin(mSelectionAnchor.row(), mSelectionEnd.row());
const int LastRow = qMax(mSelectionAnchor.row(), mSelectionEnd.row());
selectionModel()->select(QItemSelection(model()->index(FirstRow, 0), model()->index(LastRow, columnCount() - 1)), Command);
return true;
}
void lcPartsTableWidget::setSelection(const QRect& Rect, QItemSelectionModel::SelectionFlags Command)
{
if (!SelectSharedRange(Command))
QTableWidget::setSelection(Rect, Command);
}
void lcPartsTableWidget::UpdateFrozenGeometry()
{
if (!mFrozenColumn)
return;
// Reserve at least half the viewport for counts, while letting short names use less space.
const int PartWidth = qMin(mPartColumnWidth, qMax(1, viewport()->width() / 2));
if (columnWidth(0) != PartWidth)
setColumnWidth(0, PartWidth);
mFrozenColumn->setColumnWidth(0, columnWidth(0));
mFrozenColumn->horizontalHeader()->setFixedHeight(horizontalHeader()->height());
mFrozenColumn->setGeometry(viewport()->geometry().x(), frameWidth(), columnWidth(0), viewport()->height() + horizontalHeader()->height());
}
void lcPartsTableWidget::focusInEvent(QFocusEvent* Event)
{
const bool HasCurrent = currentIndex().isValid();
QTableWidget::focusInEvent(Event);
// Receiving focus should not mark the first cell before the user chooses a row.
if (!HasCurrent)
selectionModel()->clearCurrentIndex();
}
void lcPartsTableWidget::resizeEvent(QResizeEvent* Event)
{
QTableWidget::resizeEvent(Event);
UpdateFrozenGeometry();
}
void lcPartsTableWidget::EnsureCountVisible(const QModelIndex& Index)
{
if (mFrozenColumn && Index.column() > 0 && visualRect(Index).left() < columnWidth(0))
horizontalScrollBar()->setValue(horizontalScrollBar()->value() + visualRect(Index).left() - columnWidth(0));
}
QModelIndex lcPartsTableWidget::moveCursor(CursorAction Action, Qt::KeyboardModifiers Modifiers)
{
const QModelIndex Index = QTableWidget::moveCursor(Action, Modifiers);
EnsureCountVisible(Index);
return Index;
}
void lcPartsTableWidget::scrollTo(const QModelIndex& Index, ScrollHint Hint)
{
if (!mFrozenColumn || Index.column() > 0)
{
QTableWidget::scrollTo(Index, Hint);
EnsureCountVisible(Index);
}
else
mFrozenColumn->scrollTo(Index, Hint);
}
class lcPartsTableWidgetItem : public QTableWidgetItem
{
public:
@@ -138,6 +334,8 @@ lcPropertiesDialog::lcPropertiesDialog(QWidget* Parent, lcPropertiesDialogOption
Item->mLast = true;
Item->setTextAlignment(Qt::AlignCenter);
PartsTable->setItem((int)InfoTotals.size(), ColorCount + 1, Item);
ui->PartsTable->FreezePartColumn();
}
lcPropertiesDialog::~lcPropertiesDialog()
+27 -1
View File
@@ -14,6 +14,33 @@ namespace Ui
class lcPropertiesDialog;
}
class lcPartsTableWidget : public QTableWidget
{
public:
explicit lcPartsTableWidget(QWidget* Parent = nullptr);
void FreezePartColumn();
void scrollTo(const QModelIndex& Index, ScrollHint Hint = EnsureVisible) override;
protected:
bool eventFilter(QObject* Object, QEvent* Event) override;
void focusInEvent(QFocusEvent* Event) override;
void resizeEvent(QResizeEvent* Event) override;
QModelIndex moveCursor(CursorAction Action, Qt::KeyboardModifiers Modifiers) override;
QItemSelectionModel::SelectionFlags selectionCommand(const QModelIndex& Index, const QEvent* Event = nullptr) const override;
void setSelection(const QRect& Rect, QItemSelectionModel::SelectionFlags Command) override;
private:
friend class lcFrozenPartsView;
QItemSelectionModel::SelectionFlags TrackSelectionCommand(const QModelIndex& Index, const QEvent* Event, QItemSelectionModel::SelectionFlags Command) const;
bool SelectSharedRange(QItemSelectionModel::SelectionFlags Command);
void UpdateFrozenGeometry();
void EnsureCountVisible(const QModelIndex& Index);
QTableView* mFrozenColumn = nullptr;
int mPartColumnWidth = 0;
mutable QPersistentModelIndex mSelectionAnchor;
mutable QPersistentModelIndex mSelectionEnd;
};
class lcPropertiesDialog : public QDialog
{
Q_OBJECT
@@ -30,4 +57,3 @@ public slots:
private:
Ui::lcPropertiesDialog* ui;
};
+11 -1
View File
@@ -92,13 +92,16 @@
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QTableWidget" name="PartsTable">
<widget class="lcPartsTableWidget" name="PartsTable">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>75</number>
</attribute>
@@ -134,6 +137,13 @@
<tabstop>CommentsEdit</tabstop>
<tabstop>PartsTable</tabstop>
</tabstops>
<customwidgets>
<customwidget>
<class>lcPartsTableWidget</class>
<extends>QTableWidget</extends>
<header>lc_qpropertiesdialog.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>