mirror of
https://github.com/leozide/leocad.git
synced 2026-10-26 22:10:32 +00:00
Library and project name registries were accessed directly by source discovery, cache processing and UI consumers. Centralize their storage and index operations in a library-owned lcPieceManager, with ordered borrowed snapshots and copied file metadata for worker include lookup. Keep source resolution, reference counts, unload/deletion policy, cache I/O and existing synchronization in lcPiecesLibrary. Preserve project-index collisions, name normalization and reentrant teardown ordering while routing registration changes through the manager. Return the GetParts snapshot by value, name model rename/reservation operations RenameModelPieceData, and centralize manager forward declarations in the PCH. Document the registry boundary and register the new files in qmake. Validation: macOS Qt 6.10 Debug build, whitespace checks and 35 offscreen lifecycle checks, including archive/directory cold and warm caches, source precedence, worker includes, borrowed snapshot lifetimes, rename collisions, project transfer and nested external-project destruction. Successful GPU rendering is not covered by the offscreen checks.
1591 lines
46 KiB
C++
1591 lines
46 KiB
C++
#include "lc_global.h"
|
|
#include "lc_partselectionwidget.h"
|
|
#include "lc_partpalettedialog.h"
|
|
#include "lc_profile.h"
|
|
#include "lc_application.h"
|
|
#include "lc_mainwindow.h"
|
|
#include "lc_library.h"
|
|
#include "lc_thumbnailmanager.h"
|
|
#include "project.h"
|
|
#include "lc_piecedata.h"
|
|
#include "lc_glextensions.h"
|
|
#include "lc_category.h"
|
|
#include "lc_traintrack.h"
|
|
#include "lc_filter.h"
|
|
#include "lc_colors.h"
|
|
|
|
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());
|
|
QStyledItemDelegate::paint(Painter, Option, Index);
|
|
}
|
|
|
|
QSize lcPartSelectionItemDelegate::sizeHint(const QStyleOptionViewItem& Option, const QModelIndex& Index) const
|
|
{
|
|
QSize Size = QStyledItemDelegate::sizeHint(Option, Index);
|
|
int IconSize = mListModel->GetIconSize();
|
|
|
|
if (IconSize && !mListModel->IsListMode())
|
|
{
|
|
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(PixmapWidth);
|
|
Size.setHeight(qMax(PixmapWidth, Size.height()));
|
|
}
|
|
|
|
return Size;
|
|
}
|
|
|
|
lcPartSelectionListModel::lcPartSelectionListModel(QObject* Parent)
|
|
: QAbstractListModel(Parent)
|
|
{
|
|
mListView = (lcPartSelectionListView*)Parent;
|
|
const bool IsPopup = mListView->GetPartSelectionWidget()->GetIsPopup();
|
|
|
|
mIconSize = 0;
|
|
mShowPartNames = lcGetProfileInt(IsPopup ? LC_PROFILE_PARTS_POPUP_NAMES : LC_PROFILE_PARTS_LIST_NAMES);
|
|
mListMode = lcGetProfileInt(IsPopup ? LC_PROFILE_PARTS_POPUP_LISTMODE : LC_PROFILE_PARTS_LIST_LISTMODE);
|
|
mShowDecoratedParts = lcGetProfileInt(IsPopup ? LC_PROFILE_PARTS_POPUP_DECORATED : LC_PROFILE_PARTS_LIST_DECORATED);
|
|
mShowPartAliases = lcGetProfileInt(IsPopup ? LC_PROFILE_PARTS_POPUP_ALIASES : LC_PROFILE_PARTS_LIST_ALIASES);
|
|
mCaseSensitiveFilter = lcGetProfileInt(LC_PROFILE_PARTS_LIST_CASE_SENSITIVE_FILTER);
|
|
mFileNameFilter = lcGetProfileInt(LC_PROFILE_PARTS_LIST_FILE_NAME_FILTER);
|
|
mPartDescriptionFilter = lcGetProfileInt(LC_PROFILE_PARTS_LIST_PART_DESCRIPTION_FILTER);
|
|
mPartFilterType = lcGetProfilePartFilterType();
|
|
|
|
uint ColorCode = IsPopup ? LC_COLOR_NOCOLOR : lcGetProfileUInt(LC_PROFILE_PARTS_LIST_COLOR);
|
|
int LockedFlag = IsPopup ? 0 : lcGetProfileInt(LC_PROFILE_PARTS_LIST_COLOR_LOCKED);
|
|
|
|
if (ColorCode == LC_COLOR_NOCOLOR && LockedFlag == 0)
|
|
{
|
|
mColorIndex = gMainWindow->mColorIndex;
|
|
mColorLocked = false;
|
|
}
|
|
else
|
|
{
|
|
uint ColorCodeValue = (ColorCode == LC_COLOR_NOCOLOR) ? lcGetColorCode(gMainWindow->mColorIndex) : ColorCode;
|
|
|
|
mColorIndex = lcGetColorIndex(ColorCodeValue);
|
|
mColorLocked = (LockedFlag != 0) || (ColorCode != LC_COLOR_NOCOLOR);
|
|
}
|
|
|
|
connect(lcGetPiecesLibrary()->GetThumbnailManager(), &lcThumbnailManager::ThumbnailReady, this, &lcPartSelectionListModel::ThumbnailReady);
|
|
}
|
|
|
|
lcPartSelectionListModel::~lcPartSelectionListModel()
|
|
{
|
|
ReleaseThumbnails();
|
|
}
|
|
|
|
void lcPartSelectionListModel::UpdateThumbnails()
|
|
{
|
|
beginResetModel();
|
|
|
|
ReleaseThumbnails();
|
|
|
|
endResetModel();
|
|
|
|
SetFilter(mFilterString);
|
|
}
|
|
|
|
void lcPartSelectionListModel::SetColorIndex(int ColorIndex)
|
|
{
|
|
if (mColorLocked || ColorIndex == mColorIndex)
|
|
return;
|
|
|
|
mColorIndex = ColorIndex;
|
|
|
|
UpdateThumbnails();
|
|
}
|
|
|
|
void lcPartSelectionListModel::ToggleColorLocked()
|
|
{
|
|
if (mListView->GetPartSelectionWidget()->GetIsPopup())
|
|
return;
|
|
|
|
mColorLocked = !mColorLocked;
|
|
|
|
SetColorIndex(gMainWindow->mColorIndex);
|
|
if (mColorLocked)
|
|
{
|
|
lcSetProfileUInt(LC_PROFILE_PARTS_LIST_COLOR, lcGetColorCode(mColorIndex));
|
|
lcSetProfileInt(LC_PROFILE_PARTS_LIST_COLOR_LOCKED, 1);
|
|
}
|
|
else
|
|
{
|
|
lcSetProfileInt(LC_PROFILE_PARTS_LIST_COLOR_LOCKED, 0);
|
|
lcRemoveProfileKey(LC_PROFILE_PARTS_LIST_COLOR);
|
|
}
|
|
}
|
|
|
|
void lcPartSelectionListModel::ToggleListMode()
|
|
{
|
|
mListMode = !mListMode;
|
|
|
|
mListView->UpdateViewMode();
|
|
lcSetProfileInt(mListView->GetPartSelectionWidget()->GetIsPopup() ? LC_PROFILE_PARTS_POPUP_LISTMODE : LC_PROFILE_PARTS_LIST_LISTMODE, mListMode);
|
|
}
|
|
|
|
void lcPartSelectionListModel::SetCategory(int CategoryIndex)
|
|
{
|
|
beginResetModel();
|
|
|
|
ReleaseThumbnails();
|
|
|
|
lcPiecesLibrary* Library = lcGetPiecesLibrary();
|
|
std::vector<lcPieceData*> SingleParts, GroupedParts;
|
|
|
|
if (CategoryIndex != -1)
|
|
Library->GetCategoryEntries(CategoryIndex, false, SingleParts, GroupedParts);
|
|
else
|
|
{
|
|
SingleParts = Library->GetParts();
|
|
|
|
lcModel* ActiveModel = gMainWindow->GetActiveModel();
|
|
|
|
for (size_t PartIndex = 0; PartIndex < SingleParts.size(); )
|
|
{
|
|
lcPieceData* PieceData = SingleParts[PartIndex];
|
|
|
|
if (!PieceData->IsModel() || !PieceData->GetModel()->IncludesModel(ActiveModel))
|
|
PartIndex++;
|
|
else
|
|
SingleParts.erase(SingleParts.begin() + PartIndex);
|
|
}
|
|
}
|
|
|
|
auto lcPartSortFunc=[](const lcPieceData* a, const lcPieceData* b)
|
|
{
|
|
return strcmp(a->m_strDescription, b->m_strDescription) < 0;
|
|
};
|
|
|
|
std::sort(SingleParts.begin(), SingleParts.end(), lcPartSortFunc);
|
|
|
|
mParts.resize(SingleParts.size());
|
|
|
|
for (size_t PartIndex = 0; PartIndex < SingleParts.size(); PartIndex++)
|
|
mParts[PartIndex].PieceData = SingleParts[PartIndex];
|
|
|
|
endResetModel();
|
|
|
|
SetFilter(mFilterString);
|
|
}
|
|
|
|
void lcPartSelectionListModel::SetModelsCategory()
|
|
{
|
|
beginResetModel();
|
|
|
|
ReleaseThumbnails();
|
|
mParts.clear();
|
|
|
|
const std::vector<std::unique_ptr<lcModel>>& Models = lcGetActiveProject()->GetModels();
|
|
lcModel* ActiveModel = gMainWindow->GetActiveModel();
|
|
|
|
for (const std::unique_ptr<lcModel>& Model : Models)
|
|
if (!Model->IncludesModel(ActiveModel))
|
|
mParts.emplace_back().PieceData = Model->GetPieceData();
|
|
|
|
auto lcPartSortFunc = [](const lcPartSelectionListModelEntry& a, const lcPartSelectionListModelEntry& b)
|
|
{
|
|
return strcmp(a.PieceData->m_strDescription, b.PieceData->m_strDescription) < 0;
|
|
};
|
|
|
|
std::sort(mParts.begin(), mParts.end(), lcPartSortFunc);
|
|
|
|
endResetModel();
|
|
|
|
SetFilter(mFilterString);
|
|
}
|
|
|
|
void lcPartSelectionListModel::SetPaletteCategory(int SetIndex)
|
|
{
|
|
beginResetModel();
|
|
|
|
ReleaseThumbnails();
|
|
mParts.clear();
|
|
|
|
lcPartSelectionWidget* PartSelectionWidget = mListView->GetPartSelectionWidget();
|
|
const std::vector<lcPartPalette>& Palettes = PartSelectionWidget->GetPartPalettes();
|
|
std::vector<lcPieceData*> PartsList = lcGetPiecesLibrary()->GetPartsFromSet(Palettes[SetIndex].Parts);
|
|
|
|
auto lcPartSortFunc = [](const lcPieceData* a, const lcPieceData* b)
|
|
{
|
|
return strcmp(a->m_strDescription, b->m_strDescription) < 0;
|
|
};
|
|
|
|
std::sort(PartsList.begin(), PartsList.end(), lcPartSortFunc);
|
|
|
|
mParts.reserve(PartsList.size());
|
|
|
|
for (lcPieceData* PieceData : PartsList)
|
|
mParts.emplace_back().PieceData = PieceData;
|
|
|
|
endResetModel();
|
|
|
|
SetFilter(mFilterString);
|
|
}
|
|
|
|
void lcPartSelectionListModel::SetCurrentModelCategory()
|
|
{
|
|
beginResetModel();
|
|
|
|
ReleaseThumbnails();
|
|
mParts.clear();
|
|
|
|
lcModel* ActiveModel = gMainWindow->GetActiveModel();
|
|
lcPartsList PartsList;
|
|
|
|
if (ActiveModel)
|
|
ActiveModel->GetPartsList(gDefaultColor, true, true, PartsList);
|
|
|
|
for (const auto& PartIt : PartsList)
|
|
mParts.emplace_back().PieceData = (lcPieceData*)PartIt.first;
|
|
|
|
auto lcPartSortFunc = [](const lcPartSelectionListModelEntry& a, const lcPartSelectionListModelEntry& b)
|
|
{
|
|
return strcmp(a.PieceData->m_strDescription, b.PieceData->m_strDescription) < 0;
|
|
};
|
|
|
|
std::sort(mParts.begin(), mParts.end(), lcPartSortFunc);
|
|
|
|
endResetModel();
|
|
|
|
SetFilter(mFilterString);
|
|
}
|
|
|
|
void lcPartSelectionListModel::SetCustomParts(const std::vector<std::pair<lcPieceData*, std::string>>& Parts, int ColorIndex)
|
|
{
|
|
beginResetModel();
|
|
|
|
mColorLocked = true;
|
|
mColorIndex = ColorIndex;
|
|
|
|
ReleaseThumbnails();
|
|
mParts.clear();
|
|
|
|
for (const auto& [PieceData, Description] : Parts)
|
|
{
|
|
lcPartSelectionListModelEntry& Entry = mParts.emplace_back();
|
|
|
|
Entry.PieceData = PieceData;
|
|
Entry.Description = Description;
|
|
|
|
if (PieceData && PieceData->GetTrainTrackInfo())
|
|
Entry.ColorIndex = mColorIndex;
|
|
}
|
|
|
|
endResetModel();
|
|
|
|
SetFilter(mFilterString);
|
|
}
|
|
|
|
void lcPartSelectionListModel::SetFilter(const QString& FilterString)
|
|
{
|
|
mFilterString = FilterString;
|
|
|
|
lcFilter Filter(FilterString.toStdString());
|
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
|
QRegularExpression::PatternOption PatternOptions = mCaseSensitiveFilter ? QRegularExpression::NoPatternOption : QRegularExpression::CaseInsensitiveOption;
|
|
QRegularExpression FilterRx = QRegularExpression(FilterString, PatternOptions);
|
|
#else
|
|
Qt::CaseSensitivity CaseSensitive = mCaseSensitiveFilter ? Qt::CaseSensitive : Qt::CaseInsensitive;
|
|
QRegExp FilterRx(FilterString, CaseSensitive, QRegExp::RegExp2);
|
|
#endif
|
|
|
|
for (size_t PartIdx = 0; PartIdx < mParts.size(); PartIdx++)
|
|
{
|
|
lcPieceData* PieceData = mParts[PartIdx].PieceData;
|
|
bool Visible = true;
|
|
|
|
if (!PieceData)
|
|
Visible = true;
|
|
else if (!mShowDecoratedParts && PieceData->IsPatterned() && !PieceData->IsProjectPiece())
|
|
Visible = false;
|
|
else if (!mShowPartAliases && PieceData->m_strDescription[0] == '=')
|
|
Visible = false;
|
|
else if (mPartFilterType == lcPartFilterType::Default)
|
|
{
|
|
const char* Description = mParts[PartIdx].Description.empty() ? PieceData->m_strDescription : mParts[PartIdx].Description.c_str();
|
|
|
|
Visible = (mPartDescriptionFilter && Filter.Match(Description)) || (mFileNameFilter && Filter.Match(PieceData->mFileName));
|
|
}
|
|
else if (mPartFilterType == lcPartFilterType::RegularExpression)
|
|
{
|
|
QString Description = mParts[PartIdx].Description.empty() ? QString(PieceData->m_strDescription) : QString::fromStdString(mParts[PartIdx].Description);
|
|
|
|
Visible = (mPartDescriptionFilter && Description.contains(FilterRx)) || (mFileNameFilter && QString(PieceData->mFileName).contains(FilterRx));
|
|
}
|
|
|
|
mListView->setRowHidden((int)PartIdx, !Visible);
|
|
}
|
|
}
|
|
|
|
int lcPartSelectionListModel::rowCount(const QModelIndex& Parent) const
|
|
{
|
|
Q_UNUSED(Parent);
|
|
|
|
return (int)mParts.size();
|
|
}
|
|
|
|
QVariant lcPartSelectionListModel::data(const QModelIndex& Index, int Role) const
|
|
{
|
|
size_t InfoIndex = Index.row();
|
|
|
|
if (Index.isValid() && InfoIndex < mParts.size())
|
|
{
|
|
lcPieceData* PieceData = mParts[InfoIndex].PieceData;
|
|
|
|
switch (Role)
|
|
{
|
|
case Qt::DisplayRole:
|
|
if (!mIconSize || mShowPartNames || mListMode)
|
|
return QVariant(mParts[InfoIndex].Description.empty() ? QString::fromLatin1(PieceData->m_strDescription) : QString::fromStdString(mParts[InfoIndex].Description));
|
|
break;
|
|
|
|
case Qt::ToolTipRole:
|
|
if (PieceData)
|
|
{
|
|
const QString Error = lcGetPiecesLibrary()->GetPieceDataLoadError(PieceData);
|
|
|
|
if (!Error.isEmpty())
|
|
return QVariant(Error);
|
|
|
|
if (PieceData->mState == lcAssetState::Failed)
|
|
return QVariant(tr("Could not load part %1.").arg(QString::fromLatin1(PieceData->mFileName)));
|
|
|
|
return QVariant(QString("%1 (%2)").arg(QString::fromLatin1(PieceData->m_strDescription), QString::fromLatin1(PieceData->mFileName)));
|
|
}
|
|
break;
|
|
|
|
case Qt::DecorationRole:
|
|
if (mIconSize && !mParts[InfoIndex].Pixmap.isNull())
|
|
return QVariant(mParts[InfoIndex].Pixmap);
|
|
else
|
|
return QVariant(QColor(0, 0, 0, 0));
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return QVariant();
|
|
}
|
|
|
|
QVariant lcPartSelectionListModel::headerData(int Section, Qt::Orientation Orientation, int Role) const
|
|
{
|
|
Q_UNUSED(Section);
|
|
Q_UNUSED(Orientation);
|
|
|
|
return Role == Qt::DisplayRole ? QVariant(QLatin1String("Image")) : QVariant();
|
|
}
|
|
|
|
Qt::ItemFlags lcPartSelectionListModel::flags(const QModelIndex& Index) const
|
|
{
|
|
Qt::ItemFlags DefaultFlags = QAbstractListModel::flags(Index);
|
|
|
|
if (Index.isValid())
|
|
return Qt::ItemIsDragEnabled | DefaultFlags;
|
|
else
|
|
return DefaultFlags;
|
|
}
|
|
|
|
QModelIndex lcPartSelectionListModel::GetPieceDataIndex(lcPieceData* PieceData) const
|
|
{
|
|
if (PieceData)
|
|
{
|
|
for (int PartIndex = 0; PartIndex < static_cast<int>(mParts.size()); PartIndex++)
|
|
if (mParts[PartIndex].PieceData == PieceData)
|
|
return index(PartIndex, 0);
|
|
}
|
|
|
|
return QModelIndex();
|
|
}
|
|
|
|
void lcPartSelectionListModel::ReleaseThumbnails()
|
|
{
|
|
lcThumbnailManager* ThumbnailManager = lcGetPiecesLibrary()->GetThumbnailManager();
|
|
|
|
for (lcPartSelectionListModelEntry& Part : mParts)
|
|
{
|
|
ThumbnailManager->ReleaseThumbnail(Part.ThumbnailId);
|
|
Part.ThumbnailId = lcPartThumbnailId::Invalid;
|
|
Part.Pixmap = QPixmap();
|
|
}
|
|
}
|
|
|
|
void lcPartSelectionListModel::RequestThumbnail(int PartIndex)
|
|
{
|
|
if (!mIconSize || !mParts[PartIndex].Pixmap.isNull() || mParts[PartIndex].ThumbnailId != lcPartThumbnailId::Invalid)
|
|
return;
|
|
|
|
lcPieceData* PieceData = mParts[PartIndex].PieceData;
|
|
|
|
if (!PieceData)
|
|
return;
|
|
|
|
int ColorIndex = mParts[PartIndex].ColorIndex == -1 ? mColorIndex : mParts[PartIndex].ColorIndex;
|
|
|
|
auto [ThumbnailId, Thumbnail] = lcGetPiecesLibrary()->GetThumbnailManager()->RequestThumbnail(PieceData, ColorIndex, mIconSize, mDeviceScale);
|
|
|
|
mParts[PartIndex].ThumbnailId = ThumbnailId;
|
|
|
|
if (!Thumbnail.isNull())
|
|
{
|
|
mParts[PartIndex].Pixmap = Thumbnail;
|
|
|
|
emit dataChanged(index(PartIndex, 0), index(PartIndex, 0), { Qt::DecorationRole });
|
|
}
|
|
}
|
|
|
|
void lcPartSelectionListModel::ThumbnailReady(lcPartThumbnailId ThumbnailId, QPixmap Pixmap)
|
|
{
|
|
for (int PartIndex = 0; PartIndex < static_cast<int>(mParts.size()); PartIndex++)
|
|
{
|
|
if (mParts[PartIndex].ThumbnailId == ThumbnailId)
|
|
{
|
|
mParts[PartIndex].Pixmap = Pixmap;
|
|
|
|
emit dataChanged(index(PartIndex, 0), index(PartIndex, 0), { Qt::DecorationRole });
|
|
}
|
|
}
|
|
}
|
|
|
|
void lcPartSelectionListModel::SetShowDecoratedParts(bool Show)
|
|
{
|
|
if (Show == mShowDecoratedParts)
|
|
return;
|
|
|
|
mShowDecoratedParts = Show;
|
|
|
|
SetFilter(mFilterString);
|
|
}
|
|
|
|
void lcPartSelectionListModel::SetShowPartAliases(bool Show)
|
|
{
|
|
if (Show == mShowPartAliases)
|
|
return;
|
|
|
|
mShowPartAliases = Show;
|
|
|
|
SetFilter(mFilterString);
|
|
}
|
|
|
|
void lcPartSelectionListModel::SetPartFilterType(lcPartFilterType Option)
|
|
{
|
|
if (Option == mPartFilterType)
|
|
return;
|
|
|
|
mPartFilterType = Option;
|
|
|
|
SetFilter(mFilterString);
|
|
}
|
|
|
|
void lcPartSelectionListModel::SetCaseSensitiveFilter(bool Option)
|
|
{
|
|
if (Option == mCaseSensitiveFilter)
|
|
return;
|
|
|
|
mCaseSensitiveFilter = Option;
|
|
|
|
SetFilter(mFilterString);
|
|
}
|
|
|
|
void lcPartSelectionListModel::SetFileNameFilter(bool Option)
|
|
{
|
|
if (Option == mFileNameFilter)
|
|
return;
|
|
|
|
mFileNameFilter = Option;
|
|
|
|
SetFilter(mFilterString);
|
|
}
|
|
|
|
void lcPartSelectionListModel::SetPartDescriptionFilter(bool Option)
|
|
{
|
|
if (Option == mPartDescriptionFilter)
|
|
return;
|
|
|
|
mPartDescriptionFilter = Option;
|
|
|
|
SetFilter(mFilterString);
|
|
}
|
|
|
|
void lcPartSelectionListModel::SetIconSize(int Size, float DeviceScale)
|
|
{
|
|
if (Size == mIconSize && DeviceScale == mDeviceScale)
|
|
return;
|
|
|
|
mIconSize = Size;
|
|
mDeviceScale = DeviceScale;
|
|
|
|
beginResetModel();
|
|
|
|
ReleaseThumbnails();
|
|
|
|
endResetModel();
|
|
|
|
SetFilter(mFilterString);
|
|
}
|
|
|
|
void lcPartSelectionListModel::SetShowPartNames(bool Show)
|
|
{
|
|
if (Show == mShowPartNames)
|
|
return;
|
|
|
|
mShowPartNames = Show;
|
|
|
|
beginResetModel();
|
|
endResetModel();
|
|
|
|
SetFilter(mFilterString);
|
|
}
|
|
|
|
lcPartSelectionListView::lcPartSelectionListView(QWidget* Parent, lcPartSelectionWidget* PartSelectionWidget)
|
|
: QListView(Parent), mPartSelectionWidget(PartSelectionWidget)
|
|
{
|
|
setUniformItemSizes(true);
|
|
setResizeMode(QListView::Adjust);
|
|
setWordWrap(false);
|
|
setDragEnabled(true);
|
|
setContextMenuPolicy(Qt::CustomContextMenu);
|
|
setAutoScroll(false);
|
|
setTextElideMode(Qt::ElideMiddle);
|
|
|
|
mListModel = new lcPartSelectionListModel(this);
|
|
setModel(mListModel);
|
|
lcPartSelectionItemDelegate* ItemDelegate = new lcPartSelectionItemDelegate(this, mListModel);
|
|
setItemDelegate(ItemDelegate);
|
|
|
|
connect(this, &QListView::doubleClicked, this, &lcPartSelectionListView::DoubleClicked);
|
|
connect(this, &QListView::customContextMenuRequested, this, &lcPartSelectionListView::CustomContextMenuRequested);
|
|
|
|
connect(gApplication, &lcApplication::RendererInitialized, this, &lcPartSelectionListView::UpdateIconSize);
|
|
|
|
mRequestedIconSize = lcGetProfileInt(mPartSelectionWidget->GetIsPopup() ? LC_PROFILE_PARTS_POPUP_ICONS : LC_PROFILE_PARTS_LIST_ICONS);
|
|
UpdateIconSize();
|
|
}
|
|
|
|
void lcPartSelectionListView::keyPressEvent(QKeyEvent* KeyEvent)
|
|
{
|
|
if (KeyEvent->key() == Qt::Key_Enter)
|
|
{
|
|
lcPieceData* PieceData = GetCurrentPart();
|
|
|
|
emit PartPicked(PieceData);
|
|
|
|
return;
|
|
}
|
|
|
|
return QListView::keyPressEvent(KeyEvent);
|
|
}
|
|
|
|
void lcPartSelectionListView::DoubleClicked(const QModelIndex& Index)
|
|
{
|
|
lcPieceData* PieceData = GetListModel()->GetPieceData(Index.row());
|
|
|
|
emit PartPicked(PieceData);
|
|
}
|
|
|
|
void lcPartSelectionListView::CustomContextMenuRequested(QPoint Pos)
|
|
{
|
|
if (!mPartSelectionWidget || mPartSelectionWidget->GetIsPopup())
|
|
return;
|
|
|
|
QMenu* Menu = new QMenu(this);
|
|
|
|
QModelIndex Index = indexAt(Pos);
|
|
mContextPieceData = Index.isValid() ? mListModel->GetPieceData(Index.row()) : nullptr;
|
|
|
|
QMenu* SetMenu = Menu->addMenu(tr("Add to Palette"));
|
|
|
|
const std::vector<lcPartPalette>& Palettes = mPartSelectionWidget->GetPartPalettes();
|
|
|
|
if (!Palettes.empty())
|
|
{
|
|
for (const lcPartPalette& Palette : Palettes)
|
|
SetMenu->addAction(Palette.Name, mPartSelectionWidget, &lcPartSelectionWidget::AddToPalette);
|
|
}
|
|
else
|
|
{
|
|
QAction* Action = SetMenu->addAction(tr("None"));
|
|
Action->setEnabled(false);
|
|
}
|
|
|
|
QAction* RemoveAction = Menu->addAction(tr("Remove from Palette"), mPartSelectionWidget, &lcPartSelectionWidget::RemoveFromPalette);
|
|
RemoveAction->setEnabled(mCategoryType == lcPartCategoryType::Palette);
|
|
|
|
if (mContextPieceData && mContextPieceData->IsModel())
|
|
{
|
|
Menu->addSeparator();
|
|
Menu->addAction(tr("Open Submodel"), mPartSelectionWidget, &lcPartSelectionWidget::OpenSubmodel);
|
|
}
|
|
|
|
Menu->exec(viewport()->mapToGlobal(Pos));
|
|
delete Menu;
|
|
}
|
|
|
|
void lcPartSelectionListView::SetCategory(lcPartCategoryType Type, int Index)
|
|
{
|
|
QModelIndex CurrentIndex = currentIndex();
|
|
|
|
if (CurrentIndex.isValid())
|
|
mLastCategoryRow[{mCategoryType, mCategoryIndex}] = CurrentIndex.row();
|
|
|
|
mCategoryType = Type;
|
|
mCategoryIndex = Index;
|
|
|
|
switch (Type)
|
|
{
|
|
case lcPartCategoryType::AllParts:
|
|
mListModel->SetCategory(-1);
|
|
break;
|
|
case lcPartCategoryType::PartsInUse:
|
|
mListModel->SetCurrentModelCategory();
|
|
break;
|
|
case lcPartCategoryType::Submodels:
|
|
mListModel->SetModelsCategory();
|
|
break;
|
|
case lcPartCategoryType::Palette:
|
|
mListModel->SetPaletteCategory(Index);
|
|
break;
|
|
case lcPartCategoryType::Category:
|
|
mListModel->SetCategory(Index);
|
|
break;
|
|
case lcPartCategoryType::Custom:
|
|
case lcPartCategoryType::Count:
|
|
break;
|
|
}
|
|
|
|
auto CurrentIt = mLastCategoryRow.find({mCategoryType, mCategoryIndex});
|
|
bool ScrollToTop = true;
|
|
|
|
if (CurrentIt != mLastCategoryRow.end())
|
|
{
|
|
CurrentIndex = mListModel->index(CurrentIt->second, 0);
|
|
|
|
if (!isRowHidden(CurrentIndex.row()))
|
|
{
|
|
scrollTo(CurrentIndex);
|
|
setCurrentIndex(CurrentIndex);
|
|
ScrollToTop = false;
|
|
}
|
|
}
|
|
|
|
if (ScrollToTop)
|
|
{
|
|
scrollToTop();
|
|
setCurrentIndex(indexAt(QPoint(0, 0)));
|
|
}
|
|
}
|
|
|
|
void lcPartSelectionListView::SetCustomParts(const std::vector<std::pair<lcPieceData*, std::string>>& Parts, int ColorIndex)
|
|
{
|
|
mCategoryType = lcPartCategoryType::Custom;
|
|
|
|
mListModel->SetCustomParts(Parts, ColorIndex);
|
|
|
|
setCurrentIndex(mListModel->index(0, 0));
|
|
}
|
|
|
|
void lcPartSelectionListView::SetCurrentPart(lcPieceData* PieceData)
|
|
{
|
|
QModelIndex Index = mListModel->GetPieceDataIndex(PieceData);
|
|
|
|
if (Index.isValid())
|
|
{
|
|
setCurrentIndex(Index);
|
|
scrollTo(Index, QAbstractItemView::EnsureVisible);
|
|
}
|
|
}
|
|
|
|
void lcPartSelectionListView::SetNoIcons()
|
|
{
|
|
SetIconSize(NoIconSize);
|
|
}
|
|
|
|
void lcPartSelectionListView::SetSmallIcons()
|
|
{
|
|
SetIconSize(SmallIconSize);
|
|
}
|
|
|
|
void lcPartSelectionListView::SetMediumIcons()
|
|
{
|
|
SetIconSize(MediumIconSize);
|
|
}
|
|
|
|
void lcPartSelectionListView::SetLargeIcons()
|
|
{
|
|
SetIconSize(LargeIconSize);
|
|
}
|
|
|
|
void lcPartSelectionListView::SetExtraLargeIcons()
|
|
{
|
|
SetIconSize(ExtraLargeIconSize);
|
|
}
|
|
|
|
void lcPartSelectionListView::TogglePartNames()
|
|
{
|
|
bool Show = !mListModel->GetShowPartNames();
|
|
mListModel->SetShowPartNames(Show);
|
|
lcSetProfileInt(mPartSelectionWidget->GetIsPopup() ? LC_PROFILE_PARTS_POPUP_NAMES : LC_PROFILE_PARTS_LIST_NAMES, Show);
|
|
UpdateItemGeometry();
|
|
}
|
|
|
|
void lcPartSelectionListView::ToggleDecoratedParts()
|
|
{
|
|
bool Show = !mListModel->GetShowDecoratedParts();
|
|
mListModel->SetShowDecoratedParts(Show);
|
|
lcSetProfileInt(mPartSelectionWidget->GetIsPopup() ? LC_PROFILE_PARTS_POPUP_DECORATED : LC_PROFILE_PARTS_LIST_DECORATED, Show);
|
|
}
|
|
|
|
void lcPartSelectionListView::TogglePartAliases()
|
|
{
|
|
bool Show = !mListModel->GetShowPartAliases();
|
|
mListModel->SetShowPartAliases(Show);
|
|
lcSetProfileInt(mPartSelectionWidget->GetIsPopup() ? LC_PROFILE_PARTS_POPUP_ALIASES : LC_PROFILE_PARTS_LIST_ALIASES, Show);
|
|
}
|
|
|
|
void lcPartSelectionListView::ToggleRegularExpressionFilter()
|
|
{
|
|
bool Option = mListModel->GetPartFilterType() == lcPartFilterType::RegularExpression;
|
|
SetPartFilterType(Option ? lcPartFilterType::Default : lcPartFilterType::RegularExpression);
|
|
}
|
|
|
|
void lcPartSelectionListView::ToggleCaseSensitiveFilter()
|
|
{
|
|
bool Option = !mListModel->GetCaseSensitiveFilter();
|
|
mListModel->SetCaseSensitiveFilter(Option);
|
|
lcSetProfileInt(LC_PROFILE_PARTS_LIST_CASE_SENSITIVE_FILTER, Option);
|
|
}
|
|
|
|
void lcPartSelectionListView::ToggleFileNameFilter()
|
|
{
|
|
bool Option = !mListModel->GetFileNameFilter();
|
|
mListModel->SetFileNameFilter(Option);
|
|
lcSetProfileInt(LC_PROFILE_PARTS_LIST_FILE_NAME_FILTER, Option);
|
|
}
|
|
|
|
void lcPartSelectionListView::TogglePartDescriptionFilter()
|
|
{
|
|
bool Option = !mListModel->GetPartDescriptionFilter();
|
|
mListModel->SetPartDescriptionFilter(Option);
|
|
lcSetProfileInt(LC_PROFILE_PARTS_LIST_PART_DESCRIPTION_FILTER, Option);
|
|
}
|
|
|
|
void lcPartSelectionListView::SetPartFilterType(lcPartFilterType Option)
|
|
{
|
|
mListModel->SetPartFilterType(Option);
|
|
lcSetProfilePartFilterType(Option);
|
|
}
|
|
|
|
void lcPartSelectionListView::ToggleListMode()
|
|
{
|
|
mListModel->ToggleListMode();
|
|
UpdateItemGeometry();
|
|
}
|
|
|
|
void lcPartSelectionListView::ToggleFixedColor()
|
|
{
|
|
mListModel->ToggleColorLocked();
|
|
}
|
|
|
|
void lcPartSelectionListView::UpdateInUseCategory()
|
|
{
|
|
if (mCategoryType == lcPartCategoryType::PartsInUse)
|
|
mListModel->SetCurrentModelCategory();
|
|
}
|
|
|
|
void lcPartSelectionListView::UpdateViewMode()
|
|
{
|
|
setViewMode(mListModel->GetIconSize() && !mListModel->IsListMode() ? QListView::IconMode : QListView::ListMode);
|
|
setWordWrap(mListModel->IsListMode());
|
|
setDragEnabled(true);
|
|
}
|
|
|
|
void lcPartSelectionListView::SetIconSize(int Size)
|
|
{
|
|
mRequestedIconSize = Size;
|
|
lcSetProfileInt(mPartSelectionWidget->GetIsPopup() ? LC_PROFILE_PARTS_POPUP_ICONS : LC_PROFILE_PARTS_LIST_ICONS, Size);
|
|
UpdateIconSize();
|
|
}
|
|
|
|
void lcPartSelectionListView::UpdateIconSize()
|
|
{
|
|
const int Size = gSupportsFramebufferObject ? mRequestedIconSize : NoIconSize;
|
|
setIconSize(QSize(Size, Size));
|
|
|
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
|
float DeviceScale = devicePixelRatioF();
|
|
#else
|
|
float DeviceScale = devicePixelRatio();
|
|
#endif
|
|
|
|
mListModel->SetIconSize(Size, DeviceScale);
|
|
UpdateViewMode();
|
|
|
|
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 = 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)
|
|
{
|
|
Q_UNUSED(SupportedActions);
|
|
|
|
lcPieceData* PieceData = GetCurrentPart();
|
|
|
|
if (!PieceData)
|
|
return;
|
|
|
|
QByteArray ItemData;
|
|
QDataStream DataStream(&ItemData, QIODevice::WriteOnly);
|
|
DataStream << QString(PieceData->mFileName);
|
|
|
|
QMimeData* MimeData = new QMimeData;
|
|
MimeData->setData("application/vnd.leocad-part", ItemData);
|
|
|
|
QDrag* Drag = new QDrag(this);
|
|
Drag->setMimeData(MimeData);
|
|
|
|
Drag->exec(Qt::CopyAction);
|
|
}
|
|
|
|
QSize lcPartSelectionListView::GetCellSize() const
|
|
{
|
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
|
QStyleOptionViewItem Option;
|
|
initViewItemOption(&Option);
|
|
#else
|
|
QStyleOptionViewItem Option = viewOptions();
|
|
#endif
|
|
|
|
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())
|
|
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;
|
|
}
|
|
|
|
lcPartSelectionWidget::lcPartSelectionWidget(bool IsPopup, QWidget* Parent)
|
|
: QWidget(Parent), mIsPopup(IsPopup)
|
|
{
|
|
mSplitter = new QSplitter(this);
|
|
mSplitter->setOrientation(Qt::Vertical);
|
|
mSplitter->setChildrenCollapsible(false);
|
|
|
|
QWidget* CategoriesGroupWidget = new QWidget(mSplitter);
|
|
|
|
QVBoxLayout* CategoriesLayout = new QVBoxLayout();
|
|
CategoriesLayout->setContentsMargins(0, 0, 0, 0);
|
|
CategoriesGroupWidget->setLayout(CategoriesLayout);
|
|
|
|
QHBoxLayout* FilterCategoriesLayout = new QHBoxLayout();
|
|
FilterCategoriesLayout->setContentsMargins(0, 0, 0, 0);
|
|
CategoriesLayout->addLayout(FilterCategoriesLayout);
|
|
|
|
mFilterCategoriesWidget = new QLineEdit(CategoriesGroupWidget);
|
|
mFilterCategoriesWidget->setPlaceholderText(tr("Filter Categories"));
|
|
FilterCategoriesLayout->addWidget(mFilterCategoriesWidget);
|
|
|
|
mFilterCaseAction = new QAction();
|
|
mFilterCaseAction->setIcon(QIcon(":/resources/case.png"));
|
|
mFilterCaseAction->setToolTip(tr("Match Case"));
|
|
mFilterCaseAction->setCheckable(true);
|
|
mFilterCaseAction->setChecked(false);
|
|
connect(mFilterCaseAction, &QAction::triggered, this, &lcPartSelectionWidget::FilterCaseTriggered);
|
|
|
|
QToolButton* FilterCaseButton = new QToolButton();
|
|
FilterCaseButton->setDefaultAction(mFilterCaseAction);
|
|
FilterCategoriesLayout->addWidget(FilterCaseButton);
|
|
|
|
mCategoriesWidget = new QTreeWidget(mSplitter);
|
|
mCategoriesWidget->setHeaderHidden(true);
|
|
mCategoriesWidget->setUniformRowHeights(true);
|
|
mCategoriesWidget->setRootIsDecorated(false);
|
|
|
|
CategoriesLayout->addWidget(mCategoriesWidget);
|
|
|
|
QWidget* PartsGroupWidget = new QWidget(mSplitter);
|
|
|
|
QVBoxLayout* PartsLayout = new QVBoxLayout();
|
|
PartsLayout->setContentsMargins(0, 0, 0, 0);
|
|
PartsGroupWidget->setLayout(PartsLayout);
|
|
|
|
QHBoxLayout* SearchLayout = new QHBoxLayout();
|
|
SearchLayout->setContentsMargins(0, 0, 0, 0);
|
|
PartsLayout->addLayout(SearchLayout);
|
|
|
|
mFilterWidget = new QLineEdit(PartsGroupWidget);
|
|
mFilterWidget->setPlaceholderText(tr("Filter Pieces"));
|
|
SearchLayout->addWidget(mFilterWidget);
|
|
|
|
QToolButton* OptionsButton = new QToolButton();
|
|
OptionsButton->setIcon(QIcon(":/resources/gear_in.png"));
|
|
OptionsButton->setToolTip(tr("Options"));
|
|
OptionsButton->setPopupMode(QToolButton::InstantPopup);
|
|
SearchLayout->addWidget(OptionsButton);
|
|
|
|
QMenu* OptionsMenu = new QMenu(this);
|
|
OptionsButton->setMenu(OptionsMenu);
|
|
connect(OptionsMenu, &QMenu::aboutToShow, this, &lcPartSelectionWidget::OptionsMenuAboutToShow);
|
|
|
|
mPartsWidget = new lcPartSelectionListView(PartsGroupWidget, this);
|
|
if (mIsPopup)
|
|
mPartsWidget->setMinimumSize(0, 0);
|
|
PartsLayout->addWidget(mPartsWidget);
|
|
|
|
QHBoxLayout* Layout = new QHBoxLayout(this);
|
|
Layout->setContentsMargins(0, 0, 0, 0);
|
|
Layout->addWidget(mSplitter);
|
|
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);
|
|
connect(mFilterCategoriesWidget, &QLineEdit::textChanged, this, &lcPartSelectionWidget::FilterCategoriesChanged);
|
|
|
|
LoadPartPalettes();
|
|
UpdateCategories();
|
|
|
|
mSplitter->setStretchFactor(0, 0);
|
|
mSplitter->setStretchFactor(1, 1);
|
|
}
|
|
|
|
bool lcPartSelectionWidget::event(QEvent* Event)
|
|
{
|
|
if (Event->type() == QEvent::ShortcutOverride)
|
|
{
|
|
QKeyEvent* KeyEvent = (QKeyEvent*)Event;
|
|
int Key = KeyEvent->key();
|
|
|
|
if (KeyEvent->modifiers() == Qt::NoModifier && Key >= Qt::Key_A && Key <= Qt::Key_Z)
|
|
Event->accept();
|
|
|
|
switch (Key)
|
|
{
|
|
case Qt::Key_Down:
|
|
case Qt::Key_Up:
|
|
case Qt::Key_Left:
|
|
case Qt::Key_Right:
|
|
case Qt::Key_Home:
|
|
case Qt::Key_End:
|
|
case Qt::Key_PageUp:
|
|
case Qt::Key_PageDown:
|
|
case Qt::Key_Asterisk:
|
|
case Qt::Key_Plus:
|
|
case Qt::Key_Minus:
|
|
Event->accept();
|
|
break;
|
|
}
|
|
}
|
|
|
|
return QWidget::event(Event);
|
|
}
|
|
|
|
void lcPartSelectionWidget::LoadState(QSettings& Settings)
|
|
{
|
|
QList<int> Sizes = Settings.value("PartSelectionSplitter").value<QList<int>>();
|
|
|
|
if (Sizes.size() != 2)
|
|
{
|
|
int Length = mSplitter->orientation() == Qt::Horizontal ? mSplitter->width() : mSplitter->height();
|
|
Sizes = { Length / 3, 2 * Length / 3 };
|
|
}
|
|
|
|
mSplitter->setSizes(Sizes);
|
|
}
|
|
|
|
void lcPartSelectionWidget::SaveState(QSettings& Settings)
|
|
{
|
|
QList<int> Sizes = mSplitter->sizes();
|
|
Settings.setValue("PartSelectionSplitter", QVariant::fromValue(Sizes));
|
|
}
|
|
|
|
void lcPartSelectionWidget::SetCurrentPart(lcPieceData* PieceData)
|
|
{
|
|
mPartsWidget->SetCurrentPart(PieceData);
|
|
mPartsWidget->setFocus();
|
|
}
|
|
|
|
void lcPartSelectionWidget::SetCategory(lcPartCategoryType Type, int Index)
|
|
{
|
|
for (int Row = 0; Row < mCategoriesWidget->topLevelItemCount(); Row++)
|
|
{
|
|
QTreeWidgetItem* Item = mCategoriesWidget->topLevelItem(Row);
|
|
lcPartCategoryType ItemType = static_cast<lcPartCategoryType>(Item->data(0, static_cast<int>(lcPartCategoryRole::Type)).toInt());
|
|
|
|
if (ItemType != Type)
|
|
continue;
|
|
|
|
if (Type == lcPartCategoryType::Palette || Type == lcPartCategoryType::Category)
|
|
{
|
|
int ItemIndex = Item->data(0, static_cast<int>(lcPartCategoryRole::Index)).toInt();
|
|
|
|
if (ItemIndex != Index)
|
|
continue;
|
|
}
|
|
|
|
mCategoriesWidget->setCurrentItem(Item);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void lcPartSelectionWidget::SetCustomParts(const std::vector<std::pair<lcPieceData*, std::string>>& Parts, int ColorIndex)
|
|
{
|
|
mSplitter->widget(0)->setVisible(false);
|
|
|
|
mPartsWidget->SetCustomParts(Parts, ColorIndex);
|
|
}
|
|
|
|
void lcPartSelectionWidget::SetOrientation(Qt::Orientation Orientation)
|
|
{
|
|
mSplitter->setOrientation(Orientation);
|
|
|
|
int Length = mSplitter->orientation() == Qt::Horizontal ? mSplitter->width() : mSplitter->height();
|
|
QList<int> Sizes = { Length / 3, 2 * Length / 3 };
|
|
|
|
mSplitter->setSizes(Sizes);
|
|
}
|
|
|
|
void lcPartSelectionWidget::DockLocationChanged(Qt::DockWidgetArea Area)
|
|
{
|
|
if (Area == Qt::LeftDockWidgetArea || Area == Qt::RightDockWidgetArea)
|
|
mSplitter->setOrientation(Qt::Vertical);
|
|
else
|
|
mSplitter->setOrientation(Qt::Horizontal);
|
|
}
|
|
|
|
void lcPartSelectionWidget::resizeEvent(QResizeEvent* Event)
|
|
{
|
|
QDockWidget* DockWidget = qobject_cast<QDockWidget*>(parent());
|
|
|
|
if (DockWidget && DockWidget->isFloating())
|
|
{
|
|
if (Event->size().width() > Event->size().height())
|
|
mSplitter->setOrientation(Qt::Horizontal);
|
|
else
|
|
mSplitter->setOrientation(Qt::Vertical);
|
|
}
|
|
|
|
QWidget::resizeEvent(Event);
|
|
}
|
|
|
|
void lcPartSelectionWidget::FilterCategoriesChanged(const QString& Text)
|
|
{
|
|
if (mFilterCategoriesAction)
|
|
{
|
|
if (Text.isEmpty())
|
|
{
|
|
delete mFilterCategoriesAction;
|
|
mFilterCategoriesAction = nullptr;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!Text.isEmpty())
|
|
{
|
|
mFilterCategoriesAction = mFilterCategoriesWidget->addAction(QIcon(":/stylesheet/close.svg"), QLineEdit::TrailingPosition);
|
|
connect(mFilterCategoriesAction, &QAction::triggered, this, &lcPartSelectionWidget::FilterCategoriesTriggered);
|
|
}
|
|
}
|
|
|
|
bool Hide = true;
|
|
Qt::CaseSensitivity MatchCase = mFilterCaseAction->isChecked() ? Qt::CaseSensitive : Qt::CaseInsensitive;
|
|
mCategoriesWidget->setUpdatesEnabled(false);
|
|
for (int CategoryIdx = 0; CategoryIdx < mCategoriesWidget->topLevelItemCount(); CategoryIdx++)
|
|
{
|
|
QTreeWidgetItem* CategoryItem = mCategoriesWidget->topLevelItem(CategoryIdx);
|
|
Hide = false;
|
|
if (!CategoryItem->text(0).contains(Text, MatchCase))
|
|
Hide = true;
|
|
CategoryItem->setHidden(Hide);
|
|
}
|
|
mCategoriesWidget->setUpdatesEnabled(true);
|
|
mCategoriesWidget->update();
|
|
}
|
|
|
|
void lcPartSelectionWidget::FilterChanged(const QString& Text)
|
|
{
|
|
if (mFilterAction)
|
|
{
|
|
if (Text.isEmpty())
|
|
{
|
|
delete mFilterAction;
|
|
mFilterAction = nullptr;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!Text.isEmpty())
|
|
{
|
|
mFilterAction = mFilterWidget->addAction(QIcon(":/stylesheet/close.svg"), QLineEdit::TrailingPosition);
|
|
connect(mFilterAction, &QAction::triggered, this, &lcPartSelectionWidget::FilterTriggered);
|
|
}
|
|
}
|
|
|
|
mPartsWidget->GetListModel()->SetFilter(Text);
|
|
}
|
|
|
|
void lcPartSelectionWidget::FilterCategoriesTriggered()
|
|
{
|
|
mFilterCategoriesWidget->clear();
|
|
}
|
|
|
|
void lcPartSelectionWidget::FilterCaseTriggered()
|
|
{
|
|
if (!mFilterCategoriesWidget->text().isEmpty())
|
|
FilterCategoriesChanged(mFilterCategoriesWidget->text());
|
|
}
|
|
|
|
void lcPartSelectionWidget::FilterTriggered()
|
|
{
|
|
mFilterWidget->clear();
|
|
}
|
|
|
|
void lcPartSelectionWidget::CategoryChanged(QTreeWidgetItem* Current, QTreeWidgetItem* Previous)
|
|
{
|
|
Q_UNUSED(Previous);
|
|
|
|
if (!Current)
|
|
return;
|
|
|
|
int Type = Current->data(0, static_cast<int>(lcPartCategoryRole::Type)).toInt();
|
|
int Index = Current->data(0, static_cast<int>(lcPartCategoryRole::Index)).toInt();
|
|
|
|
mPartsWidget->SetCategory(static_cast<lcPartCategoryType>(Type), Index);
|
|
}
|
|
|
|
void lcPartSelectionWidget::PartViewSelectionChanged(const QModelIndex& Current, const QModelIndex& Previous)
|
|
{
|
|
Q_UNUSED(Current);
|
|
Q_UNUSED(Previous);
|
|
|
|
emit CurrentPartChanged(mPartsWidget->GetCurrentPart());
|
|
}
|
|
|
|
void lcPartSelectionWidget::PartViewPartPicked(lcPieceData* PieceData)
|
|
{
|
|
emit PartPicked(PieceData);
|
|
}
|
|
|
|
void lcPartSelectionWidget::OptionsMenuAboutToShow()
|
|
{
|
|
QMenu* Menu = (QMenu*)sender();
|
|
Menu->clear();
|
|
|
|
if (!mIsPopup)
|
|
{
|
|
Menu->addAction(tr("Edit Palettes..."), this, &lcPartSelectionWidget::EditPartPalettes);
|
|
|
|
Menu->addSeparator();
|
|
}
|
|
|
|
lcPartSelectionListModel* ListModel = mPartsWidget->GetListModel();
|
|
|
|
if (ListModel->GetIconSize() != lcPartSelectionListView::NoIconSize && !ListModel->IsListMode())
|
|
{
|
|
QAction* PartNames = Menu->addAction(tr("Show Part Names"), mPartsWidget, &lcPartSelectionListView::TogglePartNames);
|
|
PartNames->setCheckable(true);
|
|
PartNames->setChecked(ListModel->GetShowPartNames());
|
|
}
|
|
|
|
QAction* DecoratedParts = Menu->addAction(tr("Show Decorated Pieces"), mPartsWidget, &lcPartSelectionListView::ToggleDecoratedParts);
|
|
DecoratedParts->setCheckable(true);
|
|
DecoratedParts->setChecked(ListModel->GetShowDecoratedParts());
|
|
|
|
QAction* PartAliases = Menu->addAction(tr("Show Part Aliases"), mPartsWidget, &lcPartSelectionListView::TogglePartAliases);
|
|
PartAliases->setCheckable(true);
|
|
PartAliases->setChecked(ListModel->GetShowPartAliases());
|
|
|
|
Menu->addSeparator();
|
|
|
|
if (gSupportsFramebufferObject)
|
|
{
|
|
QMenu* IconMenu = Menu->addMenu(tr("Icons"));
|
|
|
|
QActionGroup* IconGroup = new QActionGroup(Menu);
|
|
|
|
QAction* NoIcons = IconMenu->addAction(tr("No Icons"), mPartsWidget, &lcPartSelectionListView::SetNoIcons);
|
|
NoIcons->setCheckable(true);
|
|
NoIcons->setChecked(ListModel->GetIconSize() == lcPartSelectionListView::NoIconSize);
|
|
IconGroup->addAction(NoIcons);
|
|
|
|
QAction* SmallIcons = IconMenu->addAction(tr("Small Icons"), mPartsWidget, &lcPartSelectionListView::SetSmallIcons);
|
|
SmallIcons->setCheckable(true);
|
|
SmallIcons->setChecked(ListModel->GetIconSize() == lcPartSelectionListView::SmallIconSize);
|
|
IconGroup->addAction(SmallIcons);
|
|
|
|
QAction* MediumIcons = IconMenu->addAction(tr("Medium Icons"), mPartsWidget, &lcPartSelectionListView::SetMediumIcons);
|
|
MediumIcons->setCheckable(true);
|
|
MediumIcons->setChecked(ListModel->GetIconSize() == lcPartSelectionListView::MediumIconSize);
|
|
IconGroup->addAction(MediumIcons);
|
|
|
|
QAction* LargeIcons = IconMenu->addAction(tr("Large Icons"), mPartsWidget, &lcPartSelectionListView::SetLargeIcons);
|
|
LargeIcons->setCheckable(true);
|
|
LargeIcons->setChecked(ListModel->GetIconSize() == lcPartSelectionListView::LargeIconSize);
|
|
IconGroup->addAction(LargeIcons);
|
|
|
|
QAction* ExtraLargeIcons = IconMenu->addAction(tr("Extra Large Icons"), mPartsWidget, &lcPartSelectionListView::SetExtraLargeIcons);
|
|
ExtraLargeIcons->setCheckable(true);
|
|
ExtraLargeIcons->setChecked(ListModel->GetIconSize() == lcPartSelectionListView::ExtraLargeIconSize);
|
|
IconGroup->addAction(ExtraLargeIcons);
|
|
|
|
if (ListModel->GetIconSize() != lcPartSelectionListView::NoIconSize)
|
|
{
|
|
IconMenu->addSeparator();
|
|
|
|
QAction* ListMode = IconMenu->addAction(tr("List Mode"), mPartsWidget, &lcPartSelectionListView::ToggleListMode);
|
|
ListMode->setCheckable(true);
|
|
ListMode->setChecked(ListModel->IsListMode());
|
|
|
|
if (!mIsPopup)
|
|
{
|
|
QAction* FixedColor = IconMenu->addAction(tr("Lock Color"), mPartsWidget, &lcPartSelectionListView::ToggleFixedColor);
|
|
FixedColor->setCheckable(true);
|
|
FixedColor->setChecked(ListModel->IsColorLocked());
|
|
}
|
|
}
|
|
}
|
|
|
|
QMenu* FilterMenu = Menu->addMenu(tr("Filter"));
|
|
|
|
QAction* RegularExpressionFilter = FilterMenu->addAction(tr("Regular Expression"), mPartsWidget, &lcPartSelectionListView::ToggleRegularExpressionFilter);
|
|
RegularExpressionFilter->setCheckable(true);
|
|
RegularExpressionFilter->setChecked(ListModel->GetPartFilterType() == lcPartFilterType::RegularExpression);
|
|
|
|
QAction* CaseSensitiveFilter = FilterMenu->addAction(tr("Match Case"), mPartsWidget, &lcPartSelectionListView::ToggleCaseSensitiveFilter);
|
|
CaseSensitiveFilter->setCheckable(true);
|
|
CaseSensitiveFilter->setChecked(ListModel->GetCaseSensitiveFilter());
|
|
|
|
FilterMenu->addSeparator();
|
|
|
|
QAction* FileNameFilter = FilterMenu->addAction(tr("Part ID"), mPartsWidget, &lcPartSelectionListView::ToggleFileNameFilter);
|
|
FileNameFilter->setCheckable(true);
|
|
FileNameFilter->setChecked(ListModel->GetFileNameFilter());
|
|
FileNameFilter->setEnabled(ListModel->GetPartDescriptionFilter());
|
|
|
|
QAction* PartDescriptionFilter = FilterMenu->addAction(tr("Part Description"), mPartsWidget, &lcPartSelectionListView::TogglePartDescriptionFilter);
|
|
PartDescriptionFilter->setCheckable(true);
|
|
PartDescriptionFilter->setChecked(ListModel->GetPartDescriptionFilter());
|
|
PartDescriptionFilter->setEnabled(ListModel->GetFileNameFilter());
|
|
}
|
|
|
|
void lcPartSelectionWidget::EditPartPalettes()
|
|
{
|
|
lcPartPaletteDialog Dialog(this, mPartPalettes);
|
|
|
|
if (Dialog.exec() != QDialog::Accepted)
|
|
return;
|
|
|
|
SavePartPalettes();
|
|
UpdateCategories();
|
|
}
|
|
|
|
void lcPartSelectionWidget::UpdateThumbnails()
|
|
{
|
|
mPartsWidget->GetListModel()->UpdateThumbnails();
|
|
}
|
|
|
|
void lcPartSelectionWidget::SetDefaultPart()
|
|
{
|
|
for (int CategoryIdx = 0; CategoryIdx < mCategoriesWidget->topLevelItemCount(); CategoryIdx++)
|
|
{
|
|
QTreeWidgetItem* CategoryItem = mCategoriesWidget->topLevelItem(CategoryIdx);
|
|
|
|
if (CategoryItem->text(0) == "Brick")
|
|
{
|
|
mCategoriesWidget->setCurrentItem(CategoryItem);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void lcPartSelectionWidget::LoadPartPalettes()
|
|
{
|
|
QByteArray Buffer = lcGetProfileBuffer(LC_PROFILE_PART_PALETTES);
|
|
QJsonDocument Document = QJsonDocument::fromJson(Buffer);
|
|
|
|
if (Document.isNull())
|
|
Document = QJsonDocument::fromJson((QString("{ \"Version\":1, \"Palettes\": { \"%1\": [] } }").arg(tr("Favorites"))).toUtf8());
|
|
|
|
QJsonObject RootObject = Document.object();
|
|
mPartPalettes.clear();
|
|
|
|
int Version = RootObject["Version"].toInt(0);
|
|
if (Version != 1)
|
|
return;
|
|
|
|
QJsonObject PalettesObject = RootObject["Palettes"].toObject();
|
|
|
|
for (QJsonObject::const_iterator ElementIt = PalettesObject.constBegin(); ElementIt != PalettesObject.constEnd(); ElementIt++)
|
|
{
|
|
if (!ElementIt.value().isArray())
|
|
continue;
|
|
|
|
lcPartPalette Palette;
|
|
Palette.Name = ElementIt.key();
|
|
|
|
const QJsonArray Parts = ElementIt.value().toArray();
|
|
|
|
for (const QJsonValue& Part : Parts)
|
|
Palette.Parts.emplace_back(Part.toString().toStdString());
|
|
|
|
mPartPalettes.emplace_back(std::move(Palette));
|
|
}
|
|
}
|
|
|
|
void lcPartSelectionWidget::SavePartPalettes()
|
|
{
|
|
QJsonObject RootObject;
|
|
|
|
RootObject["Version"] = 1;
|
|
QJsonObject PalettesObject;
|
|
|
|
for (const lcPartPalette& Palette : mPartPalettes)
|
|
{
|
|
QJsonArray Parts;
|
|
|
|
for (const std::string& PartId : Palette.Parts)
|
|
Parts.append(QString::fromStdString(PartId));
|
|
|
|
PalettesObject[Palette.Name] = Parts;
|
|
}
|
|
|
|
RootObject["Palettes"] = PalettesObject;
|
|
|
|
QByteArray Buffer = QJsonDocument(RootObject).toJson();
|
|
lcSetProfileBuffer(LC_PROFILE_PART_PALETTES, Buffer);
|
|
}
|
|
|
|
void lcPartSelectionWidget::AddToPalette()
|
|
{
|
|
lcPieceData* PieceData = mPartsWidget->GetContextInfo();
|
|
if (!PieceData)
|
|
return;
|
|
|
|
QString SetName = ((QAction*)sender())->text();
|
|
|
|
std::vector<lcPartPalette>::iterator SetIt = std::find_if(mPartPalettes.begin(), mPartPalettes.end(), [&SetName](const lcPartPalette& Set)
|
|
{
|
|
return Set.Name == SetName;
|
|
});
|
|
|
|
if (SetIt == mPartPalettes.end())
|
|
return;
|
|
|
|
std::string PartId = lcGetPiecesLibrary()->GetPartId(PieceData);
|
|
std::vector<std::string>& Parts = SetIt->Parts;
|
|
|
|
if (std::find(Parts.begin(), Parts.end(), PartId) == Parts.end())
|
|
{
|
|
Parts.emplace_back(PartId);
|
|
SavePartPalettes();
|
|
}
|
|
}
|
|
|
|
void lcPartSelectionWidget::RemoveFromPalette()
|
|
{
|
|
lcPieceData* PieceData = mPartsWidget->GetContextInfo();
|
|
if (!PieceData)
|
|
return;
|
|
|
|
QTreeWidgetItem* CurrentItem = mCategoriesWidget->currentItem();
|
|
if (!CurrentItem || CurrentItem->data(0, static_cast<int>(lcPartCategoryRole::Type)) != static_cast<int>(lcPartCategoryType::Palette))
|
|
return;
|
|
|
|
int SetIndex = CurrentItem->data(0, static_cast<int>(lcPartCategoryRole::Index)).toInt();
|
|
lcPartPalette& Palette = mPartPalettes[SetIndex];
|
|
|
|
std::string PartId = lcGetPiecesLibrary()->GetPartId(PieceData);
|
|
std::vector<std::string>::iterator PartIt = std::find(Palette.Parts.begin(), Palette.Parts.end(), PartId);
|
|
|
|
if (PartIt != Palette.Parts.end())
|
|
{
|
|
Palette.Parts.erase(PartIt);
|
|
mPartsWidget->SetCategory(lcPartCategoryType::Palette, SetIndex);
|
|
SavePartPalettes();
|
|
}
|
|
}
|
|
|
|
void lcPartSelectionWidget::OpenSubmodel()
|
|
{
|
|
lcPieceData* PieceData = mPartsWidget->GetContextInfo();
|
|
|
|
if (!PieceData || !PieceData->IsModel())
|
|
return;
|
|
|
|
lcModel* Model = PieceData->GetModel();
|
|
|
|
lcGetActiveProject()->SetActiveModel(Model, true);
|
|
}
|
|
|
|
void lcPartSelectionWidget::UpdateInUseCategory()
|
|
{
|
|
mPartsWidget->UpdateInUseCategory();
|
|
}
|
|
|
|
void lcPartSelectionWidget::UpdateCategories()
|
|
{
|
|
QTreeWidgetItem* CurrentItem = mCategoriesWidget->currentItem();
|
|
lcPartCategoryType CurrentType = lcPartCategoryType::Count;
|
|
int CurrentIndex = -1;
|
|
|
|
if (CurrentItem)
|
|
{
|
|
CurrentType = static_cast<lcPartCategoryType>(CurrentItem->data(0, static_cast<int>(lcPartCategoryRole::Type)).toInt());
|
|
CurrentIndex = CurrentItem->data(0, static_cast<int>(lcPartCategoryRole::Index)).toInt();
|
|
CurrentItem = nullptr;
|
|
}
|
|
|
|
mCategoriesWidget->clear();
|
|
|
|
mAllPartsCategoryItem = new QTreeWidgetItem(mCategoriesWidget, QStringList(tr("All Pieces")));
|
|
mAllPartsCategoryItem->setData(0, static_cast<int>(lcPartCategoryRole::Type), static_cast<int>(lcPartCategoryType::AllParts));
|
|
|
|
if (CurrentType == lcPartCategoryType::AllParts && CurrentIndex == 0)
|
|
CurrentItem = mAllPartsCategoryItem;
|
|
|
|
QTreeWidgetItem* CurrentModelCategoryItem = new QTreeWidgetItem(mCategoriesWidget, QStringList(tr("In Use")));
|
|
CurrentModelCategoryItem->setData(0, static_cast<int>(lcPartCategoryRole::Type), static_cast<int>(lcPartCategoryType::PartsInUse));
|
|
|
|
if (CurrentType == lcPartCategoryType::PartsInUse && CurrentIndex == 0)
|
|
CurrentItem = CurrentModelCategoryItem;
|
|
|
|
QTreeWidgetItem* SubmodelsCategoryItem = new QTreeWidgetItem(mCategoriesWidget, QStringList(tr("Submodels")));
|
|
SubmodelsCategoryItem->setData(0, static_cast<int>(lcPartCategoryRole::Type), static_cast<int>(lcPartCategoryType::Submodels));
|
|
|
|
if (CurrentType == lcPartCategoryType::Submodels && CurrentIndex == 0)
|
|
CurrentItem = SubmodelsCategoryItem;
|
|
|
|
for (int PaletteIdx = 0; PaletteIdx < static_cast<int>(mPartPalettes.size()); PaletteIdx++)
|
|
{
|
|
const lcPartPalette& Set = mPartPalettes[PaletteIdx];
|
|
QTreeWidgetItem* PaletteCategoryItem = new QTreeWidgetItem(mCategoriesWidget, QStringList(Set.Name));
|
|
PaletteCategoryItem->setData(0, static_cast<int>(lcPartCategoryRole::Type), static_cast<int>(lcPartCategoryType::Palette));
|
|
PaletteCategoryItem->setData(0, static_cast<int>(lcPartCategoryRole::Index), PaletteIdx);
|
|
|
|
if (CurrentType == lcPartCategoryType::Palette && CurrentIndex == PaletteIdx)
|
|
CurrentItem = PaletteCategoryItem;
|
|
}
|
|
|
|
for (int CategoryIdx = 0; CategoryIdx < static_cast<int>(gCategories.size()); CategoryIdx++)
|
|
{
|
|
QTreeWidgetItem* CategoryItem = new QTreeWidgetItem(mCategoriesWidget, QStringList(gCategories[CategoryIdx].Name));
|
|
CategoryItem->setData(0, static_cast<int>(lcPartCategoryRole::Type), static_cast<int>(lcPartCategoryType::Category));
|
|
CategoryItem->setData(0, static_cast<int>(lcPartCategoryRole::Index), CategoryIdx);
|
|
|
|
if (CurrentType == lcPartCategoryType::Category && CurrentIndex == CategoryIdx)
|
|
CurrentItem = CategoryItem;
|
|
}
|
|
|
|
if (CurrentItem)
|
|
mCategoriesWidget->setCurrentItem(CurrentItem);
|
|
}
|
|
|
|
void lcPartSelectionWidget::UpdateModels()
|
|
{
|
|
QTreeWidgetItem* CurrentItem = mCategoriesWidget->currentItem();
|
|
|
|
if (CurrentItem && CurrentItem->data(0, static_cast<int>(lcPartCategoryRole::Type)) == static_cast<int>(lcPartCategoryType::Submodels))
|
|
mPartsWidget->SetCategory(lcPartCategoryType::Submodels, 0);
|
|
}
|