forked from qt-creator/qt-creator
CppEditor: Rework showing/switching parse configurations
* Move the combox box for switching the parse configurations out of the "Additional Preprocessor Directives" dialog ('#'-button) to make it better visible/accessible. Also, decouple the extra preprocessor directives from the concrete parse context since this is not anymore in same dialog. * The combo box appears only if multiple parse configurations are available for a file. * The first time multiple parse configurations are detected, an info bar is shown that points the user to the combox box. A "Do Not Show Again" button is provided. * Upon selecting an entry, the preferred parse configuration is saved as part of the session. The setting can be cleared with the context menu entry on the combo box. Follow-up changes need to ensure that the display name and/or tooltip is unambiguous, e.g. for qbs and cmake projects. Change-Id: I9e9773704187291524ad7b605bfdddd83ef5b19d Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
@@ -280,6 +280,13 @@ void ClangEditorDocumentProcessor::editorDocumentTimerRestarted()
|
|||||||
m_updateTranslationUnitTimer.stop(); // Wait for the next call to run().
|
m_updateTranslationUnitTimer.stop(); // Wait for the next call to run().
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ClangEditorDocumentProcessor::setParserConfig(
|
||||||
|
const CppTools::BaseEditorDocumentParser::Configuration config)
|
||||||
|
{
|
||||||
|
m_parser->setConfiguration(config);
|
||||||
|
m_builtinProcessor.parser()->setConfiguration(config);
|
||||||
|
}
|
||||||
|
|
||||||
ClangBackEnd::FileContainer ClangEditorDocumentProcessor::fileContainerWithArguments() const
|
ClangBackEnd::FileContainer ClangEditorDocumentProcessor::fileContainerWithArguments() const
|
||||||
{
|
{
|
||||||
return fileContainerWithArguments(m_projectPart.data());
|
return fileContainerWithArguments(m_projectPart.data());
|
||||||
|
@@ -82,6 +82,8 @@ public:
|
|||||||
|
|
||||||
void editorDocumentTimerRestarted() override;
|
void editorDocumentTimerRestarted() override;
|
||||||
|
|
||||||
|
void setParserConfig(const CppTools::BaseEditorDocumentParser::Configuration config) override;
|
||||||
|
|
||||||
ClangBackEnd::FileContainer fileContainerWithArguments() const;
|
ClangBackEnd::FileContainer fileContainerWithArguments() const;
|
||||||
|
|
||||||
void clearDiagnosticsWithFixIts();
|
void clearDiagnosticsWithFixIts();
|
||||||
|
@@ -131,6 +131,8 @@ public:
|
|||||||
|
|
||||||
QScopedPointer<FollowSymbolUnderCursor> m_followSymbolUnderCursor;
|
QScopedPointer<FollowSymbolUnderCursor> m_followSymbolUnderCursor;
|
||||||
|
|
||||||
|
QAction *m_parseContextAction = nullptr;
|
||||||
|
ParseContextWidget *m_parseContextWidget = nullptr;
|
||||||
QToolButton *m_preprocessorButton = nullptr;
|
QToolButton *m_preprocessorButton = nullptr;
|
||||||
MinimizableInfoBars::Actions m_showInfoBarActions;
|
MinimizableInfoBars::Actions m_showInfoBarActions;
|
||||||
|
|
||||||
@@ -222,7 +224,7 @@ void CppEditorWidget::finalizeInitialization()
|
|||||||
d->m_cppSelectionChanger.onCursorPositionChanged(textCursor());
|
d->m_cppSelectionChanger.onCursorPositionChanged(textCursor());
|
||||||
});
|
});
|
||||||
|
|
||||||
// Tool bar creation
|
// Toolbar: '#' Button
|
||||||
d->m_preprocessorButton = new QToolButton(this);
|
d->m_preprocessorButton = new QToolButton(this);
|
||||||
d->m_preprocessorButton->setText(QLatin1String("#"));
|
d->m_preprocessorButton->setText(QLatin1String("#"));
|
||||||
Command *cmd = ActionManager::command(Constants::OPEN_PREPROCESSOR_DIALOG);
|
Command *cmd = ActionManager::command(Constants::OPEN_PREPROCESSOR_DIALOG);
|
||||||
@@ -231,13 +233,25 @@ void CppEditorWidget::finalizeInitialization()
|
|||||||
connect(d->m_preprocessorButton, &QAbstractButton::clicked, this, &CppEditorWidget::showPreProcessorWidget);
|
connect(d->m_preprocessorButton, &QAbstractButton::clicked, this, &CppEditorWidget::showPreProcessorWidget);
|
||||||
insertExtraToolBarWidget(TextEditorWidget::Left, d->m_preprocessorButton);
|
insertExtraToolBarWidget(TextEditorWidget::Left, d->m_preprocessorButton);
|
||||||
|
|
||||||
// Actions to show minimized info bars
|
// Toolbar: Actions to show minimized info bars
|
||||||
d->m_showInfoBarActions = MinimizableInfoBars::createShowInfoBarActions([this](QWidget *w) {
|
d->m_showInfoBarActions = MinimizableInfoBars::createShowInfoBarActions([this](QWidget *w) {
|
||||||
return this->insertExtraToolBarWidget(TextEditorWidget::Left, w);
|
return this->insertExtraToolBarWidget(TextEditorWidget::Left, w);
|
||||||
});
|
});
|
||||||
connect(&cppEditorDocument()->minimizableInfoBars(), &MinimizableInfoBars::showAction,
|
connect(&cppEditorDocument()->minimizableInfoBars(), &MinimizableInfoBars::showAction,
|
||||||
this, &CppEditorWidget::onShowInfoBarAction);
|
this, &CppEditorWidget::onShowInfoBarAction);
|
||||||
|
|
||||||
|
// Toolbar: Parse context
|
||||||
|
ParseContextModel &parseContextModel = cppEditorDocument()->parseContextModel();
|
||||||
|
d->m_parseContextWidget = new ParseContextWidget(parseContextModel, this);
|
||||||
|
d->m_parseContextAction = insertExtraToolBarWidget(TextEditorWidget::Left,
|
||||||
|
d->m_parseContextWidget);
|
||||||
|
d->m_parseContextAction->setVisible(false);
|
||||||
|
connect(&parseContextModel, &ParseContextModel::updated,
|
||||||
|
this, [this](bool areMultipleAvailable) {
|
||||||
|
d->m_parseContextAction->setVisible(areMultipleAvailable);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Toolbar: Outline/Overview combo box
|
||||||
insertExtraToolBarWidget(TextEditorWidget::Left, d->m_cppEditorOutline->widget());
|
insertExtraToolBarWidget(TextEditorWidget::Left, d->m_cppEditorOutline->widget());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,6 +266,10 @@ void CppEditorWidget::finalizeInitializationAfterDuplication(TextEditorWidget *o
|
|||||||
d->m_cppEditorOutline->update();
|
d->m_cppEditorOutline->update();
|
||||||
const Id selectionKind = CodeWarningsSelection;
|
const Id selectionKind = CodeWarningsSelection;
|
||||||
setExtraSelections(selectionKind, cppEditorWidget->extraSelections(selectionKind));
|
setExtraSelections(selectionKind, cppEditorWidget->extraSelections(selectionKind));
|
||||||
|
|
||||||
|
d->m_parseContextWidget->syncToModel();
|
||||||
|
d->m_parseContextAction->setVisible(
|
||||||
|
d->m_cppEditorDocument->parseContextModel().areMultipleAvailable());
|
||||||
}
|
}
|
||||||
|
|
||||||
CppEditorWidget::~CppEditorWidget()
|
CppEditorWidget::~CppEditorWidget()
|
||||||
@@ -972,20 +990,12 @@ void CppEditorWidget::abortDeclDefLink()
|
|||||||
|
|
||||||
void CppEditorWidget::showPreProcessorWidget()
|
void CppEditorWidget::showPreProcessorWidget()
|
||||||
{
|
{
|
||||||
const Utils::FileName fileName = textDocument()->filePath();
|
const QString filePath = textDocument()->filePath().toString();
|
||||||
|
|
||||||
// Check if this editor belongs to a project
|
CppPreProcessorDialog dialog(filePath, this);
|
||||||
QList<ProjectPart::Ptr> projectParts = d->m_modelManager->projectPart(fileName);
|
if (dialog.exec() == QDialog::Accepted) {
|
||||||
if (projectParts.isEmpty())
|
const QByteArray extraDirectives = dialog.extraPreprocessorDirectives().toUtf8();
|
||||||
projectParts = d->m_modelManager->projectPartFromDependencies(fileName);
|
cppEditorDocument()->setExtraPreprocessorDirectives(extraDirectives);
|
||||||
if (projectParts.isEmpty())
|
|
||||||
projectParts << d->m_modelManager->fallbackProjectPart();
|
|
||||||
|
|
||||||
CppPreProcessorDialog preProcessorDialog(this, textDocument()->filePath().toString(), projectParts);
|
|
||||||
if (preProcessorDialog.exec() == QDialog::Accepted) {
|
|
||||||
cppEditorDocument()->setPreprocessorSettings(
|
|
||||||
preProcessorDialog.projectPart(),
|
|
||||||
preProcessorDialog.additionalPreProcessorDirectives().toUtf8());
|
|
||||||
cppEditorDocument()->scheduleProcessDocument();
|
cppEditorDocument()->scheduleProcessDocument();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -17,6 +17,7 @@ HEADERS += \
|
|||||||
cppfunctiondecldeflink.h \
|
cppfunctiondecldeflink.h \
|
||||||
cpphighlighter.h \
|
cpphighlighter.h \
|
||||||
cpphoverhandler.h \
|
cpphoverhandler.h \
|
||||||
|
cppparsecontext.h \
|
||||||
cppincludehierarchy.h \
|
cppincludehierarchy.h \
|
||||||
cppinsertvirtualmethods.h \
|
cppinsertvirtualmethods.h \
|
||||||
cpplocalrenaming.h \
|
cpplocalrenaming.h \
|
||||||
@@ -46,6 +47,7 @@ SOURCES += \
|
|||||||
cppfunctiondecldeflink.cpp \
|
cppfunctiondecldeflink.cpp \
|
||||||
cpphighlighter.cpp \
|
cpphighlighter.cpp \
|
||||||
cpphoverhandler.cpp \
|
cpphoverhandler.cpp \
|
||||||
|
cppparsecontext.cpp \
|
||||||
cppincludehierarchy.cpp \
|
cppincludehierarchy.cpp \
|
||||||
cppinsertvirtualmethods.cpp \
|
cppinsertvirtualmethods.cpp \
|
||||||
cpplocalrenaming.cpp \
|
cpplocalrenaming.cpp \
|
||||||
|
@@ -43,6 +43,7 @@ QtcPlugin {
|
|||||||
"cppminimizableinfobars.cpp", "cppminimizableinfobars.h",
|
"cppminimizableinfobars.cpp", "cppminimizableinfobars.h",
|
||||||
"cppoutline.cpp", "cppoutline.h",
|
"cppoutline.cpp", "cppoutline.h",
|
||||||
"cpppreprocessordialog.cpp", "cpppreprocessordialog.h", "cpppreprocessordialog.ui",
|
"cpppreprocessordialog.cpp", "cpppreprocessordialog.h", "cpppreprocessordialog.ui",
|
||||||
|
"cppparsecontext.cpp", "cppparsecontext.h",
|
||||||
"cppquickfix.cpp", "cppquickfix.h",
|
"cppquickfix.cpp", "cppquickfix.h",
|
||||||
"cppquickfixassistant.cpp", "cppquickfixassistant.h",
|
"cppquickfixassistant.cpp", "cppquickfixassistant.h",
|
||||||
"cppquickfixes.cpp", "cppquickfixes.h",
|
"cppquickfixes.cpp", "cppquickfixes.h",
|
||||||
|
@@ -37,6 +37,7 @@ const char RENAME_SYMBOL_UNDER_CURSOR[] = "CppEditor.RenameSymbolUnderCursor";
|
|||||||
const char FIND_USAGES[] = "CppEditor.FindUsages";
|
const char FIND_USAGES[] = "CppEditor.FindUsages";
|
||||||
const char OPEN_PREPROCESSOR_DIALOG[] = "CppEditor.OpenPreprocessorDialog";
|
const char OPEN_PREPROCESSOR_DIALOG[] = "CppEditor.OpenPreprocessorDialog";
|
||||||
const char ERRORS_IN_HEADER_FILES[] = "CppEditor.ErrorsInHeaderFiles";
|
const char ERRORS_IN_HEADER_FILES[] = "CppEditor.ErrorsInHeaderFiles";
|
||||||
|
const char MULTIPLE_PARSE_CONTEXTS_AVAILABLE[] = "CppEditor.MultipleParseContextsAvailable";
|
||||||
const char NO_PROJECT_CONFIGURATION[] = "CppEditor.NoProjectConfiguration";
|
const char NO_PROJECT_CONFIGURATION[] = "CppEditor.NoProjectConfiguration";
|
||||||
const char M_REFACTORING_MENU_INSERTION_POINT[] = "CppEditor.RefactorGroup";
|
const char M_REFACTORING_MENU_INSERTION_POINT[] = "CppEditor.RefactorGroup";
|
||||||
const char UPDATE_CODEMODEL[] = "CppEditor.UpdateCodeModel";
|
const char UPDATE_CODEMODEL[] = "CppEditor.UpdateCodeModel";
|
||||||
@@ -50,7 +51,8 @@ const char OPEN_INCLUDE_HIERARCHY[] = "CppEditor.OpenIncludeHierarchy";
|
|||||||
|
|
||||||
const char CPP_SNIPPETS_GROUP_ID[] = "C++";
|
const char CPP_SNIPPETS_GROUP_ID[] = "C++";
|
||||||
|
|
||||||
const char CPP_PREPROCESSOR_PROJECT_PREFIX[] = "CppPreprocessorProject-";
|
const char EXTRA_PREPROCESSOR_DIRECTIVES[] = "CppEditor.ExtraPreprocessorDirectives-";
|
||||||
|
const char PREFERRED_PARSE_CONTEXT[] = "CppEditor.PreferredParseContext-";
|
||||||
|
|
||||||
} // namespace Constants
|
} // namespace Constants
|
||||||
} // namespace CppEditor
|
} // namespace CppEditor
|
||||||
|
@@ -30,6 +30,8 @@
|
|||||||
#include "cpphighlighter.h"
|
#include "cpphighlighter.h"
|
||||||
#include "cppquickfixassistant.h"
|
#include "cppquickfixassistant.h"
|
||||||
|
|
||||||
|
#include <coreplugin/infobar.h>
|
||||||
|
|
||||||
#include <cpptools/baseeditordocumentparser.h>
|
#include <cpptools/baseeditordocumentparser.h>
|
||||||
#include <cpptools/builtineditordocumentprocessor.h>
|
#include <cpptools/builtineditordocumentprocessor.h>
|
||||||
#include <cpptools/cppcodeformatter.h>
|
#include <cpptools/cppcodeformatter.h>
|
||||||
@@ -115,6 +117,9 @@ CppEditorDocument::CppEditorDocument()
|
|||||||
connect(this, &IDocument::filePathChanged,
|
connect(this, &IDocument::filePathChanged,
|
||||||
this, &CppEditorDocument::onFilePathChanged);
|
this, &CppEditorDocument::onFilePathChanged);
|
||||||
|
|
||||||
|
connect(&m_parseContextModel, &ParseContextModel::preferredParseContextChanged,
|
||||||
|
this, &CppEditorDocument::reparseWithPreferredParseContext);
|
||||||
|
|
||||||
// See also onFilePathChanged() for more initialization
|
// See also onFilePathChanged() for more initialization
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,6 +209,21 @@ void CppEditorDocument::onReloadFinished()
|
|||||||
m_fileIsBeingReloaded = false;
|
m_fileIsBeingReloaded = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CppEditorDocument::reparseWithPreferredParseContext(const QString &parseContextId)
|
||||||
|
{
|
||||||
|
using namespace CppTools;
|
||||||
|
|
||||||
|
// Update parser
|
||||||
|
setPreferredParseContext(parseContextId);
|
||||||
|
|
||||||
|
// Remember the setting
|
||||||
|
const QString key = Constants::PREFERRED_PARSE_CONTEXT + filePath().toString();
|
||||||
|
ProjectExplorer::SessionManager::setValue(key, parseContextId);
|
||||||
|
|
||||||
|
// Reprocess
|
||||||
|
scheduleProcessDocument();
|
||||||
|
}
|
||||||
|
|
||||||
void CppEditorDocument::onFilePathChanged(const Utils::FileName &oldPath,
|
void CppEditorDocument::onFilePathChanged(const Utils::FileName &oldPath,
|
||||||
const Utils::FileName &newPath)
|
const Utils::FileName &newPath)
|
||||||
{
|
{
|
||||||
@@ -222,7 +242,8 @@ void CppEditorDocument::onFilePathChanged(const Utils::FileName &oldPath,
|
|||||||
m_editorDocumentHandle.reset(new CppEditorDocumentHandleImpl(this));
|
m_editorDocumentHandle.reset(new CppEditorDocumentHandleImpl(this));
|
||||||
|
|
||||||
resetProcessor();
|
resetProcessor();
|
||||||
updatePreprocessorSettings();
|
applyPreferredParseContextFromSettings();
|
||||||
|
applyExtraPreprocessorDirectivesFromSettings();
|
||||||
m_processorRevision = document()->revision();
|
m_processorRevision = document()->revision();
|
||||||
processDocument();
|
processDocument();
|
||||||
}
|
}
|
||||||
@@ -256,34 +277,51 @@ void CppEditorDocument::resetProcessor()
|
|||||||
processor(); // creates a new processor
|
processor(); // creates a new processor
|
||||||
}
|
}
|
||||||
|
|
||||||
void CppEditorDocument::updatePreprocessorSettings()
|
void CppEditorDocument::applyPreferredParseContextFromSettings()
|
||||||
{
|
{
|
||||||
if (filePath().isEmpty())
|
if (filePath().isEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const QString prefix = QLatin1String(Constants::CPP_PREPROCESSOR_PROJECT_PREFIX);
|
const QString key = Constants::PREFERRED_PARSE_CONTEXT + filePath().toString();
|
||||||
const QString &projectPartId = ProjectExplorer::SessionManager::value(
|
const QString parseContextId = ProjectExplorer::SessionManager::value(key).toString();
|
||||||
prefix + filePath().toString()).toString();
|
|
||||||
const QString directivesKey = projectPartId + QLatin1Char(',') + filePath().toString();
|
|
||||||
const QByteArray additionalDirectives = ProjectExplorer::SessionManager::value(
|
|
||||||
directivesKey).toString().toUtf8();
|
|
||||||
|
|
||||||
setPreprocessorSettings(mm()->projectPartForId(projectPartId), additionalDirectives);
|
setPreferredParseContext(parseContextId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CppEditorDocument::setPreprocessorSettings(const CppTools::ProjectPart::Ptr &projectPart,
|
void CppEditorDocument::applyExtraPreprocessorDirectivesFromSettings()
|
||||||
const QByteArray &defines)
|
{
|
||||||
|
if (filePath().isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
const QString key = Constants::EXTRA_PREPROCESSOR_DIRECTIVES + filePath().toString();
|
||||||
|
const QByteArray directives = ProjectExplorer::SessionManager::value(key).toString().toUtf8();
|
||||||
|
|
||||||
|
setExtraPreprocessorDirectives(directives);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CppEditorDocument::setExtraPreprocessorDirectives(const QByteArray &directives)
|
||||||
{
|
{
|
||||||
const auto parser = processor()->parser();
|
const auto parser = processor()->parser();
|
||||||
QTC_ASSERT(parser, return);
|
QTC_ASSERT(parser, return);
|
||||||
if (parser->projectPartInfo().projectPart != projectPart
|
|
||||||
|| parser->configuration().editorDefines != defines) {
|
|
||||||
CppTools::BaseEditorDocumentParser::Configuration config = parser->configuration();
|
|
||||||
config.preferredProjectPartId = projectPart->id();
|
|
||||||
config.editorDefines = defines;
|
|
||||||
parser->setConfiguration(config);
|
|
||||||
|
|
||||||
emit preprocessorSettingsChanged(!defines.trimmed().isEmpty());
|
CppTools::BaseEditorDocumentParser::Configuration config = parser->configuration();
|
||||||
|
if (config.editorDefines != directives) {
|
||||||
|
config.editorDefines = directives;
|
||||||
|
processor()->setParserConfig(config);
|
||||||
|
|
||||||
|
emit preprocessorSettingsChanged(!directives.trimmed().isEmpty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CppEditorDocument::setPreferredParseContext(const QString &parseContextId)
|
||||||
|
{
|
||||||
|
const CppTools::BaseEditorDocumentParser::Ptr parser = processor()->parser();
|
||||||
|
QTC_ASSERT(parser, return);
|
||||||
|
|
||||||
|
CppTools::BaseEditorDocumentParser::Configuration config = parser->configuration();
|
||||||
|
if (config.preferredProjectPartId != parseContextId) {
|
||||||
|
config.preferredProjectPartId = parseContextId;
|
||||||
|
processor()->setParserConfig(config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,6 +337,23 @@ void CppEditorDocument::releaseResources()
|
|||||||
m_processor.reset();
|
m_processor.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CppEditorDocument::showHideInfoBarAboutMultipleParseContexts(bool show)
|
||||||
|
{
|
||||||
|
const Core::Id id = Constants::MULTIPLE_PARSE_CONTEXTS_AVAILABLE;
|
||||||
|
|
||||||
|
if (show) {
|
||||||
|
Core::InfoBarEntry info(id,
|
||||||
|
tr("Note: Multiple parse contexts are available for this file. "
|
||||||
|
"Choose the preferred one from the editor toolbar."),
|
||||||
|
Core::InfoBarEntry::GlobalSuppressionEnabled);
|
||||||
|
info.removeCancelButton();
|
||||||
|
if (infoBar()->canInfoBeAdded(id))
|
||||||
|
infoBar()->addInfo(info);
|
||||||
|
} else {
|
||||||
|
infoBar()->removeInfo(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void CppEditorDocument::initializeTimer()
|
void CppEditorDocument::initializeTimer()
|
||||||
{
|
{
|
||||||
m_processorTimer.setSingleShot(true);
|
m_processorTimer.setSingleShot(true);
|
||||||
@@ -311,6 +366,11 @@ void CppEditorDocument::initializeTimer()
|
|||||||
Qt::UniqueConnection);
|
Qt::UniqueConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ParseContextModel &CppEditorDocument::parseContextModel()
|
||||||
|
{
|
||||||
|
return m_parseContextModel;
|
||||||
|
}
|
||||||
|
|
||||||
const MinimizableInfoBars &CppEditorDocument::minimizableInfoBars() const
|
const MinimizableInfoBars &CppEditorDocument::minimizableInfoBars() const
|
||||||
{
|
{
|
||||||
return m_minimizableInfoBars;
|
return m_minimizableInfoBars;
|
||||||
@@ -321,11 +381,15 @@ CppTools::BaseEditorDocumentProcessor *CppEditorDocument::processor()
|
|||||||
if (!m_processor) {
|
if (!m_processor) {
|
||||||
m_processor.reset(mm()->editorDocumentProcessor(this));
|
m_processor.reset(mm()->editorDocumentProcessor(this));
|
||||||
connect(m_processor.data(), &CppTools::BaseEditorDocumentProcessor::projectPartInfoUpdated,
|
connect(m_processor.data(), &CppTools::BaseEditorDocumentProcessor::projectPartInfoUpdated,
|
||||||
[this] (const CppTools::ProjectPartInfo &projectPartInfo)
|
[this] (const CppTools::ProjectPartInfo &info)
|
||||||
{
|
{
|
||||||
const bool hasProjectPart
|
using namespace CppTools;
|
||||||
= projectPartInfo.hint != CppTools::ProjectPartInfo::IsFallbackMatch;
|
const bool hasProjectPart = !(info.hints & ProjectPartInfo::IsFallbackMatch);
|
||||||
m_minimizableInfoBars.processHasProjectPart(hasProjectPart);
|
m_minimizableInfoBars.processHasProjectPart(hasProjectPart);
|
||||||
|
m_parseContextModel.update(info);
|
||||||
|
const bool isAmbiguous = info.hints & ProjectPartInfo::IsAmbiguousMatch;
|
||||||
|
const bool isProjectFile = info.hints & ProjectPartInfo::IsFromProjectMatch;
|
||||||
|
showHideInfoBarAboutMultipleParseContexts(isAmbiguous && isProjectFile);
|
||||||
});
|
});
|
||||||
connect(m_processor.data(), &CppTools::BaseEditorDocumentProcessor::codeWarningsUpdated,
|
connect(m_processor.data(), &CppTools::BaseEditorDocumentProcessor::codeWarningsUpdated,
|
||||||
[this] (unsigned revision,
|
[this] (unsigned revision,
|
||||||
|
@@ -26,6 +26,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "cppminimizableinfobars.h"
|
#include "cppminimizableinfobars.h"
|
||||||
|
#include "cppparsecontext.h"
|
||||||
|
|
||||||
#include <cpptools/baseeditordocumentprocessor.h>
|
#include <cpptools/baseeditordocumentprocessor.h>
|
||||||
#include <cpptools/cppcompletionassistprovider.h>
|
#include <cpptools/cppcompletionassistprovider.h>
|
||||||
@@ -57,11 +58,13 @@ public:
|
|||||||
void recalculateSemanticInfoDetached();
|
void recalculateSemanticInfoDetached();
|
||||||
CppTools::SemanticInfo recalculateSemanticInfo(); // TODO: Remove me
|
CppTools::SemanticInfo recalculateSemanticInfo(); // TODO: Remove me
|
||||||
|
|
||||||
void setPreprocessorSettings(const CppTools::ProjectPart::Ptr &projectPart,
|
void setPreferredParseContext(const QString &parseContextId);
|
||||||
const QByteArray &defines);
|
void setExtraPreprocessorDirectives(const QByteArray &directives);
|
||||||
|
|
||||||
void scheduleProcessDocument();
|
void scheduleProcessDocument();
|
||||||
|
|
||||||
const MinimizableInfoBars &minimizableInfoBars() const;
|
const MinimizableInfoBars &minimizableInfoBars() const;
|
||||||
|
ParseContextModel &parseContextModel();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void codeWarningsUpdated(unsigned contentsRevision,
|
void codeWarningsUpdated(unsigned contentsRevision,
|
||||||
@@ -87,6 +90,8 @@ private:
|
|||||||
void onAboutToReload();
|
void onAboutToReload();
|
||||||
void onReloadFinished();
|
void onReloadFinished();
|
||||||
|
|
||||||
|
void reparseWithPreferredParseContext(const QString &id);
|
||||||
|
|
||||||
void processDocument();
|
void processDocument();
|
||||||
|
|
||||||
QByteArray contentsText() const;
|
QByteArray contentsText() const;
|
||||||
@@ -94,9 +99,12 @@ private:
|
|||||||
|
|
||||||
CppTools::BaseEditorDocumentProcessor *processor();
|
CppTools::BaseEditorDocumentProcessor *processor();
|
||||||
void resetProcessor();
|
void resetProcessor();
|
||||||
void updatePreprocessorSettings();
|
void applyPreferredParseContextFromSettings();
|
||||||
|
void applyExtraPreprocessorDirectivesFromSettings();
|
||||||
void releaseResources();
|
void releaseResources();
|
||||||
|
|
||||||
|
void showHideInfoBarAboutMultipleParseContexts(bool show);
|
||||||
|
|
||||||
void initializeTimer();
|
void initializeTimer();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -118,6 +126,7 @@ private:
|
|||||||
QScopedPointer<CppTools::CppEditorDocumentHandle> m_editorDocumentHandle;
|
QScopedPointer<CppTools::CppEditorDocumentHandle> m_editorDocumentHandle;
|
||||||
|
|
||||||
MinimizableInfoBars m_minimizableInfoBars;
|
MinimizableInfoBars m_minimizableInfoBars;
|
||||||
|
ParseContextModel m_parseContextModel;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Internal
|
} // namespace Internal
|
||||||
|
172
src/plugins/cppeditor/cppparsecontext.cpp
Normal file
172
src/plugins/cppeditor/cppparsecontext.cpp
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2017 The Qt Company Ltd.
|
||||||
|
** Contact: https://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of Qt Creator.
|
||||||
|
**
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and The Qt Company. For licensing terms
|
||||||
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||||
|
** information use the contact form at https://www.qt.io/contact-us.
|
||||||
|
**
|
||||||
|
** GNU General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU
|
||||||
|
** General Public License version 3 as published by the Free Software
|
||||||
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||||
|
** included in the packaging of this file. Please review the following
|
||||||
|
** information to ensure the GNU General Public License requirements will
|
||||||
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include "cppparsecontext.h"
|
||||||
|
|
||||||
|
#include <QAction>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
#include <utils/algorithm.h>
|
||||||
|
#include <utils/qtcassert.h>
|
||||||
|
|
||||||
|
namespace CppEditor {
|
||||||
|
namespace Internal {
|
||||||
|
|
||||||
|
void ParseContextModel::update(const CppTools::ProjectPartInfo &projectPartInfo)
|
||||||
|
{
|
||||||
|
beginResetModel();
|
||||||
|
reset(projectPartInfo);
|
||||||
|
endResetModel();
|
||||||
|
|
||||||
|
emit updated(areMultipleAvailable());
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ParseContextModel::currentToolTip() const
|
||||||
|
{
|
||||||
|
const QModelIndex index = createIndex(m_currentIndex, 0);
|
||||||
|
if (!index.isValid())
|
||||||
|
return QString();
|
||||||
|
|
||||||
|
return tr("<p><b>Active Parse Context</b>:<br/>%1</p>"
|
||||||
|
"<p>Multiple parse contexts (set of defines, include paths, and so on) "
|
||||||
|
"are available for this file.</p>"
|
||||||
|
"<p>Choose a parse context to set it as the preferred one. "
|
||||||
|
"Clear the preference from the context menu.</p>")
|
||||||
|
.arg(data(index, Qt::ToolTipRole).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ParseContextModel::setPreferred(int index)
|
||||||
|
{
|
||||||
|
if (index < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
emit preferredParseContextChanged(m_projectParts[index]->id());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ParseContextModel::clearPreferred()
|
||||||
|
{
|
||||||
|
emit preferredParseContextChanged(QString());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ParseContextModel::areMultipleAvailable() const
|
||||||
|
{
|
||||||
|
return m_projectParts.size() >= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ParseContextModel::reset(const CppTools::ProjectPartInfo &projectPartInfo)
|
||||||
|
{
|
||||||
|
// Sort
|
||||||
|
m_hints = projectPartInfo.hints;
|
||||||
|
m_projectParts = projectPartInfo.projectParts;
|
||||||
|
Utils::sort(m_projectParts, &CppTools::ProjectPart::displayName);
|
||||||
|
|
||||||
|
// Determine index for current
|
||||||
|
const QString id = projectPartInfo.projectPart->id();
|
||||||
|
m_currentIndex = Utils::indexOf(m_projectParts, [id](const CppTools::ProjectPart::Ptr &pp) {
|
||||||
|
return pp->id() == id;
|
||||||
|
});
|
||||||
|
QTC_CHECK(m_currentIndex >= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ParseContextModel::currentIndex() const
|
||||||
|
{
|
||||||
|
return m_currentIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ParseContextModel::isCurrentPreferred() const
|
||||||
|
{
|
||||||
|
return m_hints & CppTools::ProjectPartInfo::IsPreferredMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ParseContextModel::currentId() const
|
||||||
|
{
|
||||||
|
if (m_currentIndex < 0)
|
||||||
|
return QString();
|
||||||
|
|
||||||
|
return m_projectParts[m_currentIndex]->id();
|
||||||
|
}
|
||||||
|
|
||||||
|
int ParseContextModel::rowCount(const QModelIndex &) const
|
||||||
|
{
|
||||||
|
return m_projectParts.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant ParseContextModel::data(const QModelIndex &index, int role) const
|
||||||
|
{
|
||||||
|
if (m_projectParts.isEmpty())
|
||||||
|
return QVariant();
|
||||||
|
|
||||||
|
const int row = index.row();
|
||||||
|
if (role == Qt::DisplayRole)
|
||||||
|
return m_projectParts[row]->displayName;
|
||||||
|
else if (role == Qt::ToolTipRole)
|
||||||
|
return QDir::toNativeSeparators(m_projectParts[row]->projectFile);
|
||||||
|
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
ParseContextWidget::ParseContextWidget(ParseContextModel &parseContextModel, QWidget *parent)
|
||||||
|
: QComboBox(parent)
|
||||||
|
, m_parseContextModel(parseContextModel)
|
||||||
|
{
|
||||||
|
// Set up context menu with a clear action
|
||||||
|
setContextMenuPolicy(Qt::ActionsContextMenu);
|
||||||
|
m_clearPreferredAction = new QAction(tr("Clear Preferred Parse Context"), this);
|
||||||
|
connect(m_clearPreferredAction, &QAction::triggered,[&]() {
|
||||||
|
m_parseContextModel.clearPreferred();
|
||||||
|
});
|
||||||
|
addAction(m_clearPreferredAction);
|
||||||
|
|
||||||
|
// Set up sync of this widget and model in both directions
|
||||||
|
connect(this,
|
||||||
|
static_cast<void (QComboBox::*)(int)>(&QComboBox::activated),
|
||||||
|
&m_parseContextModel,
|
||||||
|
&ParseContextModel::setPreferred);
|
||||||
|
connect(&m_parseContextModel, &ParseContextModel::updated,
|
||||||
|
this, &ParseContextWidget::syncToModel);
|
||||||
|
|
||||||
|
// Set up model
|
||||||
|
setModel(&m_parseContextModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ParseContextWidget::syncToModel()
|
||||||
|
{
|
||||||
|
const int index = m_parseContextModel.currentIndex();
|
||||||
|
if (index < 0)
|
||||||
|
return; // E.g. editor was duplicated but no project context was determined yet.
|
||||||
|
|
||||||
|
if (currentIndex() != index)
|
||||||
|
setCurrentIndex(index);
|
||||||
|
|
||||||
|
setToolTip(m_parseContextModel.currentToolTip());
|
||||||
|
|
||||||
|
const bool isPreferred = m_parseContextModel.isCurrentPreferred();
|
||||||
|
m_clearPreferredAction->setEnabled(isPreferred);
|
||||||
|
QComboBox::setProperty("highlightWidget", isPreferred);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Internal
|
||||||
|
} // namespace CppEditor
|
83
src/plugins/cppeditor/cppparsecontext.h
Normal file
83
src/plugins/cppeditor/cppparsecontext.h
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2017 The Qt Company Ltd.
|
||||||
|
** Contact: https://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of Qt Creator.
|
||||||
|
**
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and The Qt Company. For licensing terms
|
||||||
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||||
|
** information use the contact form at https://www.qt.io/contact-us.
|
||||||
|
**
|
||||||
|
** GNU General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU
|
||||||
|
** General Public License version 3 as published by the Free Software
|
||||||
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||||
|
** included in the packaging of this file. Please review the following
|
||||||
|
** information to ensure the GNU General Public License requirements will
|
||||||
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cpptools/cpptools_utils.h>
|
||||||
|
|
||||||
|
#include <QAbstractListModel>
|
||||||
|
#include <QComboBox>
|
||||||
|
|
||||||
|
namespace CppEditor {
|
||||||
|
namespace Internal {
|
||||||
|
|
||||||
|
class ParseContextModel : public QAbstractListModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
void update(const CppTools::ProjectPartInfo &projectPartInfo);
|
||||||
|
|
||||||
|
void setPreferred(int index);
|
||||||
|
void clearPreferred();
|
||||||
|
|
||||||
|
int currentIndex() const;
|
||||||
|
bool isCurrentPreferred() const;
|
||||||
|
|
||||||
|
QString currentId() const;
|
||||||
|
QString currentToolTip() const;
|
||||||
|
|
||||||
|
bool areMultipleAvailable() const;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void updated(bool areMultipleAvailable);
|
||||||
|
void preferredParseContextChanged(const QString &id);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void reset(const CppTools::ProjectPartInfo &projectPartInfo);
|
||||||
|
int rowCount(const QModelIndex &parent) const override;
|
||||||
|
QVariant data(const QModelIndex &index, int role) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
CppTools::ProjectPartInfo::Hints m_hints;
|
||||||
|
QList<CppTools::ProjectPart::Ptr> m_projectParts;
|
||||||
|
int m_currentIndex = -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ParseContextWidget : public QComboBox
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
ParseContextWidget(ParseContextModel &parseContextModel, QWidget *parent);
|
||||||
|
void syncToModel();
|
||||||
|
|
||||||
|
private:
|
||||||
|
ParseContextModel &m_parseContextModel;
|
||||||
|
QAction *m_clearPreferredAction = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Internal
|
||||||
|
} // namespace CppEditor
|
@@ -31,18 +31,9 @@
|
|||||||
|
|
||||||
#include <projectexplorer/session.h>
|
#include <projectexplorer/session.h>
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
using namespace CppEditor::Internal;
|
using namespace CppEditor::Internal;
|
||||||
|
|
||||||
static bool projectPartLessThan(const CppTools::ProjectPart::Ptr &projectPart1,
|
CppPreProcessorDialog::CppPreProcessorDialog(const QString &filePath, QWidget *parent)
|
||||||
const CppTools::ProjectPart::Ptr &projectPart2)
|
|
||||||
{
|
|
||||||
return projectPart1->displayName < projectPart2->displayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
CppPreProcessorDialog::CppPreProcessorDialog(QWidget *parent, const QString &filePath,
|
|
||||||
const QList<CppTools::ProjectPart::Ptr> &projectParts)
|
|
||||||
: QDialog(parent)
|
: QDialog(parent)
|
||||||
, m_ui(new Ui::CppPreProcessorDialog())
|
, m_ui(new Ui::CppPreProcessorDialog())
|
||||||
, m_filePath(filePath)
|
, m_filePath(filePath)
|
||||||
@@ -55,32 +46,9 @@ CppPreProcessorDialog::CppPreProcessorDialog(QWidget *parent, const QString &fil
|
|||||||
|
|
||||||
CppSnippetProvider().decorateEditor(m_ui->editWidget);
|
CppSnippetProvider().decorateEditor(m_ui->editWidget);
|
||||||
|
|
||||||
const QString &projectPartIdToUse = ProjectExplorer::SessionManager::value(
|
const QString key = Constants::EXTRA_PREPROCESSOR_DIRECTIVES + m_filePath;
|
||||||
QLatin1String(Constants::CPP_PREPROCESSOR_PROJECT_PREFIX) + m_filePath).toString();
|
const QString directives = ProjectExplorer::SessionManager::value(key).toString();
|
||||||
int currentIndex = 0;
|
m_ui->editWidget->setPlainText(directives);
|
||||||
|
|
||||||
QList<CppTools::ProjectPart::Ptr> sortedProjectParts(projectParts);
|
|
||||||
std::stable_sort(sortedProjectParts.begin(), sortedProjectParts.end(), projectPartLessThan);
|
|
||||||
|
|
||||||
foreach (CppTools::ProjectPart::Ptr projectPart, sortedProjectParts) {
|
|
||||||
m_ui->projectComboBox->addItem(projectPart->displayName);
|
|
||||||
ProjectPartAddition addition;
|
|
||||||
addition.projectPart = projectPart;
|
|
||||||
addition.additionalDirectives = ProjectExplorer::SessionManager::value(
|
|
||||||
projectPart->id() + QLatin1Char(',') + m_filePath).toString();
|
|
||||||
if (projectPart->id() == projectPartIdToUse)
|
|
||||||
currentIndex = m_ui->projectComboBox->count() - 1;
|
|
||||||
m_partAdditions << addition;
|
|
||||||
}
|
|
||||||
if (m_ui->projectComboBox->count() <= 1)
|
|
||||||
m_ui->projectComboBox->setEnabled(false);
|
|
||||||
m_ui->projectComboBox->setCurrentIndex(currentIndex);
|
|
||||||
m_ui->editWidget->setPlainText(m_partAdditions.value(currentIndex).additionalDirectives);
|
|
||||||
|
|
||||||
connect(m_ui->projectComboBox,
|
|
||||||
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
|
|
||||||
this, &CppPreProcessorDialog::projectChanged);
|
|
||||||
connect(m_ui->editWidget, &QPlainTextEdit::textChanged, this, &CppPreProcessorDialog::textChanged);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CppPreProcessorDialog::~CppPreProcessorDialog()
|
CppPreProcessorDialog::~CppPreProcessorDialog()
|
||||||
@@ -93,41 +61,13 @@ int CppPreProcessorDialog::exec()
|
|||||||
if (QDialog::exec() == Rejected)
|
if (QDialog::exec() == Rejected)
|
||||||
return Rejected;
|
return Rejected;
|
||||||
|
|
||||||
ProjectExplorer::SessionManager::setValue(
|
const QString key = Constants::EXTRA_PREPROCESSOR_DIRECTIVES + m_filePath;
|
||||||
QLatin1String(Constants::CPP_PREPROCESSOR_PROJECT_PREFIX) + m_filePath,
|
ProjectExplorer::SessionManager::setValue(key, extraPreprocessorDirectives());
|
||||||
m_partAdditions[m_ui->projectComboBox->currentIndex()].projectPart->id());
|
|
||||||
|
|
||||||
foreach (ProjectPartAddition partAddition, m_partAdditions) {
|
|
||||||
const QString &previousDirectives = ProjectExplorer::SessionManager::value(
|
|
||||||
partAddition.projectPart->id()
|
|
||||||
+ QLatin1Char(',')
|
|
||||||
+ m_filePath).toString();
|
|
||||||
if (previousDirectives != partAddition.additionalDirectives) {
|
|
||||||
ProjectExplorer::SessionManager::setValue(
|
|
||||||
partAddition.projectPart->id() + QLatin1Char(',') + m_filePath,
|
|
||||||
partAddition.additionalDirectives);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Accepted;
|
return Accepted;
|
||||||
}
|
}
|
||||||
|
|
||||||
CppTools::ProjectPart::Ptr CppPreProcessorDialog::projectPart() const
|
QString CppPreProcessorDialog::extraPreprocessorDirectives() const
|
||||||
{
|
|
||||||
return m_partAdditions[m_ui->projectComboBox->currentIndex()].projectPart;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString CppPreProcessorDialog::additionalPreProcessorDirectives() const
|
|
||||||
{
|
{
|
||||||
return m_ui->editWidget->toPlainText();
|
return m_ui->editWidget->toPlainText();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CppPreProcessorDialog::projectChanged(int index)
|
|
||||||
{
|
|
||||||
m_ui->editWidget->setPlainText(m_partAdditions[index].additionalDirectives);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CppPreProcessorDialog::textChanged()
|
|
||||||
{
|
|
||||||
m_partAdditions[m_ui->projectComboBox->currentIndex()].additionalDirectives
|
|
||||||
= m_ui->editWidget->toPlainText();
|
|
||||||
}
|
|
||||||
|
@@ -25,9 +25,8 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cpptools/cppmodelmanager.h>
|
|
||||||
|
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
namespace CppEditor {
|
namespace CppEditor {
|
||||||
namespace Internal {
|
namespace Internal {
|
||||||
@@ -38,27 +37,17 @@ class CppPreProcessorDialog : public QDialog
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit CppPreProcessorDialog(QWidget *parent, const QString &filePath,
|
explicit CppPreProcessorDialog(const QString &filePath, QWidget *parent);
|
||||||
const QList<CppTools::ProjectPart::Ptr> &projectParts);
|
|
||||||
~CppPreProcessorDialog();
|
~CppPreProcessorDialog();
|
||||||
|
|
||||||
int exec();
|
int exec();
|
||||||
|
|
||||||
CppTools::ProjectPart::Ptr projectPart() const;
|
QString extraPreprocessorDirectives() const;
|
||||||
QString additionalPreProcessorDirectives() const;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void projectChanged(int index);
|
|
||||||
void textChanged();
|
|
||||||
|
|
||||||
struct ProjectPartAddition {
|
|
||||||
CppTools::ProjectPart::Ptr projectPart;
|
|
||||||
QString additionalDirectives;
|
|
||||||
};
|
|
||||||
|
|
||||||
Ui::CppPreProcessorDialog *m_ui;
|
Ui::CppPreProcessorDialog *m_ui;
|
||||||
QList<ProjectPartAddition> m_partAdditions;
|
const QString m_filePath;
|
||||||
QString m_filePath;
|
const QString m_projectPartId;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Internal
|
} // namespace Internal
|
||||||
|
@@ -14,34 +14,6 @@
|
|||||||
<string>Additional C++ Preprocessor Directives</string>
|
<string>Additional C++ Preprocessor Directives</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="projectLabel">
|
|
||||||
<property name="text">
|
|
||||||
<string>Project:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QComboBox" name="projectComboBox">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="Line" name="separator">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="editorLabel">
|
<widget class="QLabel" name="editorLabel">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
@@ -87,6 +87,12 @@ void BaseEditorDocumentProcessor::editorDocumentTimerRestarted()
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BaseEditorDocumentProcessor::setParserConfig(
|
||||||
|
const BaseEditorDocumentParser::Configuration config)
|
||||||
|
{
|
||||||
|
parser()->setConfiguration(config);
|
||||||
|
}
|
||||||
|
|
||||||
void BaseEditorDocumentProcessor::runParser(QFutureInterface<void> &future,
|
void BaseEditorDocumentProcessor::runParser(QFutureInterface<void> &future,
|
||||||
BaseEditorDocumentParser::Ptr parser,
|
BaseEditorDocumentParser::Ptr parser,
|
||||||
BaseEditorDocumentParser::UpdateParams updateParams)
|
BaseEditorDocumentParser::UpdateParams updateParams)
|
||||||
|
@@ -70,6 +70,8 @@ public:
|
|||||||
|
|
||||||
virtual void editorDocumentTimerRestarted();
|
virtual void editorDocumentTimerRestarted();
|
||||||
|
|
||||||
|
virtual void setParserConfig(const BaseEditorDocumentParser::Configuration config);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using HeaderErrorDiagnosticWidgetCreator = std::function<QWidget*()>;
|
using HeaderErrorDiagnosticWidgetCreator = std::function<QWidget*()>;
|
||||||
|
|
||||||
|
@@ -37,7 +37,7 @@ public:
|
|||||||
struct PrioritizedProjectPart
|
struct PrioritizedProjectPart
|
||||||
{
|
{
|
||||||
PrioritizedProjectPart(const ProjectPart::Ptr &projectPart, int priority)
|
PrioritizedProjectPart(const ProjectPart::Ptr &projectPart, int priority)
|
||||||
: projectPart(projectPart) , priority(priority) {}
|
: projectPart(projectPart), priority(priority) {}
|
||||||
|
|
||||||
ProjectPart::Ptr projectPart;
|
ProjectPart::Ptr projectPart;
|
||||||
int priority = 0;
|
int priority = 0;
|
||||||
@@ -46,38 +46,42 @@ public:
|
|||||||
ProjectPartPrioritizer(const QList<ProjectPart::Ptr> &projectParts,
|
ProjectPartPrioritizer(const QList<ProjectPart::Ptr> &projectParts,
|
||||||
const QString &preferredProjectPartId,
|
const QString &preferredProjectPartId,
|
||||||
const ProjectExplorer::Project *activeProject,
|
const ProjectExplorer::Project *activeProject,
|
||||||
Language languagePreference)
|
Language languagePreference,
|
||||||
: m_projectParts(projectParts)
|
bool areProjectPartsFromDependencies)
|
||||||
, m_preferredProjectPartId(preferredProjectPartId)
|
: m_preferredProjectPartId(preferredProjectPartId)
|
||||||
, m_activeProject(activeProject)
|
, m_activeProject(activeProject)
|
||||||
, m_languagePreference(languagePreference)
|
, m_languagePreference(languagePreference)
|
||||||
{
|
{
|
||||||
// Determine best project part
|
// Prioritize
|
||||||
const QList<PrioritizedProjectPart> prioritized = prioritize();
|
const QList<PrioritizedProjectPart> prioritized = prioritize(projectParts);
|
||||||
m_projectPart = prioritized.first().projectPart;
|
for (const PrioritizedProjectPart &ppp : prioritized)
|
||||||
|
m_info.projectParts << ppp.projectPart;
|
||||||
|
|
||||||
// Determine ambiguity
|
// Best project part
|
||||||
m_isAmbiguous = prioritized.size() > 1
|
m_info.projectPart = m_info.projectParts.first();
|
||||||
? prioritized[0].priority == prioritized[1].priority
|
|
||||||
: false;
|
// Hints
|
||||||
|
if (m_info.projectParts.size() > 1)
|
||||||
|
m_info.hints |= ProjectPartInfo::IsAmbiguousMatch;
|
||||||
|
if (prioritized.first().priority > 1000)
|
||||||
|
m_info.hints |= ProjectPartInfo::IsPreferredMatch;
|
||||||
|
if (areProjectPartsFromDependencies)
|
||||||
|
m_info.hints |= ProjectPartInfo::IsFromDependenciesMatch;
|
||||||
|
else
|
||||||
|
m_info.hints |= ProjectPartInfo::IsFromProjectMatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
ProjectPart::Ptr projectPart()
|
ProjectPartInfo info() const
|
||||||
{
|
{
|
||||||
return m_projectPart;
|
return m_info;
|
||||||
}
|
|
||||||
|
|
||||||
bool isAmbiguous() const
|
|
||||||
{
|
|
||||||
return m_isAmbiguous;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QList<PrioritizedProjectPart> prioritize()
|
QList<PrioritizedProjectPart> prioritize(const QList<ProjectPart::Ptr> &projectParts)
|
||||||
{
|
{
|
||||||
// Prioritize
|
// Prioritize
|
||||||
QList<PrioritizedProjectPart> prioritized = Utils::transform(m_projectParts,
|
QList<PrioritizedProjectPart> prioritized = Utils::transform(projectParts,
|
||||||
[&](const ProjectPart::Ptr &projectPart) {
|
[&](const ProjectPart::Ptr &projectPart) {
|
||||||
return PrioritizedProjectPart{projectPart, priority(*projectPart)};
|
return PrioritizedProjectPart{projectPart, priority(*projectPart)};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -118,14 +122,12 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const QList<ProjectPart::Ptr> m_projectParts;
|
|
||||||
const QString m_preferredProjectPartId;
|
const QString m_preferredProjectPartId;
|
||||||
const ProjectExplorer::Project *m_activeProject = nullptr;
|
const ProjectExplorer::Project *m_activeProject = nullptr;
|
||||||
Language m_languagePreference = Language::Cxx;
|
Language m_languagePreference = Language::Cxx;
|
||||||
|
|
||||||
// Results
|
// Results
|
||||||
ProjectPart::Ptr m_projectPart;
|
ProjectPartInfo m_info;
|
||||||
bool m_isAmbiguous = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ProjectPartInfo ProjectPartChooser::choose(
|
ProjectPartInfo ProjectPartChooser::choose(
|
||||||
@@ -141,40 +143,30 @@ ProjectPartInfo ProjectPartChooser::choose(
|
|||||||
QTC_CHECK(m_fallbackProjectPart);
|
QTC_CHECK(m_fallbackProjectPart);
|
||||||
|
|
||||||
ProjectPart::Ptr projectPart = currentProjectPartInfo.projectPart;
|
ProjectPart::Ptr projectPart = currentProjectPartInfo.projectPart;
|
||||||
ProjectPartInfo::Hint hint = ProjectPartInfo::NoHint;
|
|
||||||
|
|
||||||
QList<ProjectPart::Ptr> projectParts = m_projectPartsForFile(filePath);
|
QList<ProjectPart::Ptr> projectParts = m_projectPartsForFile(filePath);
|
||||||
|
bool areProjectPartsFromDependencies = false;
|
||||||
|
|
||||||
if (projectParts.isEmpty()) {
|
if (projectParts.isEmpty()) {
|
||||||
if (!projectsUpdated && projectPart
|
if (!projectsUpdated && projectPart
|
||||||
&& currentProjectPartInfo.hint == ProjectPartInfo::IsFallbackMatch)
|
&& currentProjectPartInfo.hints & ProjectPartInfo::IsFallbackMatch)
|
||||||
// Avoid re-calculating the expensive dependency table for non-project files.
|
// Avoid re-calculating the expensive dependency table for non-project files.
|
||||||
return {projectPart, ProjectPartInfo::IsFallbackMatch};
|
return ProjectPartInfo(projectPart, {projectPart}, ProjectPartInfo::IsFallbackMatch);
|
||||||
|
|
||||||
// Fall-back step 1: Get some parts through the dependency table:
|
// Fall-back step 1: Get some parts through the dependency table:
|
||||||
projectParts = m_projectPartsFromDependenciesForFile(filePath);
|
projectParts = m_projectPartsFromDependenciesForFile(filePath);
|
||||||
if (projectParts.isEmpty()) {
|
if (projectParts.isEmpty()) {
|
||||||
// Fall-back step 2: Use fall-back part from the model manager:
|
// Fall-back step 2: Use fall-back part from the model manager:
|
||||||
projectPart = m_fallbackProjectPart();
|
projectPart = m_fallbackProjectPart();
|
||||||
hint = ProjectPartInfo::IsFallbackMatch;
|
return ProjectPartInfo(projectPart, {projectPart}, ProjectPartInfo::IsFallbackMatch);
|
||||||
} else {
|
|
||||||
ProjectPartPrioritizer prioritizer(projectParts,
|
|
||||||
preferredProjectPartId,
|
|
||||||
activeProject,
|
|
||||||
languagePreference);
|
|
||||||
projectPart = prioritizer.projectPart();
|
|
||||||
}
|
}
|
||||||
} else {
|
areProjectPartsFromDependencies = true;
|
||||||
ProjectPartPrioritizer prioritizer(projectParts,
|
|
||||||
preferredProjectPartId,
|
|
||||||
activeProject,
|
|
||||||
languagePreference);
|
|
||||||
projectPart = prioritizer.projectPart();
|
|
||||||
hint = prioritizer.isAmbiguous()
|
|
||||||
? ProjectPartInfo::IsAmbiguousMatch
|
|
||||||
: ProjectPartInfo::NoHint;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {projectPart, hint};
|
return ProjectPartPrioritizer(projectParts,
|
||||||
|
preferredProjectPartId,
|
||||||
|
activeProject,
|
||||||
|
languagePreference,
|
||||||
|
areProjectPartsFromDependencies).info();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProjectPartChooser::setFallbackProjectPart(const FallBackProjectPart &getter)
|
void ProjectPartChooser::setFallbackProjectPart(const FallBackProjectPart &getter)
|
||||||
|
@@ -34,14 +34,29 @@ enum class Language { C, Cxx };
|
|||||||
class ProjectPartInfo {
|
class ProjectPartInfo {
|
||||||
public:
|
public:
|
||||||
enum Hint {
|
enum Hint {
|
||||||
NoHint,
|
NoHint = 0,
|
||||||
IsFallbackMatch,
|
IsFallbackMatch = 1 << 0,
|
||||||
IsAmbiguousMatch
|
IsAmbiguousMatch = 1 << 1,
|
||||||
|
IsPreferredMatch = 1 << 2,
|
||||||
|
IsFromProjectMatch = 1 << 3,
|
||||||
|
IsFromDependenciesMatch = 1 << 4,
|
||||||
};
|
};
|
||||||
|
Q_DECLARE_FLAGS(Hints, Hint)
|
||||||
|
|
||||||
|
ProjectPartInfo() = default;
|
||||||
|
ProjectPartInfo(const ProjectPart::Ptr &projectPart,
|
||||||
|
const QList<ProjectPart::Ptr> &projectParts,
|
||||||
|
Hints hints)
|
||||||
|
: projectPart(projectPart)
|
||||||
|
, projectParts(projectParts)
|
||||||
|
, hints(hints)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ProjectPart::Ptr projectPart;
|
ProjectPart::Ptr projectPart;
|
||||||
Hint hint;
|
QList<ProjectPart::Ptr> projectParts; // The one above as first plus alternatives.
|
||||||
|
Hints hints = NoHint;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace CppTools
|
} // namespace CppTools
|
||||||
|
@@ -49,7 +49,9 @@ protected:
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
QString filePath;
|
QString filePath;
|
||||||
ProjectPartInfo currentProjectPartInfo{ProjectPart::Ptr(new ProjectPart),
|
ProjectPart::Ptr currentProjectPart{new ProjectPart};
|
||||||
|
ProjectPartInfo currentProjectPartInfo{currentProjectPart,
|
||||||
|
{currentProjectPart},
|
||||||
ProjectPartInfo::NoHint};
|
ProjectPartInfo::NoHint};
|
||||||
QString preferredProjectPartId;
|
QString preferredProjectPartId;
|
||||||
const ProjectExplorer::Project *activeProject = nullptr;
|
const ProjectExplorer::Project *activeProject = nullptr;
|
||||||
@@ -74,6 +76,37 @@ TEST_F(ProjectPartChooser, ChooseManuallySet)
|
|||||||
ASSERT_THAT(chosen, Eq(p2));
|
ASSERT_THAT(chosen, Eq(p2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(ProjectPartChooser, IndicateManuallySet)
|
||||||
|
{
|
||||||
|
ProjectPart::Ptr p1(new ProjectPart);
|
||||||
|
ProjectPart::Ptr p2(new ProjectPart);
|
||||||
|
p2->projectFile = preferredProjectPartId = "someId";
|
||||||
|
projectPartsForFile += {p1, p2};
|
||||||
|
|
||||||
|
const ProjectPartInfo::Hints hints = choose().hints;
|
||||||
|
|
||||||
|
ASSERT_TRUE(hints & ProjectPartInfo::IsPreferredMatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ProjectPartChooser, IndicateManuallySetForFallbackToProjectPartFromDependencies)
|
||||||
|
{
|
||||||
|
ProjectPart::Ptr p1(new ProjectPart);
|
||||||
|
ProjectPart::Ptr p2(new ProjectPart);
|
||||||
|
p2->projectFile = preferredProjectPartId = "someId";
|
||||||
|
projectPartsFromDependenciesForFile += {p1, p2};
|
||||||
|
|
||||||
|
const ProjectPartInfo::Hints hints = choose().hints;
|
||||||
|
|
||||||
|
ASSERT_TRUE(hints & ProjectPartInfo::IsPreferredMatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ProjectPartChooser, DoNotIndicateNotManuallySet)
|
||||||
|
{
|
||||||
|
const ProjectPartInfo::Hints hints = choose().hints;
|
||||||
|
|
||||||
|
ASSERT_FALSE(hints & ProjectPartInfo::IsPreferredMatch);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(ProjectPartChooser, ForMultipleChooseFromActiveProject)
|
TEST_F(ProjectPartChooser, ForMultipleChooseFromActiveProject)
|
||||||
{
|
{
|
||||||
const QList<ProjectPart::Ptr> projectParts = createProjectPartsWithDifferentProjects();
|
const QList<ProjectPart::Ptr> projectParts = createProjectPartsWithDifferentProjects();
|
||||||
@@ -154,9 +187,20 @@ TEST_F(ProjectPartChooser, IndicateMultiple)
|
|||||||
const ProjectPart::Ptr p2{new ProjectPart};
|
const ProjectPart::Ptr p2{new ProjectPart};
|
||||||
projectPartsForFile += { p1, p2 };
|
projectPartsForFile += { p1, p2 };
|
||||||
|
|
||||||
const ProjectPartInfo::Hint hint = choose().hint;
|
const ProjectPartInfo::Hints hints = choose().hints;
|
||||||
|
|
||||||
ASSERT_THAT(hint, Eq(ProjectPartInfo::Hint::IsAmbiguousMatch));
|
ASSERT_TRUE(hints & ProjectPartInfo::IsAmbiguousMatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ProjectPartChooser, IndicateMultipleForFallbackToProjectPartFromDependencies)
|
||||||
|
{
|
||||||
|
const ProjectPart::Ptr p1{new ProjectPart};
|
||||||
|
const ProjectPart::Ptr p2{new ProjectPart};
|
||||||
|
projectPartsFromDependenciesForFile += { p1, p2 };
|
||||||
|
|
||||||
|
const ProjectPartInfo::Hints hints = choose().hints;
|
||||||
|
|
||||||
|
ASSERT_TRUE(hints & ProjectPartInfo::IsAmbiguousMatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(ProjectPartChooser, ForMultipleChooseNewIfPreviousIsGone)
|
TEST_F(ProjectPartChooser, ForMultipleChooseNewIfPreviousIsGone)
|
||||||
@@ -193,7 +237,7 @@ TEST_F(ProjectPartChooser, ContinueUsingFallbackFromModelManagerIfProjectDoesNot
|
|||||||
// ...without re-calculating the dependency table.
|
// ...without re-calculating the dependency table.
|
||||||
fallbackProjectPart.reset(new ProjectPart);
|
fallbackProjectPart.reset(new ProjectPart);
|
||||||
currentProjectPartInfo.projectPart = fallbackProjectPart;
|
currentProjectPartInfo.projectPart = fallbackProjectPart;
|
||||||
currentProjectPartInfo.hint = ProjectPartInfo::IsFallbackMatch;
|
currentProjectPartInfo.hints |= ProjectPartInfo::IsFallbackMatch;
|
||||||
projectPartsFromDependenciesForFile += ProjectPart::Ptr(new ProjectPart);
|
projectPartsFromDependenciesForFile += ProjectPart::Ptr(new ProjectPart);
|
||||||
|
|
||||||
const ProjectPart::Ptr chosen = choose().projectPart;
|
const ProjectPart::Ptr chosen = choose().projectPart;
|
||||||
@@ -205,7 +249,7 @@ TEST_F(ProjectPartChooser, StopUsingFallbackFromModelManagerIfProjectChanges1)
|
|||||||
{
|
{
|
||||||
fallbackProjectPart.reset(new ProjectPart);
|
fallbackProjectPart.reset(new ProjectPart);
|
||||||
currentProjectPartInfo.projectPart = fallbackProjectPart;
|
currentProjectPartInfo.projectPart = fallbackProjectPart;
|
||||||
currentProjectPartInfo.hint = ProjectPartInfo::IsFallbackMatch;
|
currentProjectPartInfo.hints |= ProjectPartInfo::IsFallbackMatch;
|
||||||
const ProjectPart::Ptr addedProject(new ProjectPart);
|
const ProjectPart::Ptr addedProject(new ProjectPart);
|
||||||
projectPartsForFile += addedProject;
|
projectPartsForFile += addedProject;
|
||||||
|
|
||||||
@@ -218,7 +262,7 @@ TEST_F(ProjectPartChooser, StopUsingFallbackFromModelManagerIfProjectChanges2)
|
|||||||
{
|
{
|
||||||
fallbackProjectPart.reset(new ProjectPart);
|
fallbackProjectPart.reset(new ProjectPart);
|
||||||
currentProjectPartInfo.projectPart = fallbackProjectPart;
|
currentProjectPartInfo.projectPart = fallbackProjectPart;
|
||||||
currentProjectPartInfo.hint = ProjectPartInfo::IsFallbackMatch;
|
currentProjectPartInfo.hints |= ProjectPartInfo::IsFallbackMatch;
|
||||||
const ProjectPart::Ptr addedProject(new ProjectPart);
|
const ProjectPart::Ptr addedProject(new ProjectPart);
|
||||||
projectPartsFromDependenciesForFile += addedProject;
|
projectPartsFromDependenciesForFile += addedProject;
|
||||||
projectsChanged = true;
|
projectsChanged = true;
|
||||||
@@ -232,9 +276,27 @@ TEST_F(ProjectPartChooser, IndicateFallbacktoProjectPartFromModelManager)
|
|||||||
{
|
{
|
||||||
fallbackProjectPart.reset(new ProjectPart);
|
fallbackProjectPart.reset(new ProjectPart);
|
||||||
|
|
||||||
const ProjectPartInfo::Hint hint = choose().hint;
|
const ProjectPartInfo::Hints hints = choose().hints;
|
||||||
|
|
||||||
ASSERT_THAT(hint, Eq(ProjectPartInfo::Hint::IsFallbackMatch));
|
ASSERT_TRUE(hints & ProjectPartInfo::IsFallbackMatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ProjectPartChooser, IndicateFromDependencies)
|
||||||
|
{
|
||||||
|
projectPartsFromDependenciesForFile += ProjectPart::Ptr(new ProjectPart);
|
||||||
|
|
||||||
|
const ProjectPartInfo::Hints hints = choose().hints;
|
||||||
|
|
||||||
|
ASSERT_TRUE(hints & ProjectPartInfo::IsFromDependenciesMatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ProjectPartChooser, DoNotIndicateFromDependencies)
|
||||||
|
{
|
||||||
|
projectPartsForFile += ProjectPart::Ptr(new ProjectPart);
|
||||||
|
|
||||||
|
const ProjectPartInfo::Hints hints = choose().hints;
|
||||||
|
|
||||||
|
ASSERT_FALSE(hints & ProjectPartInfo::IsFromDependenciesMatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProjectPartChooser::SetUp()
|
void ProjectPartChooser::SetUp()
|
||||||
|
Reference in New Issue
Block a user