mirror of
https://github.com/leozide/leocad.git
synced 2026-10-26 22:10:32 +00:00
Add Help/F1 support to Preferences, Render Log, Blender Add-on Reset, and the piece and custom color choosers. Share help routing between dialogs and menu-embedded widgets while preserving chooser selections and shortcut recording. Explain all Preferences controls, link labels to their fields, and correct the Color Theme label buddy. Make Blender reset cancellation preserve pending edits and retain Apply availability after an accepted reset. Refresh catalogs while keeping existing translations and new strings unfinished. Validation: debug build, all seven Preferences tabs, 85 field F1 routes, label buddies, F1 shortcut recording, native chooser menu Help/Escape, reset Cancel/Apply behavior, layouts, and catalog preservation checks passed.
1600 lines
47 KiB
C++
1600 lines
47 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->IsDecorated() && !PieceData->IsProjectPiece())
|
|
Visible = false;
|
|
else if (!mShowPartAliases && PieceData->IsAlias())
|
|
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 piece '%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"));
|
|
mFilterCategoriesWidget->setAccessibleName(tr("Filter Categories"));
|
|
mFilterCategoriesWidget->setToolTip(tr("Filter category names to narrow the category list."));
|
|
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->setAccessibleName(tr("Piece Categories"));
|
|
mCategoriesWidget->setToolTip(tr("Choose a category or palette to display its pieces."));
|
|
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"));
|
|
mFilterWidget->setAccessibleName(tr("Filter Pieces"));
|
|
mFilterWidget->setToolTip(tr("Filter the displayed pieces using the matching options."));
|
|
SearchLayout->addWidget(mFilterWidget);
|
|
|
|
QToolButton* OptionsButton = new QToolButton();
|
|
OptionsButton->setIcon(QIcon(":/resources/gear_in.png"));
|
|
OptionsButton->setToolTip(tr("Configure piece filtering and list display."));
|
|
OptionsButton->setAccessibleName(tr("Piece List 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);
|
|
mPartsWidget->setAccessibleName(tr("Available Pieces"));
|
|
mPartsWidget->setToolTip(tr("Select a piece from the filtered list."));
|
|
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 Piece 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 Piece 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("Piece ID"), mPartsWidget, &lcPartSelectionListView::ToggleFileNameFilter);
|
|
FileNameFilter->setCheckable(true);
|
|
FileNameFilter->setChecked(ListModel->GetFileNameFilter());
|
|
FileNameFilter->setEnabled(ListModel->GetPartDescriptionFilter());
|
|
|
|
QAction* PartDescriptionFilter = FilterMenu->addAction(tr("Piece 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);
|
|
}
|