Merge remote-tracking branch 'origin/4.15'

Conflicts:
	cmake/QtCreatorIDEBranding.cmake
	qbs/modules/qtc/qtc.qbs
	qtcreator_ide_branding.pri
	src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp

Change-Id: I403b236c40d73a61ae22304e289e9d4374366395
This commit is contained in:
Eike Ziller
2021-04-06 15:17:40 +02:00
221 changed files with 4167 additions and 3192 deletions

View File

@@ -421,17 +421,17 @@ void BoostTestOutputReader::onFinished(int exitCode, QProcess::ExitStatus /*exit
if (m_logLevel == LogLevel::Nothing && m_reportLevel == ReportLevel::No) {
switch (exitCode) {
case 0:
reportNoOutputFinish(tr("Running tests exited with ") + "boost::exit_success.",
reportNoOutputFinish(tr("Running tests exited with %1").arg("boost::exit_success."),
ResultType::Pass);
break;
case 200:
reportNoOutputFinish(
tr("Running tests exited with ") + "boost::exit_test_exception.",
tr("Running tests exited with %1").arg("boost::exit_test_exception."),
ResultType::MessageFatal);
break;
case 201:
reportNoOutputFinish(tr("Running tests exited with ")
+ "boost::exit_test_failure.", ResultType::Fail);
reportNoOutputFinish(tr("Running tests exited with %1")
.arg("boost::exit_test_failure."), ResultType::Fail);
break;
}
} else if (exitCode != 0 && exitCode != 201 && !m_description.isEmpty()) {

View File

@@ -27,51 +27,54 @@
namespace ClangRefactoring {
template<typename Database,
typename ReadStatement>
template<typename Database>
class QuerySqliteStatementFactory
{
public:
using DatabaseType = Database;
using ReadStatementType = ReadStatement;
template<int ResultCount>
using ReadStatement = typename Database::template ReadStatement<ResultCount>;
QuerySqliteStatementFactory(Database &database)
: database(database)
{}
Database &database;
ReadStatement selectLocationsForSymbolLocation{
ReadStatement<3> selectLocationsForSymbolLocation{
"SELECT sourceId, line, column FROM locations WHERE symbolId = "
" (SELECT symbolId FROM locations WHERE sourceId=? AND line=? AND column=?) "
"ORDER BY sourceId, line, column",
database};
ReadStatement selectSourceUsagesForSymbolLocation{
ReadStatement<3> selectSourceUsagesForSymbolLocation{
"SELECT directoryPath || '/' || sourceName, line, column "
"FROM locations NATURAL JOIN sources NATURAL JOIN directories "
"WHERE symbolId = (SELECT symbolId FROM locations WHERE sourceId=? AND line=? AND "
"column=?)",
database};
ReadStatement selectSourceUsagesOrderedForSymbolLocation{
ReadStatement<3> selectSourceUsagesOrderedForSymbolLocation{
"SELECT directoryPath || '/' || sourceName, line, column "
"FROM locations NATURAL JOIN sources NATURAL JOIN directories "
"WHERE symbolId = (SELECT symbolId FROM locations WHERE sourceId=? AND line=? AND "
"column=?) ORDER BY locationKind LIMIT 2",
database};
ReadStatement selectSourceUsagesByLocationKindForSymbolLocation{
ReadStatement<3> selectSourceUsagesByLocationKindForSymbolLocation{
"SELECT directoryPath || '/' || sourceName, line, column "
"FROM locations NATURAL JOIN sources NATURAL JOIN directories "
"WHERE symbolId = (SELECT symbolId FROM locations WHERE sourceId=? AND line=? AND "
"column=?) AND locationKind = ?",
database};
ReadStatement selectSymbolsForKindAndStartsWith{
"SELECT symbolId, symbolName, signature FROM symbols WHERE symbolKind = ? AND symbolName LIKE ?",
ReadStatement<3> selectSymbolsForKindAndStartsWith{
"SELECT symbolId, symbolName, signature FROM symbols WHERE symbolKind = ? AND symbolName "
"LIKE ?",
database};
ReadStatement selectSymbolsForKindAndStartsWith2{
"SELECT symbolId, symbolName, signature FROM symbols WHERE symbolKind IN (?,?) AND symbolName LIKE ?",
ReadStatement<3> selectSymbolsForKindAndStartsWith2{
"SELECT symbolId, symbolName, signature FROM symbols WHERE symbolKind IN (?,?) AND "
"symbolName LIKE ?",
database};
ReadStatement selectSymbolsForKindAndStartsWith3{
"SELECT symbolId, symbolName, signature FROM symbols WHERE symbolKind IN (?,?,?) AND symbolName LIKE ?",
ReadStatement<3> selectSymbolsForKindAndStartsWith3{
"SELECT symbolId, symbolName, signature FROM symbols WHERE symbolKind IN (?,?,?) AND "
"symbolName LIKE ?",
database};
ReadStatement selectLocationOfSymbol{
ReadStatement<3> selectLocationOfSymbol{
"SELECT sourceId, line, column FROM locations AS l WHERE symbolId = ? AND locationKind = ?",
database};
};

View File

@@ -40,8 +40,6 @@ namespace ClangRefactoring {
template <typename StatementFactory>
class SymbolQuery final : public SymbolQueryInterface
{
using ReadStatement = typename StatementFactory::ReadStatementType;
public:
SymbolQuery(StatementFactory &statementFactory)
: m_statementFactory(statementFactory)
@@ -51,28 +49,28 @@ public:
int line,
int utf8Column) const override
{
ReadStatement &locationsStatement = m_statementFactory.selectLocationsForSymbolLocation;
auto &locationsStatement = m_statementFactory.selectLocationsForSymbolLocation;
const std::size_t reserveSize = 128;
return locationsStatement.template values<SourceLocation, 3>(reserveSize,
filePathId.filePathId,
line,
utf8Column);
return locationsStatement.template values<SourceLocation>(reserveSize,
filePathId.filePathId,
line,
utf8Column);
}
CppTools::Usages sourceUsagesAt(ClangBackEnd::FilePathId filePathId,
int line,
int utf8Column) const override
{
ReadStatement &locationsStatement = m_statementFactory.selectSourceUsagesForSymbolLocation;
auto &locationsStatement = m_statementFactory.selectSourceUsagesForSymbolLocation;
const std::size_t reserveSize = 128;
return locationsStatement.template values<CppTools::Usage, 3>(reserveSize,
filePathId.filePathId,
line,
utf8Column);
return locationsStatement.template values<CppTools::Usage>(reserveSize,
filePathId.filePathId,
line,
utf8Column);
}
CppTools::Usages sourceUsagesAtByLocationKind(ClangBackEnd::FilePathId filePathId,
@@ -80,46 +78,46 @@ public:
int utf8Column,
ClangBackEnd::SourceLocationKind kind) const override
{
ReadStatement &locationsStatement = m_statementFactory.selectSourceUsagesByLocationKindForSymbolLocation;
auto &locationsStatement = m_statementFactory.selectSourceUsagesByLocationKindForSymbolLocation;
const std::size_t reserveSize = 128;
return locationsStatement.template values<CppTools::Usage, 3>(reserveSize,
filePathId.filePathId,
line,
utf8Column,
int(kind));
return locationsStatement.template values<CppTools::Usage>(reserveSize,
filePathId.filePathId,
line,
utf8Column,
int(kind));
}
CppTools::Usages declarationsAt(ClangBackEnd::FilePathId filePathId,
int line,
int utf8Column) const override
{
ReadStatement &locationsStatement = m_statementFactory.selectSourceUsagesOrderedForSymbolLocation;
auto &locationsStatement = m_statementFactory.selectSourceUsagesOrderedForSymbolLocation;
const std::size_t reserveSize = 128;
return locationsStatement.template values<CppTools::Usage, 3>(reserveSize,
filePathId.filePathId,
line,
utf8Column);
return locationsStatement.template values<CppTools::Usage>(reserveSize,
filePathId.filePathId,
line,
utf8Column);
}
Symbols symbolsWithOneSymbolKinds(ClangBackEnd::SymbolKind symbolKind,
Utils::SmallStringView searchTerm) const
{
ReadStatement &statement = m_statementFactory.selectSymbolsForKindAndStartsWith;
auto &statement = m_statementFactory.selectSymbolsForKindAndStartsWith;
return statement.template values<Symbol, 3>(100, int(symbolKind), searchTerm);
return statement.template values<Symbol>(100, int(symbolKind), searchTerm);
}
Symbols symbolsWithTwoSymbolKinds(ClangBackEnd::SymbolKind symbolKind1,
ClangBackEnd::SymbolKind symbolKind2,
Utils::SmallStringView searchTerm) const
{
ReadStatement &statement = m_statementFactory.selectSymbolsForKindAndStartsWith2;
auto &statement = m_statementFactory.selectSymbolsForKindAndStartsWith2;
return statement.template values<Symbol, 3>(100, int(symbolKind1), int(symbolKind2), searchTerm);
return statement.template values<Symbol>(100, int(symbolKind1), int(symbolKind2), searchTerm);
}
Symbols symbolsWithThreeSymbolKinds(ClangBackEnd::SymbolKind symbolKind1,
@@ -127,9 +125,13 @@ public:
ClangBackEnd::SymbolKind symbolKind3,
Utils::SmallStringView searchTerm) const
{
ReadStatement &statement = m_statementFactory.selectSymbolsForKindAndStartsWith3;
auto &statement = m_statementFactory.selectSymbolsForKindAndStartsWith3;
return statement.template values<Symbol, 3>(100, int(symbolKind1), int(symbolKind2), int(symbolKind3), searchTerm);
return statement.template values<Symbol>(100,
int(symbolKind1),
int(symbolKind2),
int(symbolKind3),
searchTerm);
}
Symbols symbols(const ClangBackEnd::SymbolKinds &symbolKinds,
@@ -148,9 +150,9 @@ public:
Utils::optional<SourceLocation> locationForSymbolId(SymbolId symbolId,
ClangBackEnd::SourceLocationKind kind) const override
{
ReadStatement &statement = m_statementFactory.selectLocationOfSymbol;
auto &statement = m_statementFactory.selectLocationOfSymbol;
return statement.template value<SourceLocation, 3>(symbolId, int(kind));
return statement.template value<SourceLocation>(symbolId, int(kind));
}
private:
StatementFactory &m_statementFactory;

View File

@@ -1155,7 +1155,7 @@ void ClangTool::updateForCurrentState()
const bool hasErrorText = !m_infoBarWidget->errorText().isEmpty();
const bool hasErrors = m_filesFailed > 0;
if (hasErrors && !hasErrorText) {
const QString text = makeLink( tr("Failed to analyze %1 files.").arg(m_filesFailed));
const QString text = makeLink(tr("Failed to analyze %n file(s).", nullptr, m_filesFailed));
m_infoBarWidget->setError(InfoBarWidget::Warning, text, [this]() { showOutputPane(); });
}
@@ -1171,9 +1171,8 @@ void ClangTool::updateForCurrentState()
if (m_filesCount == 0) {
infoText = tr("Analyzing..."); // Not yet fully started/initialized
} else {
infoText = tr("Analyzing... %1 of %2 files processed.")
.arg(m_filesSucceeded + m_filesFailed)
.arg(m_filesCount);
infoText = tr("Analyzing... %1 of %n file(s) processed.", nullptr, m_filesCount)
.arg(m_filesSucceeded + m_filesFailed);
}
break;
case State::PreparationStarted:
@@ -1186,7 +1185,7 @@ void ClangTool::updateForCurrentState()
infoText = tr("Analysis stopped by user.");
break;
case State::AnalyzerFinished:
infoText = tr("Finished processing %1 files.").arg(m_filesCount);
infoText = tr("Finished processing %n file(s).", nullptr, m_filesCount);
break;
case State::ImportFinished:
infoText = tr("Diagnostics imported.");

View File

@@ -418,7 +418,7 @@ void ClangToolRunWorker::finalize()
{
const QString toolName = tool()->name();
if (m_filesNotAnalyzed.size() != 0) {
appendMessage(tr("Error: Failed to analyze %1 files.").arg(m_filesNotAnalyzed.size()),
appendMessage(tr("Error: Failed to analyze %n files.", nullptr, m_filesNotAnalyzed.size()),
ErrorMessageFormat);
Target *target = runControl()->target();
if (target && target->activeBuildConfiguration() && !target->activeBuildConfiguration()->buildDirectory().exists()

View File

@@ -891,6 +891,7 @@ CMakeBuildConfiguration::CMakeBuildConfiguration(Target *target, Id id)
const Kit *k = target->kit();
QStringList initialArgs = defaultInitialCMakeArguments(k, info.typeName);
setIsMultiConfig(CMakeGeneratorKitAspect::isMultiConfigGenerator(k));
// Android magic:
if (DeviceTypeKitAspect::deviceTypeId(k) == Android::Constants::ANDROID_DEVICE_TYPE) {
@@ -1335,7 +1336,7 @@ QString CMakeBuildConfiguration::cmakeBuildType() const
config = CMakeConfigItem::itemsFromArguments(initialCMakeArguments());
}
if (!config.isEmpty()) {
if (!config.isEmpty() && !isMultiConfig()) {
cmakeBuildType = QString::fromUtf8(CMakeConfigItem::valueOf("CMAKE_BUILD_TYPE", config));
const_cast<CMakeBuildConfiguration*>(this)
->setCMakeBuildType(cmakeBuildType);
@@ -1356,7 +1357,12 @@ void CMakeBuildConfiguration::setCMakeBuildType(const QString &cmakeBuildType, b
bool CMakeBuildConfiguration::isMultiConfig() const
{
return m_buildSystem->isMultiConfig();
return m_isMultiConfig;
}
void CMakeBuildConfiguration::setIsMultiConfig(bool isMultiConfig)
{
m_isMultiConfig = isMultiConfig;
}
namespace Internal {

View File

@@ -78,6 +78,7 @@ public:
void setCMakeBuildType(const QString &cmakeBuildType, bool quiet = false);
bool isMultiConfig() const;
void setIsMultiConfig(bool isMultiConfig);
signals:
void errorOccurred(const QString &message);
@@ -115,6 +116,7 @@ private:
Internal::CMakeBuildSystem *m_buildSystem = nullptr;
QStringList m_extraCMakeArguments;
bool m_isMultiConfig = false;
friend class Internal::CMakeBuildSettingsWidget;
friend class Internal::CMakeBuildSystem;

View File

@@ -530,8 +530,6 @@ void CMakeBuildStep::recreateBuildTargetsModel()
if (idx != -1)
m_buildTargets[idx] = QString("INSTALL");
}
targetList.sort();
targetList.removeDuplicates();
addItem(QString(), true);

View File

@@ -78,26 +78,31 @@ using namespace Utils;
namespace CMakeProjectManager {
namespace Internal {
static void copySourcePathToClipboard(Utils::optional<QString> srcPath, const ProjectNode *node)
static void copySourcePathsToClipboard(QStringList srcPaths, const ProjectNode *node)
{
QClipboard *clip = QGuiApplication::clipboard();
QDir projDir{node->filePath().toFileInfo().absoluteFilePath()};
clip->setText(QDir::cleanPath(projDir.relativeFilePath(srcPath.value())));
QString data = Utils::transform(srcPaths, [projDir](const QString &path) {
return QDir::cleanPath(projDir.relativeFilePath(path));
}).join(" ");
clip->setText(data);
}
static void noAutoAdditionNotify(const QStringList &filePaths, const ProjectNode *node)
{
Utils::optional<QString> srcPath{};
const QStringList srcPaths = Utils::filtered(filePaths, [](const QString& file) {
const auto mimeType = Utils::mimeTypeForFile(file).name();
return mimeType == CppTools::Constants::C_SOURCE_MIMETYPE ||
mimeType == CppTools::Constants::C_HEADER_MIMETYPE ||
mimeType == CppTools::Constants::CPP_SOURCE_MIMETYPE ||
mimeType == CppTools::Constants::CPP_HEADER_MIMETYPE ||
mimeType == ProjectExplorer::Constants::FORM_MIMETYPE ||
mimeType == ProjectExplorer::Constants::RESOURCE_MIMETYPE ||
mimeType == ProjectExplorer::Constants::SCXML_MIMETYPE;
});
for (const QString &file : filePaths) {
if (Utils::mimeTypeForFile(file).name() == CppTools::Constants::CPP_SOURCE_MIMETYPE) {
srcPath = file;
break;
}
}
if (srcPath) {
if (!srcPaths.empty()) {
CMakeSpecificSettings *settings = CMakeProjectPlugin::projectTypeSpecificSettings();
switch (settings->afterAddFileSetting.value()) {
case AskUser: {
@@ -122,13 +127,13 @@ static void noAutoAdditionNotify(const QStringList &filePaths, const ProjectNode
}
if (QDialogButtonBox::Yes == reply)
copySourcePathToClipboard(srcPath, node);
copySourcePathsToClipboard(srcPaths, node);
break;
}
case CopyFilePath: {
copySourcePathToClipboard(srcPath, node);
copySourcePathsToClipboard(srcPaths, node);
break;
}
@@ -1063,7 +1068,11 @@ const QList<BuildTargetInfo> CMakeBuildSystem::appTargets() const
QStringList CMakeBuildSystem::buildTargetTitles() const
{
return transform(m_buildTargets, &CMakeBuildTarget::title);
auto nonUtilityTargets = filtered(m_buildTargets, [this](const CMakeBuildTarget &target){
return target.targetType != UtilityType ||
CMakeBuildStep::specialTargets(usesAllCapsTargets()).contains(target.title);
});
return transform(nonUtilityTargets, &CMakeBuildTarget::title);
}
const QList<CMakeBuildTarget> &CMakeBuildSystem::buildTargets() const

View File

@@ -12,7 +12,6 @@
\"\",
\"Alternatively, this plugin 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 plugin. 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.\"
],
\"Category\" : \"Utilities\",
\"Experimental\" : true,
\"Description\" : \"Conan integration.\",
\"Url\" : \"http://www.qt.io\",

View File

@@ -186,16 +186,10 @@ extend_qtc_plugin(Core
SOURCES progressmanager/progressmanager_x11.cpp
)
if (CRASHPAD_BACKEND_URL
AND CRASHPAD_SRC
AND EXISTS "${CRASHPAD_SRC}"
AND CRASHPAD_BUILD
AND EXISTS "${CRASHPAD_BUILD}"
AND (WIN32 OR APPLE)) # LINUX isn't supported for now
extend_qtc_plugin(Core
DEFINES ENABLE_CRASHPAD
)
endif()
extend_qtc_plugin(Core
CONDITION BUILD_WITH_CRASHPAD
DEFINES ENABLE_CRASHPAD
)
if ((NOT WIN32) AND (NOT APPLE))
# install logo

View File

@@ -91,9 +91,9 @@ static ApplicationProgressView *sharedProgressView = nil;
Q_UNUSED(rect)
NSRect boundary = [self bounds];
[[NSApp applicationIconImage] drawInRect:boundary
fromRect:NSZeroRect
operation:NSCompositeCopy
fraction:1.0];
fromRect:NSZeroRect
operation:NSCompositingOperationCopy
fraction:1.0];
NSRect progressBoundary = boundary;
progressBoundary.size.height *= 0.13;
progressBoundary.size.width *= 0.8;

View File

@@ -10,9 +10,6 @@
<height>345</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="1">
<widget class="QPushButton" name="pushButton_custom">

View File

@@ -10,9 +10,6 @@
<height>1074</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QGroupBox" name="groupBox_2">

View File

@@ -28,10 +28,10 @@
#include <utils/qtcassert.h>
#include <QDebug>
#include <QElapsedTimer>
#include <QMutex>
#include <QSet>
#include <QThreadPool>
#include <QTime>
#include <QTimer>
using namespace CppTools::Internal;
@@ -139,10 +139,10 @@ void StringTablePrivate::GC()
QMutexLocker locker(&m_lock);
int initialSize = 0;
QTime startTime;
QElapsedTimer timer;
if (DebugStringTable) {
initialSize = m_strings.size();
startTime = QTime::currentTime();
timer.start();
}
// Collect all QStrings which have refcount 1. (One reference in m_strings and nowhere else.)
@@ -159,7 +159,6 @@ void StringTablePrivate::GC()
if (DebugStringTable) {
const int currentSize = m_strings.size();
qDebug() << "StringTable::GC removed" << initialSize - currentSize
<< "strings in" << startTime.msecsTo(QTime::currentTime())
<< "ms, size is now" << currentSize;
<< "strings in" << timer.elapsed() << "ms, size is now" << currentSize;
}
}

View File

@@ -66,6 +66,7 @@ LiteHtmlHelpViewer::LiteHtmlHelpViewer(QWidget *parent)
, m_viewer(new QLiteHtmlWidget)
{
m_viewer->setResourceHandler([](const QUrl &url) { return getData(url); });
m_viewer->setFrameStyle(QFrame::NoFrame);
m_viewer->viewport()->installEventFilter(this);
connect(m_viewer, &QLiteHtmlWidget::linkClicked, this, &LiteHtmlHelpViewer::setSource);
connect(m_viewer,

View File

@@ -267,6 +267,7 @@ TextBrowserHelpWidget::TextBrowserHelpWidget(TextBrowserHelpViewer *parent)
: QTextBrowser(parent)
, m_parent(parent)
{
setFrameShape(QFrame::NoFrame);
installEventFilter(this);
document()->setDocumentMargin(8);
}

View File

@@ -247,7 +247,7 @@ void CompileOutputWindow::registerPositionOf(const Task &task, int linkedOutputL
if (linkedOutputLines <= 0)
return;
const int blocknumber = m_outputWindow->document()->blockCount() - offset - 1;
const int blocknumber = m_outputWindow->document()->blockCount() - offset;
const int firstLine = blocknumber - linkedOutputLines - skipLines;
const int lastLine = firstLine + linkedOutputLines - 1;

View File

@@ -431,9 +431,7 @@ RunConfigurationFactory::~RunConfigurationFactory()
QString RunConfigurationFactory::decoratedTargetName(const QString &targetName, Target *target)
{
QString displayName;
if (!targetName.isEmpty())
displayName = QFileInfo(targetName).completeBaseName();
QString displayName = targetName;
Utils::Id devType = DeviceTypeKitAspect::deviceTypeId(target->kit());
if (devType != Constants::DESKTOP_DEVICE_TYPE) {
if (IDevice::ConstPtr dev = DeviceKitAspect::device(target->kit())) {

View File

@@ -31,6 +31,7 @@ add_qtc_plugin(QmlDesigner
shortcutmanager.cpp shortcutmanager.h
designermcumanager.cpp designermcumanager.h
richtexteditordialog.cpp richtexteditordialog.h
editorproxy.cpp editorproxy.h
EXPLICIT_MOC
components/propertyeditor/propertyeditorvalue.h
components/connectioneditor/connectionviewwidget.h
@@ -637,6 +638,10 @@ extend_qtc_plugin(QmlDesigner
globalannotationeditordialog.cpp globalannotationeditordialog.h globalannotationeditordialog.ui
annotationeditor.cpp annotationeditor.h
globalannotationeditor.cpp globalannotationeditor.h
defaultannotations.cpp defaultannotations.h
annotationtableview.cpp annotationtableview.h
annotationtabwidget.cpp annotationtabwidget.h
annotationeditor.qrc
)
extend_qtc_plugin(QmlDesigner

View File

@@ -34,6 +34,8 @@
#include <private/qquicktext_p.h>
#include <algorithm>
namespace {
const QHash<QString, QString> AlignMapping{
{"AlignRight", "RIGHT"},
@@ -63,7 +65,10 @@ TextNodeDumper::TextNodeDumper(const QByteArrayList &lineage, const ModelNode &n
bool TextNodeDumper::isExportable() const
{
return lineage().contains("QtQuick.Text");
const QByteArrayList &baseClasses = lineage();
return std::any_of(baseClasses.cbegin(), baseClasses.cend(), [](const QByteArray &type) {
return type == "QtQuick.Text" || type == "QtQuick.Controls.Label";
});
}
QJsonObject TextNodeDumper::json(Component &component) const

View File

@@ -24,12 +24,12 @@
****************************************************************************/
#include "annotationcommenttab.h"
#include "defaultannotations.h"
#include "ui_annotationcommenttab.h"
#include "richtexteditor/richtexteditor.h"
#include <QCryptographicHash>
#include "QStringListModel"
#include "projectexplorer/session.h"
#include "projectexplorer/target.h"
@@ -40,7 +40,7 @@ namespace QmlDesigner {
AnnotationCommentTab::AnnotationCommentTab(QWidget *parent)
: QWidget(parent)
, ui(new Ui::AnnotationCommentTab)
, ui(std::make_unique<Ui::AnnotationCommentTab>())
{
ui->setupUi(this);
@@ -57,33 +57,12 @@ AnnotationCommentTab::AnnotationCommentTab(QWidget *parent)
ui->formLayout->setWidget(3, QFormLayout::FieldRole, m_editor);
ui->titleEdit->setModel(new QStringListModel{QStringList{"Description",
"Display Condition",
"helper lines",
"position marker",
"highlight",
"project author",
"project confirmed",
"project developer",
"project distributor",
"project modified",
"project type",
"project version",
"Screen Description",
"Section",
"normalcolor",
"focuscolor",
"selectedcolor",
"pressedcolor"}});
connect(ui->titleEdit, &QComboBox::currentTextChanged,
this, &AnnotationCommentTab::commentTitleChanged);
connect(ui->titleEdit, &QComboBox::currentTextChanged, this, [this](QString const &text) {
emit titleChanged(text, this);
});
}
AnnotationCommentTab::~AnnotationCommentTab()
{
delete ui;
}
AnnotationCommentTab::~AnnotationCommentTab() {}
Comment AnnotationCommentTab::currentComment() const
{
@@ -91,7 +70,10 @@ Comment AnnotationCommentTab::currentComment() const
result.setTitle(ui->titleEdit->currentText().trimmed());
result.setAuthor(ui->authorEdit->text().trimmed());
result.setText(m_editor->richText().trimmed());
if (defaultAnnotations() && !defaultAnnotations()->isRichText(result)) {
result.setText(m_editor->plainText().trimmed());
} else
result.setText(m_editor->richText().trimmed());
if (m_comment.sameContent(result))
result.setTimestamp(m_comment.timestamp());
@@ -129,9 +111,15 @@ void AnnotationCommentTab::resetComment()
m_comment = currentComment();
}
void AnnotationCommentTab::commentTitleChanged(const QString &text)
DefaultAnnotationsModel *AnnotationCommentTab::defaultAnnotations() const
{
emit titleChanged(text, this);
return m_defaults;
}
void AnnotationCommentTab::setDefaultAnnotations(DefaultAnnotationsModel *defaults)
{
m_defaults = defaults;
ui->titleEdit->setModel(m_defaults);
}
QString AnnotationCommentTab::backupFile(const QString &filePath)
@@ -153,10 +141,8 @@ QString AnnotationCommentTab::backupFile(const QString &filePath)
if (!newFile.exists()) {
QFile(oldFile.absoluteFilePath()).copy(newFile.absoluteFilePath());
break;
} else if (compareFileChecksum(oldFile.absoluteFilePath(),
newFile.absoluteFilePath()) == 0) {
} else if (compareFileChecksum(oldFile.absoluteFilePath(), newFile.absoluteFilePath()) == 0)
break;
}
newFile.setFile(imgDir, newName.arg(i));
}
@@ -166,9 +152,8 @@ QString AnnotationCommentTab::backupFile(const QString &filePath)
void AnnotationCommentTab::ensureDir(const QDir &dir)
{
if (!dir.exists()) {
if (!dir.exists())
dir.mkdir(".");
}
}
int AnnotationCommentTab::compareFileChecksum(const QString &firstFile, const QString &secondFile)

