mirror of
https://github.com/leozide/leocad.git
synced 2026-10-27 06:20:55 +00:00
Fix part selection highlighting and picker sizing
Highlight the full selected item and preserve thumbnail colors when part names are hidden. Keep cell dimensions stable while thumbnails load and update every entry sharing a completed thumbnail. Use four columns for the default parts grid and fixed popup layouts for each icon size. Resize open popups when icons, names, or list mode change, account for capped widths and menu margins, and keep complete rows visible with room for at least two when needed. Enable part names by default for fresh preferences. Validated with a Qt build, 240 sizing cases, and actual popup checks for constrained widths, name toggling, and menu frames across three Qt styles. Closes #670
This commit is contained in:
1 parent
50aa42f195
commit
ce26574116
5 files changed
+220
-24
No files matched your search
@@ -10,10 +10,12 @@ lcPartSelectionPopup::lcPartSelectionPopup(PieceInfo* InitialPart, QWidget* Pare
|
||||
|
||||
mPartSelectionWidget = new lcPartSelectionWidget(this);
|
||||
Layout->addWidget(mPartSelectionWidget);
|
||||
mPartSelectionWidget->SetOrientation(Qt::Horizontal);
|
||||
|
||||
mPartSelectionWidget->SetDragEnabled(false);
|
||||
|
||||
connect(mPartSelectionWidget, &lcPartSelectionWidget::PartPicked, this, &lcPartSelectionPopup::Accept);
|
||||
connect(mPartSelectionWidget, &lcPartSelectionWidget::LayoutChanged, this, &lcPartSelectionPopup::ResizeToContents, Qt::QueuedConnection);
|
||||
|
||||
QDialogButtonBox* ButtonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
||||
Layout->addWidget(ButtonBox);
|
||||
@@ -22,6 +24,34 @@ lcPartSelectionPopup::lcPartSelectionPopup(PieceInfo* InitialPart, QWidget* Pare
|
||||
QObject::connect(ButtonBox, &QDialogButtonBox::rejected, this, &lcPartSelectionPopup::Reject);
|
||||
}
|
||||
|
||||
QSize lcPartSelectionPopup::GetPartsViewSizeHint(const lcPartSelectionListView* PartsView, const QSize& PopupSize) const
|
||||
{
|
||||
const int WidthOverhead = PopupSize.width() - PartsView->sizeHint().width();
|
||||
const int ViewWidth = qMax(1, qMin(PopupSize.width(), maximumWidth()) - WidthOverhead);
|
||||
return PartsView->GetPreferredSize(ViewWidth);
|
||||
}
|
||||
|
||||
QSize lcPartSelectionPopup::sizeHint() const
|
||||
{
|
||||
QSize Size = QWidget::sizeHint();
|
||||
const lcPartSelectionListView* PartsView = mPartSelectionWidget->findChild<lcPartSelectionListView*>();
|
||||
if (!PartsView || PartsView->GetListModel()->GetIconSize() == lcPartSelectionListView::NoIconSize || PartsView->GetListModel()->IsListMode())
|
||||
return Size;
|
||||
|
||||
const int RowHeight = PartsView->GetCellSize().height();
|
||||
if (RowHeight <= 0)
|
||||
return Size;
|
||||
|
||||
const int Overhead = Size.height() - PartsView->sizeHint().height() + 2 * PartsView->frameWidth();
|
||||
const int ViewHeight = GetPartsViewSizeHint(PartsView, Size).height();
|
||||
const int PreferredHeight = Overhead + ViewHeight - 2 * PartsView->frameWidth();
|
||||
const int AvailableHeight = qMin(PreferredHeight, maximumHeight()) - Overhead;
|
||||
const int Rows = qMax(1, AvailableHeight / RowHeight);
|
||||
Size.setHeight(Overhead + Rows * RowHeight);
|
||||
|
||||
return Size;
|
||||
}
|
||||
|
||||
void lcPartSelectionPopup::showEvent(QShowEvent* ShowEvent)
|
||||
{
|
||||
QWidget::showEvent(ShowEvent);
|
||||
@@ -32,6 +62,42 @@ void lcPartSelectionPopup::showEvent(QShowEvent* ShowEvent)
|
||||
mPartSelectionWidget->FocusPartFilterWidget();
|
||||
}
|
||||
|
||||
void lcPartSelectionPopup::SetHeightLimit(int Height)
|
||||
{
|
||||
mHeightLimit = Height;
|
||||
UpdateHeightLimit();
|
||||
}
|
||||
|
||||
void lcPartSelectionPopup::UpdateHeightLimit()
|
||||
{
|
||||
int Height = mHeightLimit;
|
||||
const lcPartSelectionListView* PartsView = mPartSelectionWidget->findChild<lcPartSelectionListView*>();
|
||||
if (PartsView && PartsView->GetListModel()->GetIconSize() && !PartsView->GetListModel()->IsListMode())
|
||||
{
|
||||
const int RowHeight = PartsView->GetCellSize().height();
|
||||
if (RowHeight > 0)
|
||||
{
|
||||
const QSize PopupSize = QWidget::sizeHint();
|
||||
const int ViewHeight = GetPartsViewSizeHint(PartsView, PopupSize).height();
|
||||
const int Overhead = PopupSize.height() - PartsView->sizeHint().height() + 2 * PartsView->frameWidth();
|
||||
const int Rows = qMin(2, (ViewHeight - 2 * PartsView->frameWidth()) / RowHeight);
|
||||
Height = qMax(Height, Overhead + Rows * RowHeight);
|
||||
}
|
||||
}
|
||||
|
||||
setMaximumHeight(Height);
|
||||
QMenu* Menu = qobject_cast<QMenu*>(parent());
|
||||
if (Menu)
|
||||
{
|
||||
QStyleOption Option;
|
||||
Option.initFrom(Menu);
|
||||
QStyle* Style = Menu->style();
|
||||
const int Margin = Style->pixelMetric(QStyle::PM_MenuPanelWidth, &Option, Menu) + Style->pixelMetric(QStyle::PM_MenuVMargin, &Option, Menu);
|
||||
const QSize MenuSize = Style->sizeFromContents(QStyle::CT_Menu, &Option, QSize(0, Height + 2 * Margin), Menu);
|
||||
Menu->setMaximumHeight(qMin(QWIDGETSIZE_MAX, MenuSize.height()));
|
||||
}
|
||||
}
|
||||
|
||||
void lcPartSelectionPopup::Accept()
|
||||
{
|
||||
mPickedPiece = mPartSelectionWidget->GetCurrentPart();
|
||||
@@ -45,6 +111,40 @@ void lcPartSelectionPopup::Reject()
|
||||
Close();
|
||||
}
|
||||
|
||||
void lcPartSelectionPopup::ResizeToContents()
|
||||
{
|
||||
QMenu* Menu = qobject_cast<QMenu*>(parent());
|
||||
if (!Menu || !Menu->isVisible())
|
||||
return;
|
||||
|
||||
updateGeometry();
|
||||
UpdateHeightLimit();
|
||||
|
||||
// Refresh QMenu's cached widget action geometry as well as its own size.
|
||||
for (QAction* Action : Menu->actions())
|
||||
{
|
||||
QWidgetAction* WidgetAction = qobject_cast<QWidgetAction*>(Action);
|
||||
if (WidgetAction && WidgetAction->defaultWidget() == this)
|
||||
{
|
||||
QActionEvent Event(QEvent::ActionChanged, Action);
|
||||
QCoreApplication::sendEvent(Menu, &Event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (QScreen* Screen : QGuiApplication::screens())
|
||||
{
|
||||
const QRect AvailableGeometry = Screen->availableGeometry();
|
||||
if (!Screen->geometry().contains(Menu->pos()))
|
||||
continue;
|
||||
|
||||
const int X = qBound(AvailableGeometry.left(), Menu->x(), qMax(AvailableGeometry.left(), AvailableGeometry.right() - Menu->width() + 1));
|
||||
const int Y = qBound(AvailableGeometry.top(), Menu->y(), qMax(AvailableGeometry.top(), AvailableGeometry.bottom() - Menu->height() + 1));
|
||||
Menu->move(X, Y);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void lcPartSelectionPopup::Close()
|
||||
{
|
||||
QMenu* Menu = qobject_cast<QMenu*>(parent());
|
||||
@@ -84,8 +184,9 @@ std::optional<PieceInfo*> lcShowPartSelectionPopup(PieceInfo* InitialPart, const
|
||||
{
|
||||
const QSize ScreenSize = PopupScreen->availableGeometry().size();
|
||||
const QSize MaximumSize(ScreenSize.width() * 3 / 5, ScreenSize.height() * 3 / 5);
|
||||
Popup->setMaximumSize(MaximumSize);
|
||||
Menu->setMaximumSize(MaximumSize);
|
||||
Popup->setMaximumWidth(MaximumSize.width());
|
||||
Menu->setMaximumWidth(MaximumSize.width());
|
||||
Popup->SetHeightLimit(MaximumSize.height());
|
||||
}
|
||||
|
||||
Action->setDefaultWidget(Popup);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
class PieceInfo;
|
||||
class lcPartSelectionWidget;
|
||||
class lcPartSelectionListView;
|
||||
|
||||
class lcPartSelectionPopup : public QWidget
|
||||
{
|
||||
@@ -10,6 +11,8 @@ class lcPartSelectionPopup : public QWidget
|
||||
public:
|
||||
lcPartSelectionPopup(PieceInfo* InitialPart, QWidget* Parent);
|
||||
virtual ~lcPartSelectionPopup() = default;
|
||||
QSize sizeHint() const override;
|
||||
void SetHeightLimit(int Height);
|
||||
|
||||
std::optional<PieceInfo*> GetPickedPart() const
|
||||
{
|
||||
@@ -24,15 +27,19 @@ public:
|
||||
protected slots:
|
||||
void Accept();
|
||||
void Reject();
|
||||
void ResizeToContents();
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent* ShowEvent) override;
|
||||
void Close();
|
||||
void UpdateHeightLimit();
|
||||
QSize GetPartsViewSizeHint(const lcPartSelectionListView* PartsView, const QSize& PopupSize) const;
|
||||
|
||||
lcPartSelectionWidget* mPartSelectionWidget = nullptr;
|
||||
PieceInfo* mInitialPart = nullptr;
|
||||
PieceInfo* mPickedPiece = nullptr;
|
||||
bool mAccepted = false;
|
||||
int mHeightLimit = QWIDGETSIZE_MAX;
|
||||
};
|
||||
|
||||
std::optional<PieceInfo*> lcShowPartSelectionPopup(PieceInfo* InitialPart, const std::vector<std::pair<PieceInfo*, std::string>>& CustomParts, int ColorIndex, QWidget* Parent, QPoint Position);
|
||||
@@ -16,6 +16,20 @@
|
||||
|
||||
Q_DECLARE_METATYPE(QList<int>)
|
||||
|
||||
void lcPartSelectionItemDelegate::initStyleOption(QStyleOptionViewItem* Option, const QModelIndex& Index) const
|
||||
{
|
||||
QStyledItemDelegate::initStyleOption(Option, Index);
|
||||
Option->showDecorationSelected = true;
|
||||
|
||||
if (mListModel->GetIconSize())
|
||||
Option->decorationSize = QSize(mListModel->GetIconSize(), mListModel->GetIconSize());
|
||||
|
||||
// Keep the rendered part colors when the style requests a selected icon.
|
||||
const QPixmap Pixmap = qvariant_cast<QPixmap>(Index.data(Qt::DecorationRole));
|
||||
if (!Pixmap.isNull())
|
||||
Option->icon.addPixmap(Pixmap, QIcon::Selected);
|
||||
}
|
||||
|
||||
void lcPartSelectionItemDelegate::paint(QPainter* Painter, const QStyleOptionViewItem& Option, const QModelIndex& Index) const
|
||||
{
|
||||
mListModel->RequestThumbnail(Index.row());
|
||||
@@ -27,12 +41,16 @@ QSize lcPartSelectionItemDelegate::sizeHint(const QStyleOptionViewItem& Option,
|
||||
QSize Size = QStyledItemDelegate::sizeHint(Option, Index);
|
||||
int IconSize = mListModel->GetIconSize();
|
||||
|
||||
if (IconSize)
|
||||
if (IconSize && !mListModel->IsListMode())
|
||||
{
|
||||
QWidget* Widget = (QWidget*)parent();
|
||||
const int PixmapMargin = Widget->style()->pixelMetric(QStyle::PM_FocusFrameHMargin, &Option, Widget) + 1;
|
||||
const QWidget* Widget = Option.widget;
|
||||
if (!Widget)
|
||||
Widget = qobject_cast<QWidget*>(parent());
|
||||
QStyle* Style = Widget ? Widget->style() : QApplication::style();
|
||||
const int PixmapMargin = Style->pixelMetric(QStyle::PM_FocusFrameHMargin, &Option, Widget) + 1;
|
||||
int PixmapWidth = IconSize + 2 * PixmapMargin;
|
||||
Size.setWidth(qMin(PixmapWidth, Size.width()));
|
||||
Size.setWidth(PixmapWidth);
|
||||
Size.setHeight(qMax(PixmapWidth, Size.height()));
|
||||
}
|
||||
|
||||
return Size;
|
||||
@@ -443,8 +461,6 @@ void lcPartSelectionListModel::ThumbnailReady(lcPartThumbnailId ThumbnailId, QPi
|
||||
mParts[PartIndex].Pixmap = Pixmap;
|
||||
|
||||
emit dataChanged(index(PartIndex, 0), index(PartIndex, 0), { Qt::DecorationRole });
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -724,6 +740,7 @@ void lcPartSelectionListView::TogglePartNames()
|
||||
bool Show = !mListModel->GetShowPartNames();
|
||||
mListModel->SetShowPartNames(Show);
|
||||
lcSetProfileInt(LC_PROFILE_PARTS_LIST_NAMES, Show);
|
||||
UpdateItemGeometry();
|
||||
}
|
||||
|
||||
void lcPartSelectionListView::ToggleDecoratedParts()
|
||||
@@ -776,6 +793,7 @@ void lcPartSelectionListView::SetPartFilterType(lcPartFilterType Option)
|
||||
void lcPartSelectionListView::ToggleListMode()
|
||||
{
|
||||
mListModel->ToggleListMode();
|
||||
UpdateItemGeometry();
|
||||
}
|
||||
|
||||
void lcPartSelectionListView::ToggleFixedColor()
|
||||
@@ -810,13 +828,33 @@ void lcPartSelectionListView::SetIconSize(int Size)
|
||||
mListModel->SetIconSize(Size, DeviceScale);
|
||||
UpdateViewMode();
|
||||
|
||||
int Width = Size + 2 * frameWidth() + 6;
|
||||
const int PixmapMargin = style()->pixelMetric(QStyle::PM_FocusFrameHMargin, nullptr, this) + 1;
|
||||
int Width = Size + 2 * frameWidth() + 2 * PixmapMargin;
|
||||
if (verticalScrollBar())
|
||||
Width += verticalScrollBar()->sizeHint().width();
|
||||
int Height = Size + 2 * frameWidth() + 2;
|
||||
int Height = qMax(Size, fontMetrics().height()) + 2 * frameWidth() + 2 * PixmapMargin;
|
||||
if (horizontalScrollBar())
|
||||
Height += horizontalScrollBar()->sizeHint().height();
|
||||
setMinimumSize(mPartSelectionWidget->GetIsPopup() ? QSize(0, 0) : QSize(Width, Height));
|
||||
UpdateItemGeometry();
|
||||
}
|
||||
|
||||
void lcPartSelectionListView::UpdateItemGeometry()
|
||||
{
|
||||
updateGeometry();
|
||||
|
||||
if (mPartSelectionWidget->GetIsPopup())
|
||||
{
|
||||
// Invalidate the layout item caches through the picker splitter.
|
||||
for (QWidget* Widget = parentWidget(); Widget; Widget = Widget->parentWidget())
|
||||
{
|
||||
Widget->updateGeometry();
|
||||
if (Widget == mPartSelectionWidget)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
emit LayoutChanged();
|
||||
}
|
||||
|
||||
void lcPartSelectionListView::startDrag(Qt::DropActions SupportedActions)
|
||||
@@ -841,17 +879,8 @@ void lcPartSelectionListView::startDrag(Qt::DropActions SupportedActions)
|
||||
Drag->exec(Qt::CopyAction);
|
||||
}
|
||||
|
||||
QSize lcPartSelectionListView::sizeHint() const
|
||||
QSize lcPartSelectionListView::GetCellSize() const
|
||||
{
|
||||
if (model()->rowCount() == 0)
|
||||
return QListView::sizeHint();
|
||||
|
||||
if (mListModel->GetIconSize() == NoIconSize)
|
||||
return QSize(500, 350);
|
||||
|
||||
int Columns = 5;
|
||||
int Rows = qMin(4, (model()->rowCount() + Columns - 1) / Columns);
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
QStyleOptionViewItem Option;
|
||||
initViewItemOption(&Option);
|
||||
@@ -859,11 +888,62 @@ QSize lcPartSelectionListView::sizeHint() const
|
||||
QStyleOptionViewItem Option = viewOptions();
|
||||
#endif
|
||||
|
||||
QSize CellSize = itemDelegate()->sizeHint(Option, model()->index(0,0));
|
||||
QSize Size(CellSize.width() * Columns + frameWidth() * 2, CellSize.height() * Rows + frameWidth() * 2);
|
||||
return itemDelegate()->sizeHint(Option, model()->index(0, 0));
|
||||
}
|
||||
|
||||
QSize lcPartSelectionListView::sizeHint() const
|
||||
{
|
||||
return GetPreferredSize(QWIDGETSIZE_MAX);
|
||||
}
|
||||
|
||||
QSize lcPartSelectionListView::GetPreferredSize(int MaximumWidth) const
|
||||
{
|
||||
if (model()->rowCount() == 0 && (mListModel->GetIconSize() == NoIconSize || mListModel->IsListMode()))
|
||||
return QListView::sizeHint();
|
||||
|
||||
if (mListModel->GetIconSize() == NoIconSize)
|
||||
return QSize(500, 350);
|
||||
|
||||
int Columns = mListModel->IsListMode() ? 1 : 4;
|
||||
int MaximumRows = 4;
|
||||
|
||||
QSize CellSize = GetCellSize();
|
||||
if (mPartSelectionWidget->GetIsPopup() && !mListModel->IsListMode())
|
||||
{
|
||||
switch (mListModel->GetIconSize())
|
||||
{
|
||||
case SmallIconSize:
|
||||
Columns = 9;
|
||||
MaximumRows = 5;
|
||||
break;
|
||||
|
||||
case MediumIconSize:
|
||||
Columns = 5;
|
||||
MaximumRows = 4;
|
||||
break;
|
||||
|
||||
case LargeIconSize:
|
||||
Columns = 4;
|
||||
MaximumRows = 3;
|
||||
break;
|
||||
|
||||
case ExtraLargeIconSize:
|
||||
Columns = 3;
|
||||
MaximumRows = 4;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int WidthOverhead = frameWidth() * 2;
|
||||
if (verticalScrollBar())
|
||||
Size += QSize(verticalScrollBar()->width() + 1, 0);
|
||||
WidthOverhead += verticalScrollBar()->sizeHint().width() + 1;
|
||||
|
||||
const int Width = qMin(CellSize.width() * Columns + WidthOverhead, MaximumWidth);
|
||||
if (mPartSelectionWidget->GetIsPopup() && !mListModel->IsListMode() && CellSize.width() > 0)
|
||||
Columns = qMin(Columns, qMax(1, (Width - WidthOverhead) / CellSize.width()));
|
||||
|
||||
int Rows = model()->rowCount() ? qMin(MaximumRows, (model()->rowCount() + Columns - 1) / Columns) : MaximumRows;
|
||||
QSize Size(Width, CellSize.height() * Rows + frameWidth() * 2);
|
||||
|
||||
return Size;
|
||||
}
|
||||
@@ -940,6 +1020,7 @@ lcPartSelectionWidget::lcPartSelectionWidget(QWidget* Parent)
|
||||
setLayout(Layout);
|
||||
|
||||
connect(mPartsWidget, &lcPartSelectionListView::PartPicked, this, &lcPartSelectionWidget::PartViewPartPicked);
|
||||
connect(mPartsWidget, &lcPartSelectionListView::LayoutChanged, this, &lcPartSelectionWidget::LayoutChanged);
|
||||
connect(mPartsWidget->selectionModel(), &QItemSelectionModel::currentChanged, this, &lcPartSelectionWidget::PartViewSelectionChanged);
|
||||
connect(mFilterWidget, &QLineEdit::textChanged, this, &lcPartSelectionWidget::FilterChanged);
|
||||
connect(mCategoriesWidget, &QTreeWidget::currentItemChanged, this, &lcPartSelectionWidget::CategoryChanged);
|
||||
|
||||
@@ -50,6 +50,8 @@ public:
|
||||
QSize sizeHint(const QStyleOptionViewItem& Option, const QModelIndex& Index) const override;
|
||||
|
||||
protected:
|
||||
void initStyleOption(QStyleOptionViewItem* Option, const QModelIndex& Index) const override;
|
||||
|
||||
lcPartSelectionListModel* mListModel;
|
||||
};
|
||||
|
||||
@@ -200,6 +202,8 @@ public:
|
||||
|
||||
void startDrag(Qt::DropActions SupportedActions) override;
|
||||
QSize sizeHint() const override;
|
||||
QSize GetCellSize() const;
|
||||
QSize GetPreferredSize(int MaximumWidth) const;
|
||||
|
||||
void SetCategory(lcPartCategoryType Type, int Index);
|
||||
void SetCustomParts(const std::vector<std::pair<PieceInfo*, std::string>>& Parts, int ColorIndex);
|
||||
@@ -230,6 +234,7 @@ public:
|
||||
|
||||
signals:
|
||||
void PartPicked(PieceInfo* Info);
|
||||
void LayoutChanged();
|
||||
|
||||
public slots:
|
||||
void CustomContextMenuRequested(QPoint Pos);
|
||||
@@ -253,6 +258,7 @@ protected slots:
|
||||
|
||||
protected:
|
||||
void SetIconSize(int Size);
|
||||
void UpdateItemGeometry();
|
||||
void SetPartFilterType(lcPartFilterType Option);
|
||||
void keyPressEvent(QKeyEvent* KeyEvent) override;
|
||||
|
||||
@@ -331,6 +337,7 @@ public:
|
||||
signals:
|
||||
void PartPicked(PieceInfo* Info);
|
||||
void CurrentPartChanged(PieceInfo* Info);
|
||||
void LayoutChanged();
|
||||
|
||||
public slots:
|
||||
void AddToPalette();
|
||||
|
||||
@@ -118,7 +118,7 @@ static lcProfileEntry gProfileEntries[LC_NUM_PROFILE_KEYS] =
|
||||
lcProfileEntry("Settings", "ImageHeight", 720), // LC_PROFILE_IMAGE_HEIGHT
|
||||
lcProfileEntry("Settings", "ImageExtension", ".png"), // LC_PROFILE_IMAGE_EXTENSION
|
||||
lcProfileEntry("Settings", "PartsListIcons", 64), // LC_PROFILE_PARTS_LIST_ICONS
|
||||
lcProfileEntry("Settings", "PartsListNames", 0), // LC_PROFILE_PARTS_LIST_NAMES
|
||||
lcProfileEntry("Settings", "PartsListNames", 1), // LC_PROFILE_PARTS_LIST_NAMES
|
||||
lcProfileEntry("Settings", "PartsListFixedColor", -1), // LC_PROFILE_PARTS_LIST_COLOR
|
||||
lcProfileEntry("Settings", "PartsListColorLocked", false), // LC_PROFILE_PARTS_LIST_COLOR_LOCKED
|
||||
lcProfileEntry("Settings", "PartsListDecorated", 1), // LC_PROFILE_PARTS_LIST_DECORATED
|
||||
|
||||
Reference in new issue
Block a user