View File

@@ -25,10 +25,13 @@
#pragma once
#include <QWidget>
#include "annotation.h"
#include <QWidget>
#include <QPointer>
#include <memory>
QT_BEGIN_NAMESPACE
class QDir;
QT_END_NAMESPACE
@@ -40,6 +43,7 @@ class AnnotationCommentTab;
}
class RichTextEditor;
class DefaultAnnotationsModel;
class AnnotationCommentTab : public QWidget
{
@@ -57,17 +61,18 @@ public:
void resetUI();
void resetComment();
DefaultAnnotationsModel *defaultAnnotations() const;
void setDefaultAnnotations(DefaultAnnotationsModel *);
signals:
void titleChanged(const QString &text, QWidget *widget);
private slots:
void commentTitleChanged(const QString &text);
private:
Ui::AnnotationCommentTab *ui;
std::unique_ptr<Ui::AnnotationCommentTab> ui;
RichTextEditor *m_editor;
Comment m_comment;
QPointer<DefaultAnnotationsModel> m_defaults;
QString backupFile(const QString &filePath);
void ensureDir(const QDir &dir);

View File

@@ -25,8 +25,8 @@
#include "annotationeditor.h"
#include "annotationeditordialog.h"
#include "annotation.h"
#include "annotationeditordialog.h"
#include "qmlmodelnodeproxy.h"
@@ -35,166 +35,79 @@
#include <coreplugin/icore.h>
#include <QObject>
#include <QToolBar>
#include <QAction>
#include <QMessageBox>
#include <QObject>
#include <QToolBar>
namespace QmlDesigner {
AnnotationEditor::AnnotationEditor(QObject *parent)
: QObject(parent)
{
}
: ModelNodeEditorProxy(parent)
{}
AnnotationEditor::~AnnotationEditor()
AnnotationEditor::~AnnotationEditor() {}
QWidget *AnnotationEditor::createWidget()
{
hideWidget();
const auto &node = m_modelNode;
auto dialog = new AnnotationEditorDialog(Core::ICore::dialogParent(),
node.id(),
node.customId());
dialog->setAnnotation(node.annotation());
QObject::connect(dialog,
&AnnotationEditorDialog::acceptedDialog,
this,
&AnnotationEditor::acceptedClicked);
QObject::connect(dialog,
&AnnotationEditorDialog::rejected,
this,
&AnnotationEditor::cancelClicked);
return dialog;
}
void AnnotationEditor::registerDeclarativeType()
{
qmlRegisterType<AnnotationEditor>("HelperWidgets", 2, 0, "AnnotationEditor");
}
void AnnotationEditor::showWidget()
{
m_dialog = new AnnotationEditorDialog(Core::ICore::dialogParent(),
m_modelNode.id(),
m_modelNode.customId(),
m_modelNode.annotation());
QObject::connect(m_dialog, &AnnotationEditorDialog::acceptedDialog,
this, &AnnotationEditor::acceptedClicked);
QObject::connect(m_dialog, &AnnotationEditorDialog::rejected,
this, &AnnotationEditor::cancelClicked);
m_dialog->setAttribute(Qt::WA_DeleteOnClose);
m_dialog->show();
m_dialog->raise();
}
void AnnotationEditor::showWidget(int x, int y)
{
showWidget();
m_dialog->move(x, y);
}
void AnnotationEditor::hideWidget()
{
if (m_dialog)
m_dialog->close();
m_dialog = nullptr;
}
AnnotationEditor* AnnotationEditor::showWidget(const ModelNode &modelNode)
{
auto editor = new AnnotationEditor;
editor->setModelNode(modelNode);
editor->showWidget();
connect(editor->m_dialog, &QDialog::destroyed,
[editor]() { editor->deleteLater(); } );
return editor;
}
void AnnotationEditor::setModelNode(const ModelNode &modelNode)
{
m_modelNodeBackend = {};
m_modelNode = modelNode;
}
ModelNode AnnotationEditor::modelNode() const
{
return m_modelNode;
}
void AnnotationEditor::setModelNodeBackend(const QVariant &modelNodeBackend)
{
if (!modelNodeBackend.isNull() && modelNodeBackend.isValid()) {
m_modelNodeBackend = modelNodeBackend;
const auto modelNodeBackendObject = modelNodeBackend.value<QObject*>();
const auto backendObjectCasted =
qobject_cast<const QmlDesigner::QmlModelNodeProxy *>(modelNodeBackendObject);
if (backendObjectCasted)
m_modelNode = backendObjectCasted->qmlObjectNode().modelNode();
emit modelNodeBackendChanged();
}
}
QVariant AnnotationEditor::modelNodeBackend() const
{
return m_modelNodeBackend;
}
bool AnnotationEditor::hasCustomId() const
{
if (m_modelNode.isValid())
return m_modelNode.hasCustomId();
return false;
}
bool AnnotationEditor::hasAnnotation() const
{
if (m_modelNode.isValid())
return m_modelNode.hasAnnotation();
return false;
registerType<AnnotationEditor>("AnnotationEditor");
}
void AnnotationEditor::removeFullAnnotation()
{
if (!m_modelNode.isValid())
auto &node = this->m_modelNode;
if (!node.isValid())
return;
QString dialogTitle = tr("Annotation");
if (!m_modelNode.customId().isNull()) {
dialogTitle = m_modelNode.customId();
if (QMessageBox::question(Core::ICore::dialogParent(),
node.customId().isNull() ? tr("Annotation") : node.customId(),
tr("Delete this annotation?"))
== QMessageBox::Yes) {
node.removeCustomId();
node.removeAnnotation();
emit customIdChanged();
emit annotationChanged();
}
QPointer<QMessageBox> deleteDialog = new QMessageBox(Core::ICore::dialogParent());
deleteDialog->setWindowTitle(dialogTitle);
deleteDialog->setText(tr("Delete this annotation?"));
deleteDialog->setStandardButtons(QMessageBox::Yes | QMessageBox::No);
deleteDialog->setDefaultButton(QMessageBox::Yes);
int result = deleteDialog->exec();
if (deleteDialog)
deleteDialog->deleteLater();
if (result == QMessageBox::Yes) {
m_modelNode.removeCustomId();
m_modelNode.removeAnnotation();
}
emit customIdChanged();
emit annotationChanged();
}
void AnnotationEditor::acceptedClicked()
{
if (m_dialog) {
if (const auto *dialog = qobject_cast<AnnotationEditorDialog *>(widget())) {
QmlDesignerPlugin::emitUsageStatistics(Constants::EVENT_ANNOTATION_ADDED);
QString customId = m_dialog->customId();
Annotation annotation = m_dialog->annotation();
const QString customId = dialog->customId();
const Annotation annotation = dialog->annotation();
auto &node = this->m_modelNode;
m_modelNode.setCustomId(customId);
node.setCustomId(customId);
if (annotation.comments().isEmpty())
m_modelNode.removeAnnotation();
node.removeAnnotation();
else
m_modelNode.setAnnotation(annotation);
node.setAnnotation(annotation);
}
hideWidget();
emit accepted();
emit customIdChanged();
emit annotationChanged();
}

View File

@@ -25,65 +25,31 @@
#pragma once
#include <QObject>
#include <QtQml>
#include <QPointer>
#include "annotationeditordialog.h"
#include "annotation.h"
#include "modelnode.h"
#include "editorproxy.h"
namespace QmlDesigner {
class AnnotationEditor : public QObject
class AnnotationEditor : public ModelNodeEditorProxy
{
Q_OBJECT
Q_PROPERTY(QVariant modelNodeBackendProperty READ modelNodeBackend WRITE setModelNodeBackend NOTIFY modelNodeBackendChanged)
Q_PROPERTY(bool hasCustomId READ hasCustomId NOTIFY customIdChanged)
Q_PROPERTY(bool hasAnnotation READ hasAnnotation NOTIFY annotationChanged)
public:
explicit AnnotationEditor(QObject *parent = nullptr);
~AnnotationEditor();
static void registerDeclarativeType();
Q_INVOKABLE void showWidget();
Q_INVOKABLE void showWidget(int x, int y);
Q_INVOKABLE void hideWidget();
static AnnotationEditor* showWidget(const ModelNode &modelNode);
void setModelNode(const ModelNode &modelNode);
ModelNode modelNode() const;
void setModelNodeBackend(const QVariant &modelNodeBackend);
QVariant modelNodeBackend() const;
Q_INVOKABLE bool hasCustomId() const;
Q_INVOKABLE bool hasAnnotation() const;
QWidget *createWidget() override;
Q_INVOKABLE void removeFullAnnotation();
static void registerDeclarativeType();
signals:
void accepted();
void canceled();
void modelNodeBackendChanged();
void customIdChanged();
void annotationChanged();
private slots:
void acceptedClicked();
void cancelClicked();
private:
QPointer<AnnotationEditorDialog> m_dialog;
ModelNode m_modelNode;
QVariant m_modelNodeBackend;
};
} //namespace QmlDesigner

View File

@@ -3,13 +3,23 @@ HEADERS += $$PWD/annotationeditordialog.h
HEADERS += $$PWD/annotationeditor.h
HEADERS += $$PWD/globalannotationeditor.h
HEADERS += $$PWD/globalannotationeditordialog.h
HEADERS += $$PWD/defaultannotations.h
HEADERS += $$PWD/annotationtableview.h
HEADERS += $$PWD/annotationtabwidget.h
SOURCES += $$PWD/annotationcommenttab.cpp
SOURCES += $$PWD/annotationeditordialog.cpp
SOURCES += $$PWD/annotationeditor.cpp
SOURCES += $$PWD/globalannotationeditor.cpp
SOURCES += $$PWD/globalannotationeditordialog.cpp
SOURCES += $$PWD/defaultannotations.cpp
SOURCES += $$PWD/annotationtableview.cpp
SOURCES += $$PWD/annotationtabwidget.cpp
FORMS += $$PWD/annotationcommenttab.ui
FORMS += $$PWD/annotationeditordialog.ui
FORMS += $$PWD/globalannotationeditordialog.ui
INCLUDEPATH += $$PWD
RESOURCES += $$PWD/annotationeditor.qrc

View File

@@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/annotationeditor">
<file>defaultannotations.json</file>
</qresource>
</RCC>

View File

@@ -24,80 +24,65 @@
****************************************************************************/
#include "annotationeditordialog.h"
#include "ui_annotationeditordialog.h"
#include "annotation.h"
#include "annotationcommenttab.h"
#include "defaultannotations.h"
#include "ui_annotationcommenttab.h"
#include "ui_annotationeditordialog.h"
#include <timelineicons.h>
#include <utils/qtcassert.h>
#include <QObject>
#include <QToolBar>
#include <QAction>
#include <QMessageBox>
#include <QObject>
#include <QToolBar>
namespace QmlDesigner {
AnnotationEditorDialog::AnnotationEditorDialog(QWidget *parent, const QString &targetId, const QString &customId, const Annotation &annotation)
BasicAnnotationEditorDialog::BasicAnnotationEditorDialog(QWidget *parent)
: QDialog(parent)
, ui(new Ui::AnnotationEditorDialog)
, m_customId(customId)
, m_annotation(annotation)
, m_defaults(std::make_unique<DefaultAnnotationsModel>())
{
ui->setupUi(this);
setWindowFlag(Qt::Tool, true);
setModal(true);
loadDefaultAnnotations(DefaultAnnotationsModel::defaultJsonFilePath());
connect(this, &QDialog::accepted, this, &AnnotationEditorDialog::acceptedClicked);
connect(this, &QDialog::accepted, this, &BasicAnnotationEditorDialog::acceptedClicked);
}
connect(ui->tabWidget, &QTabWidget::currentChanged, this, &AnnotationEditorDialog::tabChanged);
BasicAnnotationEditorDialog::~BasicAnnotationEditorDialog() {}
auto *commentCornerWidget = new QToolBar;
Annotation const &BasicAnnotationEditorDialog::annotation() const
{
return m_annotation;
}
auto *commentAddAction = new QAction(TimelineIcons::ADD_TIMELINE.icon(), tr("Add Comment")); //timeline icons?
auto *commentRemoveAction = new QAction(TimelineIcons::REMOVE_TIMELINE.icon(),
tr("Remove Comment")); //timeline icons?
void BasicAnnotationEditorDialog::setAnnotation(const Annotation &annotation)
{
m_annotation = annotation;
fillFields();
}
connect(commentAddAction, &QAction::triggered, this, [this]() {
addComment(Comment());
});
void BasicAnnotationEditorDialog::loadDefaultAnnotations(QString const &filename)
{
m_defaults->loadFromFile(filename);
}
connect(commentRemoveAction, &QAction::triggered, this, [this]() {
DefaultAnnotationsModel *BasicAnnotationEditorDialog::defaultAnnotations() const
{
return m_defaults.get();
}
if (ui->tabWidget->count() == 0) { //it is not even supposed to happen but lets be sure
QTC_ASSERT(true, return);
return;
}
int currentIndex = ui->tabWidget->currentIndex();
QString currentTitle = ui->tabWidget->tabText(currentIndex);
QMessageBox *deleteDialog = new QMessageBox(this);
deleteDialog->setWindowTitle(currentTitle);
deleteDialog->setText(tr("Delete this comment?"));
deleteDialog->setStandardButtons(QMessageBox::Yes | QMessageBox::No);
deleteDialog->setDefaultButton(QMessageBox::Yes);
int result = deleteDialog->exec();
if (result == QMessageBox::Yes) {
removeComment(currentIndex);
}
if (ui->tabWidget->count() == 0) //lets be sure that tabWidget is never empty
addComment(Comment());
});
commentCornerWidget->addAction(commentAddAction);
commentCornerWidget->addAction(commentRemoveAction);
ui->tabWidget->setCornerWidget(commentCornerWidget, Qt::TopRightCorner);
AnnotationEditorDialog::AnnotationEditorDialog(QWidget *parent,
const QString &targetId,
const QString &customId)
: BasicAnnotationEditorDialog(parent)
, ui(new Ui::AnnotationEditorDialog)
, m_customId(customId)
{
ui->setupUi(this);
ui->targetIdEdit->setText(targetId);
fillFields();
setWindowTitle(annotationEditorTitle);
}
@@ -106,17 +91,6 @@ AnnotationEditorDialog::~AnnotationEditorDialog()
delete ui;
}
void AnnotationEditorDialog::setAnnotation(const Annotation &annotation)
{
m_annotation = annotation;
fillFields();
}
Annotation AnnotationEditorDialog::annotation() const
{
return m_annotation;
}
void AnnotationEditorDialog::setCustomId(const QString &customId)
{
m_customId = customId;
@@ -130,117 +104,34 @@ QString AnnotationEditorDialog::customId() const
void AnnotationEditorDialog::acceptedClicked()
{
m_customId = ui->customIdEdit->text();
Annotation annotation;
annotation.removeComments();
for (int i = 0; i < ui->tabWidget->count(); i++) {
AnnotationCommentTab* tab = reinterpret_cast<AnnotationCommentTab*>(ui->tabWidget->widget(i));
if (!tab)
continue;
Comment comment = tab->currentComment();
if (!comment.isEmpty())
annotation.addComment(comment);
}
m_annotation = annotation;
updateAnnotation();
emit AnnotationEditorDialog::acceptedDialog();
}
void AnnotationEditorDialog::commentTitleChanged(const QString &text, QWidget *tab)
{
int tabIndex = ui->tabWidget->indexOf(tab);
if (tabIndex >= 0)
ui->tabWidget->setTabText(tabIndex, text);
if (text.isEmpty())
ui->tabWidget->setTabText(tabIndex,
(defaultTabName + " " + QString::number(tabIndex+1)));
}
void AnnotationEditorDialog::fillFields()
{
ui->customIdEdit->setText(m_customId);
setupComments();
ui->tabWidget->setupComments(m_annotation.comments());
}
void AnnotationEditorDialog::setupComments()
void AnnotationEditorDialog::updateAnnotation()
{
ui->tabWidget->setUpdatesEnabled(false);
deleteAllTabs();
const QVector<Comment> comments = m_annotation.comments();
if (comments.isEmpty())
addComment(Comment());
for (const Comment &comment : comments) {
addCommentTab(comment);
}
ui->tabWidget->setUpdatesEnabled(true);
m_customId = ui->customIdEdit->text();
Annotation annotation;
annotation.setComments(ui->tabWidget->fetchComments());
m_annotation = annotation;
}
void AnnotationEditorDialog::addComment(const Comment &comment)
{
m_annotation.addComment(comment);
addCommentTab(comment);
ui->tabWidget->addCommentTab(comment);
}
void AnnotationEditorDialog::removeComment(int index)
{
if ((m_annotation.commentsSize() > index) && (index >= 0)) {
m_annotation.removeComment(index);
removeCommentTab(index);
}
}
void AnnotationEditorDialog::addCommentTab(const Comment &comment)
{
auto commentTab = new AnnotationCommentTab();
commentTab->setComment(comment);
QString tabTitle(comment.title());
int tabIndex = ui->tabWidget->addTab(commentTab, tabTitle);
ui->tabWidget->setCurrentIndex(tabIndex);
if (tabTitle.isEmpty()) {
const QString appendix = ((tabIndex > 0) ? QString::number(tabIndex+1) : "");
tabTitle = QString("%1 %2").arg(defaultTabName).arg(appendix);
ui->tabWidget->setTabText(tabIndex, tabTitle);
}
connect(commentTab, &AnnotationCommentTab::titleChanged,
this, &AnnotationEditorDialog::commentTitleChanged);
}
void AnnotationEditorDialog::removeCommentTab(int index)
{
if ((ui->tabWidget->count() > index) && (index >= 0)) {
ui->tabWidget->removeTab(index);
}
}
void AnnotationEditorDialog::deleteAllTabs()
{
while (ui->tabWidget->count() > 0) {
QWidget *w = ui->tabWidget->widget(0);
ui->tabWidget->removeTab(0);
delete w;
}
}
void AnnotationEditorDialog::tabChanged(int index)
{
(void) index;
m_annotation.removeComment(index);
ui->tabWidget->removeTab(index);
}
} //namespace QmlDesigner

View File

@@ -34,46 +34,62 @@ namespace QmlDesigner {
namespace Ui {
class AnnotationEditorDialog;
}
class DefaultAnnotationsModel;
class AnnotationEditorDialog : public QDialog
class BasicAnnotationEditorDialog : public QDialog
{
Q_OBJECT
public:
explicit AnnotationEditorDialog(QWidget *parent, const QString &targetId, const QString &customId, const Annotation &annotation);
~AnnotationEditorDialog();
explicit BasicAnnotationEditorDialog(QWidget *parent);
~BasicAnnotationEditorDialog();
Annotation const &annotation() const;
void setAnnotation(const Annotation &annotation);
Annotation annotation() const;
void setCustomId(const QString &customId);
QString customId() const;
void loadDefaultAnnotations(QString const &filename);
DefaultAnnotationsModel *defaultAnnotations() const;
signals:
void acceptedDialog(); //use instead of QDialog::accepted
protected:
virtual void fillFields() = 0;
virtual void acceptedClicked() = 0;
Annotation m_annotation;
std::unique_ptr<DefaultAnnotationsModel> m_defaults;
};
class AnnotationEditorDialog : public BasicAnnotationEditorDialog
{
Q_OBJECT
public:
explicit AnnotationEditorDialog(QWidget *parent,
const QString &targetId,
const QString &customId);
~AnnotationEditorDialog();
void setCustomId(const QString &customId);
QString customId() const;
private slots:
void acceptedClicked();
void tabChanged(int index);
void commentTitleChanged(const QString &text, QWidget *tab);
void acceptedClicked() override;
private:
void fillFields();
void setupComments();
void fillFields() override;
void updateAnnotation();
void addComment(const Comment &comment);
void removeComment(int index);
void addCommentTab(const Comment &comment);
void removeCommentTab(int index);
void deleteAllTabs();
private:
const QString annotationEditorTitle = {tr("Annotation Editor")};
const QString defaultTabName = {tr("Annotation")};
Ui::AnnotationEditorDialog *ui;
QString m_customId;
Annotation m_annotation;
};
} //namespace QmlDesigner

View File

@@ -56,7 +56,7 @@
</widget>
</item>
<item>
<widget class="QTabWidget" name="tabWidget">
<widget class="AnnotationTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
@@ -90,6 +90,14 @@
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>AnnotationTabWidget</class>
<extends>QTabWidget</extends>
<header>annotationtabwidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>targetIdEdit</tabstop>
<tabstop>customIdEdit</tabstop>

View File

@@ -0,0 +1,446 @@
/****************************************************************************
**
** Copyright (C) 2021 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 "annotationtableview.h"
#include "defaultannotations.h"
#include <utils/qtcolorbutton.h>
#include <QApplication>
#include <QCheckBox>
#include <QComboBox>
#include <QCompleter>
#include <QDoubleSpinBox>
#include <QHeaderView>
#include <QItemEditorFactory>
#include <QKeyEvent>
#include <QLabel>
#include <QLineEdit>
#include <QPainter>
#include <QPushButton>
#include <QStandardItem>
#include <QStandardItemModel>
#include <QStringListModel>
#include <QStyle>
#include <QTextEdit>
namespace QmlDesigner {
struct ColumnId
{
enum Column {
Title = 0,
Author = 1,
Value = 2,
};
};
CommentDelegate::CommentDelegate(QObject *parent)
: QItemDelegate(parent)
, m_completer(std::make_unique<QCompleter>())
{}
CommentDelegate::~CommentDelegate() {}
DefaultAnnotationsModel *CommentDelegate::defaultAnnotations() const
{
return m_defaults;
}
void CommentDelegate::setDefaultAnnotations(DefaultAnnotationsModel *defaults)
{
m_defaults = defaults;
m_completer->setModel(m_defaults);
}
QCompleter *CommentDelegate::completer() const
{
return m_completer.get();
}
void CommentDelegate::updateEditorGeometry(QWidget *editor,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
editor->setGeometry(option.rect);
}
Comment CommentDelegate::comment(QModelIndex const &index)
{
auto *model = index.model();
return model->data(model->index(index.row(), ColumnId::Title), CommentRole).value<Comment>();
}
CommentTitleDelegate::CommentTitleDelegate(QObject *parent)
: CommentDelegate(parent)
{}
CommentTitleDelegate::~CommentTitleDelegate() {}
QWidget *CommentTitleDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
auto *editor = new QComboBox(parent);
editor->setEditable(true);
editor->setCompleter(completer());
editor->setFrame(false);
editor->setFocusPolicy(Qt::StrongFocus);
return editor;
}
void CommentTitleDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
QString text = index.model()->data(index, Qt::DisplayRole).toString();
auto *comboBox = qobject_cast<QComboBox *>(editor);
comboBox->setModel(defaultAnnotations());
comboBox->setCurrentText(text);
}
void CommentTitleDelegate::setModelData(QWidget *editor,
QAbstractItemModel *model,
const QModelIndex &index) const
{
auto *comboBox = qobject_cast<QComboBox *>(editor);
auto oldText = model->data(index, Qt::EditRole).toString();
auto newText = comboBox->currentText();
if (oldText != newText) {
model->setData(index, comboBox->currentText(), Qt::EditRole);
auto comment = model->data(index, CommentRole).value<Comment>();
comment.setTitle(newText);
model->setData(index, QVariant::fromValue(comment), CommentRole);
// Set default value to data item
auto colIdx = model->index(index.row(), ColumnId::Value);
if (defaultAnnotations()->hasDefault(comment))
model->setData(colIdx, defaultAnnotations()->defaultValue(comment), Qt::DisplayRole);
else
// Reset to rich text when there is no default item
model->setData(colIdx,
QVariant::fromValue<RichTextProxy>({comment.text()}),
Qt::DisplayRole);
}
}
CommentValueDelegate::CommentValueDelegate(QObject *parent)
: CommentDelegate(parent)
{}
CommentValueDelegate::~CommentValueDelegate() {}
void CommentValueDelegate::paint(QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
auto data = index.model()->data(index, Qt::DisplayRole);
if (data.userType() == qMetaTypeId<RichTextProxy>())
drawDisplay(painter, option, option.rect, data.value<RichTextProxy>().plainText());
else if (data.userType() == QMetaType::QColor)
painter->fillRect(option.rect, data.value<QColor>());
else
QItemDelegate::paint(painter, option, index);
}
void CommentValueDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
auto data = index.model()->data(index, Qt::DisplayRole);
if (data.userType() == qMetaTypeId<RichTextProxy>()) {
auto richText = data.value<RichTextProxy>();
auto *e = qobject_cast<RichTextCellEditor *>(editor);
e->setText(richText.plainText());
e->setupSignal(index.row(), comment(index).title());
connect(e,
&RichTextCellEditor::richTextClicked,
this,
&CommentValueDelegate::richTextEditorRequested,
Qt::UniqueConnection);
} else if (data.userType() == QMetaType::QString) {
auto *e = qobject_cast<QLineEdit *>(editor);
e->setText(data.toString());
} else if (data.userType() == QMetaType::QColor) {
auto *e = qobject_cast<Utils::QtColorButton *>(editor);
e->setColor(data.value<QColor>());
} else
QItemDelegate::setEditorData(editor, index);
}
void CommentValueDelegate::setModelData(QWidget *editor,
QAbstractItemModel *model,
const QModelIndex &index) const
{
auto data = model->data(index, Qt::EditRole);
if (data.userType() == qMetaTypeId<RichTextProxy>())
return;
else if (data.userType() == QMetaType::QColor)
model->setData(index,
qobject_cast<Utils::QtColorButton *>(editor)->color(),
Qt::DisplayRole);
else if (data.userType() == QMetaType::QString)
model->setData(index, qobject_cast<QLineEdit *>(editor)->text(), Qt::DisplayRole);
else
QItemDelegate::setModelData(editor, model, index);
}
RichTextCellEditor::RichTextCellEditor(QWidget *parent)
: QLabel(parent)
{}
RichTextCellEditor::~RichTextCellEditor() {}
RichTextProxy RichTextCellEditor::richText() const
{
return m_richText;
}
void RichTextCellEditor::setRichText(const RichTextProxy &richText)
{
if (richText.text == m_richText.text)
return;
m_richText = richText;
setText(richText.plainText());
emit richTextChanged();
}
void RichTextCellEditor::setupSignal(int index, const QString &commentTitle)
{
if (m_connection)
disconnect(m_connection);
m_connection = connect(this, &RichTextCellEditor::clicked, this, [=]() {
emit richTextClicked(index, commentTitle);
});
}
void RichTextCellEditor::mouseReleaseEvent(QMouseEvent *)
{
emit clicked();
}
AnnotationTableView::AnnotationTableView(QWidget *parent)
: QTableView(parent)
, m_model(std::make_unique<QStandardItemModel>())
, m_editorFactory(std::make_unique<QItemEditorFactory>())
{
setSelectionBehavior(QAbstractItemView::SelectRows);
setSelectionMode(QAbstractItemView::ContiguousSelection);
setModel(m_model.get());
connect(m_model.get(), &QStandardItemModel::itemChanged, this, [this](QStandardItem *item) {
if (item->isCheckable())
m_model->setData(item->index(), item->checkState() == Qt::Checked);
if (this->m_modelUpdating)
return;
auto *valueItem = m_model->item(item->row(), ColumnId::Value);
// When comment title was edited, make value item editable
if (item->column() == ColumnId::Title && valueItem) {
valueItem->setEditable(!item->text().isEmpty());
valueItem->setCheckable(valueItem->data(Qt::DisplayRole).userType() == QMetaType::Bool);
}
m_modelUpdating = true;
if (!rowIsEmpty(m_model->rowCount() - 1))
addEmptyRow();
m_modelUpdating = false;
});
horizontalHeader()->setStretchLastSection(true);
horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive);
m_editorFactory->registerEditor(qMetaTypeId<RichTextProxy>(),
new QItemEditorCreator<RichTextCellEditor>("richText"));
m_editorFactory->registerEditor(QMetaType::QColor,
new QItemEditorCreator<Utils::QtColorButton>("color"));
m_valueDelegate.setItemEditorFactory(m_editorFactory.get());
connect(&m_valueDelegate,
&CommentValueDelegate::richTextEditorRequested,
this,
&AnnotationTableView::richTextEditorRequested);
verticalHeader()->hide();
}
AnnotationTableView::~AnnotationTableView() {}
QVector<Comment> AnnotationTableView::fetchComments() const
{
QVector<Comment> comments;
for (int i = 0; i < m_model->rowCount(); ++i) {
Comment comment = fetchComment(i);
if (!comment.isEmpty())
comments.push_back(comment);
}
return comments;
}
Comment AnnotationTableView::fetchComment(int row) const
{
auto *item = m_model->item(row, ColumnId::Title);
if (item->text().isEmpty())
return {};
Comment comment = item->data().value<Comment>();
comment.setTitle(item->text());
comment.setAuthor(m_model->item(row, ColumnId::Author)->text());
comment.setText(dataToCommentText(m_model->item(row, ColumnId::Value)->data(Qt::DisplayRole)));
return comment;
}
void AnnotationTableView::setupComments(QVector<Comment> const &comments)
{
m_model->clear();
m_modelUpdating = true;
m_model->setColumnCount(3);
m_model->setHeaderData(ColumnId::Title, Qt::Horizontal, tr("Title"));
m_model->setHeaderData(ColumnId::Author, Qt::Horizontal, tr("Author"));
m_model->setHeaderData(ColumnId::Value, Qt::Horizontal, tr("Value"));
setItemDelegateForColumn(ColumnId::Title, &m_titleDelegate);
setItemDelegateForColumn(ColumnId::Value, &m_valueDelegate);
for (auto &comment : comments) {
if (comment.isEmpty())
continue;
addEmptyRow();
changeRow(m_model->rowCount() - 1, comment);
}
addEmptyRow();
m_modelUpdating = false;
}
DefaultAnnotationsModel *AnnotationTableView::defaultAnnotations() const
{
return m_defaults;
}
void AnnotationTableView::setDefaultAnnotations(DefaultAnnotationsModel *defaults)
{
m_defaults = defaults;
m_titleDelegate.setDefaultAnnotations(defaults);
m_valueDelegate.setDefaultAnnotations(defaults);
}
void AnnotationTableView::changeRow(int index, Comment const &comment)
{
auto *titleItem = m_model->item(index, ColumnId::Title);
auto *authorItem = m_model->item(index, ColumnId::Author);
auto *textItem = m_model->item(index, ColumnId::Value);
titleItem->setText(comment.title());
titleItem->setData(QVariant::fromValue<Comment>(comment));
authorItem->setText(comment.author());
QVariant data = commentToData(comment,
m_defaults ? m_defaults->defaultType(comment)
: QMetaType::UnknownType);
textItem->setEditable(data.isValid());
textItem->setCheckable(data.userType() == QMetaType::Bool);
textItem->setData(data, Qt::DisplayRole);
}
void AnnotationTableView::removeRow(int index)
{
m_model->removeRow(index);
}
void AnnotationTableView::removeSelectedRows()
{
const auto selRows = selectionModel()->selectedRows();
for (auto it = selRows.rbegin(); it != selRows.rend(); ++it)
removeRow(it->row());
}
void AnnotationTableView::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Delete)
removeSelectedRows();
}
void AnnotationTableView::addEmptyRow()
{
auto *valueItem = new QStandardItem;
valueItem->setEditable(false);
m_model->appendRow({new QStandardItem, new QStandardItem, valueItem});
}
bool AnnotationTableView::rowIsEmpty(int row) const
{
auto itemText = [&](int col) {
return m_model->item(row, col) ? m_model->item(row, col)->text() : QString();
};
return QString(itemText(0) + itemText(1) + itemText(2)).isEmpty();
}
QString AnnotationTableView::dataToCommentText(QVariant const &data)
{
auto type = data.userType();
if (type == qMetaTypeId<RichTextProxy>())
return data.value<RichTextProxy>().text;
switch (type) {
case QMetaType::QColor:
return data.value<QColor>().name();
case QMetaType::Bool:
return data.toBool() ? QStringLiteral("true") : QStringLiteral("false");
case QMetaType::QString:
return data.toString();
}
return {};
}
QVariant AnnotationTableView::commentToData(Comment const& comment, QMetaType::Type type)
{
switch (type) {
case QMetaType::Bool:
return QVariant::fromValue(comment.deescapedText().toLower().trimmed() == "true");
case QMetaType::QColor:
return QVariant::fromValue(QColor(comment.deescapedText().toLower().trimmed()));
break;
case QMetaType::QString:
return QVariant::fromValue(comment.text());
break;
default:
if (type == qMetaTypeId<RichTextProxy>() || type == QMetaType::UnknownType)
return QVariant::fromValue<RichTextProxy>({comment.text()});
}
return {};
}
} // namespace QmlDesigner

View File

@@ -0,0 +1,170 @@
/****************************************************************************
**
** Copyright (C) 2021 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 <QItemDelegate>
#include <QLabel>
#include <QPointer>
#include <QTableView>
#include <memory>
#include "annotation.h"
#include "defaultannotations.h"
QT_BEGIN_NAMESPACE
class QStandardItemModel;
class QCompleter;
QT_END_NAMESPACE
namespace QmlDesigner {
class CommentDelegate : public QItemDelegate
{
Q_OBJECT
public:
enum Role { CommentRole = Qt::UserRole + 1 };
CommentDelegate(QObject *parent = nullptr);
~CommentDelegate() override;
DefaultAnnotationsModel *defaultAnnotations() const;
void setDefaultAnnotations(DefaultAnnotationsModel *);
QCompleter *completer() const;
void updateEditorGeometry(QWidget *editor,
const QStyleOptionViewItem &option,
const QModelIndex &index) const override;
static Comment comment(QModelIndex const &);
private:
std::unique_ptr<QCompleter> m_completer;
QPointer<DefaultAnnotationsModel> m_defaults;
};
class CommentTitleDelegate : public CommentDelegate
{
Q_OBJECT
public:
CommentTitleDelegate(QObject *parent = nullptr);
~CommentTitleDelegate() override;
QWidget *createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const override;
void setEditorData(QWidget *editor, const QModelIndex &index) const override;
void setModelData(QWidget *editor,
QAbstractItemModel *model,
const QModelIndex &index) const override;
signals:
void commentChanged(int row, Comment const &);
};
class CommentValueDelegate : public CommentDelegate
{
Q_OBJECT
public:
CommentValueDelegate(QObject *parent = nullptr);
~CommentValueDelegate();
void paint(QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index) const override;
void setEditorData(QWidget *editor, const QModelIndex &index) const override;
void setModelData(QWidget *editor,
QAbstractItemModel *model,
const QModelIndex &index) const override;
signals:
void richTextEditorRequested(int index, QString const &richText);
};
class RichTextCellEditor : public QLabel
{
Q_OBJECT
Q_PROPERTY(QmlDesigner::RichTextProxy richText READ richText WRITE setRichText NOTIFY
richTextChanged USER true)
public:
RichTextCellEditor(QWidget *parent = nullptr);
~RichTextCellEditor() override;
RichTextProxy richText() const;
void setRichText(RichTextProxy const &);
void setupSignal(int row, QString const &commentTitle);
signals:
void clicked();
void richTextChanged();
void richTextClicked(int index, QString const &text);
protected:
void mouseReleaseEvent(QMouseEvent *) override;
private:
RichTextProxy m_richText;
QMetaObject::Connection m_connection;
};
class AnnotationTableView : public QTableView
{
Q_OBJECT
public:
AnnotationTableView(QWidget *parent = nullptr);
~AnnotationTableView();
QVector<Comment> fetchComments() const;
Comment fetchComment(int row) const;
void setupComments(QVector<Comment> const &comments);
DefaultAnnotationsModel *defaultAnnotations() const;
void setDefaultAnnotations(DefaultAnnotationsModel *);
void changeRow(int index, Comment const &comment);
void removeRow(int index);
void removeSelectedRows();
signals:
void richTextEditorRequested(int index, QString const &commentTitle);
protected:
void keyPressEvent(QKeyEvent *) override;
private:
void addEmptyRow();
bool rowIsEmpty(int row) const;
static QString dataToCommentText(QVariant const &);
static QVariant commentToData(Comment const&, QMetaType::Type type);
CommentTitleDelegate m_titleDelegate;
CommentValueDelegate m_valueDelegate;
bool m_modelUpdating = false;
std::unique_ptr<QStandardItemModel> m_model;
std::unique_ptr<QItemEditorFactory> m_editorFactory;
QPointer<DefaultAnnotationsModel> m_defaults;
};
} // namespace QmlDesigner

View File

@@ -0,0 +1,157 @@
/****************************************************************************
**
** Copyright (C) 2020 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 "annotationtabwidget.h"
#include "annotationcommenttab.h"
#include <timelineeditor/timelineicons.h>
#include <QAction>
#include <QMessageBox>
#include <QToolBar>
namespace QmlDesigner {
AnnotationTabWidget::AnnotationTabWidget(QWidget *parent)
: QTabWidget(parent)
{
auto *commentCornerWidget = new QToolBar;
//Making it look similar to timeline editor button:
commentCornerWidget->setStyleSheet("QToolBar { background-color: transparent; border-width: 1px; }");
auto *commentAddAction = new QAction(TimelineIcons::ADD_TIMELINE.icon(),
tr("Add Comment")); //timeline icons?
auto *commentRemoveAction = new QAction(TimelineIcons::REMOVE_TIMELINE.icon(),
tr("Remove Comment")); //timeline icons?
connect(commentAddAction, &QAction::triggered, this, [this]() { addCommentTab(); });
connect(commentRemoveAction, &QAction::triggered, this, [this]() {
int currentIndex = this->currentIndex();
QString currentTitle = tabText(currentIndex);
if (QMessageBox::question(this,
currentTitle,
tr("Delete this comment?"))
== QMessageBox::Yes) {
removeTab(currentIndex);
if (count() == 0) //lets be sure that tabWidget is never empty
addCommentTab();
}
});
commentCornerWidget->addAction(commentAddAction);
commentCornerWidget->addAction(commentRemoveAction);
setCornerWidget(commentCornerWidget, Qt::TopRightCorner);
}
AnnotationTabWidget::~AnnotationTabWidget() {}
QVector<Comment> AnnotationTabWidget::fetchComments() const
{
QVector<Comment> comments;
for (int i = 0; i < count(); i++) {
auto *tab = qobject_cast<AnnotationCommentTab *>(widget(i));
if (!tab)
continue;
Comment comment = tab->currentComment();
if (!comment.isEmpty())
comments.push_back(comment);
}
return comments;
}
void AnnotationTabWidget::setupComments(QVector<Comment> const &comments)
{
setUpdatesEnabled(false);
deleteAllTabs();
if (comments.isEmpty())
addCommentTab();
for (const Comment &comment : comments)
addCommentTab(comment);
setUpdatesEnabled(true);
}
DefaultAnnotationsModel *AnnotationTabWidget::defaultAnnotations() const
{
return m_defaults;
}
void AnnotationTabWidget::setDefaultAnnotations(DefaultAnnotationsModel *defaults)
{
m_defaults = defaults;
for (int i = 0; i < count(); i++) {
// The tab widget might be contain regular QTabs initially, hence we need this qobject_cast test
if (auto *tab = qobject_cast<AnnotationCommentTab *>(widget(i)))
tab->setDefaultAnnotations(defaults);
}
}
void AnnotationTabWidget::onCommentTitleChanged(const QString &text, QWidget *tab)
{
int tabIndex = indexOf(tab);
if (tabIndex >= 0)
setTabText(tabIndex, text);
if (text.isEmpty())
setTabText(tabIndex, defaultTabName + " " + QString::number(tabIndex + 1));
}
void AnnotationTabWidget::addCommentTab(const Comment &comment)
{
auto *commentTab = new AnnotationCommentTab();
commentTab->setDefaultAnnotations(m_defaults);
commentTab->setComment(comment);
QString tabTitle(comment.title());
int tabIndex = addTab(commentTab, tabTitle);
setCurrentIndex(tabIndex);
if (tabTitle.isEmpty()) {
const QString appendix = ((tabIndex > 0) ? QString::number(tabIndex + 1) : "");
tabTitle = QString("%1 %2").arg(defaultTabName).arg(appendix);
setTabText(tabIndex, tabTitle);
}
connect(commentTab,
&AnnotationCommentTab::titleChanged,
this,
&AnnotationTabWidget::onCommentTitleChanged);
}
void AnnotationTabWidget::deleteAllTabs()
{
while (count() > 0) {
QWidget *w = widget(0);
removeTab(0);
delete w;
}
}
} // namespace QmlDesigner

View File

@@ -0,0 +1,60 @@
/****************************************************************************
**
** Copyright (C) 2021 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 <QPointer>
#include <QTabWidget>
#include "annotation.h"
#include "defaultannotations.h"
namespace QmlDesigner {
class AnnotationCommentTab;
class AnnotationTabWidget : public QTabWidget
{
Q_OBJECT
public:
AnnotationTabWidget(QWidget *parent = nullptr);
~AnnotationTabWidget();
QVector<Comment> fetchComments() const;
void setupComments(QVector<Comment> const &comments);
DefaultAnnotationsModel *defaultAnnotations() const;
void setDefaultAnnotations(DefaultAnnotationsModel *);
public slots:
void addCommentTab(const Comment &comment = {});
void deleteAllTabs();
private slots:
void onCommentTitleChanged(const QString &text, QWidget *tab);
private:
const QString defaultTabName = {tr("Annotation")};
QPointer<DefaultAnnotationsModel> m_defaults;
};
} // namespace QmlDesigner

View File

@@ -0,0 +1,182 @@
/****************************************************************************
**
** Copyright (C) 2021 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 "defaultannotations.h"
#include <QColor>
#include <QFile>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include <QRegularExpression>
namespace QmlDesigner {
DefaultAnnotationsModel::DefaultAnnotationsModel(QObject *parent)
: QAbstractListModel(parent)
{
qRegisterMetaType<RichTextProxy>();
}
DefaultAnnotationsModel::~DefaultAnnotationsModel() {}
int DefaultAnnotationsModel::rowCount(const QModelIndex &) const
{
return static_cast<int>(m_defaults.size());
}
QVariant DefaultAnnotationsModel::data(const QModelIndex &index, int role) const
{
const auto row = static_cast<size_t>(index.row());
if (!index.isValid() || m_defaults.size() < row)
return {};
auto &item = m_defaults[row];
switch (role) {
case Qt::DisplayRole:
case Qt::EditRole:
case Name:
return item.first;
case Type:
return item.second.typeName();
case Default:
return item.second;
}
return {};
}
QVariantMap DefaultAnnotationsModel::fetchData() const
{
return m_defaultMap;
}
bool DefaultAnnotationsModel::hasDefault(const Comment &comment) const
{
return m_defaultMap.count(comment.title().toLower());
}
QMetaType::Type DefaultAnnotationsModel::defaultType(const Comment &comment) const
{
return hasDefault(comment) ? QMetaType::Type(m_defaultMap[comment.title().toLower()].userType())
: QMetaType::UnknownType;
}
QVariant DefaultAnnotationsModel::defaultValue(const Comment &comment) const
{
return hasDefault(comment) ? m_defaultMap.value(comment.title().toLower()) : QVariant();
}
bool DefaultAnnotationsModel::isRichText(const Comment &comment) const
{
const auto type = defaultType(comment);
return type == QMetaType::UnknownType || type == qMetaTypeId<RichTextProxy>();
}
void DefaultAnnotationsModel::loadFromFile(QString const &filename)
{
QFile file(filename);
if (file.open(QFile::ReadOnly)) {
loadFromFile(&file);
}
}
void DefaultAnnotationsModel::loadFromFile(QIODevice *io)
{
QJsonParseError error;
auto doc = QJsonDocument::fromJson(io->readAll(), &error);
if (error.error == QJsonParseError::NoError)
loadFromJson(doc);
else {
} // TODO: Error handling
}
void DefaultAnnotationsModel::loadFromJson(const QJsonDocument &doc)
{
beginResetModel();
m_defaultMap = asVariantMapFromJson(doc);
m_defaults.clear();
m_defaults.reserve(m_defaultMap.size());
for (auto &key : m_defaultMap.keys())
m_defaults.emplace_back(key, m_defaultMap.value(key));
endResetModel();
}
QVariantMap DefaultAnnotationsModel::asVariantMapFromJson(const QJsonDocument &doc)
{
QVariantMap map;
QJsonObject obj = doc.object();
for (auto key : obj.keys()) {
key = key.toLower();
auto val = obj[key];
switch (val.type()) {
case QJsonValue::Double:
map[key] = double{0.0};
break;
case QJsonValue::String:
map[key] = QString{};
break;
case QJsonValue::Bool:
map[key] = false;
break;
case QJsonValue::Object: {
auto o = val.toObject();
auto type = o["type"].toString().toLower();
auto val = o["value"].toVariant();
if (type == QStringLiteral("richtext"))
map[key] = QVariant::fromValue(RichTextProxy{val.toString()});
else if (type == QStringLiteral("string"))
map[key] = QVariant::fromValue(val.toString());
else if (type == QStringLiteral("bool"))
map[key] = QVariant::fromValue(val.toBool());
else if (type == QStringLiteral("double"))
map[key] = QVariant::fromValue(val.toDouble());
else if (type == QStringLiteral("color"))
map[key] = QVariant::fromValue(QColor(val.toString()));
}
}
}
return map;
}
QString DefaultAnnotationsModel::defaultJsonFilePath()
{
return QStringLiteral(":/annotationeditor/defaultannotations.json");
}
QString RichTextProxy::plainText() const
{
QString plainText(text);
plainText.remove(QRegularExpression("<.*?>"));
return plainText.mid(plainText.indexOf("}") + 1);
}
} // namespace QmlDesigner

View File

@@ -0,0 +1,80 @@
/****************************************************************************
**
** Copyright (C) 2021 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 "annotation.h"
#include <QAbstractListModel>
QT_BEGIN_NAMESPACE
class QJsonDocument;
QT_END_NAMESPACE
namespace QmlDesigner {
// We need this proxy type to distinguish between a 'normal' QString
// and a 'richtext' string when they are stored in a QVariant
struct RichTextProxy
{
QString text;
QString plainText() const;
};
class DefaultAnnotationsModel : public QAbstractListModel
{
Q_OBJECT
public:
enum Role { Name = Qt::UserRole + 1, Type, Default };
Q_ENUM(Role)
DefaultAnnotationsModel(QObject *parent = nullptr);
~DefaultAnnotationsModel() override;
int rowCount(const QModelIndex & = {}) const override;
QVariant data(const QModelIndex &, int role) const override;
QVariantMap fetchData() const;
bool hasDefault(const Comment &comment) const;
QMetaType::Type defaultType(const Comment &comment) const;
QVariant defaultValue(const Comment &comment) const;
bool isRichText(const Comment &comment) const;
void loadFromFile(QString const &);
void loadFromFile(QIODevice *);
void loadFromJson(const QJsonDocument &);
static QVariantMap asVariantMapFromJson(const QJsonDocument &);
static QString defaultJsonFilePath();
private:
std::vector<std::pair<QString, QVariant>> m_defaults;
QVariantMap m_defaultMap;
};
} // namespace QmlDesigner
Q_DECLARE_METATYPE(QmlDesigner::RichTextProxy);

View File

@@ -0,0 +1,36 @@
{
"description" : "",
"display condition" : "",
"helper lines" : true,
"position marker" : true,
"highlight" : true,
"project author" : "",
"project confirmed" : true,
"project developer" : "",
"project distributor" : "",
"project modified" : "",
"project type" : "",
"project version" : "",
"screen description" : "",
"section" : "",
"normalcolor" : {
"type": "color",
"value": "#000000"
},
"focuscolor" : {
"type": "color",
"value": "#000000"
},
"selectedcolor" : {
"type": "color",
"value": "#000000"
},
"pressedcolor" : {
"type": "color",
"value": "#000000"
},
"overview" : {
"type": "richtext",
"value": ""
}
}

View File

@@ -25,124 +25,74 @@
#include "globalannotationeditor.h"
#include "globalannotationeditordialog.h"
#include "annotation.h"
#include "globalannotationeditordialog.h"
#include "qmlmodelnodeproxy.h"
#include <coreplugin/icore.h>
#include <QObject>
#include <QToolBar>
#include <QAction>
#include <QMessageBox>
namespace QmlDesigner {
GlobalAnnotationEditor::GlobalAnnotationEditor(QObject *)
GlobalAnnotationEditor::GlobalAnnotationEditor(QObject *parent)
: ModelNodeEditorProxy(parent)
{}
GlobalAnnotationEditor::~GlobalAnnotationEditor() {}
QWidget *GlobalAnnotationEditor::createWidget()
{
}
GlobalAnnotationEditor::~GlobalAnnotationEditor()
{
hideWidget();
}
void GlobalAnnotationEditor::showWidget()
{
m_dialog = new GlobalAnnotationEditorDialog(Core::ICore::dialogParent(),
modelNode().globalAnnotation(),
modelNode().globalStatus());
QObject::connect(m_dialog, &GlobalAnnotationEditorDialog::acceptedDialog,
this, &GlobalAnnotationEditor::acceptedClicked);
QObject::connect(m_dialog, &GlobalAnnotationEditorDialog::rejected,
this, &GlobalAnnotationEditor::cancelClicked);
m_dialog->setAttribute(Qt::WA_DeleteOnClose);
m_dialog->show();
m_dialog->raise();
}
void GlobalAnnotationEditor::showWidget(int x, int y)
{
showWidget();
m_dialog->move(x, y);
}
void GlobalAnnotationEditor::hideWidget()
{
if (m_dialog)
m_dialog->close();
m_dialog = nullptr;
}
void GlobalAnnotationEditor::setModelNode(const ModelNode &modelNode)
{
m_modelNode = modelNode;
}
ModelNode GlobalAnnotationEditor::modelNode() const
{
return m_modelNode;
}
bool GlobalAnnotationEditor::hasAnnotation() const
{
if (m_modelNode.isValid())
return m_modelNode.hasGlobalAnnotation();
return false;
}
auto* dialog = new GlobalAnnotationEditorDialog(Core::ICore::dialogParent(),
this->m_modelNode.globalStatus());
dialog->setAnnotation(this->m_modelNode.globalAnnotation());
QObject::connect(dialog,
&GlobalAnnotationEditorDialog::acceptedDialog,
this,
&GlobalAnnotationEditor::acceptedClicked);
QObject::connect(dialog,
&GlobalAnnotationEditorDialog::rejected,
this,
&GlobalAnnotationEditor::cancelClicked);
return dialog;
};
void GlobalAnnotationEditor::removeFullAnnotation()
{
if (!m_modelNode.isValid())
auto &node = this->m_modelNode;
if (!node.isValid())
return;
QString dialogTitle = tr("Global Annotation");
QMessageBox *deleteDialog = new QMessageBox(Core::ICore::dialogParent());
deleteDialog->setWindowTitle(dialogTitle);
deleteDialog->setText(tr("Delete this annotation?"));
deleteDialog->setStandardButtons(QMessageBox::Yes | QMessageBox::No);
deleteDialog->setDefaultButton(QMessageBox::Yes);
int result = deleteDialog->exec();
if (deleteDialog) deleteDialog->deleteLater();
if (result == QMessageBox::Yes) {
m_modelNode.removeGlobalAnnotation();
if (QMessageBox::question(Core::ICore::dialogParent(),
tr("Global Annotation"),
tr("Delete this annotation?"))
== QMessageBox::Yes) {
node.removeGlobalAnnotation();
emit annotationChanged();
}
emit annotationChanged();
}
void GlobalAnnotationEditor::acceptedClicked()
{
if (m_dialog) {
Annotation annotation = m_dialog->annotation();
if (const auto *dialog = qobject_cast<GlobalAnnotationEditorDialog *>(widget())) {
auto &node = this->m_modelNode;
const Annotation annotation = dialog->annotation();
if (annotation.comments().isEmpty())
m_modelNode.removeGlobalAnnotation();
node.removeGlobalAnnotation();
else
m_modelNode.setGlobalAnnotation(annotation);
node.setGlobalAnnotation(annotation);
GlobalAnnotationStatus status = m_dialog->globalStatus();
const GlobalAnnotationStatus status = dialog->globalStatus();
if (status.status() == GlobalAnnotationStatus::NoStatus) {
if (m_modelNode.hasGlobalStatus()) {
m_modelNode.removeGlobalStatus();
}
}
else {
m_modelNode.setGlobalStatus(status);
}
if (status.status() == GlobalAnnotationStatus::NoStatus)
node.removeGlobalStatus();
else
node.setGlobalStatus(status);
}
hideWidget();
emit accepted();
emit annotationChanged();
}
@@ -151,7 +101,6 @@ void GlobalAnnotationEditor::cancelClicked()
hideWidget();
emit canceled();
emit annotationChanged();
}

View File

@@ -26,32 +26,25 @@
#pragma once
#include <QObject>
#include <QtQml>
#include <QPointer>
#include <QtQml>
#include "globalannotationeditordialog.h"
#include "abstractaction.h"
#include "annotation.h"
#include "globalannotationeditordialog.h"
#include "editorproxy.h"
#include "modelnode.h"
namespace QmlDesigner {
class GlobalAnnotationEditor : public QObject
class GlobalAnnotationEditor : public ModelNodeEditorProxy
{
Q_OBJECT
public:
explicit GlobalAnnotationEditor(QObject *parent = nullptr);
~GlobalAnnotationEditor();
Q_INVOKABLE void showWidget();
Q_INVOKABLE void showWidget(int x, int y);
Q_INVOKABLE void hideWidget();
void setModelNode(const ModelNode &modelNode);
ModelNode modelNode() const;
Q_INVOKABLE bool hasAnnotation() const;
QWidget *createWidget() override;
Q_INVOKABLE void removeFullAnnotation();
@@ -59,17 +52,11 @@ signals:
void accepted();
void canceled();
void modelNodeBackendChanged();
void annotationChanged();
private slots:
void acceptedClicked();
void cancelClicked();
private:
QPointer<GlobalAnnotationEditorDialog> m_dialog;
ModelNode m_modelNode;
};
} //namespace QmlDesigner

View File

@@ -24,87 +24,55 @@
****************************************************************************/
#include "globalannotationeditordialog.h"
#include "ui_globalannotationeditordialog.h"
#include "annotation.h"
#include "annotationcommenttab.h"
#include "ui_annotationcommenttab.h"
#include "ui_globalannotationeditordialog.h"
#include <timelineicons.h>
#include <utils/qtcassert.h>
#include <QObject>
#include <QToolBar>
#include <QAction>
#include <QMessageBox>
#include <QObject>
#include <QToolBar>
namespace QmlDesigner {
GlobalAnnotationEditorDialog::GlobalAnnotationEditorDialog(QWidget *parent, const Annotation &annotation, GlobalAnnotationStatus status)
: QDialog(parent)
GlobalAnnotationEditorDialog::GlobalAnnotationEditorDialog(QWidget *parent,
GlobalAnnotationStatus status)
: BasicAnnotationEditorDialog(parent)
, ui(new Ui::GlobalAnnotationEditorDialog)
, m_annotation(annotation)
, m_globalStatus(status)
, m_statusIsActive(false)
{
ui->setupUi(this);
ui->tabWidget->setDefaultAnnotations(defaultAnnotations());
ui->tableView->setDefaultAnnotations(defaultAnnotations());
setWindowFlag(Qt::Tool, true);
setModal(true);
connect(ui->tableView,
&AnnotationTableView::richTextEditorRequested,
this,
[&](int index, QString const &) {
switchToTabView();
ui->tabWidget->setCurrentIndex(index);
});
connect(this, &QDialog::accepted, this, &GlobalAnnotationEditorDialog::acceptedClicked);
connect(ui->tabWidget, &QTabWidget::currentChanged, this, &GlobalAnnotationEditorDialog::tabChanged);
auto *commentCornerWidget = new QToolBar;
auto *commentAddAction = new QAction(TimelineIcons::ADD_TIMELINE.icon(), tr("Add Comment")); //timeline icons?
auto *commentRemoveAction = new QAction(TimelineIcons::REMOVE_TIMELINE.icon(),
tr("Remove Comment")); //timeline icons?
connect(commentAddAction, &QAction::triggered, this, [this]() {
addComment(Comment());
});
connect(commentRemoveAction, &QAction::triggered, this, [this]() {
if (ui->tabWidget->count() == 0) { //it is not even supposed to happen but lets be sure
QTC_ASSERT(false, return);
return;
}
int currentIndex = ui->tabWidget->currentIndex();
QString currentTitle = ui->tabWidget->tabText(currentIndex);
QMessageBox *deleteDialog = new QMessageBox(this);
deleteDialog->setWindowTitle(currentTitle);
deleteDialog->setText(tr("Delete this comment?"));
deleteDialog->setStandardButtons(QMessageBox::Yes | QMessageBox::No);
deleteDialog->setDefaultButton(QMessageBox::Yes);
int result = deleteDialog->exec();
if (result == QMessageBox::Yes) {
removeComment(currentIndex);
}
if (ui->tabWidget->count() == 0) //lets be sure that tabWidget is never empty
addComment(Comment());
});
commentCornerWidget->addAction(commentAddAction);
commentCornerWidget->addAction(commentRemoveAction);
ui->tabWidget->setCornerWidget(commentCornerWidget, Qt::TopRightCorner);
connect(ui->statusAddButton, &QPushButton::clicked, [&](bool){
connect(ui->statusAddButton, &QPushButton::clicked, this, [&](bool) {
setStatusVisibility(true);
});
setStatus(m_globalStatus);
connect(ui->rbTableView,
&QRadioButton::clicked,
this,
&GlobalAnnotationEditorDialog::switchToTableView);
connect(ui->rbTabView,
&QRadioButton::clicked,
this,
&GlobalAnnotationEditorDialog::switchToTabView);
fillFields();
setStatus(m_globalStatus);
setWindowTitle(globalEditorTitle);
switchToTabView();
}
GlobalAnnotationEditorDialog::~GlobalAnnotationEditorDialog()
@@ -112,26 +80,18 @@ GlobalAnnotationEditorDialog::~GlobalAnnotationEditorDialog()
delete ui;
}
void GlobalAnnotationEditorDialog::setAnnotation(const Annotation &annotation)
GlobalAnnotationEditorDialog::ViewMode GlobalAnnotationEditorDialog::viewMode() const
{
m_annotation = annotation;
fillFields();
}
Annotation GlobalAnnotationEditorDialog::annotation() const
{
return m_annotation;
return ui->rbTableView->isChecked() ? TableView : TabsView;
}
void GlobalAnnotationEditorDialog::setStatus(GlobalAnnotationStatus status)
{
m_globalStatus = status;
bool hasStatus = status.status() != GlobalAnnotationStatus::NoStatus;
bool hasStatus = (status.status() != GlobalAnnotationStatus::NoStatus);
if (hasStatus) {
if (hasStatus)
ui->statusComboBox->setCurrentIndex(int(status.status()));
}
setStatusVisibility(hasStatus);
}
@@ -141,117 +101,73 @@ GlobalAnnotationStatus GlobalAnnotationEditorDialog::globalStatus() const
return m_globalStatus;
}
void GlobalAnnotationEditorDialog::acceptedClicked()
void GlobalAnnotationEditorDialog::showStatusContainer(bool show)
{
Annotation annotation;
annotation.removeComments();
for (int i = 0; i < ui->tabWidget->count(); i++) {
AnnotationCommentTab* tab = reinterpret_cast<AnnotationCommentTab*>(ui->tabWidget->widget(i));
if (!tab)
continue;
Comment comment = tab->currentComment();
if (!comment.isEmpty())
annotation.addComment(comment);
}
m_annotation = annotation;
if (m_statusIsActive) {
m_globalStatus.setStatus(ui->statusComboBox->currentIndex());
}
emit GlobalAnnotationEditorDialog::acceptedDialog();
ui->statusContainer->setVisible(show);
}
void GlobalAnnotationEditorDialog::commentTitleChanged(const QString &text, QWidget *tab)
void GlobalAnnotationEditorDialog::switchToTabView()
{
int tabIndex = ui->tabWidget->indexOf(tab);
if (tabIndex >= 0)
ui->tabWidget->setTabText(tabIndex, text);
m_annotation.setComments(ui->tableView->fetchComments());
ui->rbTabView->setChecked(true);
ui->tableView->hide();
ui->tabWidget->show();
fillFields();
}
if (text.isEmpty())
ui->tabWidget->setTabText(tabIndex,
(defaultTabName + " " + QString::number(tabIndex+1)));
void GlobalAnnotationEditorDialog::switchToTableView()
{
m_annotation.setComments(ui->tabWidget->fetchComments());
ui->rbTableView->setChecked(true);
ui->tabWidget->hide();
ui->tableView->show();
fillFields();
}
void GlobalAnnotationEditorDialog::acceptedClicked()
{
updateAnnotation();
emit GlobalAnnotationEditorDialog::acceptedDialog();
}
void GlobalAnnotationEditorDialog::fillFields()
{
setupComments();
ui->tabWidget->setupComments(m_annotation.comments());
ui->tableView->setupComments(m_annotation.comments());
}
void GlobalAnnotationEditorDialog::setupComments()
void GlobalAnnotationEditorDialog::updateAnnotation()
{
ui->tabWidget->setUpdatesEnabled(false);
deleteAllTabs();
const QVector<Comment> comments = m_annotation.comments();
if (comments.isEmpty())
addComment(Comment());
for (const Comment &comment : comments) {
addCommentTab(comment);
Annotation annotation;
switch (viewMode()) {
case TabsView:
annotation.setComments(ui->tabWidget->fetchComments());
break;
case TableView:
annotation.setComments(ui->tableView->fetchComments());
break;
}
ui->tabWidget->setUpdatesEnabled(true);
m_annotation = annotation;
if (m_statusIsActive)
m_globalStatus.setStatus(ui->statusComboBox->currentIndex());
}
void GlobalAnnotationEditorDialog::addComment(const Comment &comment)
{
m_annotation.addComment(comment);
addCommentTab(comment);
ui->tabWidget->addCommentTab(comment);
}
void GlobalAnnotationEditorDialog::removeComment(int index)
{
if ((m_annotation.commentsSize() > index) && (index >= 0)) {
m_annotation.removeComment(index);
removeCommentTab(index);
}
}
void GlobalAnnotationEditorDialog::addCommentTab(const Comment &comment)
{
auto commentTab = new AnnotationCommentTab();
commentTab->setComment(comment);
QString tabTitle(comment.title());
int tabIndex = ui->tabWidget->addTab(commentTab, tabTitle);
ui->tabWidget->setCurrentIndex(tabIndex);
if (tabTitle.isEmpty()) {
const QString appendix = ((tabIndex > 0) ? QString::number(tabIndex+1) : "");
tabTitle = QString("%1 %2").arg(defaultTabName).arg(appendix);
ui->tabWidget->setTabText(tabIndex, tabTitle);
}
connect(commentTab, &AnnotationCommentTab::titleChanged,
this, &GlobalAnnotationEditorDialog::commentTitleChanged);
}
void GlobalAnnotationEditorDialog::removeCommentTab(int index)
{
if ((ui->tabWidget->count() > index) && (index >= 0)) {
ui->tabWidget->removeTab(index);
}
}
void GlobalAnnotationEditorDialog::deleteAllTabs()
{
while (ui->tabWidget->count() > 0) {
QWidget *w = ui->tabWidget->widget(0);
ui->tabWidget->removeTab(0);
delete w;
}
}
void GlobalAnnotationEditorDialog::setStatusVisibility(bool hasStatus)
{
ui->statusAddButton->setVisible(!hasStatus);
@@ -260,9 +176,4 @@ void GlobalAnnotationEditorDialog::setStatusVisibility(bool hasStatus)
m_statusIsActive = hasStatus;
}
void GlobalAnnotationEditorDialog::tabChanged(int index)
{
(void) index;
}
} //namespace QmlDesigner

View File

@@ -25,9 +25,7 @@
#pragma once
#include <QDialog>
#include "annotation.h"
#include "annotationeditordialog.h"
namespace QmlDesigner {
@@ -35,46 +33,46 @@ namespace Ui {
class GlobalAnnotationEditorDialog;
}
class GlobalAnnotationEditorDialog : public QDialog
class GlobalAnnotationEditorDialog : public BasicAnnotationEditorDialog
{
Q_OBJECT
public:
explicit GlobalAnnotationEditorDialog(QWidget *parent, const Annotation &annotation, GlobalAnnotationStatus status);
enum ViewMode {
TableView,
TabsView
};
explicit GlobalAnnotationEditorDialog(
QWidget *parent = nullptr, GlobalAnnotationStatus status = GlobalAnnotationStatus::NoStatus);
~GlobalAnnotationEditorDialog();
void setAnnotation(const Annotation &annotation);
Annotation annotation() const;
ViewMode viewMode() const;
void setStatus(GlobalAnnotationStatus status);
GlobalAnnotationStatus globalStatus() const;
signals:
void acceptedDialog(); //use instead of QDialog::accepted
public slots:
void showStatusContainer(bool show);
void switchToTabView();
void switchToTableView();
private slots:
void acceptedClicked();
void tabChanged(int index);
void commentTitleChanged(const QString &text, QWidget *tab);
void acceptedClicked() override;
private:
void fillFields();
void setupComments();
void fillFields() override;
void updateAnnotation();
void addComment(const Comment &comment);
void removeComment(int index);
void addCommentTab(const Comment &comment);
void removeCommentTab(int index);
void deleteAllTabs();
void setStatusVisibility(bool hasStatus);
private:
const QString globalEditorTitle = {tr("Global Annotation Editor")};
const QString defaultTabName = {tr("Annotation")};
Ui::GlobalAnnotationEditorDialog *ui;
Annotation m_annotation;
GlobalAnnotationStatus m_globalStatus;
bool m_statusIsActive;
};

View File

@@ -73,6 +73,23 @@
</item>
</widget>
</item>
<item>
<widget class="QRadioButton" name="rbTabView">
<property name="text">
<string>Tab View</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="rbTableView">
<property name="text">
<string>Table View</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
@@ -90,7 +107,7 @@
</widget>
</item>
<item>
<widget class="QTabWidget" name="tabWidget">
<widget class="AnnotationTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
@@ -109,6 +126,9 @@
</widget>
</widget>
</item>
<item>
<widget class="AnnotationTableView" name="tableView"/>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="focusPolicy">
@@ -124,6 +144,19 @@
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>AnnotationTabWidget</class>
<extends>QTabWidget</extends>
<header>annotationtabwidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>AnnotationTableView</class>
<extends>QTableView</extends>
<header>annotationtableview.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>tabWidget</tabstop>
</tabstops>

View File

@@ -96,8 +96,8 @@ void SignalList::prepareDialog()
m_dialog = new SignalListDialog(Core::ICore::dialogParent());
m_dialog->setAttribute(Qt::WA_DeleteOnClose);
m_dialog->initialize(m_model);
m_dialog->setWindowTitle(::QmlDesigner::SignalList::tr("Signal List for ")
+ m_modelNode.validId());
m_dialog->setWindowTitle(::QmlDesigner::SignalList::tr("Signal List for %1")
.arg(m_modelNode.validId()));
auto *delegate = static_cast<SignalListDelegate *>(m_dialog->tableView()->itemDelegate());
connect(delegate, &SignalListDelegate::connectClicked, this, &SignalList::connectClicked);

View File

@@ -1556,7 +1556,7 @@ void editAnnotation(const SelectionContext &selectionContext)
{
ModelNode selectedNode = selectionContext.currentSingleSelectedNode();
AnnotationEditor::showWidget(selectedNode);
ModelNodeEditorProxy::fromModelNode<AnnotationEditor>(selectedNode);
}
QVariant previewImageDataForGenericNode(const ModelNode &modelNode)

View File

@@ -118,6 +118,11 @@ void CurveEditorView::nodeReparented(const ModelNode &node,
updateKeyframes();
else if (QmlTimelineKeyframeGroup::checkKeyframesType(node))
updateKeyframes();
else if (newPropertyParent.isValid() && !oldPropertyParent.isValid()) {
if (activeTimeline().hasKeyframeGroupForTarget(node)) {
updateKeyframes();
}
}
}
void CurveEditorView::auxiliaryDataChanged(const ModelNode &node,

View File

@@ -163,8 +163,21 @@ void DebugView::nodeIdChanged(const ModelNode &node, const QString &newId, const
}
}
void DebugView::propertiesAboutToBeRemoved(const QList<AbstractProperty> & /*propertyList*/)
void DebugView::propertiesAboutToBeRemoved(const QList<AbstractProperty> &propertyList)
{
if (isDebugViewEnabled()) {
QTextStream message;
QString string;
message.setString(&string);
for (const AbstractProperty &property : propertyList) {
message << property;
if (property.isNodeAbstractProperty())
message << " is NodeAbstractProperty";
if (property.isDefaultProperty())
message << " is DefaultProperty";
}
log("::propertiesAboutToBeRemoved:", string);
}
}
void DebugView::variantPropertiesChanged(const QList<VariantProperty> &propertyList,

View File

@@ -316,7 +316,7 @@ QGraphicsItem *FormEditorAnnotationIcon::createCommentBubble(QRectF rect, const
const QString &author, const QString &text,
const QString &date, QGraphicsItem *parent)
{
static QColor textColor = Utils::creatorTheme()->color(Utils::Theme::QmlDesigner_FormEditorForegroundColor);
static QColor textColor = Utils::creatorTheme()->color(Utils::Theme::DStextColor);
static QColor backgroundColor = Utils::creatorTheme()->color(Utils::Theme::QmlDesigner_BackgroundColorDarker);
static QColor frameColor = Utils::creatorTheme()->color(Utils::Theme::QmlDesigner_BackgroundColor);
QFont font;
@@ -384,7 +384,7 @@ QGraphicsItem *FormEditorAnnotationIcon::createCommentBubble(QRectF rect, const
QGraphicsItem *FormEditorAnnotationIcon::createTitleBubble(const QRectF &rect, const QString &text, QGraphicsItem *parent)
{
static QColor textColor = Utils::creatorTheme()->color(Utils::Theme::QmlDesigner_FormEditorForegroundColor);
static QColor textColor = Utils::creatorTheme()->color(Utils::Theme::DStextColor);
static QColor backgroundColor = Utils::creatorTheme()->color(Utils::Theme::QmlDesigner_BackgroundColorDarker);
static QColor frameColor = Utils::creatorTheme()->color(Utils::Theme::QmlDesigner_BackgroundColor);
QFont font;
@@ -426,8 +426,8 @@ void FormEditorAnnotationIcon::createAnnotationEditor()
m_annotationEditor = new AnnotationEditorDialog(Core::ICore::dialogParent(),
m_modelNode.displayName(),
m_modelNode.customId(),
m_modelNode.annotation());
m_modelNode.customId());
m_annotationEditor->setAnnotation(m_modelNode.annotation());
connect(m_annotationEditor, &AnnotationEditorDialog::acceptedDialog,
this, &FormEditorAnnotationIcon::annotationDialogAccepted);

View File

@@ -212,6 +212,7 @@ void ItemLibraryModel::update(ItemLibraryInfo *itemLibraryInfo, Model *model)
QString projectName = project ? project->displayName() : "";
// create import sections
const QList<Import> usedImports = model->usedImports();
QHash<QString, ItemLibraryImport *> importHash;
for (const Import &import : model->imports()) {
if (import.url() != projectName) {
@@ -239,6 +240,7 @@ void ItemLibraryModel::update(ItemLibraryInfo *itemLibraryInfo, Model *model)
auto sectionType = isQuick3DAsset ? ItemLibraryImport::SectionType::Quick3DAssets
: ItemLibraryImport::SectionType::Default;
ItemLibraryImport *itemLibImport = new ItemLibraryImport(import, this, sectionType);
itemLibImport->setImportUsed(usedImports.contains(import));
importHash.insert(importUrl, itemLibImport);
}
}

View File

@@ -75,7 +75,7 @@ ItemLibraryResourceView::ItemLibraryResourceView(AsynchronousImageCache &fontIma
setSpacing(4);
setViewMode(QListView::IconMode);
setMovement(QListView::Static);
setMovement(QListView::Snap);
setResizeMode(QListView::Adjust);
setSelectionRectVisible(false);
setWrapping(true);

View File

@@ -669,7 +669,6 @@ QString PropertyEditorQmlBackend::templateGeneration(const NodeMetaInfo &type,
bool emptyTemplate = true;
const QString anchorLeftRight = "anchors.left: parent.left\nanchors.right: parent.right\n";
const QString paddingLeftTopBottom = "leftPadding: 0\ntopPadding: 0\nbottomPadding: 0\n";
qmlTemplate += "Column {\n";
qmlTemplate += anchorLeftRight;
@@ -680,7 +679,6 @@ QString PropertyEditorQmlBackend::templateGeneration(const NodeMetaInfo &type,
qmlTemplate += "Section {\n";
qmlTemplate += "caption: \"User added properties\"\n";
qmlTemplate += anchorLeftRight;
qmlTemplate += paddingLeftTopBottom;
qmlTemplate += "Column {\n";
qmlTemplate += "width: parent.width\n";
@@ -747,7 +745,6 @@ QString PropertyEditorQmlBackend::templateGeneration(const NodeMetaInfo &type,
qmlTemplate += "Section {\n";
qmlTemplate += QStringLiteral("caption: \"%1 - %2\"\n").arg(QString::fromUtf8(p)).arg(QString::fromUtf8(parentTypeName));
qmlTemplate += anchorLeftRight;
qmlTemplate += paddingLeftTopBottom;
qmlTemplate += "level: 1\n";
qmlTemplate += "Column {\n";
qmlTemplate += "width: parent.width\n";

View File

@@ -119,6 +119,14 @@ RichTextEditor::RichTextEditor(QWidget *parent)
ui->textEdit->setTextInteractionFlags(Qt::TextEditorInteraction | Qt::LinksAccessibleByMouse);
ui->tableBar->setVisible(false);
const QColor backColor = Theme::getColor(Theme::DSpanelBackground);
const QString toolBarStyleSheet =
QString("QToolBar { background-color: %1; border-width: 1px }").arg(backColor.name());
ui->toolBar->setStyleSheet(toolBarStyleSheet);
ui->tableBar->setStyleSheet(toolBarStyleSheet);
setupEditActions();
setupTextActions();
setupImageActions();
@@ -201,7 +209,7 @@ void RichTextEditor::setDocumentBaseUrl(const QUrl& url)
QIcon RichTextEditor::getIcon(Theme::Icon icon)
{
const QString fontName = "qtds_propertyIconFont.ttf";
const QColor iconColorNormal(Theme::getColor(Theme::IconsBaseColor));
const QColor iconColorNormal(Theme::getColor(Theme::DStextColor));
return Utils::StyleHelper::getIconFromIconFont(
fontName, Theme::getIconUnicode(icon), 20, 20, iconColorNormal);

View File

@@ -205,9 +205,10 @@ TimelineWidget::TimelineWidget(TimelineView *view)
auto *topSpacer = new QSpacerItem(40, 20, QSizePolicy::Minimum, QSizePolicy::Expanding);
auto *bottomSpacer = new QSpacerItem(40, 20, QSizePolicy::Minimum, QSizePolicy::Expanding);
QString labelText =
tr("This file does not contain a timeline. <br><br> \
To create an animation, add a timeline by clicking the + button.");
const QString labelText =
tr("This file does not contain a timeline. <br><br>"
"To create an animation, add a timeline by clicking the + button.");
onboardingTopLabel->setText(labelText);
onboardingTopLabel->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
@@ -241,7 +242,7 @@ TimelineWidget::TimelineWidget(TimelineView *view)
{
QPalette timelinePalette;
timelinePalette.setColor(QPalette::Text, Utils::creatorTheme()->color(
Utils::Theme::QmlDesigner_FormEditorForegroundColor));
Utils::Theme::DStextColor));
timelinePalette.setColor(QPalette::WindowText, timelinePalette.color(QPalette::Text));
timelinePalette.setColor(QPalette::Window, Utils::creatorTheme()->color(
Utils::Theme::QmlDesigner_BackgroundColorDarkAlternate));

View File

@@ -43,7 +43,8 @@ template<typename DatabaseType>
class ImageCacheStorage : public ImageCacheStorageInterface
{
public:
using ReadStatement = typename DatabaseType::ReadStatement;
template<int ResultCount>
using ReadStatement = typename DatabaseType::template ReadStatement<ResultCount>;
using WriteStatement = typename DatabaseType::WriteStatement;
ImageCacheStorage(DatabaseType &database)
@@ -272,11 +273,11 @@ public:
DatabaseType &database;
Initializer initializer{database};
Sqlite::ImmediateNonThrowingDestructorTransaction transaction{database};
mutable ReadStatement selectImageStatement{
mutable ReadStatement<1> selectImageStatement{
"SELECT image FROM images WHERE name=?1 AND mtime >= ?2", database};
mutable ReadStatement selectSmallImageStatement{
mutable ReadStatement<1> selectSmallImageStatement{
"SELECT smallImage FROM images WHERE name=?1 AND mtime >= ?2", database};
mutable ReadStatement selectIconStatement{
mutable ReadStatement<1> selectIconStatement{
"SELECT icon FROM icons WHERE name=?1 AND mtime >= ?2", database};
WriteStatement upsertImageStatement{
"INSERT INTO images(name, mtime, image, smallImage) VALUES (?1, ?2, ?3, ?4) ON "

View File

@@ -934,15 +934,23 @@ void TextToModelMerger::setupUsedImports()
const QList<QmlJS::Import> allImports = imports->all();
QSet<QString> usedImportsSet;
QList<Import> usedImports;
foreach (const QmlJS::Import &import, allImports) {
if (import.used && !import.info.name().isEmpty()) {
if (import.info.type() == ImportType::Library) {
// populate usedImportsSet from current model nodes
const QList<ModelNode> allModelNodes = m_rewriterView->allModelNodes();
for (const ModelNode &modelNode : allModelNodes) {
QString type = QString::fromUtf8(modelNode.type());
if (type.contains('.'))
usedImportsSet.insert(type.left(type.lastIndexOf('.')));
}
for (const QmlJS::Import &import : allImports) {
if (!import.info.name().isEmpty() && usedImportsSet.contains(import.info.name())) {
if (import.info.type() == ImportType::Library)
usedImports.append(Import::createLibraryImport(import.info.name(), import.info.version().toString(), import.info.as()));
} else if (import.info.type() == ImportType::Directory || import.info.type() == ImportType::File) {
else if (import.info.type() == ImportType::Directory || import.info.type() == ImportType::File)
usedImports.append(Import::createFileImport(import.info.name(), import.info.version().toString(), import.info.as()));
}
}
}

View File

@@ -324,6 +324,9 @@ void DocumentManager::addFileToVersionControl(const QString &directoryPath, cons
Utils::FilePath DocumentManager::currentFilePath()
{
if (!QmlDesignerPlugin::instance()->currentDesignDocument())
return {};
return QmlDesignerPlugin::instance()->documentManager().currentDesignDocument()->fileName();
}

View File

@@ -0,0 +1,118 @@
/****************************************************************************
**
** Copyright (C) 2021 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 "editorproxy.h"
#include "qmlmodelnodeproxy.h"
#include <QWidget>
namespace QmlDesigner {
EditorProxy::EditorProxy(QObject *parent)
: QObject(parent)
{}
EditorProxy::~EditorProxy()
{
hideWidget();
}
void EditorProxy::showWidget()
{
if ((m_widget = createWidget())) {
m_widget->setAttribute(Qt::WA_DeleteOnClose);
m_widget->show();
m_widget->raise();
}
}
void EditorProxy::showWidget(int x, int y)
{
showWidget();
if (m_widget) {
m_widget->move(x, y);
}
}
void EditorProxy::hideWidget()
{
if (m_widget)
m_widget->close();
m_widget = nullptr;
}
QWidget *EditorProxy::widget() const
{
return m_widget;
}
ModelNodeEditorProxy::ModelNodeEditorProxy(QObject *parent)
: EditorProxy(parent)
{}
ModelNodeEditorProxy::~ModelNodeEditorProxy() {}
ModelNode ModelNodeEditorProxy::modelNode() const
{
return m_modelNode;
}
void ModelNodeEditorProxy::setModelNode(const ModelNode &modelNode)
{
m_modelNodeBackend = {};
m_modelNode = modelNode;
}
void ModelNodeEditorProxy::setModelNodeBackend(const QVariant &modelNodeBackend)
{
if (!modelNodeBackend.isNull() && modelNodeBackend.isValid()) {
const auto modelNodeBackendObject = modelNodeBackend.value<QObject *>();
const auto backendObjectCasted = qobject_cast<const QmlDesigner::QmlModelNodeProxy *>(
modelNodeBackendObject);
if (backendObjectCasted)
m_modelNode = backendObjectCasted->qmlObjectNode().modelNode();
m_modelNodeBackend = modelNodeBackend;
emit modelNodeBackendChanged();
}
}
QVariant ModelNodeEditorProxy::modelNodeBackend() const
{
return m_modelNodeBackend;
}
bool ModelNodeEditorProxy::hasCustomId() const
{
return m_modelNode.isValid() ? m_modelNode.hasCustomId() : false;
}
bool ModelNodeEditorProxy::hasAnnotation() const
{
return m_modelNode.isValid() ? m_modelNode.hasAnnotation() : false;
}
} // namespace QmlDesigner

View File

@@ -0,0 +1,101 @@
/****************************************************************************
**
** Copyright (C) 2021 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 <QQmlEngine>
#include <QPointer>
#include "modelnode.h"
namespace QmlDesigner {
class EditorProxy : public QObject
{
Q_OBJECT
public:
EditorProxy(QObject *parent = nullptr);
~EditorProxy();
Q_INVOKABLE virtual void showWidget();
Q_INVOKABLE void showWidget(int x, int y);
Q_INVOKABLE virtual void hideWidget();
QWidget *widget() const;
virtual QWidget *createWidget() = 0;
template<typename T>
static void registerType(const char *className)
{
qmlRegisterType<T>("HelperWidgets", 2, 0, className);
}
protected:
QPointer<QWidget> m_widget;
};
class ModelNodeEditorProxy : public EditorProxy
{
Q_OBJECT
Q_PROPERTY(bool hasCustomId READ hasCustomId NOTIFY customIdChanged)
Q_PROPERTY(bool hasAnnotation READ hasAnnotation NOTIFY annotationChanged)
Q_PROPERTY(QVariant modelNodeBackendProperty READ modelNodeBackend WRITE setModelNodeBackend
NOTIFY modelNodeBackendChanged)
public:
ModelNodeEditorProxy(QObject *parent = nullptr);
~ModelNodeEditorProxy();
ModelNode modelNode() const;
virtual void setModelNode(const ModelNode &modelNode);
void setModelNodeBackend(const QVariant &modelNodeBackend);
QVariant modelNodeBackend() const;
Q_INVOKABLE bool hasCustomId() const;
Q_INVOKABLE bool hasAnnotation() const;
template<typename T>
static T *fromModelNode(const ModelNode &modelNode, QVariant const &modelNodeBackend = {})
{
auto *editor = new T;
editor->setModelNode(modelNode);
if (!modelNodeBackend.isNull())
editor->setModelNodeBackend(modelNodeBackend);
editor->showWidget();
if (editor->m_widget) {
connect(editor->m_widget, &QObject::destroyed, [editor]() { editor->deleteLater(); });
}
return editor;
}
signals:
void customIdChanged();
void annotationChanged();
void modelNodeBackendChanged();
protected:
QVariant m_modelNodeBackend;
ModelNode m_modelNode;
};
} // namespace QmlDesigner

View File

@@ -3,6 +3,7 @@ HEADERS += $$PWD/qmldesignerconstants.h \
$$PWD/qmldesignerplugin.h \
$$PWD/designmodewidget.h \
$$PWD/designersettings.h \
$$PWD/editorproxy.h \
$$PWD/generateresource.h \
$$PWD/settingspage.h \
$$PWD/designmodecontext.h \
@@ -17,6 +18,7 @@ SOURCES += $$PWD/qmldesignerplugin.cpp \
$$PWD/shortcutmanager.cpp \
$$PWD/designmodewidget.cpp \
$$PWD/designersettings.cpp \
$$PWD/editorproxy.cpp \
$$PWD/generateresource.cpp \
$$PWD/settingspage.cpp \
$$PWD/designmodecontext.cpp \

View File

@@ -43,6 +43,7 @@ Project {
"../../../share/qtcreator/qml/qmlpuppet/commands",
"../../../share/qtcreator/qml/qmlpuppet/types",
"components",
"components/annotationeditor",
"components/componentcore",
"components/curveeditor",
"components/connectioneditor",
@@ -730,6 +731,7 @@ Project {
"annotationeditor/annotationcommenttab.ui",
"annotationeditor/annotationeditor.cpp",
"annotationeditor/annotationeditor.h",
"annotationeditor/annotationeditor.qrc",
"annotationeditor/globalannotationeditor.cpp",
"annotationeditor/globalannotationeditor.h",
"annotationeditor/annotationeditordialog.cpp",
@@ -738,6 +740,12 @@ Project {
"annotationeditor/globalannotationeditordialog.cpp",
"annotationeditor/globalannotationeditordialog.h",
"annotationeditor/globalannotationeditordialog.ui",
"annotationeditor/defaultannotations.cpp",
"annotationeditor/defaultannotations.h",
"annotationeditor/annotationtableview.cpp",
"annotationeditor/annotationtableview.h",
"annotationeditor/annotationtabwidget.cpp",
"annotationeditor/annotationtabwidget.h",
"bindingeditor/bindingeditor.cpp",
"bindingeditor/bindingeditor.h",
"bindingeditor/actioneditor.cpp",
@@ -970,6 +978,8 @@ Project {
"documentmanager.h",
"documentwarningwidget.cpp",
"documentwarningwidget.h",
"editorproxy.h",
"editorproxy.cpp",
"openuiqmlfiledialog.cpp",
"openuiqmlfiledialog.h",
"openuiqmlfiledialog.ui",

View File

@@ -19,3 +19,8 @@ if (TARGET StudioWelcome)
)
qtc_add_resources(StudioWelcome StudioWelcome_qml FILES ${qmlfiles})
endif()
extend_qtc_plugin(StudioWelcome
CONDITION BUILD_WITH_CRASHPAD
DEFINES ENABLE_CRASHPAD
)

View File

@@ -92,11 +92,20 @@ ListModel {
}
ListElement {
projectName: "highendivisystem"
projectName: "digitalcluster"
qmlFileName: "Screen01.ui.qml"
thumbnail: "images/digital_cluster_thumbnail.png"
displayName: "Digital Cluster"
url: "https://download.qt.io/learning/examples/qtdesignstudio/digitalcluster.zip"
showDownload: true
}
ListElement {
projectName: "effectdemo"
qmlFileName: "Screen01.ui.qml"
thumbnail: "images/effectdemo_thumbnail.png"
displayName: "Effect Demo"
url: "https://download.qt.io/learning/examples/qtdesignstudio/effectdemo.zip"
showDownload: true
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -163,7 +163,8 @@ public:
{
const QString projectFile = data(index(row, 0),
ProjectModel::FilePathRole).toString();
ProjectExplorer::ProjectExplorerPlugin::openProjectWelcomePage(projectFile);
if (QFileInfo::exists(projectFile))
ProjectExplorer::ProjectExplorerPlugin::openProjectWelcomePage(projectFile);
}
Q_INVOKABLE int get(int)

View File

@@ -546,10 +546,10 @@ void SubmitEditorWidget::verifyDescription()
"<ul>"
"<li>Avoid very short commit messages.</li>"
"<li>Consider the first line as subject (like in email) "
"and keep it shorter than %1 characters.</li>"
"and keep it shorter than %n characters.</li>"
"<li>After an empty second line, a longer description can be added.</li>"
"<li>Describe why the change was done, not how it was done.</li>"
"</ul>").arg(MaxSubjectLength));
"</ul>", nullptr, MaxSubjectLength));
}
}

View File

@@ -37,6 +37,9 @@
</property>
<item>
<widget class="QScrollArea" name="scrollArea">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>

View File

@@ -104,10 +104,7 @@ QVersionNumber WebAssemblyEmSdk::version(const FilePath &sdkRoot)
return {};
const QString cacheKey = sdkRoot.toString();
if (!emSdkVersionCache()->contains(cacheKey)) {
Environment env;
// Non-Windows: Need python in path (not provided by emsdk), thus use systemEnvironment
if (!HostOsInfo::isWindowsHost())
env = Environment::systemEnvironment();
Environment env = Environment::systemEnvironment();
WebAssemblyEmSdk::addToEnvironment(sdkRoot, env);
const QString scriptFile =
QLatin1String("emcc") + QLatin1String(HostOsInfo::isWindowsHost() ? ".bat" : "");