Merge remote-tracking branch 'origin/qds/dev' into 12.0

Conflicts:
	share/qtcreator/qmldesigner/connectionseditor/SuggestionPopup.qml
	share/qtcreator/themes/dark.creatortheme
	share/qtcreator/themes/default.creatortheme
	share/qtcreator/themes/flat-dark.creatortheme
	share/qtcreator/themes/flat-light.creatortheme
	share/qtcreator/themes/flat.creatortheme
	src/libs/utils/CMakeLists.txt
	src/plugins/CMakeLists.txt
	src/plugins/qmlprojectmanager/qmlproject.cpp
	src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp

Change-Id: Idd87c281e1aa7b7fd2702473ad55e18563cbfb21
This commit is contained in:
Tim Jenssen
2023-10-05 15:01:35 +02:00
329 changed files with 14186 additions and 5756 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -146,9 +146,9 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()].
*/
#define SQLITE_VERSION "3.42.0"
#define SQLITE_VERSION_NUMBER 3042000
#define SQLITE_SOURCE_ID "2023-05-16 12:36:15 831d0fb2836b71c9bc51067c49fee4b8f18047814f2ff22d817d25195cf350b0"
#define SQLITE_VERSION "3.43.1"
#define SQLITE_VERSION_NUMBER 3043001
#define SQLITE_SOURCE_ID "2023-09-11 12:01:27 2d3a40c05c49e1a49264912b1a05bc2143ac0e7c3df588276ce80a4cbc9bd1b0"
/*
** CAPI3REF: Run-Time Library Version Numbers
@@ -528,6 +528,7 @@ SQLITE_API int sqlite3_exec(
#define SQLITE_IOERR_ROLLBACK_ATOMIC (SQLITE_IOERR | (31<<8))
#define SQLITE_IOERR_DATA (SQLITE_IOERR | (32<<8))
#define SQLITE_IOERR_CORRUPTFS (SQLITE_IOERR | (33<<8))
#define SQLITE_IOERR_IN_PAGE (SQLITE_IOERR | (34<<8))
#define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8))
#define SQLITE_LOCKED_VTAB (SQLITE_LOCKED | (2<<8))
#define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8))
@@ -1190,7 +1191,7 @@ struct sqlite3_io_methods {
** by clients within the current process, only within other processes.
**
** <li>[[SQLITE_FCNTL_CKSM_FILE]]
** The [SQLITE_FCNTL_CKSM_FILE] opcode is for use interally by the
** The [SQLITE_FCNTL_CKSM_FILE] opcode is for use internally by the
** [checksum VFS shim] only.
**
** <li>[[SQLITE_FCNTL_RESET_CACHE]]
@@ -2454,7 +2455,7 @@ struct sqlite3_mem_methods {
** the [VACUUM] command will fail with an obscure error when attempting to
** process a table with generated columns and a descending index. This is
** not considered a bug since SQLite versions 3.3.0 and earlier do not support
** either generated columns or decending indexes.
** either generated columns or descending indexes.
** </dd>
**
** [[SQLITE_DBCONFIG_STMT_SCANSTATUS]]
@@ -2735,6 +2736,7 @@ SQLITE_API sqlite3_int64 sqlite3_total_changes64(sqlite3*);
**
** ^The [sqlite3_is_interrupted(D)] interface can be used to determine whether
** or not an interrupt is currently in effect for [database connection] D.
** It returns 1 if an interrupt is currently in effect, or 0 otherwise.
*/
SQLITE_API void sqlite3_interrupt(sqlite3*);
SQLITE_API int sqlite3_is_interrupted(sqlite3*);
@@ -3388,8 +3390,10 @@ SQLITE_API SQLITE_DEPRECATED void *sqlite3_profile(sqlite3*,
** M argument should be the bitwise OR-ed combination of
** zero or more [SQLITE_TRACE] constants.
**
** ^Each call to either sqlite3_trace() or sqlite3_trace_v2() overrides
** (cancels) any prior calls to sqlite3_trace() or sqlite3_trace_v2().
** ^Each call to either sqlite3_trace(D,X,P) or sqlite3_trace_v2(D,M,X,P)
** overrides (cancels) all prior calls to sqlite3_trace(D,X,P) or
** sqlite3_trace_v2(D,M,X,P) for the [database connection] D. Each
** database connection may have at most one trace callback.
**
** ^The X callback is invoked whenever any of the events identified by
** mask M occur. ^The integer return value from the callback is currently
@@ -3758,7 +3762,7 @@ SQLITE_API int sqlite3_open_v2(
** as F) must be one of:
** <ul>
** <li> A database filename pointer created by the SQLite core and
** passed into the xOpen() method of a VFS implemention, or
** passed into the xOpen() method of a VFS implementation, or
** <li> A filename obtained from [sqlite3_db_filename()], or
** <li> A new filename constructed using [sqlite3_create_filename()].
** </ul>
@@ -3871,7 +3875,7 @@ SQLITE_API sqlite3_file *sqlite3_database_file_object(const char*);
/*
** CAPI3REF: Create and Destroy VFS Filenames
**
** These interfces are provided for use by [VFS shim] implementations and
** These interfaces are provided for use by [VFS shim] implementations and
** are not useful outside of that context.
**
** The sqlite3_create_filename(D,J,W,N,P) allocates memory to hold a version of
@@ -4418,6 +4422,41 @@ SQLITE_API int sqlite3_stmt_readonly(sqlite3_stmt *pStmt);
*/
SQLITE_API int sqlite3_stmt_isexplain(sqlite3_stmt *pStmt);
/*
** CAPI3REF: Change The EXPLAIN Setting For A Prepared Statement
** METHOD: sqlite3_stmt
**
** The sqlite3_stmt_explain(S,E) interface changes the EXPLAIN
** setting for [prepared statement] S. If E is zero, then S becomes
** a normal prepared statement. If E is 1, then S behaves as if
** its SQL text began with "[EXPLAIN]". If E is 2, then S behaves as if
** its SQL text began with "[EXPLAIN QUERY PLAN]".
**
** Calling sqlite3_stmt_explain(S,E) might cause S to be reprepared.
** SQLite tries to avoid a reprepare, but a reprepare might be necessary
** on the first transition into EXPLAIN or EXPLAIN QUERY PLAN mode.
**
** Because of the potential need to reprepare, a call to
** sqlite3_stmt_explain(S,E) will fail with SQLITE_ERROR if S cannot be
** reprepared because it was created using [sqlite3_prepare()] instead of
** the newer [sqlite3_prepare_v2()] or [sqlite3_prepare_v3()] interfaces and
** hence has no saved SQL text with which to reprepare.
**
** Changing the explain setting for a prepared statement does not change
** the original SQL text for the statement. Hence, if the SQL text originally
** began with EXPLAIN or EXPLAIN QUERY PLAN, but sqlite3_stmt_explain(S,0)
** is called to convert the statement into an ordinary statement, the EXPLAIN
** or EXPLAIN QUERY PLAN keywords will still appear in the sqlite3_sql(S)
** output, even though the statement now acts like a normal SQL statement.
**
** This routine returns SQLITE_OK if the explain mode is successfully
** changed, or an error code if the explain mode could not be changed.
** The explain mode cannot be changed while a statement is active.
** Hence, it is good practice to call [sqlite3_reset(S)]
** immediately prior to calling sqlite3_stmt_explain(S,E).
*/
SQLITE_API int sqlite3_stmt_explain(sqlite3_stmt *pStmt, int eMode);
/*
** CAPI3REF: Determine If A Prepared Statement Has Been Reset
** METHOD: sqlite3_stmt
@@ -4581,7 +4620,7 @@ typedef struct sqlite3_context sqlite3_context;
** with it may be passed. ^It is called to dispose of the BLOB or string even
** if the call to the bind API fails, except the destructor is not called if
** the third parameter is a NULL pointer or the fourth parameter is negative.
** ^ (2) The special constant, [SQLITE_STATIC], may be passsed to indicate that
** ^ (2) The special constant, [SQLITE_STATIC], may be passed to indicate that
** the application remains responsible for disposing of the object. ^In this
** case, the object and the provided pointer to it must remain valid until
** either the prepared statement is finalized or the same SQL parameter is
@@ -5260,14 +5299,26 @@ SQLITE_API int sqlite3_finalize(sqlite3_stmt *pStmt);
** ^The [sqlite3_reset(S)] interface resets the [prepared statement] S
** back to the beginning of its program.
**
** ^If the most recent call to [sqlite3_step(S)] for the
** [prepared statement] S returned [SQLITE_ROW] or [SQLITE_DONE],
** or if [sqlite3_step(S)] has never before been called on S,
** then [sqlite3_reset(S)] returns [SQLITE_OK].
** ^The return code from [sqlite3_reset(S)] indicates whether or not
** the previous evaluation of prepared statement S completed successfully.
** ^If [sqlite3_step(S)] has never before been called on S or if
** [sqlite3_step(S)] has not been called since the previous call
** to [sqlite3_reset(S)], then [sqlite3_reset(S)] will return
** [SQLITE_OK].
**
** ^If the most recent call to [sqlite3_step(S)] for the
** [prepared statement] S indicated an error, then
** [sqlite3_reset(S)] returns an appropriate [error code].
** ^The [sqlite3_reset(S)] interface might also return an [error code]
** if there were no prior errors but the process of resetting
** the prepared statement caused a new error. ^For example, if an
** [INSERT] statement with a [RETURNING] clause is only stepped one time,
** that one call to [sqlite3_step(S)] might return SQLITE_ROW but
** the overall statement might still fail and the [sqlite3_reset(S)] call
** might return SQLITE_BUSY if locking constraints prevent the
** database change from committing. Therefore, it is important that
** applications check the return code from [sqlite3_reset(S)] even if
** no prior call to [sqlite3_step(S)] indicated a problem.
**
** ^The [sqlite3_reset(S)] interface does not change the values
** of any [sqlite3_bind_blob|bindings] on the [prepared statement] S.
@@ -5484,7 +5535,7 @@ SQLITE_API int sqlite3_create_window_function(
** [application-defined SQL function]
** that has side-effects or that could potentially leak sensitive information.
** This will prevent attacks in which an application is tricked
** into using a database file that has had its schema surreptiously
** into using a database file that has had its schema surreptitiously
** modified to invoke the application-defined function in ways that are
** harmful.
** <p>
@@ -8161,7 +8212,8 @@ SQLITE_API int sqlite3_test_control(int op, ...);
#define SQLITE_TESTCTRL_TRACEFLAGS 31
#define SQLITE_TESTCTRL_TUNE 32
#define SQLITE_TESTCTRL_LOGEST 33
#define SQLITE_TESTCTRL_LAST 33 /* Largest TESTCTRL */
#define SQLITE_TESTCTRL_USELONGDOUBLE 34
#define SQLITE_TESTCTRL_LAST 34 /* Largest TESTCTRL */
/*
** CAPI3REF: SQL Keyword Checking
@@ -9193,8 +9245,8 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p);
** blocked connection already has a registered unlock-notify callback,
** then the new callback replaces the old.)^ ^If sqlite3_unlock_notify() is
** called with a NULL pointer as its second argument, then any existing
** unlock-notify callback is canceled. ^The blocked connections
** unlock-notify callback may also be canceled by closing the blocked
** unlock-notify callback is cancelled. ^The blocked connections
** unlock-notify callback may also be cancelled by closing the blocked
** connection using [sqlite3_close()].
**
** The unlock-notify callback is not reentrant. If an application invokes
@@ -9617,7 +9669,7 @@ SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...);
** [[SQLITE_VTAB_DIRECTONLY]]<dt>SQLITE_VTAB_DIRECTONLY</dt>
** <dd>Calls of the form
** [sqlite3_vtab_config](db,SQLITE_VTAB_DIRECTONLY) from within the
** the [xConnect] or [xCreate] methods of a [virtual table] implmentation
** the [xConnect] or [xCreate] methods of a [virtual table] implementation
** prohibits that virtual table from being used from within triggers and
** views.
** </dd>
@@ -9807,7 +9859,7 @@ SQLITE_API int sqlite3_vtab_distinct(sqlite3_index_info*);
** communicated to the xBestIndex method as a
** [SQLITE_INDEX_CONSTRAINT_EQ] constraint.)^ If xBestIndex wants to use
** this constraint, it must set the corresponding
** aConstraintUsage[].argvIndex to a postive integer. ^(Then, under
** aConstraintUsage[].argvIndex to a positive integer. ^(Then, under
** the usual mode of handling IN operators, SQLite generates [bytecode]
** that invokes the [xFilter|xFilter() method] once for each value
** on the right-hand side of the IN operator.)^ Thus the virtual table
@@ -10236,7 +10288,7 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*);
** When the [sqlite3_blob_write()] API is used to update a blob column,
** the pre-update hook is invoked with SQLITE_DELETE. This is because the
** in this case the new values are not available. In this case, when a
** callback made with op==SQLITE_DELETE is actuall a write using the
** callback made with op==SQLITE_DELETE is actually a write using the
** sqlite3_blob_write() API, the [sqlite3_preupdate_blobwrite()] returns
** the index of the column being written. In other cases, where the
** pre-update hook is being invoked for some other reason, including a
@@ -12754,7 +12806,7 @@ struct Fts5PhraseIter {
** See xPhraseFirstColumn above.
*/
struct Fts5ExtensionApi {
int iVersion; /* Currently always set to 3 */
int iVersion; /* Currently always set to 2 */
void *(*xUserData)(Fts5Context*);
@@ -12983,8 +13035,8 @@ struct Fts5ExtensionApi {
** as separate queries of the FTS index are required for each synonym.
**
** When using methods (2) or (3), it is important that the tokenizer only
** provide synonyms when tokenizing document text (method (2)) or query
** text (method (3)), not both. Doing so will not cause any errors, but is
** provide synonyms when tokenizing document text (method (3)) or query
** text (method (2)), not both. Doing so will not cause any errors, but is
** inefficient.
*/
typedef struct Fts5Tokenizer Fts5Tokenizer;
@@ -13032,7 +13084,7 @@ struct fts5_api {
int (*xCreateTokenizer)(
fts5_api *pApi,
const char *zName,
void *pContext,
void *pUserData,
fts5_tokenizer *pTokenizer,
void (*xDestroy)(void*)
);
@@ -13041,7 +13093,7 @@ struct fts5_api {
int (*xFindTokenizer)(
fts5_api *pApi,
const char *zName,
void **ppContext,
void **ppUserData,
fts5_tokenizer *pTokenizer
);
@@ -13049,7 +13101,7 @@ struct fts5_api {
int (*xCreateFunction)(
fts5_api *pApi,
const char *zName,
void *pContext,
void *pUserData,
fts5_extension_function xFunction,
void (*xDestroy)(void*)
);

View File

@@ -361,6 +361,8 @@ struct sqlite3_api_routines {
int (*value_encoding)(sqlite3_value*);
/* Version 3.41.0 and later */
int (*is_interrupted)(sqlite3*);
/* Version 3.43.0 and later */
int (*stmt_explain)(sqlite3_stmt*,int);
};
/*
@@ -689,6 +691,8 @@ typedef int (*sqlite3_loadext_entry)(
#define sqlite3_value_encoding sqlite3_api->value_encoding
/* Version 3.41.0 and later */
#define sqlite3_is_interrupted sqlite3_api->is_interrupted
/* Version 3.43.0 and later */
#define sqlite3_stmt_explain sqlite3_api->stmt_explain
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)

View File

@@ -1377,7 +1377,11 @@ bool Check::visit(BinaryExpression *ast)
SourceLocation expressionSourceLocation = locationFromRange(ast->firstSourceLocation(),
ast->lastSourceLocation());
if (expressionAffectsVisualAspects(ast))
const bool isDirectInConnectionsScope = (!m_typeStack.isEmpty()
&& m_typeStack.last() == "Connections");
if (expressionAffectsVisualAspects(ast) && !isDirectInConnectionsScope)
addMessage(WarnImperativeCodeNotEditableInVisualDesigner, expressionSourceLocation);
// check ==, !=

View File

@@ -89,7 +89,10 @@ static Q_LOGGING_CATEGORY(simpleReaderLog, "qtc.qmljs.simpleReader", QtWarningMs
return newNode;
}
const SimpleReaderNode::List SimpleReaderNode::children() const { return m_children; }
const SimpleReaderNode::List &SimpleReaderNode::children() const
{
return m_children;
}
void SimpleReaderNode::setProperty(const QString &name,
const SourceLocation &nameLocation,

View File

@@ -34,6 +34,7 @@ public:
SourceLocation valueLocation;
bool isValid() const { return !value.isNull() && value.isValid(); }
explicit operator bool() const { return isValid(); }
bool isDefaultValue() const
{
return !value.isNull() && !nameLocation.isValid() && !valueLocation.isValid();
@@ -53,7 +54,7 @@ public:
WeakPtr parent() const;
QString name() const;
SourceLocation nameLocation() const;
const List children() const;
const List &children() const;
protected:
SimpleReaderNode();

View File

@@ -44,7 +44,7 @@ enum class View3DActionType {
ParticlesPlay,
ParticlesRestart,
ParticlesSeek,
SyncBackgroundColor,
SyncEnvBackground,
GetNodeAtPos,
SetBakeLightsView3D
};

View File

@@ -10,6 +10,7 @@ add_qtc_library(Utils
ansiescapecodehandler.cpp ansiescapecodehandler.h
appinfo.cpp appinfo.h
appmainwindow.cpp appmainwindow.h
array.h
aspects.cpp aspects.h
async.cpp async.h
basetreeview.cpp basetreeview.h

View File

@@ -597,13 +597,91 @@ public:
MapInsertIterator<Container> operator++(int) { return *this; }
};
// inserter helper function, returns a std::back_inserter for most containers
// and is overloaded for QSet<> and other containers without push_back, returning custom inserters
template<typename C>
inline std::back_insert_iterator<C>
inserter(C &container)
// because Qt container are not implementing the standard interface we need
// this helper functions for generic code
template<typename Type>
void append(QList<Type> *container, QList<Type> &&input)
{
return std::back_inserter(container);
container->append(std::move(input));
}
template<typename Type>
void append(QList<Type> *container, const QList<Type> &input)
{
container->append(input);
}
template<typename Container>
void append(Container *container, Container &&input)
{
container->insert(container->end(),
std::make_move_iterator(input.begin()),
std::make_move_iterator(input.end()));
}
template<typename Container>
void append(Container *container, const Container &input)
{
container->insert(container->end(), input.begin(), input.end());
}
// BackInsertIterator behaves like std::back_insert_iterator except is adds the back insertion for
// container of the same type
template<typename Container>
class BackInsertIterator
{
public:
using iterator_category = std::output_iterator_tag;
using value_type = void;
using difference_type = ptrdiff_t;
using pointer = void;
using reference = void;
using container_type = Container;
explicit constexpr BackInsertIterator(Container &container)
: m_container(std::addressof(container))
{}
constexpr BackInsertIterator &operator=(const typename Container::value_type &value)
{
m_container->push_back(value);
return *this;
}
constexpr BackInsertIterator &operator=(typename Container::value_type &&value)
{
m_container->push_back(std::move(value));
return *this;
}
constexpr BackInsertIterator &operator=(const Container &container)
{
append(m_container, container);
return *this;
}
constexpr BackInsertIterator &operator=(Container &&container)
{
append(m_container, container);
return *this;
}
[[nodiscard]] constexpr BackInsertIterator &operator*() { return *this; }
constexpr BackInsertIterator &operator++() { return *this; }
constexpr BackInsertIterator operator++(int) { return *this; }
private:
Container *m_container;
};
// inserter helper function, returns a BackInsertIterator for most containers
// and is overloaded for QSet<> and other containers without push_back, returning custom inserters
template<typename Container>
inline BackInsertIterator<Container> inserter(Container &container)
{
return BackInsertIterator(container);
}
template<typename X>

23
src/libs/utils/array.h Normal file
View File

@@ -0,0 +1,23 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
namespace Utils {
namespace Internal {
template<typename Type, std::size_t size, std::size_t... index>
constexpr std::array<std::remove_cv_t<Type>, size> to_array_implementation(
Type (&&array)[size], std::index_sequence<index...>)
{
return {{std::move(array[index])...}};
}
} // namespace Internal
template<typename Type, std::size_t size>
constexpr std::array<std::remove_cv_t<Type>, size> to_array(Type (&&array)[size])
{
return Internal::to_array_implementation(std::move(array), std::make_index_sequence<size>{});
}
} // namespace Utils

View File

@@ -33,11 +33,6 @@
#define unittest_public private
#endif
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#endif
namespace Utils {
template<uint Size>
@@ -425,10 +420,9 @@ public:
size_type newSize = oldSize + string.size();
reserve(optimalCapacity(newSize));
QT_WARNING_PUSH
QT_WARNING_DISABLE_CLANG("-Wunsafe-buffer-usage")
std::char_traits<char>::copy(data() + oldSize, string.data(), string.size());
QT_WARNING_POP
std::char_traits<char>::copy(std::next(data(), static_cast<std::ptrdiff_t>(oldSize)),
string.data(),
string.size());
setSize(newSize);
}
@@ -566,30 +560,20 @@ public:
static
BasicSmallString number(int number)
{
#ifdef __cpp_lib_to_chars
// +1 for the sign, +1 for the extra digit
// 2 bytes for the sign and because digits10 returns the floor
char buffer[std::numeric_limits<int>::digits10 + 2];
auto result = std::to_chars(buffer, buffer + sizeof(buffer), number, 10);
Q_ASSERT(result.ec == std::errc{});
auto endOfConversionString = result.ptr;
return BasicSmallString(buffer, endOfConversionString);
#else
return std::to_string(number);
#endif
}
static BasicSmallString number(long long int number) noexcept
{
#ifdef __cpp_lib_to_chars
// +1 for the sign, +1 for the extra digit
// 2 bytes for the sign and because digits10 returns the floor
char buffer[std::numeric_limits<long long int>::digits10 + 2];
auto result = std::to_chars(buffer, buffer + sizeof(buffer), number, 10);
Q_ASSERT(result.ec == std::errc{});
auto endOfConversionString = result.ptr;
return BasicSmallString(buffer, endOfConversionString);
#else
return std::to_string(number);
#endif
}
static BasicSmallString number(double number) noexcept { return std::to_string(number); }
@@ -927,7 +911,3 @@ SmallString operator+(const char(&first)[Size], SmallStringView second)
}
} // namespace Utils
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic pop
#endif

View File

@@ -37,6 +37,7 @@ constexpr char C_PANEL_WIDGET[] = "panelwidget";
constexpr char C_PANEL_WIDGET_SINGLE_ROW[] = "panelwidget_singlerow";
constexpr char C_SHOW_BORDER[] = "showborder";
constexpr char C_TOP_BORDER[] = "topBorder";
constexpr char C_TOOLBAR_ACTIONWIDGET[] = "toolbar_actionWidget";
enum ToolbarStyle {
ToolbarStyleCompact,

View File

@@ -345,6 +345,17 @@ public:
DSscrollBarTrack,
DSscrollBarHandle,
DSscrollBarHandle_idle,
DSconnectionCodeEditor,
DSpillText,
DSpillTextSelected,
DspillTextEdit,
DSpillDefaultBackgroundIdle,
DSpillDefaultBackgroundHover,
DSpillOperatorBackgroundIdle,
DSpillOperatorBackgroundHover,
DSpillLiteralBackgroundIdle,
DSpillLiteralBackgroundHover,
/*Legacy QtDS*/
DSiconColor,

View File

@@ -112,4 +112,8 @@ add_subdirectory(mcusupport)
add_subdirectory(saferenderer)
add_subdirectory(copilot)
add_subdirectory(terminal)
if (WITH_QMLDESIGNER)
add_subdirectory(effectmakernew)
endif()
add_subdirectory(compilerexplorer)

View File

@@ -543,6 +543,12 @@ bool ICore::showWarningWithOptions(const QString &title, const QString &text,
return false;
}
bool ICore::isQtDesignStudio()
{
QtcSettings *settings = Core::ICore::settings();
return settings->value("QML/Designer/StandAloneMode", false).toBool();
}
/*!
Returns the application's main settings object.

View File

@@ -63,6 +63,7 @@ public:
Utils::Id settingsId = {},
QWidget *parent = nullptr);
static bool isQtDesignStudio();
static Utils::QtcSettings *settings(QSettings::Scope scope = QSettings::UserScope);
static QPrinter *printer();
static QString userInterfaceLanguage();

View File

@@ -732,8 +732,17 @@ void ManhattanStyle::drawPrimitiveForPanelWidget(PrimitiveElement element,
painter->drawLine(borderRect.topRight(), borderRect.bottomRight());
}
} else if (option->state & State_Enabled && option->state & State_MouseOver) {
StyleHelper::drawPanelBgRect(
painter, rect, creatorTheme()->color(Theme::FancyToolButtonHoverColor));
if (widget->property(StyleHelper::C_TOOLBAR_ACTIONWIDGET).toBool()) {
painter->save();
painter->setBrush(creatorTheme()->color(Theme::FancyToolButtonHoverColor));
painter->drawRoundedRect(rect, 5, 5);
painter->restore();
} else {
StyleHelper::drawPanelBgRect(painter,
rect,
creatorTheme()->color(
Theme::FancyToolButtonHoverColor));
}
}
if (option->state & State_HasFocus && (option->state & State_KeyboardFocusChange)) {
QColor highlight = option->palette.highlight().color();

View File

@@ -0,0 +1,26 @@
find_package(Qt6 OPTIONAL_COMPONENTS Gui Quick ShaderTools)
add_qtc_plugin(EffectMakerNew
CONDITION TARGET QmlDesigner AND TARGET Qt::ShaderTools
PLUGIN_DEPENDS
QtCreator::Core QtCreator::QmlDesigner
DEPENDS
Qt::Core
QtCreator::Utils Qt::CorePrivate Qt::Widgets Qt::Qml Qt::QmlPrivate Qt::Quick Qt::ShaderTools Qt::ShaderToolsPrivate
SOURCES
effectmakerplugin.cpp effectmakerplugin.h
effectmakerwidget.cpp effectmakerwidget.h
effectmakerview.cpp effectmakerview.h
effectmakermodel.cpp effectmakermodel.h
effectmakernodesmodel.cpp effectmakernodesmodel.h
effectmakeruniformsmodel.cpp effectmakeruniformsmodel.h
effectnode.cpp effectnode.h
effectnodescategory.cpp effectnodescategory.h
compositionnode.cpp compositionnode.h
uniform.cpp uniform.h
effectutils.cpp effectutils.h
effectmakercontextobject.cpp effectmakercontextobject.h
shaderfeatures.cpp shaderfeatures.h
syntaxhighlighterdata.cpp syntaxhighlighterdata.h
BUILD_DEFAULT OFF
)

View File

@@ -0,0 +1,15 @@
{
\"Name\" : \"EffectMakerNew\",
\"Version\" : \"$$QTCREATOR_VERSION\",
\"CompatVersion\" : \"$$QTCREATOR_COMPAT_VERSION\",
\"Revision\" : \"$$QTC_PLUGIN_REVISION\",
\"Vendor\" : \"The Qt Company Ltd\",
\"Copyright\" : \"(C) $$QTCREATOR_COPYRIGHT_YEAR The Qt Company Ltd\",
\"License\" : [ \"Commercial Usage\",
\"\",
\"Licensees holding valid Qt Enterprise licenses may use this plugin in accordance with the Qt Enterprise License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and The Qt Company.\"
],
\"Description\" : \"Plugin for Effect Maker.\",
\"Url\" : \"http://www.qt.io\",
$$dependencyList
}

View File

@@ -12,7 +12,7 @@
#include <QJsonDocument>
#include <QJsonObject>
namespace QmlDesigner {
namespace EffectMaker {
CompositionNode::CompositionNode(const QString &qenPath)
{
@@ -119,4 +119,5 @@ void CompositionNode::parse(const QString &qenPath)
}
}
} // namespace QmlDesigner
} // namespace EffectMaker

View File

@@ -7,7 +7,7 @@
#include <QObject>
namespace QmlDesigner {
namespace EffectMaker {
class CompositionNode : public QObject
{
@@ -57,4 +57,5 @@ private:
EffectMakerUniformsModel m_unifomrsModel;
};
} // namespace QmlDesigner
} // namespace EffectMaker

View File

@@ -9,7 +9,7 @@
#include <nodemetainfo.h>
#include <rewritingexception.h>
#include <qmldesignerplugin.h>
#include <qmlmodelnodeproxy.h>
#include <qmldesigner/components/propertyeditor/qmlmodelnodeproxy.h>
#include <qmlobjectnode.h>
#include <qmltimeline.h>
#include <qmltimelinekeyframegroup.h>
@@ -27,7 +27,7 @@
#include <coreplugin/icore.h>
namespace QmlDesigner {
namespace EffectMaker {
EffectMakerContextObject::EffectMakerContextObject(QQmlContext *context, QObject *parent)
: QObject(parent)
@@ -117,7 +117,7 @@ void EffectMakerContextObject::setBackendValues(QQmlPropertyMap *newBackendValue
emit backendValuesChanged();
}
void EffectMakerContextObject::setModel(Model *model)
void EffectMakerContextObject::setModel(QmlDesigner::Model *model)
{
m_model = model;
}
@@ -169,7 +169,7 @@ int EffectMakerContextObject::devicePixelRatio()
QStringList EffectMakerContextObject::allStatesForId(const QString &id)
{
if (m_model && m_model->rewriterView()) {
const QmlObjectNode node = m_model->rewriterView()->modelNodeForId(id);
const QmlDesigner::QmlObjectNode node = m_model->rewriterView()->modelNodeForId(id);
if (node.isValid())
return node.allStateNames();
}
@@ -182,4 +182,5 @@ bool EffectMakerContextObject::isBlocked(const QString &) const
return false;
}
} // QmlDesigner
} // namespace EffectMaker

View File

@@ -14,7 +14,7 @@
#include <QPoint>
#include <QMouseEvent>
namespace QmlDesigner {
namespace EffectMaker {
class EffectMakerContextObject : public QObject
{
@@ -90,7 +90,7 @@ private:
int m_majorVersion = 1;
QQmlPropertyMap *m_backendValues = nullptr;
Model *m_model = nullptr;
QmlDesigner::Model *m_model = nullptr;
QPoint m_lastPos;
@@ -98,4 +98,5 @@ private:
bool m_selectionChanged = false;
};
} // QmlDesigner
} // namespace EffectMaker

View File

@@ -12,11 +12,43 @@
#include <utils/qtcassert.h>
namespace QmlDesigner {
namespace EffectMaker {
enum class FileType
{
Binary,
Text
};
static bool writeToFile(const QByteArray &buf, const QString &filename, FileType fileType)
{
QDir().mkpath(QFileInfo(filename).path());
QFile f(filename);
QIODevice::OpenMode flags = QIODevice::WriteOnly | QIODevice::Truncate;
if (fileType == FileType::Text)
flags |= QIODevice::Text;
if (!f.open(flags)) {
qWarning() << "Failed to open file for writing:" << filename;
return false;
}
f.write(buf);
return true;
}
EffectMakerModel::EffectMakerModel(QObject *parent)
: QAbstractListModel{parent}
{
m_vertexShaderFile.setFileTemplate(QDir::tempPath() + "/dsem_XXXXXX.vert.qsb");
m_fragmentShaderFile.setFileTemplate(QDir::tempPath() + "/dsem_XXXXXX.frag.qsb");
// TODO: Will be revisted later when saving output files
if (m_vertexShaderFile.open())
qInfo() << "Using temporary vs file:" << m_vertexShaderFile.fileName();
if (m_fragmentShaderFile.open())
qInfo() << "Using temporary fs file:" << m_fragmentShaderFile.fileName();
// Prepare baker
m_baker.setGeneratedShaderVariants({ QShader::StandardShader });
updateBakedShaderVersions();
}
QHash<int, QByteArray> EffectMakerModel::roleNames() const
@@ -72,6 +104,8 @@ void EffectMakerModel::addNode(const QString &nodeQenPath)
endInsertRows();
setIsEmpty(false);
bakeShaders();
}
void EffectMakerModel::moveNode(int fromIdx, int toIdx)
@@ -83,6 +117,8 @@ void EffectMakerModel::moveNode(int fromIdx, int toIdx)
beginMoveRows({}, fromIdx, fromIdx, {}, toIdxAdjusted);
m_nodes.move(fromIdx, toIdx);
endMoveRows();
bakeShaders();
}
void EffectMakerModel::removeNode(int idx)
@@ -95,6 +131,23 @@ void EffectMakerModel::removeNode(int idx)
if (m_nodes.isEmpty())
setIsEmpty(true);
else
bakeShaders();
}
void EffectMakerModel::updateBakedShaderVersions()
{
QList<QShaderBaker::GeneratedShader> targets;
targets.append({ QShader::SpirvShader, QShaderVersion(100) }); // Vulkan 1.0
targets.append({ QShader::HlslShader, QShaderVersion(50) }); // Shader Model 5.0
targets.append({ QShader::MslShader, QShaderVersion(12) }); // Metal 1.2
targets.append({ QShader::GlslShader, QShaderVersion(300, QShaderVersion::GlslEs) }); // GLES 3.0+
targets.append({ QShader::GlslShader, QShaderVersion(410) }); // OpenGL 4.1+
targets.append({ QShader::GlslShader, QShaderVersion(330) }); // OpenGL 3.3
targets.append({ QShader::GlslShader, QShaderVersion(140) }); // OpenGL 3.1
//TODO: Do we need support for legacy shaders 100, 120?
m_baker.setGeneratedShaders(targets);
}
QString EffectMakerModel::fragmentShader() const
@@ -123,6 +176,11 @@ void EffectMakerModel::setVertexShader(const QString &newVertexShader)
m_vertexShader = newVertexShader;
}
const QString &EffectMakerModel::qmlComponentString() const
{
return m_qmlComponentString;
}
const QList<Uniform *> EffectMakerModel::allUniforms()
{
QList<Uniform *> uniforms = {};
@@ -554,7 +612,7 @@ QString EffectMakerModel::generateVertexShader(bool includeUniforms)
const bool removeTags = includeUniforms;
s += getDefineProperties();
s += getConstVariables();
// s += getConstVariables(); // Not sure yet, will check on this later
// When the node is complete, add shader code in correct nodes order
// split to root and main parts
@@ -614,7 +672,7 @@ QString EffectMakerModel::generateFragmentShader(bool includeUniforms)
const bool removeTags = includeUniforms;
s += getDefineProperties();
s += getConstVariables();
// s += getConstVariables(); // Not sure yet, will check on this later
// When the node is complete, add shader code in correct nodes order
// split to root and main parts
@@ -740,11 +798,44 @@ void EffectMakerModel::bakeShaders()
// First update the features based on shader content
// This will make sure that next calls to "generate" will produce correct uniforms.
m_shaderFeatures.update(generateVertexShader(false), generateFragmentShader(false), m_previewEffectPropertiesString);
m_shaderFeatures.update(generateVertexShader(false), generateFragmentShader(false),
m_previewEffectPropertiesString);
updateCustomUniforms();
// TODO: Shaders baking
setVertexShader(generateVertexShader());
QString vs = m_vertexShader;
m_baker.setSourceString(vs.toUtf8(), QShader::VertexStage);
QShader vertShader = m_baker.bake();
if (!vertShader.isValid()) {
qWarning() << "Shader baking failed:" << qPrintable(m_baker.errorMessage());
setEffectError(m_baker.errorMessage().split('\n').first(), ErrorVert);
} else {
QString filename = m_vertexShaderFile.fileName();
writeToFile(vertShader.serialized(), filename, FileType::Binary);
resetEffectError(ErrorVert);
}
setFragmentShader(generateFragmentShader());
QString fs = m_fragmentShader;
m_baker.setSourceString(fs.toUtf8(), QShader::FragmentStage);
QShader fragShader = m_baker.bake();
if (!fragShader.isValid()) {
qWarning() << "Shader baking failed:" << qPrintable(m_baker.errorMessage());
setEffectError(m_baker.errorMessage().split('\n').first(), ErrorFrag);
} else {
QString filename = m_fragmentShaderFile.fileName();
writeToFile(fragShader.serialized(), filename, FileType::Binary);
resetEffectError(ErrorFrag);
}
if (vertShader.isValid() && fragShader.isValid()) {
Q_EMIT shadersBaked();
setShadersUpToDate(true);
}
// TODO: Mark shaders as baked, required by export later
}
bool EffectMakerModel::shadersUpToDate() const
@@ -760,4 +851,86 @@ void EffectMakerModel::setShadersUpToDate(bool UpToDate)
emit shadersUpToDateChanged();
}
} // namespace QmlDesigner
QString EffectMakerModel::getQmlImagesString(bool localFiles)
{
Q_UNUSED(localFiles)
// TODO
return QString();
}
QString EffectMakerModel::getQmlComponentString(bool localFiles)
{
auto addProperty = [localFiles](const QString &name, const QString &var,
const QString &type, bool blurHelper = false)
{
if (localFiles) {
const QString parent = blurHelper ? QString("blurHelper.") : QString("rootItem.");
return QString("readonly property alias %1: %2%3\n").arg(name, parent, var);
} else {
const QString parent = blurHelper ? "blurHelper." : QString();
return QString("readonly property %1 %2: %3%4\n").arg(type, name, parent, var);
}
};
QString customImagesString = getQmlImagesString(localFiles);
QString vertexShaderFilename = "file:///" + m_fragmentShaderFile.fileName();
QString fragmentShaderFilename = "file:///" + m_vertexShaderFile.fileName();
QString s;
QString l1 = localFiles ? QStringLiteral(" ") : QStringLiteral("");
QString l2 = localFiles ? QStringLiteral(" ") : QStringLiteral(" ");
QString l3 = localFiles ? QStringLiteral(" ") : QStringLiteral(" ");
if (!localFiles)
s += "import QtQuick\n";
s += l1 + "ShaderEffect {\n";
if (m_shaderFeatures.enabled(ShaderFeatures::Source))
s += l2 + addProperty("iSource", "source", "Item");
if (m_shaderFeatures.enabled(ShaderFeatures::Time))
s += l2 + addProperty("iTime", "animatedTime", "real");
if (m_shaderFeatures.enabled(ShaderFeatures::Frame))
s += l2 + addProperty("iFrame", "animatedFrame", "int");
if (m_shaderFeatures.enabled(ShaderFeatures::Resolution)) {
// Note: Pixel ratio is currently always 1.0
s += l2 + "readonly property vector3d iResolution: Qt.vector3d(width, height, 1.0)\n";
}
if (m_shaderFeatures.enabled(ShaderFeatures::Mouse)) { // Do we need interactive effects?
s += l2 + "readonly property vector4d iMouse: Qt.vector4d(rootItem._effectMouseX, rootItem._effectMouseY,\n";
s += l2 + " rootItem._effectMouseZ, rootItem._effectMouseW)\n";
}
if (m_shaderFeatures.enabled(ShaderFeatures::BlurSources)) {
s += l2 + addProperty("iSourceBlur1", "blurSrc1", "Item", true);
s += l2 + addProperty("iSourceBlur2", "blurSrc2", "Item", true);
s += l2 + addProperty("iSourceBlur3", "blurSrc3", "Item", true);
s += l2 + addProperty("iSourceBlur4", "blurSrc4", "Item", true);
s += l2 + addProperty("iSourceBlur5", "blurSrc5", "Item", true);
}
// When used in preview component, we need property with value
// and when in exported component, property with binding to root value.
s += localFiles ? m_exportedEffectPropertiesString : m_previewEffectPropertiesString;
if (!customImagesString.isEmpty())
s += '\n' + customImagesString;
s += '\n';
s += l2 + "vertexShader: '" + vertexShaderFilename + "'\n";
s += l2 + "fragmentShader: '" + fragmentShaderFilename + "'\n";
s += l2 + "anchors.fill: parent\n";
if (m_shaderFeatures.enabled(ShaderFeatures::GridMesh)) {
QString gridSize = QString("%1, %2").arg(m_shaderFeatures.gridMeshWidth()).arg(m_shaderFeatures.gridMeshHeight());
s += l2 + "mesh: GridMesh {\n";
s += l3 + QString("resolution: Qt.size(%1)\n").arg(gridSize);
s += l2 + "}\n";
}
s += l1 + "}\n";
return s;
}
void EffectMakerModel::updateQmlComponent()
{
// Clear possible QML runtime errors
resetEffectError(ErrorQMLRuntime);
m_qmlComponentString = getQmlComponentString(false);
}
} // namespace EffectMaker

View File

@@ -8,8 +8,11 @@
#include <QMap>
#include <QRegularExpression>
#include <QStandardItemModel>
#include <QTemporaryFile>
namespace QmlDesigner {
#include <QtShaderTools/private/qshaderbaker_p.h>
namespace EffectMaker {
class CompositionNode;
class Uniform;
@@ -33,6 +36,8 @@ class EffectMakerModel : public QAbstractListModel
Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged)
Q_PROPERTY(int selectedIndex MEMBER m_selectedIndex NOTIFY selectedIndexChanged)
Q_PROPERTY(bool shadersUpToDate READ shadersUpToDate WRITE setShadersUpToDate NOTIFY shadersUpToDateChanged)
Q_PROPERTY(QString qmlComponentString READ qmlComponentString)
public:
EffectMakerModel(QObject *parent = nullptr);
@@ -58,11 +63,17 @@ public:
QString vertexShader() const;
void setVertexShader(const QString &newVertexShader);
const QString &qmlComponentString() const;
void setQmlComponentString(const QString &string);
Q_INVOKABLE void updateQmlComponent();
signals:
void isEmptyChanged();
void selectedIndexChanged(int idx);
void effectErrorChanged();
void shadersUpToDateChanged();
void shadersBaked();
private:
enum Roles {
@@ -88,6 +99,7 @@ private:
const QString getVSUniforms();
const QString getFSUniforms();
void updateBakedShaderVersions();
QString detectErrorMessage(const QString &errorMessage);
EffectError effectError() const;
void setEffectError(const QString &errorMessage, int type = -1, int lineNumber = -1);
@@ -112,6 +124,9 @@ private:
void updateCustomUniforms();
void bakeShaders();
QString getQmlImagesString(bool localFiles);
QString getQmlComponentString(bool localFiles);
QList<CompositionNode *> m_nodes;
int m_selectedIndex = -1;
@@ -125,14 +140,19 @@ private:
QString m_vertexShader;
QStringList m_defaultRootVertexShader;
QStringList m_defaultRootFragmentShader;
QShaderBaker m_baker;
QTemporaryFile m_fragmentShaderFile;
QTemporaryFile m_vertexShaderFile;
// Used in exported QML, at root of the file
QString m_exportedRootPropertiesString;
// Used in exported QML, at ShaderEffect component of the file
QString m_exportedEffectPropertiesString;
// Used in preview QML, at ShaderEffect component of the file
QString m_previewEffectPropertiesString;
QString m_qmlComponentString;
const QRegularExpression m_spaceReg = QRegularExpression("\\s+");
};
} // namespace QmlDesigner
} // namespace EffectMaker

View File

@@ -7,7 +7,7 @@
#include <QCoreApplication>
namespace QmlDesigner {
namespace EffectMaker {
EffectMakerNodesModel::EffectMakerNodesModel(QObject *parent)
: QAbstractListModel{parent}
@@ -73,6 +73,8 @@ void EffectMakerNodesModel::loadModel()
return;
}
m_categories = {};
QDirIterator itCategories(m_nodesPath.toString(), QDir::Dirs | QDir::NoDotAndDotDot);
while (itCategories.hasNext()) {
itCategories.next();
@@ -104,4 +106,5 @@ void EffectMakerNodesModel::resetModel()
endResetModel();
}
} // namespace QmlDesigner
} // namespace EffectMaker

View File

@@ -9,7 +9,7 @@
#include <QStandardItemModel>
namespace QmlDesigner {
namespace EffectMaker {
class EffectMakerNodesModel : public QAbstractListModel
{
@@ -40,4 +40,5 @@ private:
bool m_probeNodesDir = false;
};
} // namespace QmlDesigner
} // namespace EffectMaker

View File

@@ -0,0 +1,46 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "effectmakerplugin.h"
#include "effectmakerview.h"
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/actionmanager/actioncontainer.h>
#include <coreplugin/coreconstants.h>
#include <coreplugin/icore.h>
#include <coreplugin/imode.h>
#include <coreplugin/modemanager.h>
#include <coreplugin/messagemanager.h>
#include <coreplugin/externaltool.h>
#include <coreplugin/externaltoolmanager.h>
#include <componentcore_constants.h>
#include <designeractionmanager.h>
#include <viewmanager.h>
#include <qmldesigner/dynamiclicensecheck.h>
#include <qmldesignerplugin.h>
#include <modelnodeoperations.h>
#include <QJsonDocument>
#include <QMap>
namespace EffectMaker {
bool EffectMakerPlugin::delayedInitialize()
{
if (m_delayedInitialized)
return true;
auto *designerPlugin = QmlDesigner::QmlDesignerPlugin::instance();
auto &viewManager = designerPlugin->viewManager();
viewManager.registerView(std::make_unique<EffectMakerView>(
QmlDesigner::QmlDesignerPlugin::externalDependenciesForPluginInitializationOnly()));
m_delayedInitialized = true;
return true;
}
} // namespace EffectMaker

View File

@@ -0,0 +1,32 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <extensionsystem/iplugin.h>
#include <utils/process.h>
namespace Core {
class ActionContainer;
class ExternalTool;
}
namespace EffectMaker {
class EffectMakerPlugin : public ExtensionSystem::IPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "EffectMakerNew.json")
public:
EffectMakerPlugin() {}
~EffectMakerPlugin() override {}
bool delayedInitialize() override;
private:
bool m_delayedInitialized = false;
};
} // namespace EffectMaker

View File

@@ -7,7 +7,7 @@
#include <utils/qtcassert.h>
namespace QmlDesigner {
namespace EffectMaker {
EffectMakerUniformsModel::EffectMakerUniformsModel(QObject *parent)
: QAbstractListModel{parent}
@@ -72,4 +72,5 @@ QList<Uniform *> EffectMakerUniformsModel::uniforms() const
return m_uniforms;
}
} // namespace QmlDesigner
} // namespace EffectMaker

View File

@@ -5,7 +5,7 @@
#include <QStandardItemModel>
namespace QmlDesigner {
namespace EffectMaker {
class Uniform;
@@ -42,4 +42,5 @@ private:
QList<Uniform *> m_uniforms;
};
} // namespace QmlDesigner
} // namespace EffectMaker

View File

@@ -0,0 +1,83 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "effectmakerview.h"
#include "effectmakerwidget.h"
#include "effectmakernodesmodel.h"
#include "nodeinstanceview.h"
#include "qmldesignerconstants.h"
#include <coreplugin/icore.h>
#include <QQmlContext>
#include <QQmlEngine>
#include <QQuickItem>
#include <QQuickView>
#include <QTimer>
namespace EffectMaker {
EffectMakerContext::EffectMakerContext(QWidget *widget)
: IContext(widget)
{
setWidget(widget);
setContext(Core::Context(QmlDesigner::Constants::C_QMLEFFECTMAKER,
QmlDesigner::Constants::C_QT_QUICK_TOOLS_MENU));
}
void EffectMakerContext::contextHelp(const HelpCallback &callback) const
{
qobject_cast<EffectMakerWidget *>(m_widget)->contextHelp(callback);
}
EffectMakerView::EffectMakerView(QmlDesigner::ExternalDependenciesInterface &externalDependencies)
: AbstractView{externalDependencies}
{
}
EffectMakerView::~EffectMakerView()
{}
bool EffectMakerView::hasWidget() const
{
return true;
}
QmlDesigner::WidgetInfo EffectMakerView::widgetInfo()
{
if (m_widget.isNull()) {
m_widget = new EffectMakerWidget{this};
auto context = new EffectMakerContext(m_widget.data());
Core::ICore::addContextObject(context);
}
return createWidgetInfo(m_widget.data(), "Effect Maker",
QmlDesigner::WidgetInfo::LeftPane, 0, tr("Effect Maker"));
}
void EffectMakerView::customNotification(const AbstractView * /*view*/,
const QString & /*identifier*/,
const QList<QmlDesigner::ModelNode> & /*nodeList*/,
const QList<QVariant> & /*data*/)
{
// TODO
}
void EffectMakerView::modelAttached(QmlDesigner::Model *model)
{
AbstractView::modelAttached(model);
m_widget->effectMakerNodesModel()->loadModel();
m_widget->initView();
}
void EffectMakerView::modelAboutToBeDetached(QmlDesigner::Model *model)
{
AbstractView::modelAboutToBeDetached(model);
}
} // namespace EffectMaker

View File

@@ -0,0 +1,46 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include "abstractview.h"
#include <coreplugin/icontext.h>
#include <QPointer>
namespace EffectMaker {
class EffectMakerWidget;
class EffectMakerContext : public Core::IContext
{
Q_OBJECT
public:
EffectMakerContext(QWidget *widget);
void contextHelp(const Core::IContext::HelpCallback &callback) const override;
};
class EffectMakerView : public QmlDesigner::AbstractView
{
public:
EffectMakerView(QmlDesigner::ExternalDependenciesInterface &externalDependencies);
~EffectMakerView() override;
bool hasWidget() const override;
QmlDesigner::WidgetInfo widgetInfo() override;
// AbstractView
void modelAttached(QmlDesigner::Model *model) override;
void modelAboutToBeDetached(QmlDesigner::Model *model) override;
private:
void customNotification(const AbstractView *view, const QString &identifier,
const QList<QmlDesigner::ModelNode> &nodeList, const QList<QVariant> &data) override;
QPointer<EffectMakerWidget> m_widget;
};
} // namespace EffectMaker

View File

@@ -23,7 +23,7 @@
#include <QHBoxLayout>
#include <QQmlEngine>
namespace QmlDesigner {
namespace EffectMaker {
static QString propertyEditorResourcesPath()
{
@@ -46,21 +46,22 @@ EffectMakerWidget::EffectMakerWidget(EffectMakerView *view)
m_quickWidget->quickWidget()->installEventFilter(this);
// create the inner widget
m_quickWidget->quickWidget()->setObjectName(Constants::OBJECT_NAME_EFFECT_MAKER);
m_quickWidget->quickWidget()->setObjectName(QmlDesigner::Constants::OBJECT_NAME_EFFECT_MAKER);
m_quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
Theme::setupTheme(m_quickWidget->engine());
QmlDesigner::Theme::setupTheme(m_quickWidget->engine());
m_quickWidget->engine()->addImportPath(propertyEditorResourcesPath() + "/imports");
m_quickWidget->setClearColor(Theme::getColor(Theme::Color::QmlDesigner_BackgroundColorDarkAlternate));
m_quickWidget->setClearColor(QmlDesigner::Theme::getColor(
QmlDesigner::Theme::Color::QmlDesigner_BackgroundColorDarkAlternate));
auto layout = new QHBoxLayout(this);
layout->setContentsMargins({});
layout->setSpacing(0);
layout->addWidget(m_quickWidget.data());
setStyleSheet(Theme::replaceCssColors(
setStyleSheet(QmlDesigner::Theme::replaceCssColors(
QString::fromUtf8(Utils::FileReader::fetchQrc(":/qmldesigner/stylesheet.css"))));
QmlDesignerPlugin::trackWidgetFocusTime(this, Constants::EVENT_EFFECTMAKER_TIME);
QmlDesigner::QmlDesignerPlugin::trackWidgetFocusTime(this, QmlDesigner::Constants::EVENT_EFFECTMAKER_TIME);
auto map = m_quickWidget->registerPropertyMap("EffectMakerBackend");
map->setProperties({{"effectMakerNodesModel", QVariant::fromValue(m_effectMakerNodesModel.data())},
@@ -142,4 +143,5 @@ void EffectMakerWidget::reloadQmlSource()
m_quickWidget->setSource(QUrl::fromLocalFile(effectMakerQmlPath));
}
} // namespace QmlDesigner
} // namespace EffectMaker

View File

@@ -3,7 +3,7 @@
#pragma once
#include "qmlmodelnodeproxy.h"
#include "qmldesigner/components/propertyeditor/qmlmodelnodeproxy.h"
#include <coreplugin/icontext.h>
@@ -11,7 +11,7 @@
class StudioQuickWidget;
namespace QmlDesigner {
namespace EffectMaker {
class EffectMakerView;
class EffectMakerModel;
@@ -53,7 +53,8 @@ private:
QPointer<EffectMakerNodesModel> m_effectMakerNodesModel;
QPointer<EffectMakerView> m_effectMakerView;
QPointer<StudioQuickWidget> m_quickWidget;
QmlModelNodeProxy m_backendModelNode;
QmlDesigner::QmlModelNodeProxy m_backendModelNode;
};
} // namespace QmlDesigner
} // namespace EffectMaker

View File

@@ -6,7 +6,7 @@
#include <QDir>
#include <QFileInfo>
namespace QmlDesigner {
namespace EffectMaker {
EffectNode::EffectNode(const QString &qenPath)
: m_qenPath(qenPath)
@@ -39,4 +39,5 @@ QString EffectNode::qenPath() const
return m_qenPath;
}
} // namespace QmlDesigner
} // namespace EffectMaker

View File

@@ -6,7 +6,7 @@
#include <QObject>
#include <QUrl>
namespace QmlDesigner {
namespace EffectMaker {
class EffectNode : public QObject
{
@@ -31,4 +31,5 @@ private:
QUrl m_iconPath;
};
} // namespace QmlDesigner
} // namespace EffectMaker

View File

@@ -3,7 +3,7 @@
#include "effectnodescategory.h"
namespace QmlDesigner {
namespace EffectMaker {
EffectNodesCategory::EffectNodesCategory(const QString &name, const QList<EffectNode *> &nodes)
: m_name(name),
@@ -19,4 +19,5 @@ QList<EffectNode *> EffectNodesCategory::nodes() const
return m_categoryNodes;
}
} // namespace QmlDesigner
} // namespace EffectMaker

View File

@@ -7,7 +7,7 @@
#include <QObject>
namespace QmlDesigner {
namespace EffectMaker {
class EffectNodesCategory : public QObject
{
@@ -27,4 +27,5 @@ private:
QList<EffectNode *> m_categoryNodes;
};
} // namespace QmlDesigner
} // namespace EffectMaker

View File

@@ -5,7 +5,7 @@
#include <QJsonArray>
namespace QmlDesigner {
namespace EffectMaker {
QString EffectUtils::codeFromJsonArray(const QJsonArray &codeArray)
{
@@ -20,4 +20,5 @@ QString EffectUtils::codeFromJsonArray(const QJsonArray &codeArray)
return codeString;
}
} // namespace QmlDesigner
} // namespace EffectMaker

View File

@@ -7,7 +7,7 @@
QT_FORWARD_DECLARE_CLASS(QJsonArray)
namespace QmlDesigner {
namespace EffectMaker {
class EffectUtils
{
@@ -17,4 +17,5 @@ public:
static QString codeFromJsonArray(const QJsonArray &codeArray);
};
} // namespace QmlDesigner
} // namespace EffectMaker

View File

@@ -5,7 +5,7 @@
#include <QStringList>
#include <QDebug>
namespace QmlDesigner {
namespace EffectMaker {
ShaderFeatures::ShaderFeatures()
{
@@ -77,4 +77,15 @@ void ShaderFeatures::checkLine(const QString &line, Features &features)
features.setFlag(BlurSources, true);
}
} // namespace QmlDesigner
int ShaderFeatures::gridMeshHeight() const
{
return m_gridMeshHeight;
}
int ShaderFeatures::gridMeshWidth() const
{
return m_gridMeshWidth;
}
} // namespace EffectMaker

View File

@@ -6,7 +6,7 @@
#include <QFlags>
#include <QString>
namespace QmlDesigner {
namespace EffectMaker {
class ShaderFeatures
{
@@ -28,6 +28,10 @@ public:
bool enabled(ShaderFeatures::Feature feature) const;
int gridMeshWidth() const;
int gridMeshHeight() const;
private:
void checkLine(const QString &line, ShaderFeatures::Features &features);
ShaderFeatures::Features m_enabledFeatures;
@@ -36,4 +40,5 @@ private:
};
Q_DECLARE_OPERATORS_FOR_FLAGS(ShaderFeatures::Features)
} // namespace QmlDesigner
} // namespace EffectMaker

View File

@@ -3,7 +3,7 @@
#include "syntaxhighlighterdata.h"
namespace QmlDesigner {
namespace EffectMaker {
static constexpr QByteArrayView shader_arg_names[] {
{ "gl_Position" },
@@ -186,5 +186,6 @@ QList<QByteArrayView> SyntaxHighlighterData::reservedFunctionNames()
return { std::begin(shader_function_names), std::end(shader_function_names) };
}
} // namespace QmlDesigner
} // namespace EffectMaker

View File

@@ -6,7 +6,7 @@
#include <QByteArrayView>
#include <QList>
namespace QmlDesigner {
namespace EffectMaker {
class SyntaxHighlighterData
{
@@ -18,5 +18,6 @@ public:
static QList<QByteArrayView> reservedFunctionNames();
};
} // namespace QmlDesigner
} // namespace EffectMaker

View File

@@ -2,14 +2,14 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "uniform.h"
#include <qmldesignerplugin.h>
#include "propertyeditorvalue.h"
#include <QColor>
#include <QJsonObject>
#include <QVector2D>
namespace QmlDesigner {
namespace EffectMaker {
Uniform::Uniform(const QJsonObject &propObj)
{
@@ -45,7 +45,7 @@ Uniform::Uniform(const QJsonObject &propObj)
setValueData(value, defaultValue, minValue, maxValue);
m_backendValue = new PropertyEditorValue(this);
m_backendValue = new QmlDesigner::PropertyEditorValue(this);
m_backendValue->setValue(value);
}
@@ -321,4 +321,4 @@ QString Uniform::typeToProperty(Uniform::Type type)
return QString();
}
} // namespace QmlDesigner
} // namespace EffectMaker

View File

@@ -6,13 +6,15 @@
#include <QObject>
#include <QVariant>
#include <qmldesigner/components/propertyeditor/propertyeditorvalue.h>
QT_FORWARD_DECLARE_CLASS(QColor)
QT_FORWARD_DECLARE_CLASS(QJsonObject)
QT_FORWARD_DECLARE_CLASS(QVector2D)
namespace QmlDesigner {
namespace EffectMaker {
class PropertyEditorValue;
class Uniform : public QObject
{
@@ -97,7 +99,7 @@ private:
bool m_useCustomValue = false;
bool m_enabled = true;
bool m_enableMipmap = false;
PropertyEditorValue *m_backendValue = nullptr;
QmlDesigner::PropertyEditorValue *m_backendValue = nullptr;
bool operator==(const Uniform &rhs) const noexcept
{
@@ -105,4 +107,4 @@ private:
}
};
} // namespace QmlDesigner
} // namespace EffectMaker

View File

@@ -125,7 +125,10 @@ void McuSupportPlugin::initialize()
// Temporary fix for CodeModel/Checker race condition
// Remove after https://bugreports.qt.io/browse/QTCREATORBUG-29269 is closed
connect(QmlJS::ModelManagerInterface::instance(),
if (!Core::ICore::isQtDesignStudio()) {
connect(
QmlJS::ModelManagerInterface::instance(),
&QmlJS::ModelManagerInterface::documentUpdated,
[lasttime = QTime::currentTime()](QmlJS::Document::Ptr doc) mutable {
// Prevent inifinite recall loop
@@ -157,6 +160,7 @@ void McuSupportPlugin::initialize()
->action()
->trigger();
});
}
dd->m_options.registerQchFiles();
dd->m_options.registerExamples();

View File

@@ -11,6 +11,10 @@ endif()
add_compile_options("$<$<COMPILE_LANG_AND_ID:CXX,Clang>:-Wno-error=maybe-uninitialized>")
add_compile_options("$<$<COMPILE_LANG_AND_ID:CXX,GNU>:-Wno-error=maybe-uninitialized>")
env_with_default("QDS_USE_PROJECTSTORAGE" ENV_QDS_USE_PROJECTSTORAGE OFF)
option(USE_PROJECTSTORAGE "Use ProjectStorage" ${ENV_QDS_USE_PROJECTSTORAGE})
add_feature_info("ProjectStorage" ${USE_PROJECTSTORAGE} "")
add_qtc_library(QmlDesignerUtils STATIC
DEPENDS
Qt::Gui Utils Qt::QmlPrivate Core
@@ -64,7 +68,10 @@ add_qtc_library(QmlDesignerCore STATIC
QmlDesignerUtils
TextEditor
Sqlite
DEFINES QMLDESIGNERCORE_LIBRARY QMLDESIGNERUTILS_LIBRARY
DEFINES
QMLDESIGNERCORE_LIBRARY
QMLDESIGNERUTILS_LIBRARY
$<$<BOOL:${USE_PROJECTSTORAGE}>:QDS_USE_PROJECTSTORAGE>
INCLUDES
${CMAKE_CURRENT_LIST_DIR}
PUBLIC_INCLUDES
@@ -76,13 +83,6 @@ add_qtc_library(QmlDesignerCore STATIC
rewritertransaction.h
)
if(TARGET QmlDesignerCore)
env_with_default("QDS_USE_PROJECTSTORAGE" ENV_QDS_USE_PROJECTSTORAGE OFF)
option(USE_PROJECTSTORAGE "Use ProjectStorage" ${ENV_QDS_USE_PROJECTSTORAGE})
add_feature_info("ProjectStorage" ${USE_PROJECTSTORAGE} "")
endif()
extend_qtc_library(QmlDesignerCore
CONDITION ENABLE_COMPILE_WARNING_AS_ERROR
PROPERTIES COMPILE_WARNING_AS_ERROR ON
@@ -238,6 +238,7 @@ extend_qtc_library(QmlDesignerCore
modelmerger.h
modelnode.h
modelnodepositionstorage.h
module.h
nodeabstractproperty.h
nodeinstance.h
nodelistproperty.h
@@ -574,6 +575,9 @@ extend_qtc_plugin(QmlDesigner
modelnodeoperations.cpp modelnodeoperations.h
formatoperation.cpp formatoperation.h
navigation2d.cpp navigation2d.h
propertyeditorcomponentgenerator.cpp propertyeditorcomponentgenerator.h
propertycomponentgenerator.cpp propertycomponentgenerator.h
propertycomponentgeneratorinterface.h
qmldesignericonprovider.cpp qmldesignericonprovider.h
qmleditormenu.cpp qmleditormenu.h
selectioncontext.cpp selectioncontext.h
@@ -683,6 +687,7 @@ extend_qtc_plugin(QmlDesigner
assetimportupdatetreemodel.cpp assetimportupdatetreemodel.h
assetimportupdatetreeview.cpp assetimportupdatetreeview.h
itemlibrary.qrc
itemlibraryconstants.h
itemlibraryimageprovider.cpp itemlibraryimageprovider.h
itemlibraryitem.cpp itemlibraryitem.h
itemlibrarymodel.cpp itemlibrarymodel.h
@@ -709,24 +714,6 @@ extend_qtc_plugin(QmlDesigner
assetslibraryiconprovider.cpp assetslibraryiconprovider.h
)
extend_qtc_plugin(QmlDesigner
SOURCES_PREFIX components/effectmaker
SOURCES
effectmakerwidget.cpp effectmakerwidget.h
effectmakerview.cpp effectmakerview.h
effectmakermodel.cpp effectmakermodel.h
effectmakernodesmodel.cpp effectmakernodesmodel.h
effectmakeruniformsmodel.cpp effectmakeruniformsmodel.h
effectnode.cpp effectnode.h
effectnodescategory.cpp effectnodescategory.h
compositionnode.cpp compositionnode.h
uniform.cpp uniform.h
effectutils.cpp effectutils.h
effectmakercontextobject.cpp effectmakercontextobject.h
shaderfeatures.cpp shaderfeatures.h
syntaxhighlighterdata.cpp syntaxhighlighterdata.h
)
extend_qtc_plugin(QmlDesigner
SOURCES_PREFIX components/navigator
SOURCES
@@ -805,7 +792,9 @@ extend_qtc_plugin(QmlDesigner
extend_qtc_plugin(QmlDesigner
SOURCES_PREFIX components/collectioneditor
SOURCES
collectionmodel.cpp collectionmodel.h
collectioneditorconstants.h
collectionlistmodel.cpp collectionlistmodel.h
collectionsourcemodel.cpp collectionsourcemodel.h
collectionview.cpp collectionview.h
collectionwidget.cpp collectionwidget.h
singlecollectionmodel.cpp singlecollectionmodel.h
@@ -1214,4 +1203,3 @@ extend_qtc_plugin(qtquickplugin
qtquickplugin.cpp qtquickplugin.h
qtquickplugin.qrc
)
add_subdirectory(studioplugin)

View File

@@ -56,8 +56,15 @@ Thumbnail AssetsLibraryIconProvider::createThumbnail(const QString &id, const QS
originalSize = KtxImage(id).dimensions();
}
if (requestedSize.isValid())
pixmap = pixmap.scaled(requestedSize, Qt::KeepAspectRatio);
if (requestedSize.isValid()) {
double ratio = requestedSize.width() / 48.;
if (ratio * pixmap.size().width() > requestedSize.width()
|| ratio * pixmap.size().height() > requestedSize.height()) {
pixmap = pixmap.scaled(requestedSize, Qt::KeepAspectRatio);
} else if (!qFuzzyCompare(ratio, 1.)) {
pixmap = pixmap.scaled(pixmap.size() * ratio, Qt::KeepAspectRatio);
}
}
return Thumbnail{pixmap, originalSize, assetType, fileSize};
}

View File

@@ -3,18 +3,17 @@
#include "abstracteditordialog.h"
#include <texteditor/textdocument.h>
#include <texteditor/texteditor.h>
#include <qmldesigner/qmldesignerplugin.h>
#include <qmljseditor/qmljseditor.h>
#include <qmljseditor/qmljseditordocument.h>
#include <texteditor/textdocument.h>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPlainTextEdit>
#include <QPushButton>
#include <QVBoxLayout>
namespace QmlDesigner {
@@ -71,7 +70,7 @@ QString AbstractEditorDialog::editorValue() const
void AbstractEditorDialog::setEditorValue(const QString &text)
{
if (m_editorWidget)
m_editorWidget->document()->setPlainText(text);
m_editorWidget->setEditorTextWithIndentation(text);
}
void AbstractEditorDialog::unregisterAutoCompletion()
@@ -102,8 +101,6 @@ void AbstractEditorDialog::setupJSEditor()
m_editorWidget->setLineNumbersVisible(false);
m_editorWidget->setMarksVisible(false);
m_editorWidget->setCodeFoldingSupported(false);
m_editorWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
m_editorWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
m_editorWidget->setTabChangesFocus(true);
}
@@ -123,7 +120,8 @@ void AbstractEditorDialog::setupUIComponents()
m_buttonBox->button(QDialogButtonBox::Ok)->setDefault(true);
m_verticalLayout->addLayout(m_comboBoxLayout);
m_verticalLayout->addWidget(m_editorWidget);
//editor widget has to stretch the most among the other siblings:
m_verticalLayout->addWidget(m_editorWidget, 10);
m_verticalLayout->addWidget(m_buttonBox);
this->resize(660, 240);

View File

@@ -83,12 +83,54 @@ void ActionEditor::hideWidget()
}
}
void ActionEditor::showControls(bool show)
{
if (m_dialog)
m_dialog->showControls(show);
}
void QmlDesigner::ActionEditor::setMultilne(bool multiline)
{
if (m_dialog)
m_dialog->setMultiline(multiline);
}
QString ActionEditor::connectionValue() const
{
if (!m_dialog)
return {};
return m_dialog->editorValue();
QString value = m_dialog->editorValue().trimmed();
//using parsed qml for unenclosed multistring (QDS-10681)
const QString testingString = QString("Item { \n"
" onWidthChanged: %1 \n"
"}")
.arg(value);
QmlJS::Document::MutablePtr firstAttemptDoc = QmlJS::Document::create({},
QmlJS::Dialect::QmlQtQuick2);
firstAttemptDoc->setSource(testingString);
firstAttemptDoc->parseQml();
if (!firstAttemptDoc->isParsedCorrectly()) {
const QString testingString2 = QString("Item { \n"
" onWidthChanged: { \n"
" %1 \n"
" } \n"
"} \n")
.arg(value);
QmlJS::Document::MutablePtr secondAttemptDoc = QmlJS::Document::create({},
QmlJS::Dialect::QmlQtQuick2);
secondAttemptDoc->setSource(testingString2);
secondAttemptDoc->parseQml();
if (secondAttemptDoc->isParsedCorrectly())
return QString("{\n%1\n}").arg(value);
}
return value;
}
void ActionEditor::setConnectionValue(const QString &text)
@@ -97,6 +139,14 @@ void ActionEditor::setConnectionValue(const QString &text)
m_dialog->setEditorValue(text);
}
QString ActionEditor::rawConnectionValue() const
{
if (!m_dialog)
return {};
return m_dialog->editorValue();
}
bool ActionEditor::hasModelIndex() const
{
return m_index.isValid();

View File

@@ -31,9 +31,14 @@ public:
Q_INVOKABLE void showWidget(int x, int y);
Q_INVOKABLE void hideWidget();
Q_INVOKABLE void showControls(bool show);
Q_INVOKABLE void setMultilne(bool multiline);
QString connectionValue() const;
void setConnectionValue(const QString &text);
QString rawConnectionValue() const;
bool hasModelIndex() const;
void resetModelIndex();
QModelIndex modelIndex() const;

View File

@@ -376,6 +376,47 @@ void ActionEditorDialog::updateComboBoxes([[maybe_unused]] int index, ComboBox t
}
}
void ActionEditorDialog::showControls(bool show)
{
if (m_comboBoxType)
m_comboBoxType->setVisible(show);
if (m_actionPlaceholder)
m_actionPlaceholder->setVisible(show);
if (m_assignmentPlaceholder)
m_assignmentPlaceholder->setVisible(show);
if (m_actionTargetItem)
m_actionTargetItem->setVisible(show);
if (m_actionMethod)
m_actionMethod->setVisible(show);
if (m_assignmentTargetItem)
m_assignmentTargetItem->setVisible(show);
if (m_assignmentTargetProperty)
m_assignmentTargetProperty->setVisible(show);
if (m_assignmentSourceItem)
m_assignmentSourceItem->setVisible(show);
if (m_assignmentSourceProperty)
m_assignmentSourceProperty->setVisible(show);
if (m_stackedLayout)
m_stackedLayout->setEnabled(show);
if (m_actionLayout)
m_actionLayout->setEnabled(show);
if (m_assignmentLayout)
m_assignmentLayout->setEnabled(show);
if (m_comboBoxLayout)
m_comboBoxLayout->setEnabled(show);
}
void ActionEditorDialog::setMultiline(bool multiline)
{
if (m_editorWidget)
m_editorWidget->m_isMultiline = multiline;
}
void ActionEditorDialog::setupUIComponents()
{
m_comboBoxType = new QComboBox(this);

View File

@@ -96,6 +96,9 @@ public:
void updateComboBoxes(int idx, ComboBox type);
void showControls(bool show);
void setMultiline(bool multiline);
private:
void setupUIComponents();

View File

@@ -6,6 +6,7 @@
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/coreplugintr.h>
#include <coreplugin/icore.h>
#include <plaintexteditmodifier.h>
#include <qmljseditor/qmljsautocompleter.h>
#include <qmljseditor/qmljscompletionassist.h>
#include <qmljseditor/qmljseditor.h>
@@ -18,6 +19,7 @@
#include <projectexplorer/projectexplorerconstants.h>
#include <utils/fancylineedit.h>
#include <utils/transientscroll.h>
#include <QAction>
@@ -33,6 +35,8 @@ BindingEditorWidget::BindingEditorWidget()
m_context->setContext(context);
Core::ICore::addContextObject(m_context);
Utils::TransientScrollAreaSupport::support(this);
/*
* We have to register our own active auto completion shortcut, because the original short cut will
* use the cursor position of the original editor in the editor manager.
@@ -46,7 +50,7 @@ BindingEditorWidget::BindingEditorWidget()
? tr("Meta+Space")
: tr("Ctrl+Space")));
connect(m_completionAction, &QAction::triggered, [this]() {
connect(m_completionAction, &QAction::triggered, this, [this]() {
invokeAssist(TextEditor::Completion);
});
}
@@ -68,8 +72,17 @@ void BindingEditorWidget::unregisterAutoCompletion()
bool BindingEditorWidget::event(QEvent *event)
{
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if ((keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) && !keyEvent->modifiers()) {
const QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
const bool returnPressed = (keyEvent->key() == Qt::Key_Return)
|| (keyEvent->key() == Qt::Key_Enter);
const Qt::KeyboardModifiers mods = keyEvent->modifiers();
constexpr Qt::KeyboardModifier submitModifier = Qt::ControlModifier;
const bool submitModed = mods.testFlag(submitModifier);
if (!m_isMultiline && (returnPressed && !mods)) {
emit returnKeyClicked();
return true;
} else if (m_isMultiline && (returnPressed && submitModed)) {
emit returnKeyClicked();
return true;
}
@@ -81,8 +94,22 @@ std::unique_ptr<TextEditor::AssistInterface> BindingEditorWidget::createAssistIn
[[maybe_unused]] TextEditor::AssistKind assistKind, TextEditor::AssistReason assistReason) const
{
return std::make_unique<QmlJSEditor::QmlJSCompletionAssistInterface>(
textCursor(), Utils::FilePath(),
assistReason, qmljsdocument->semanticInfo());
textCursor(), Utils::FilePath(), assistReason, qmljsdocument->semanticInfo());
}
void BindingEditorWidget::setEditorTextWithIndentation(const QString &text)
{
auto *doc = document();
doc->setPlainText(text);
//we don't need to indent an empty text
//but is also needed for safer text.length()-1 below
if (text.isEmpty())
return;
auto modifier = std::make_unique<IndentingTextEditModifier>(
doc, QTextCursor{doc});
modifier->indent(0, text.length()-1);
}
BindingDocument::BindingDocument()

View File

@@ -32,6 +32,8 @@ public:
std::unique_ptr<TextEditor::AssistInterface> createAssistInterface(
TextEditor::AssistKind assistKind, TextEditor::AssistReason assistReason) const override;
void setEditorTextWithIndentation(const QString &text);
signals:
void returnKeyClicked();
@@ -39,6 +41,7 @@ public:
QmlJSEditor::QmlJSEditorDocument *qmljsdocument = nullptr;
Core::IContext *m_context = nullptr;
QAction *m_completionAction = nullptr;
bool m_isMultiline = false;
};
class BindingDocument : public QmlJSEditor::QmlJSEditorDocument

View File

@@ -0,0 +1,14 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
namespace QmlDesigner::CollectionEditor {
inline constexpr char SOURCEFILE_PROPERTY[] = "sourceFile";
inline constexpr char COLLECTIONMODEL_IMPORT[] = "QtQuick.Studio.Models";
inline constexpr char JSONCOLLECTIONMODEL_TYPENAME[] = "QtQuick.Studio.Models.JsonSourceModel";
inline constexpr char CSVCOLLECTIONMODEL_TYPENAME[] = "QtQuick.Studio.Models.CsvSourceModel";
} // namespace QmlDesigner::CollectionEditor

View File

@@ -0,0 +1,147 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "collectionlistmodel.h"
#include "collectioneditorconstants.h"
#include "variantproperty.h"
#include <utils/algorithm.h>
#include <utils/qtcassert.h>
namespace {
template<typename ValueType>
bool containsItem(const std::initializer_list<ValueType> &container, const ValueType &value)
{
auto begin = std::cbegin(container);
auto end = std::cend(container);
auto it = std::find(begin, end, value);
return it != end;
}
} // namespace
namespace QmlDesigner {
CollectionListModel::CollectionListModel(const ModelNode &sourceModel)
: QStringListModel()
, m_sourceNode(sourceModel)
{
connect(this, &CollectionListModel::modelReset, this, &CollectionListModel::updateEmpty);
connect(this, &CollectionListModel::rowsRemoved, this, &CollectionListModel::updateEmpty);
connect(this, &CollectionListModel::rowsInserted, this, &CollectionListModel::updateEmpty);
}
QHash<int, QByteArray> CollectionListModel::roleNames() const
{
static QHash<int, QByteArray> roles;
if (roles.isEmpty()) {
roles.insert(Super::roleNames());
roles.insert({
{IdRole, "collectionId"},
{NameRole, "collectionName"},
{SelectedRole, "collectionIsSelected"},
});
}
return roles;
}
bool CollectionListModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (!index.isValid())
return false;
if (containsItem<int>({IdRole, Qt::EditRole, Qt::DisplayRole}, role)) {
return Super::setData(index, value);
} else if (role == SelectedRole) {
if (value.toBool() != index.data(SelectedRole).toBool()) {
setSelectedIndex(value.toBool() ? index.row() : -1);
return true;
}
}
return false;
}
QVariant CollectionListModel::data(const QModelIndex &index, int role) const
{
QTC_ASSERT(index.isValid(), return {});
switch (role) {
case IdRole:
return index.row();
case NameRole:
return Super::data(index);
case SelectedRole:
return index.row() == m_selectedIndex;
}
return Super::data(index, role);
}
int CollectionListModel::selectedIndex() const
{
return m_selectedIndex;
}
ModelNode CollectionListModel::sourceNode() const
{
return m_sourceNode;
}
QString CollectionListModel::sourceAddress() const
{
return m_sourceNode.variantProperty(CollectionEditor::SOURCEFILE_PROPERTY).value().toString();
}
void CollectionListModel::selectCollectionIndex(int idx, bool selectAtLeastOne)
{
int collectionCount = stringList().size();
int preferredIndex = -1;
if (collectionCount) {
if (selectAtLeastOne)
preferredIndex = std::max(0, std::min(idx, collectionCount - 1));
else if (idx > -1 && idx < collectionCount)
preferredIndex = idx;
}
setSelectedIndex(preferredIndex);
}
QString CollectionListModel::collectionNameAt(int idx) const
{
return index(idx).data(NameRole).toString();
}
void CollectionListModel::setSelectedIndex(int idx)
{
idx = (idx > -1 && idx < rowCount()) ? idx : -1;
if (m_selectedIndex != idx) {
QModelIndex previousIndex = index(m_selectedIndex);
QModelIndex newIndex = index(idx);
m_selectedIndex = idx;
if (previousIndex.isValid())
emit dataChanged(previousIndex, previousIndex, {SelectedRole});
if (newIndex.isValid())
emit dataChanged(newIndex, newIndex, {SelectedRole});
emit selectedIndexChanged(idx);
}
}
void CollectionListModel::updateEmpty()
{
bool isEmptyNow = stringList().isEmpty();
if (m_isEmpty != isEmptyNow) {
m_isEmpty = isEmptyNow;
emit isEmptyChanged(m_isEmpty);
if (m_isEmpty)
setSelectedIndex(-1);
}
}
} // namespace QmlDesigner

View File

@@ -0,0 +1,50 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <QHash>
#include <QStringListModel>
#include "modelnode.h"
namespace QmlDesigner {
class CollectionListModel : public QStringListModel
{
Q_OBJECT
Q_PROPERTY(int selectedIndex MEMBER m_selectedIndex NOTIFY selectedIndexChanged)
Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged)
public:
enum Roles { IdRole = Qt::UserRole + 1, NameRole, SourceRole, SelectedRole, CollectionsRole };
explicit CollectionListModel(const ModelNode &sourceModel);
virtual QHash<int, QByteArray> roleNames() const override;
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
Q_INVOKABLE int selectedIndex() const;
Q_INVOKABLE ModelNode sourceNode() const;
Q_INVOKABLE QString sourceAddress() const;
void selectCollectionIndex(int idx, bool selectAtLeastOne = false);
QString collectionNameAt(int idx) const;
signals:
void selectedIndexChanged(int idx);
void isEmptyChanged(bool);
private:
void setSelectedIndex(int idx);
void updateEmpty();
using Super = QStringListModel;
int m_selectedIndex = -1;
bool m_isEmpty = false;
const ModelNode m_sourceNode;
};
} // namespace QmlDesigner

View File

@@ -1,262 +0,0 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "collectionmodel.h"
#include "abstractview.h"
#include "variantproperty.h"
#include <utils/qtcassert.h>
namespace QmlDesigner {
CollectionModel::CollectionModel() {}
int CollectionModel::rowCount(const QModelIndex &) const
{
return m_collections.size();
}
QVariant CollectionModel::data(const QModelIndex &index, int role) const
{
QTC_ASSERT(index.isValid(), return {});
const ModelNode *collection = &m_collections.at(index.row());
switch (role) {
case IdRole:
return collection->id();
case NameRole:
return collection->variantProperty("objectName").value();
case SelectedRole:
return index.row() == m_selectedIndex;
}
return {};
}
bool CollectionModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (!index.isValid())
return false;
ModelNode collection = m_collections.at(index.row());
switch (role) {
case IdRole: {
if (collection.id() == value)
return false;
bool duplicatedId = Utils::anyOf(std::as_const(m_collections),
[&collection, &value](const ModelNode &otherCollection) {
return (otherCollection.id() == value
&& otherCollection != collection);
});
if (duplicatedId)
return false;
collection.setIdWithRefactoring(value.toString());
} break;
case Qt::DisplayRole:
case NameRole: {
auto collectionName = collection.variantProperty("objectName");
if (collectionName.value() == value)
return false;
collectionName.setValue(value.toString());
} break;
case SelectedRole: {
if (value.toBool() != index.data(SelectedRole).toBool())
setSelectedIndex(value.toBool() ? index.row() : -1);
else
return false;
} break;
default:
return false;
}
return true;
}
bool CollectionModel::removeRows(int row, int count, [[maybe_unused]] const QModelIndex &parent)
{
const int rowMax = std::min(row + count, rowCount());
if (row >= rowMax || row < 0)
return false;
AbstractView *view = m_collections.at(row).view();
if (!view)
return false;
count = rowMax - row;
bool selectionUpdateNeeded = m_selectedIndex >= row && m_selectedIndex < rowMax;
// It's better to remove the group of nodes here because of the performance issue for the list,
// and update issue for the view
beginRemoveRows({}, row, rowMax - 1);
view->executeInTransaction(Q_FUNC_INFO, [row, count, this]() {
for (ModelNode node : Utils::span<const ModelNode>(m_collections).subspan(row, count)) {
m_collectionsIndexHash.remove(node.internalId());
node.destroy();
}
});
m_collections.remove(row, count);
int idx = row;
for (const ModelNode &node : Utils::span<const ModelNode>(m_collections).subspan(row))
m_collectionsIndexHash.insert(node.internalId(), ++idx);
endRemoveRows();
if (selectionUpdateNeeded)
updateSelectedCollection();
updateEmpty();
return true;
}
QHash<int, QByteArray> CollectionModel::roleNames() const
{
static QHash<int, QByteArray> roles;
if (roles.isEmpty()) {
roles.insert(Super::roleNames());
roles.insert({
{IdRole, "collectionId"},
{NameRole, "collectionName"},
{SelectedRole, "collectionIsSelected"},
});
}
return roles;
}
void CollectionModel::setCollections(const ModelNodes &collections)
{
beginResetModel();
bool wasEmpty = isEmpty();
m_collections = collections;
m_collectionsIndexHash.clear();
int i = 0;
for (const ModelNode &collection : collections)
m_collectionsIndexHash.insert(collection.internalId(), i++);
if (wasEmpty != isEmpty())
emit isEmptyChanged(isEmpty());
endResetModel();
updateSelectedCollection(true);
}
void CollectionModel::removeCollection(const ModelNode &node)
{
int nodePlace = m_collectionsIndexHash.value(node.internalId(), -1);
if (nodePlace < 0)
return;
removeRow(nodePlace);
}
int CollectionModel::collectionIndex(const ModelNode &node) const
{
return m_collectionsIndexHash.value(node.internalId(), -1);
}
void CollectionModel::selectCollection(const ModelNode &node)
{
int nodePlace = m_collectionsIndexHash.value(node.internalId(), -1);
if (nodePlace < 0)
return;
selectCollectionIndex(nodePlace, true);
}
QmlDesigner::ModelNode CollectionModel::collectionNodeAt(int idx)
{
QModelIndex data = index(idx);
if (!data.isValid())
return {};
return m_collections.at(idx);
}
bool CollectionModel::isEmpty() const
{
return m_collections.isEmpty();
}
void CollectionModel::selectCollectionIndex(int idx, bool selectAtLeastOne)
{
int collectionCount = m_collections.size();
int prefferedIndex = -1;
if (collectionCount) {
if (selectAtLeastOne)
prefferedIndex = std::max(0, std::min(idx, collectionCount - 1));
else if (idx > -1 && idx < collectionCount)
prefferedIndex = idx;
}
setSelectedIndex(prefferedIndex);
}
void CollectionModel::deselect()
{
setSelectedIndex(-1);
}
void CollectionModel::updateSelectedCollection(bool selectAtLeastOne)
{
int idx = m_selectedIndex;
m_selectedIndex = -1;
selectCollectionIndex(idx, selectAtLeastOne);
}
void CollectionModel::updateNodeName(const ModelNode &node)
{
QModelIndex index = indexOfNode(node);
emit dataChanged(index, index, {NameRole, Qt::DisplayRole});
}
void CollectionModel::updateNodeId(const ModelNode &node)
{
QModelIndex index = indexOfNode(node);
emit dataChanged(index, index, {IdRole});
}
void CollectionModel::setSelectedIndex(int idx)
{
idx = (idx > -1 && idx < m_collections.count()) ? idx : -1;
if (m_selectedIndex != idx) {
QModelIndex previousIndex = index(m_selectedIndex);
QModelIndex newIndex = index(idx);
m_selectedIndex = idx;
if (previousIndex.isValid())
emit dataChanged(previousIndex, previousIndex, {SelectedRole});
if (newIndex.isValid())
emit dataChanged(newIndex, newIndex, {SelectedRole});
emit selectedIndexChanged(idx);
}
}
void CollectionModel::updateEmpty()
{
bool isEmptyNow = isEmpty();
if (m_isEmpty != isEmptyNow) {
m_isEmpty = isEmptyNow;
emit isEmptyChanged(m_isEmpty);
if (m_isEmpty)
setSelectedIndex(-1);
}
}
QModelIndex CollectionModel::indexOfNode(const ModelNode &node) const
{
return index(m_collectionsIndexHash.value(node.internalId(), -1));
}
} // namespace QmlDesigner

View File

@@ -0,0 +1,401 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "collectionsourcemodel.h"
#include "abstractview.h"
#include "collectioneditorconstants.h"
#include "collectionlistmodel.h"
#include "variantproperty.h"
#include <utils/qtcassert.h>
#include <QFile>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonParseError>
namespace {
QSharedPointer<QmlDesigner::CollectionListModel> loadCollection(
const QmlDesigner::ModelNode &sourceNode,
QSharedPointer<QmlDesigner::CollectionListModel> initialCollection = {})
{
using namespace QmlDesigner::CollectionEditor;
QString sourceFileAddress = sourceNode.variantProperty(SOURCEFILE_PROPERTY).value().toString();
QSharedPointer<QmlDesigner::CollectionListModel> collectionsList;
auto setupCollectionList = [&sourceNode, &initialCollection, &collectionsList]() {
if (initialCollection.isNull())
collectionsList.reset(new QmlDesigner::CollectionListModel(sourceNode));
else if (initialCollection->sourceNode() == sourceNode)
collectionsList = initialCollection;
else
collectionsList.reset(new QmlDesigner::CollectionListModel(sourceNode));
};
if (sourceNode.type() == JSONCOLLECTIONMODEL_TYPENAME) {
QFile sourceFile(sourceFileAddress);
if (!sourceFile.open(QFile::ReadOnly))
return {};
QJsonParseError parseError;
QJsonDocument document = QJsonDocument::fromJson(sourceFile.readAll(), &parseError);
if (parseError.error != QJsonParseError::NoError)
return {};
setupCollectionList();
if (document.isObject()) {
const QJsonObject sourceObject = document.object();
collectionsList->setStringList(sourceObject.toVariantMap().keys());
}
} else if (sourceNode.type() == CSVCOLLECTIONMODEL_TYPENAME) {
QmlDesigner::VariantProperty collectionNameProperty = sourceNode.variantProperty(
"objectName");
setupCollectionList();
collectionsList->setStringList({collectionNameProperty.value().toString()});
}
return collectionsList;
}
} // namespace
namespace QmlDesigner {
CollectionSourceModel::CollectionSourceModel() {}
int CollectionSourceModel::rowCount(const QModelIndex &) const
{
return m_collectionSources.size();
}
QVariant CollectionSourceModel::data(const QModelIndex &index, int role) const
{
QTC_ASSERT(index.isValid(), return {});
const ModelNode *collectionSource = &m_collectionSources.at(index.row());
switch (role) {
case IdRole:
return collectionSource->id();
case NameRole:
return collectionSource->variantProperty("objectName").value();
case SourceRole:
return collectionSource->variantProperty(CollectionEditor::SOURCEFILE_PROPERTY).value();
case SelectedRole:
return index.row() == m_selectedIndex;
case CollectionsRole:
return QVariant::fromValue(m_collectionList.at(index.row()).data());
}
return {};
}
bool CollectionSourceModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (!index.isValid())
return false;
ModelNode collectionSource = m_collectionSources.at(index.row());
switch (role) {
case IdRole: {
if (collectionSource.id() == value)
return false;
bool duplicatedId = Utils::anyOf(std::as_const(m_collectionSources),
[&collectionSource, &value](const ModelNode &otherCollection) {
return (otherCollection.id() == value
&& otherCollection != collectionSource);
});
if (duplicatedId)
return false;
collectionSource.setIdWithRefactoring(value.toString());
} break;
case Qt::DisplayRole:
case NameRole: {
auto collectionName = collectionSource.variantProperty("objectName");
if (collectionName.value() == value)
return false;
collectionName.setValue(value.toString());
} break;
case SourceRole: {
auto sourceAddress = collectionSource.variantProperty(CollectionEditor::SOURCEFILE_PROPERTY);
if (sourceAddress.value() == value)
return false;
sourceAddress.setValue(value.toString());
} break;
case SelectedRole: {
if (value.toBool() != index.data(SelectedRole).toBool())
setSelectedIndex(value.toBool() ? index.row() : -1);
else
return false;
} break;
default:
return false;
}
return true;
}
bool CollectionSourceModel::removeRows(int row, int count, [[maybe_unused]] const QModelIndex &parent)
{
const int rowMax = std::min(row + count, rowCount());
if (row >= rowMax || row < 0)
return false;
AbstractView *view = m_collectionSources.at(row).view();
if (!view)
return false;
count = rowMax - row;
bool selectionUpdateNeeded = m_selectedIndex >= row && m_selectedIndex < rowMax;
// It's better to remove the group of nodes here because of the performance issue for the list,
// and update issue for the view
beginRemoveRows({}, row, rowMax - 1);
view->executeInTransaction(Q_FUNC_INFO, [row, count, this]() {
for (ModelNode node : Utils::span<const ModelNode>(m_collectionSources).subspan(row, count)) {
m_sourceIndexHash.remove(node.internalId());
node.destroy();
}
m_collectionSources.remove(row, count);
m_collectionList.remove(row, count);
});
int idx = row;
for (const ModelNode &node : Utils::span<const ModelNode>(m_collectionSources).subspan(row))
m_sourceIndexHash.insert(node.internalId(), ++idx);
endRemoveRows();
if (selectionUpdateNeeded)
updateSelectedSource();
updateEmpty();
return true;
}
QHash<int, QByteArray> CollectionSourceModel::roleNames() const
{
static QHash<int, QByteArray> roles;
if (roles.isEmpty()) {
roles.insert(Super::roleNames());
roles.insert({{IdRole, "sourceId"},
{NameRole, "sourceName"},
{SelectedRole, "sourceIsSelected"},
{SourceRole, "sourceAddress"},
{CollectionsRole, "collections"}});
}
return roles;
}
void CollectionSourceModel::setSources(const ModelNodes &sources)
{
beginResetModel();
m_collectionSources = sources;
m_sourceIndexHash.clear();
m_collectionList.clear();
int i = -1;
for (const ModelNode &collectionSource : sources) {
m_sourceIndexHash.insert(collectionSource.internalId(), ++i);
auto loadedCollection = loadCollection(collectionSource);
m_collectionList.append(loadedCollection);
connect(loadedCollection.data(),
&CollectionListModel::selectedIndexChanged,
this,
&CollectionSourceModel::onSelectedCollectionChanged,
Qt::UniqueConnection);
}
updateEmpty();
endResetModel();
updateSelectedSource(true);
}
void CollectionSourceModel::removeSource(const ModelNode &node)
{
int nodePlace = m_sourceIndexHash.value(node.internalId(), -1);
if (nodePlace < 0)
return;
removeRow(nodePlace);
}
int CollectionSourceModel::sourceIndex(const ModelNode &node) const
{
return m_sourceIndexHash.value(node.internalId(), -1);
}
void CollectionSourceModel::addSource(const ModelNode &node)
{
int newRowId = m_collectionSources.count();
beginInsertRows({}, newRowId, newRowId);
m_collectionSources.append(node);
m_sourceIndexHash.insert(node.internalId(), newRowId);
auto loadedCollection = loadCollection(node);
m_collectionList.append(loadedCollection);
connect(loadedCollection.data(),
&CollectionListModel::selectedIndexChanged,
this,
&CollectionSourceModel::onSelectedCollectionChanged,
Qt::UniqueConnection);
updateEmpty();
endInsertRows();
updateSelectedSource(true);
}
void CollectionSourceModel::selectSource(const ModelNode &node)
{
int nodePlace = m_sourceIndexHash.value(node.internalId(), -1);
if (nodePlace < 0)
return;
selectSourceIndex(nodePlace, true);
}
QmlDesigner::ModelNode CollectionSourceModel::sourceNodeAt(int idx)
{
QModelIndex data = index(idx);
if (!data.isValid())
return {};
return m_collectionSources.at(idx);
}
CollectionListModel *CollectionSourceModel::selectedCollectionList()
{
QModelIndex idx = index(m_selectedIndex);
if (!idx.isValid())
return {};
return idx.data(CollectionsRole).value<CollectionListModel *>();
}
void CollectionSourceModel::selectSourceIndex(int idx, bool selectAtLeastOne)
{
int collectionCount = m_collectionSources.size();
int preferredIndex = -1;
if (collectionCount) {
if (selectAtLeastOne)
preferredIndex = std::max(0, std::min(idx, collectionCount - 1));
else if (idx > -1 && idx < collectionCount)
preferredIndex = idx;
}
setSelectedIndex(preferredIndex);
}
void CollectionSourceModel::deselect()
{
setSelectedIndex(-1);
}
void CollectionSourceModel::updateSelectedSource(bool selectAtLeastOne)
{
int idx = m_selectedIndex;
m_selectedIndex = -1;
selectSourceIndex(idx, selectAtLeastOne);
}
void CollectionSourceModel::updateNodeName(const ModelNode &node)
{
QModelIndex index = indexOfNode(node);
emit dataChanged(index, index, {NameRole, Qt::DisplayRole});
updateCollectionList(index);
}
void CollectionSourceModel::updateNodeSource(const ModelNode &node)
{
QModelIndex index = indexOfNode(node);
emit dataChanged(index, index, {SourceRole});
updateCollectionList(index);
}
void CollectionSourceModel::updateNodeId(const ModelNode &node)
{
QModelIndex index = indexOfNode(node);
emit dataChanged(index, index, {IdRole});
}
QString CollectionSourceModel::selectedSourceAddress() const
{
return index(m_selectedIndex).data(SourceRole).toString();
}
void CollectionSourceModel::onSelectedCollectionChanged(int collectionIndex)
{
CollectionListModel *collectionList = qobject_cast<CollectionListModel *>(sender());
if (collectionIndex > -1 && collectionList) {
if (_previousSelectedList && _previousSelectedList != collectionList)
_previousSelectedList->selectCollectionIndex(-1);
emit collectionSelected(collectionList->sourceNode(),
collectionList->collectionNameAt(collectionIndex));
_previousSelectedList = collectionList;
}
}
void CollectionSourceModel::setSelectedIndex(int idx)
{
idx = (idx > -1 && idx < m_collectionSources.count()) ? idx : -1;
if (m_selectedIndex != idx) {
QModelIndex previousIndex = index(m_selectedIndex);
QModelIndex newIndex = index(idx);
m_selectedIndex = idx;
if (previousIndex.isValid())
emit dataChanged(previousIndex, previousIndex, {SelectedRole});
if (newIndex.isValid())
emit dataChanged(newIndex, newIndex, {SelectedRole});
emit selectedIndexChanged(idx);
}
}
void CollectionSourceModel::updateEmpty()
{
bool isEmptyNow = m_collectionSources.isEmpty();
if (m_isEmpty != isEmptyNow) {
m_isEmpty = isEmptyNow;
emit isEmptyChanged(m_isEmpty);
if (m_isEmpty)
setSelectedIndex(-1);
}
}
void CollectionSourceModel::updateCollectionList(QModelIndex index)
{
if (!index.isValid())
return;
ModelNode sourceNode = sourceNodeAt(index.row());
QSharedPointer<CollectionListModel> currentList = m_collectionList.at(index.row());
QSharedPointer<CollectionListModel> newList = loadCollection(sourceNode, currentList);
if (currentList != newList) {
m_collectionList.replace(index.row(), newList);
emit this->dataChanged(index, index, {CollectionsRole});
}
}
QModelIndex CollectionSourceModel::indexOfNode(const ModelNode &node) const
{
return index(m_sourceIndexHash.value(node.internalId(), -1));
}
} // namespace QmlDesigner

View File

@@ -1,5 +1,6 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#pragma once
#include "modelnode.h"
@@ -7,22 +8,19 @@
#include <QAbstractListModel>
#include <QHash>
QT_BEGIN_NAMESPACE
class QJsonArray;
QT_END_NAMESPACE
namespace QmlDesigner {
class CollectionModel : public QAbstractListModel
class CollectionListModel;
class CollectionSourceModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(int selectedIndex MEMBER m_selectedIndex NOTIFY selectedIndexChanged)
Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged)
public:
enum Roles { IdRole = Qt::UserRole + 1, NameRole, SelectedRole };
enum Roles { IdRole = Qt::UserRole + 1, NameRole, SourceRole, SelectedRole, CollectionsRole };
explicit CollectionModel();
explicit CollectionSourceModel();
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override;
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
@@ -36,35 +34,44 @@ public:
virtual QHash<int, QByteArray> roleNames() const override;
void setCollections(const ModelNodes &collections);
void removeCollection(const ModelNode &node);
int collectionIndex(const ModelNode &node) const;
void selectCollection(const ModelNode &node);
void setSources(const ModelNodes &sources);
void removeSource(const ModelNode &node);
int sourceIndex(const ModelNode &node) const;
void addSource(const ModelNode &node);
void selectSource(const ModelNode &node);
ModelNode collectionNodeAt(int idx);
ModelNode sourceNodeAt(int idx);
CollectionListModel *selectedCollectionList();
Q_INVOKABLE bool isEmpty() const;
Q_INVOKABLE void selectCollectionIndex(int idx, bool selectAtLeastOne = false);
Q_INVOKABLE void selectSourceIndex(int idx, bool selectAtLeastOne = false);
Q_INVOKABLE void deselect();
Q_INVOKABLE void updateSelectedCollection(bool selectAtLeastOne = false);
Q_INVOKABLE void updateSelectedSource(bool selectAtLeastOne = false);
void updateNodeName(const ModelNode &node);
void updateNodeSource(const ModelNode &node);
void updateNodeId(const ModelNode &node);
QString selectedSourceAddress() const;
signals:
void selectedIndexChanged(int idx);
void renameCollectionTriggered(const QmlDesigner::ModelNode &collection, const QString &newName);
void addNewCollectionTriggered();
void collectionSelected(const ModelNode &sourceNode, const QString &collectionName);
void isEmptyChanged(bool);
private slots:
void onSelectedCollectionChanged(int collectionIndex);
private:
void setSelectedIndex(int idx);
void updateEmpty();
void updateCollectionList(QModelIndex index);
using Super = QAbstractListModel;
QModelIndex indexOfNode(const ModelNode &node) const;
ModelNodes m_collections;
QHash<qint32, int> m_collectionsIndexHash; // internalId -> index
ModelNodes m_collectionSources;
QHash<qint32, int> m_sourceIndexHash; // internalId -> index
QList<QSharedPointer<CollectionListModel>> m_collectionList;
QPointer<CollectionListModel> _previousSelectedList;
int m_selectedIndex = -1;
bool m_isEmpty = true;
};

View File

@@ -2,12 +2,13 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "collectionview.h"
#include "collectionmodel.h"
#include "collectioneditorconstants.h"
#include "collectionsourcemodel.h"
#include "collectionwidget.h"
#include "designmodecontext.h"
#include "nodelistproperty.h"
#include "nodeabstractproperty.h"
#include "nodemetainfo.h"
#include "qmldesignerconstants.h"
#include "qmldesignerplugin.h"
#include "singlecollectionmodel.h"
#include "variantproperty.h"
@@ -21,326 +22,21 @@
#include <utils/qtcassert.h>
namespace {
using Data = std::variant<bool, double, QString, QDateTime>;
using DataRecord = QMap<QString, Data>;
struct DataHeader
inline bool isStudioCollectionModel(const QmlDesigner::ModelNode &node)
{
enum class Type { Unknown, Bool, Numeric, String, DateTime };
Type type;
QString name;
};
using DataHeaderMap = QMap<QString, DataHeader>; // Lowercase Name - Header Data
inline constexpr QStringView BoolDataType{u"Bool"};
inline constexpr QStringView NumberDataType{u"Number"};
inline constexpr QStringView StringDataType{u"String"};
inline constexpr QStringView DateTimeDataType{u"Date/Time"};
QString removeSpaces(QString string)
{
string.replace(" ", "_");
string.replace("-", "_");
return string;
}
DataHeader getDataType(const QString &type, const QString &name)
{
static const QMap<QString, DataHeader::Type> typeMap = {
{BoolDataType.toString().toLower(), DataHeader::Type::Bool},
{NumberDataType.toString().toLower(), DataHeader::Type::Numeric},
{StringDataType.toString().toLower(), DataHeader::Type::String},
{DateTimeDataType.toString().toLower(), DataHeader::Type::DateTime}};
if (name.isEmpty())
return {};
if (type.isEmpty())
return {DataHeader::Type::String, removeSpaces(name)};
return {typeMap.value(type.toLower(), DataHeader::Type::Unknown), removeSpaces(name)};
}
struct JsonDocumentError : public std::exception
{
enum Error {
InvalidDocumentType,
InvalidCollectionName,
InvalidCollectionId,
InvalidCollectionObject,
InvalidArrayPosition,
InvalidLiteralType,
InvalidCollectionHeader,
IsNotJsonArray,
CollectionHeaderNotFound
};
const Error error;
JsonDocumentError(Error error)
: error(error)
{}
const char *what() const noexcept override
{
switch (error) {
case InvalidDocumentType:
return "Current JSON document contains errors.";
case InvalidCollectionName:
return "Invalid collection name.";
case InvalidCollectionId:
return "Invalid collection Id.";
case InvalidCollectionObject:
return "A collection should be a json object.";
case InvalidArrayPosition:
return "Arrays are not supported inside the collection.";
case InvalidLiteralType:
return "Invalid literal type for collection items";
case InvalidCollectionHeader:
return "Invalid Collection Header";
case IsNotJsonArray:
return "Json file should be an array";
case CollectionHeaderNotFound:
return "Collection Header not found";
default:
return "Unknown Json Error";
}
}
};
struct CsvDocumentError : public std::exception
{
enum Error {
HeaderNotFound,
DataNotFound,
};
const Error error;
CsvDocumentError(Error error)
: error(error)
{}
const char *what() const noexcept override
{
switch (error) {
case HeaderNotFound:
return "CSV Header not found";
case DataNotFound:
return "CSV data not found";
default:
return "Unknown CSV Error";
}
}
};
Data getLiteralDataValue(const QVariant &value, const DataHeader &header, bool *typeWarningCheck = nullptr)
{
if (header.type == DataHeader::Type::Bool)
return value.toBool();
if (header.type == DataHeader::Type::Numeric)
return value.toDouble();
if (header.type == DataHeader::Type::String)
return value.toString();
if (header.type == DataHeader::Type::DateTime) {
QDateTime dateTimeStr = QDateTime::fromString(value.toString());
if (dateTimeStr.isValid())
return dateTimeStr;
}
if (typeWarningCheck)
*typeWarningCheck = true;
return value.toString();
}
void loadJsonHeaders(QList<DataHeader> &collectionHeaders,
DataHeaderMap &headerDataMap,
const QJsonObject &collectionJsonObject)
{
const QJsonArray collectionHeader = collectionJsonObject.value("headers").toArray();
for (const QJsonValue &headerValue : collectionHeader) {
const QJsonObject headerJsonObject = headerValue.toObject();
DataHeader dataHeader = getDataType(headerJsonObject.value("type").toString(),
headerJsonObject.value("name").toString());
if (dataHeader.type == DataHeader::Type::Unknown)
throw JsonDocumentError{JsonDocumentError::InvalidCollectionHeader};
collectionHeaders.append(dataHeader);
headerDataMap.insert(dataHeader.name.toLower(), dataHeader);
}
if (collectionHeaders.isEmpty())
throw JsonDocumentError{JsonDocumentError::CollectionHeaderNotFound};
}
void loadJsonRecords(QList<DataRecord> &collectionItems,
DataHeaderMap &headerDataMap,
const QJsonObject &collectionJsonObject)
{
auto addItemFromValue = [&headerDataMap, &collectionItems](const QJsonValue &jsonValue) {
const QVariantMap dataMap = jsonValue.toObject().toVariantMap();
DataRecord recordData;
for (const auto &dataPair : dataMap.asKeyValueRange()) {
const DataHeader correspondingHeader = headerDataMap.value(removeSpaces(
dataPair.first.toLower()),
{});
const QString &fieldName = correspondingHeader.name;
if (fieldName.size())
recordData.insert(fieldName,
getLiteralDataValue(dataPair.second, correspondingHeader));
}
if (!recordData.isEmpty())
collectionItems.append(recordData);
};
const QJsonValue jsonDataValue = collectionJsonObject.value("data");
if (jsonDataValue.isObject()) {
addItemFromValue(jsonDataValue);
} else if (jsonDataValue.isArray()) {
const QJsonArray jsonDataArray = jsonDataValue.toArray();
for (const QJsonValue &jsonItem : jsonDataArray) {
if (jsonItem.isObject())
addItemFromValue(jsonItem);
}
}
}
inline bool isCollectionLib(const QmlDesigner::ModelNode &node)
{
return node.parentProperty().parentModelNode().isRootNode()
&& node.id() == QmlDesigner::Constants::COLLECTION_LIB_ID;
}
inline bool isListModel(const QmlDesigner::ModelNode &node)
{
return node.metaInfo().isQtQuickListModel();
}
inline bool isListElement(const QmlDesigner::ModelNode &node)
{
return node.metaInfo().isQtQuickListElement();
}
inline bool isCollection(const QmlDesigner::ModelNode &node)
{
return isCollectionLib(node.parentProperty().parentModelNode()) && isListModel(node);
}
inline bool isCollectionElement(const QmlDesigner::ModelNode &node)
{
return isListElement(node) && isCollection(node.parentProperty().parentModelNode());
using namespace QmlDesigner::CollectionEditor;
return node.metaInfo().typeName() == JSONCOLLECTIONMODEL_TYPENAME
|| node.metaInfo().typeName() == CSVCOLLECTIONMODEL_TYPENAME;
}
} // namespace
namespace QmlDesigner {
struct Collection
{
QString name;
QString id;
QList<DataHeader> headers;
QList<DataRecord> items;
};
CollectionView::CollectionView(ExternalDependenciesInterface &externalDependencies)
: AbstractView(externalDependencies)
{}
bool CollectionView::loadJson(const QByteArray &data)
{
try {
QJsonParseError parseError;
QJsonDocument document = QJsonDocument::fromJson(data, &parseError);
if (parseError.error != QJsonParseError::NoError)
throw JsonDocumentError{JsonDocumentError::InvalidDocumentType};
QList<Collection> collections;
if (document.isArray()) {
const QJsonArray collectionsJsonArray = document.array();
for (const QJsonValue &collectionJson : collectionsJsonArray) {
Collection collection;
if (!collectionJson.isObject())
throw JsonDocumentError{JsonDocumentError::InvalidCollectionObject};
QJsonObject collectionJsonObject = collectionJson.toObject();
const QString &collectionName = collectionJsonObject.value(u"name").toString();
if (!collectionName.size())
throw JsonDocumentError{JsonDocumentError::InvalidCollectionName};
const QString &collectionId = collectionJsonObject.value(u"id").toString();
if (!collectionId.size())
throw JsonDocumentError{JsonDocumentError::InvalidCollectionId};
DataHeaderMap headerDataMap;
loadJsonHeaders(collection.headers, headerDataMap, collectionJsonObject);
loadJsonRecords(collection.items, headerDataMap, collectionJsonObject);
if (collection.items.count())
collections.append(collection);
}
} else {
throw JsonDocumentError{JsonDocumentError::InvalidDocumentType};
}
addLoadedModel(collections);
} catch (const std::exception &error) {
m_widget->warn("Json Import Problem", QString::fromLatin1(error.what()));
return false;
}
return true;
}
bool CollectionView::loadCsv(const QString &collectionName, const QByteArray &data)
{
QTextStream stream(data);
Collection collection;
collection.name = collectionName;
try {
if (!stream.atEnd()) {
const QStringList recordData = stream.readLine().split(',');
for (const QString &name : recordData)
collection.headers.append(getDataType({}, name));
}
if (collection.headers.isEmpty())
throw CsvDocumentError{CsvDocumentError::HeaderNotFound};
while (!stream.atEnd()) {
const QStringList recordDataList = stream.readLine().split(',');
DataRecord recordData;
int column = -1;
for (const QString &cellData : recordDataList) {
if (++column == collection.headers.size())
break;
recordData.insert(collection.headers.at(column).name, cellData);
}
if (recordData.count())
collection.items.append(recordData);
}
if (collection.items.isEmpty())
throw CsvDocumentError{CsvDocumentError::DataNotFound};
addLoadedModel({collection});
} catch (const std::exception &error) {
m_widget->warn("Json Import Problem", QString::fromLatin1(error.what()));
return false;
}
return true;
}
bool CollectionView::hasWidget() const
{
return true;
@@ -353,12 +49,14 @@ QmlDesigner::WidgetInfo CollectionView::widgetInfo()
auto collectionEditorContext = new Internal::CollectionEditorContext(m_widget.data());
Core::ICore::addContextObject(collectionEditorContext);
CollectionModel *collectionModel = m_widget->collectionModel().data();
CollectionSourceModel *sourceModel = m_widget->sourceModel().data();
connect(collectionModel, &CollectionModel::selectedIndexChanged, this, [&](int selectedIndex) {
m_widget->singleCollectionModel()->setCollection(
m_widget->collectionModel()->collectionNodeAt(selectedIndex));
});
connect(sourceModel,
&CollectionSourceModel::collectionSelected,
this,
[this](const ModelNode &sourceNode, const QString &collection) {
m_widget->singleCollectionModel()->loadCollection(sourceNode, collection);
});
}
return createWidgetInfo(m_widget.data(),
@@ -376,47 +74,31 @@ void CollectionView::modelAttached(Model *model)
}
void CollectionView::nodeReparented(const ModelNode &node,
const NodeAbstractProperty &newPropertyParent,
const NodeAbstractProperty &oldPropertyParent,
[[maybe_unused]] const NodeAbstractProperty &newPropertyParent,
[[maybe_unused]] const NodeAbstractProperty &oldPropertyParent,
[[maybe_unused]] PropertyChangeFlags propertyChange)
{
if (!isListModel(node))
return;
ModelNode newParentNode = newPropertyParent.parentModelNode();
ModelNode oldParentNode = oldPropertyParent.parentModelNode();
bool added = isCollectionLib(newParentNode);
bool removed = isCollectionLib(oldParentNode);
if (!added && !removed)
if (!isStudioCollectionModel(node))
return;
refreshModel();
if (isCollection(node))
m_widget->collectionModel()->selectCollection(node);
m_widget->sourceModel()->selectSource(node);
}
void CollectionView::nodeAboutToBeRemoved(const ModelNode &removedNode)
{
// removing the collections lib node
if (isCollectionLib(removedNode)) {
m_widget->collectionModel()->setCollections({});
return;
}
if (isCollection(removedNode))
m_widget->collectionModel()->removeCollection(removedNode);
if (isStudioCollectionModel(removedNode))
m_widget->sourceModel()->removeSource(removedNode);
}
void CollectionView::nodeRemoved([[maybe_unused]] const ModelNode &removedNode,
const NodeAbstractProperty &parentProperty,
void CollectionView::nodeRemoved(const ModelNode &removedNode,
[[maybe_unused]] const NodeAbstractProperty &parentProperty,
[[maybe_unused]] PropertyChangeFlags propertyChange)
{
if (parentProperty.parentModelNode().id() != Constants::COLLECTION_LIB_ID)
return;
m_widget->collectionModel()->updateSelectedCollection(true);
if (isStudioCollectionModel(removedNode))
m_widget->sourceModel()->updateSelectedSource(true);
}
void CollectionView::variantPropertiesChanged(const QList<VariantProperty> &propertyList,
@@ -424,11 +106,13 @@ void CollectionView::variantPropertiesChanged(const QList<VariantProperty> &prop
{
for (const VariantProperty &property : propertyList) {
ModelNode node(property.parentModelNode());
if (isCollection(node)) {
if (isStudioCollectionModel(node)) {
if (property.name() == "objectName")
m_widget->collectionModel()->updateNodeName(node);
m_widget->sourceModel()->updateNodeName(node);
else if (property.name() == CollectionEditor::SOURCEFILE_PROPERTY)
m_widget->sourceModel()->updateNodeSource(node);
else if (property.name() == "id")
m_widget->collectionModel()->updateNodeId(node);
m_widget->sourceModel()->updateNodeId(node);
}
}
}
@@ -436,50 +120,36 @@ void CollectionView::variantPropertiesChanged(const QList<VariantProperty> &prop
void CollectionView::selectedNodesChanged(const QList<ModelNode> &selectedNodeList,
[[maybe_unused]] const QList<ModelNode> &lastSelectedNodeList)
{
QList<ModelNode> selectedCollections = Utils::filtered(selectedNodeList, &isCollection);
QList<ModelNode> selectedJsonCollections = Utils::filtered(selectedNodeList,
&isStudioCollectionModel);
// More than one collections are selected. So ignore them
if (selectedCollections.size() > 1)
if (selectedJsonCollections.size() > 1)
return;
if (selectedCollections.size() == 1) { // If exactly one collection is selected
m_widget->collectionModel()->selectCollection(selectedCollections.first());
if (selectedJsonCollections.size() == 1) { // If exactly one collection is selected
m_widget->sourceModel()->selectSource(selectedJsonCollections.first());
return;
}
// If no collection is selected, check the elements
QList<ModelNode> selectedElements = Utils::filtered(selectedNodeList, &isCollectionElement);
if (selectedElements.size()) {
const ModelNode parentElement = selectedElements.first().parentProperty().parentModelNode();
bool haveSameParent = Utils::allOf(selectedElements, [&parentElement](const ModelNode &element) {
return element.parentProperty().parentModelNode() == parentElement;
});
if (haveSameParent)
m_widget->collectionModel()->selectCollection(parentElement);
}
}
void CollectionView::addNewCollection(const QString &name)
void CollectionView::addResource(const QUrl &url, const QString &name, const QString &type)
{
executeInTransaction(__FUNCTION__, [&] {
ensureCollectionLibraryNode();
ModelNode collectionLib = collectionLibraryNode();
if (!collectionLib.isValid())
return;
NodeMetaInfo listModelMetaInfo = model()->qtQmlModelsListModelMetaInfo();
ModelNode collectionNode = createModelNode(listModelMetaInfo.typeName(),
listModelMetaInfo.majorVersion(),
listModelMetaInfo.minorVersion());
QString collectionName = name.isEmpty() ? "Collection" : name;
renameCollection(collectionNode, collectionName);
QmlDesignerPlugin::emitUsageStatistics(Constants::EVENT_PROPERTY_ADDED);
auto headersProperty = collectionNode.variantProperty("headers");
headersProperty.setDynamicTypeNameAndValue("string", {});
collectionLib.defaultNodeListProperty().reparentHere(collectionNode);
executeInTransaction(Q_FUNC_INFO, [this, &url, &name, &type]() {
ensureStudioModelImport();
QString sourceAddress = url.isLocalFile() ? url.toLocalFile() : url.toString();
const NodeMetaInfo resourceMetaInfo = type.compare("json", Qt::CaseInsensitive) == 0
? jsonCollectionMetaInfo()
: csvCollectionMetaInfo();
ModelNode resourceNode = createModelNode(resourceMetaInfo.typeName(),
resourceMetaInfo.majorVersion(),
resourceMetaInfo.minorVersion());
VariantProperty sourceProperty = resourceNode.variantProperty(
CollectionEditor::SOURCEFILE_PROPERTY);
VariantProperty nameProperty = resourceNode.variantProperty("objectName");
sourceProperty.setValue(sourceAddress);
nameProperty.setValue(name);
rootModelNode().defaultNodeAbstractProperty().reparentHere(resourceNode);
});
}
@@ -488,118 +158,32 @@ void CollectionView::refreshModel()
if (!model())
return;
ModelNode collectionLib = modelNodeForId(Constants::COLLECTION_LIB_ID);
ModelNodes collections;
if (collectionLib.isValid()) {
const QList<ModelNode> collectionLibNodes = collectionLib.directSubModelNodes();
for (const ModelNode &node : collectionLibNodes) {
if (isCollection(node))
collections.append(node);
}
}
m_widget->collectionModel()->setCollections(collections);
// Load Json Collections
const ModelNodes jsonSourceNodes = rootModelNode().subModelNodesOfType(jsonCollectionMetaInfo());
m_widget->sourceModel()->setSources(jsonSourceNodes);
}
ModelNode CollectionView::getNewCollectionNode(const Collection &collection)
NodeMetaInfo CollectionView::jsonCollectionMetaInfo() const
{
QTC_ASSERT(model(), return {});
ModelNode collectionNode;
executeInTransaction(__FUNCTION__, [&] {
NodeMetaInfo listModelMetaInfo = model()->qtQmlModelsListModelMetaInfo();
collectionNode = createModelNode(listModelMetaInfo.typeName(),
listModelMetaInfo.majorVersion(),
listModelMetaInfo.minorVersion());
QString collectionName = collection.name.isEmpty() ? "Collection" : collection.name;
renameCollection(collectionNode, collectionName);
QStringList headers;
for (const DataHeader &header : collection.headers)
headers.append(header.name);
QmlDesignerPlugin::emitUsageStatistics(Constants::EVENT_PROPERTY_ADDED);
auto headersProperty = collectionNode.variantProperty("headers");
headersProperty.setDynamicTypeNameAndValue("string", headers.join(","));
NodeMetaInfo listElementMetaInfo = model()->qtQmlModelsListElementMetaInfo();
for (const DataRecord &item : collection.items) {
ModelNode elementNode = createModelNode(listElementMetaInfo.typeName(),
listElementMetaInfo.majorVersion(),
listElementMetaInfo.minorVersion());
for (const auto &headerMapElement : item.asKeyValueRange()) {
auto property = elementNode.variantProperty(headerMapElement.first.toLatin1());
QVariant value = std::visit([](const auto &data)
-> QVariant { return QVariant::fromValue(data); },
headerMapElement.second);
property.setValue(value);
}
collectionNode.defaultNodeListProperty().reparentHere(elementNode);
}
});
return collectionNode;
return model()->metaInfo(CollectionEditor::JSONCOLLECTIONMODEL_TYPENAME);
}
void CollectionView::addLoadedModel(const QList<Collection> &newCollection)
NodeMetaInfo CollectionView::csvCollectionMetaInfo() const
{
return model()->metaInfo(CollectionEditor::CSVCOLLECTIONMODEL_TYPENAME);
}
void CollectionView::ensureStudioModelImport()
{
executeInTransaction(__FUNCTION__, [&] {
ensureCollectionLibraryNode();
ModelNode collectionLib = collectionLibraryNode();
if (!collectionLib.isValid())
return;
for (const Collection &collection : newCollection) {
ModelNode collectionNode = getNewCollectionNode(collection);
collectionLib.defaultNodeListProperty().reparentHere(collectionNode);
Import import = Import::createLibraryImport(CollectionEditor::COLLECTIONMODEL_IMPORT);
try {
if (!model()->hasImport(import, true, true))
model()->changeImports({import}, {});
} catch (const Exception &) {
QTC_ASSERT(false, return);
}
});
}
void CollectionView::renameCollection(ModelNode &collection, const QString &newName)
{
QTC_ASSERT(collection.isValid(), return);
QVariant objName = collection.variantProperty("objectName").value();
if (objName.isValid() && objName.toString() == newName)
return;
executeInTransaction(__FUNCTION__, [&] {
collection.setIdWithRefactoring(model()->generateIdFromName(newName, "collection"));
VariantProperty objNameProp = collection.variantProperty("objectName");
objNameProp.setValue(newName);
});
}
void CollectionView::ensureCollectionLibraryNode()
{
ModelNode collectionLib = modelNodeForId(Constants::COLLECTION_LIB_ID);
if (collectionLib.isValid()
|| (!rootModelNode().metaInfo().isQtQuick3DNode()
&& !rootModelNode().metaInfo().isQtQuickItem())) {
return;
}
executeInTransaction(__FUNCTION__, [&] {
// Create collection library node
#ifdef QDS_USE_PROJECTSTORAGE
TypeName nodeTypeName = rootModelNode().metaInfo().isQtQuick3DNode() ? "Node" : "Item";
matLib = createModelNode(nodeTypeName, -1, -1);
#else
auto nodeType = rootModelNode().metaInfo().isQtQuick3DNode()
? model()->qtQuick3DNodeMetaInfo()
: model()->qtQuickItemMetaInfo();
collectionLib = createModelNode(nodeType.typeName(),
nodeType.majorVersion(),
nodeType.minorVersion());
#endif
collectionLib.setIdWithoutRefactoring(Constants::COLLECTION_LIB_ID);
rootModelNode().defaultNodeListProperty().reparentHere(collectionLib);
});
}
ModelNode CollectionView::collectionLibraryNode()
{
return modelNodeForId(Constants::COLLECTION_LIB_ID);
}
} // namespace QmlDesigner

View File

@@ -1,5 +1,6 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include "abstractview.h"
@@ -9,7 +10,6 @@
namespace QmlDesigner {
struct Collection;
class CollectionWidget;
class CollectionView : public AbstractView
@@ -19,9 +19,6 @@ class CollectionView : public AbstractView
public:
explicit CollectionView(ExternalDependenciesInterface &externalDependencies);
bool loadJson(const QByteArray &data);
bool loadCsv(const QString &collectionName, const QByteArray &data);
bool hasWidget() const override;
WidgetInfo widgetInfo() override;
@@ -44,15 +41,13 @@ public:
void selectedNodesChanged(const QList<ModelNode> &selectedNodeList,
const QList<ModelNode> &lastSelectedNodeList) override;
void addNewCollection(const QString &name);
void addResource(const QUrl &url, const QString &name, const QString &type);
private:
void refreshModel();
ModelNode getNewCollectionNode(const Collection &collection);
void addLoadedModel(const QList<Collection> &newCollection);
void renameCollection(ModelNode &material, const QString &newName);
void ensureCollectionLibraryNode();
ModelNode collectionLibraryNode();
NodeMetaInfo jsonCollectionMetaInfo() const;
NodeMetaInfo csvCollectionMetaInfo() const;
void ensureStudioModelImport();
QPointer<CollectionWidget> m_widget;
};

View File

@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "collectionwidget.h"
#include "collectionmodel.h"
#include "collectionsourcemodel.h"
#include "collectionview.h"
#include "qmldesignerconstants.h"
#include "qmldesignerplugin.h"
@@ -13,6 +13,7 @@
#include <coreplugin/icore.h>
#include <QFile>
#include <QFileInfo>
#include <QJsonDocument>
#include <QJsonParseError>
#include <QMetaObject>
@@ -36,7 +37,7 @@ namespace QmlDesigner {
CollectionWidget::CollectionWidget(CollectionView *view)
: QFrame()
, m_view(view)
, m_model(new CollectionModel)
, m_sourceModel(new CollectionSourceModel)
, m_singleCollectionModel(new SingleCollectionModel)
, m_quickWidget(new StudioQuickWidget(this))
{
@@ -65,7 +66,7 @@ CollectionWidget::CollectionWidget(CollectionView *view)
auto map = m_quickWidget->registerPropertyMap("CollectionEditorBackend");
map->setProperties(
{{"rootView", QVariant::fromValue(this)},
{"model", QVariant::fromValue(m_model.data())},
{"model", QVariant::fromValue(m_sourceModel.data())},
{"singleCollectionModel", QVariant::fromValue(m_singleCollectionModel.data())}});
auto hotReloadShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_F4), this);
@@ -82,9 +83,9 @@ void CollectionWidget::contextHelp(const Core::IContext::HelpCallback &callback)
callback({});
}
QPointer<CollectionModel> CollectionWidget::collectionModel() const
QPointer<CollectionSourceModel> CollectionWidget::sourceModel() const
{
return m_model;
return m_sourceModel;
}
QPointer<SingleCollectionModel> CollectionWidget::singleCollectionModel() const
@@ -103,26 +104,23 @@ void CollectionWidget::reloadQmlSource()
bool CollectionWidget::loadJsonFile(const QString &jsonFileAddress)
{
QUrl jsonUrl(jsonFileAddress);
QString fileAddress = jsonUrl.isLocalFile() ? jsonUrl.toLocalFile() : jsonUrl.toString();
QFile file(fileAddress);
if (file.open(QFile::ReadOnly))
return m_view->loadJson(file.readAll());
if (!isJsonFile(jsonFileAddress))
return false;
warn("Unable to open the file", file.errorString());
return false;
QUrl jsonUrl(jsonFileAddress);
QFileInfo fileInfo(jsonUrl.isLocalFile() ? jsonUrl.toLocalFile() : jsonUrl.toString());
m_view->addResource(jsonUrl, fileInfo.completeBaseName(), "json");
return true;
}
bool CollectionWidget::loadCsvFile(const QString &collectionName, const QString &csvFileAddress)
{
QUrl csvUrl(csvFileAddress);
QString fileAddress = csvUrl.isLocalFile() ? csvUrl.toLocalFile() : csvUrl.toString();
QFile file(fileAddress);
if (file.open(QFile::ReadOnly))
return m_view->loadCsv(collectionName, file.readAll());
m_view->addResource(csvUrl, collectionName, "csv");
warn("Unable to open the file", file.errorString());
return false;
return true;
}
bool CollectionWidget::isJsonFile(const QString &jsonFileAddress) const
@@ -155,10 +153,10 @@ bool CollectionWidget::isCsvFile(const QString &csvFileAddress) const
return true;
}
bool CollectionWidget::addCollection(const QString &collectionName) const
bool CollectionWidget::addCollection([[maybe_unused]] const QString &collectionName) const
{
m_view->addNewCollection(collectionName);
return true;
// TODO
return false;
}
void CollectionWidget::warn(const QString &title, const QString &body)

View File

@@ -1,5 +1,6 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <QFrame>
@@ -10,7 +11,7 @@ class StudioQuickWidget;
namespace QmlDesigner {
class CollectionModel;
class CollectionSourceModel;
class CollectionView;
class SingleCollectionModel;
@@ -22,7 +23,7 @@ public:
CollectionWidget(CollectionView *view);
void contextHelp(const Core::IContext::HelpCallback &callback) const;
QPointer<CollectionModel> collectionModel() const;
QPointer<CollectionSourceModel> sourceModel() const;
QPointer<SingleCollectionModel> singleCollectionModel() const;
void reloadQmlSource();
@@ -37,7 +38,7 @@ public:
private:
QPointer<CollectionView> m_view;
QPointer<CollectionModel> m_model;
QPointer<CollectionSourceModel> m_sourceModel;
QPointer<SingleCollectionModel> m_singleCollectionModel;
QScopedPointer<StudioQuickWidget> m_quickWidget;
};

View File

@@ -3,31 +3,36 @@
#include "singlecollectionmodel.h"
#include "nodemetainfo.h"
#include "collectioneditorconstants.h"
#include "modelnode.h"
#include "variantproperty.h"
#include <utils/qtcassert.h>
namespace {
inline bool isListElement(const QmlDesigner::ModelNode &node)
{
return node.metaInfo().isQtQuickListElement();
}
#include <QFile>
#include <QJsonArray>
#include <QJsonParseError>
inline QByteArrayList getHeaders(const QByteArray &headersValue)
namespace {
QStringList getJsonHeaders(const QJsonArray &collectionArray)
{
QByteArrayList result;
const QByteArrayList initialHeaders = headersValue.split(',');
for (QByteArray header : initialHeaders) {
header = header.trimmed();
if (header.size())
result.append(header);
QSet<QString> result;
for (const QJsonValue &value : collectionArray) {
if (value.isObject()) {
const QJsonObject object = value.toObject();
const QStringList headers = object.toVariantMap().keys();
for (const QString &header : headers)
result.insert(header);
}
}
return result;
return result.values();
}
} // namespace
namespace QmlDesigner {
SingleCollectionModel::SingleCollectionModel(QObject *parent)
: QAbstractTableModel(parent)
{}
@@ -47,11 +52,11 @@ QVariant SingleCollectionModel::data(const QModelIndex &index, int) const
if (!index.isValid())
return {};
const QByteArray &propertyName = m_headers.at(index.column());
const ModelNode &elementNode = m_elements.at(index.row());
const QString &propertyName = m_headers.at(index.column());
const QJsonObject &elementNode = m_elements.at(index.row());
if (elementNode.hasVariantProperty(propertyName))
return elementNode.variantProperty(propertyName).value();
if (elementNode.contains(propertyName))
return elementNode.value(propertyName).toVariant();
return {};
}
@@ -79,32 +84,110 @@ QVariant SingleCollectionModel::headerData(int section,
return {};
}
void SingleCollectionModel::setCollection(const ModelNode &collection)
void SingleCollectionModel::loadCollection(const ModelNode &sourceNode, const QString &collection)
{
QString fileName = sourceNode.variantProperty(CollectionEditor::SOURCEFILE_PROPERTY).value().toString();
if (sourceNode.type() == CollectionEditor::JSONCOLLECTIONMODEL_TYPENAME)
loadJsonCollection(fileName, collection);
else if (sourceNode.type() == CollectionEditor::CSVCOLLECTIONMODEL_TYPENAME)
loadCsvCollection(fileName, collection);
}
void SingleCollectionModel::loadJsonCollection(const QString &source, const QString &collection)
{
beginResetModel();
m_collectionNode = collection;
updateCollectionName();
setCollectionName(collection);
QFile sourceFile(source);
QJsonArray collectionNodes;
bool jsonFileIsOk = false;
if (sourceFile.open(QFile::ReadOnly)) {
QJsonParseError jpe;
QJsonDocument document = QJsonDocument::fromJson(sourceFile.readAll(), &jpe);
if (jpe.error == QJsonParseError::NoError) {
jsonFileIsOk = true;
if (document.isObject()) {
QJsonObject collectionMap = document.object();
if (collectionMap.contains(collection)) {
QJsonValue collectionVal = collectionMap.value(collection);
if (collectionVal.isArray())
collectionNodes = collectionVal.toArray();
else
collectionNodes.append(collectionVal);
}
}
}
}
QTC_ASSERT(collection.isValid() && collection.hasVariantProperty("headers"), {
setCollectionSourceFormat(jsonFileIsOk ? SourceFormat::Json : SourceFormat::Unknown);
if (collectionNodes.isEmpty()) {
m_headers.clear();
m_elements.clear();
endResetModel();
return;
});
}
m_headers = getJsonHeaders(collectionNodes);
m_elements.clear();
for (const QJsonValue &value : std::as_const(collectionNodes)) {
if (value.isObject()) {
QJsonObject object = value.toObject();
m_elements.append(object);
}
}
m_headers = getHeaders(collection.variantProperty("headers").value().toByteArray());
m_elements = Utils::filtered(collection.allSubModelNodes(), &isListElement);
endResetModel();
}
void SingleCollectionModel::updateCollectionName()
void SingleCollectionModel::loadCsvCollection(const QString &source, const QString &collectionName)
{
beginResetModel();
setCollectionName(collectionName);
QFile sourceFile(source);
m_headers.clear();
m_elements.clear();
bool csvFileIsOk = false;
if (sourceFile.open(QFile::ReadOnly)) {
QTextStream stream(&sourceFile);
if (!stream.atEnd())
m_headers = stream.readLine().split(',');
if (!m_headers.isEmpty()) {
while (!stream.atEnd()) {
const QStringList recordDataList = stream.readLine().split(',');
int column = -1;
QJsonObject recordData;
for (const QString &cellData : recordDataList) {
if (++column == m_headers.size())
break;
recordData.insert(m_headers.at(column), cellData);
}
if (recordData.count())
m_elements.append(recordData);
}
csvFileIsOk = true;
}
}
setCollectionSourceFormat(csvFileIsOk ? SourceFormat::Csv : SourceFormat::Unknown);
endResetModel();
}
void SingleCollectionModel::setCollectionName(const QString &newCollectionName)
{
QString newCollectionName = m_collectionNode.isValid()
? m_collectionNode.variantProperty("objectName").value().toString()
: "";
if (m_collectionName != newCollectionName) {
m_collectionName = newCollectionName;
emit this->collectionNameChanged(m_collectionName);
}
}
void SingleCollectionModel::setCollectionSourceFormat(SourceFormat sourceFormat)
{
m_sourceFormat = sourceFormat;
}
} // namespace QmlDesigner

View File

@@ -1,16 +1,15 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include "modelnode.h"
#include <QAbstractTableModel>
QT_BEGIN_NAMESPACE
class QJsonArray;
QT_END_NAMESPACE
#include <QJsonObject>
namespace QmlDesigner {
class ModelNode;
class SingleCollectionModel : public QAbstractTableModel
{
Q_OBJECT
@@ -18,6 +17,7 @@ class SingleCollectionModel : public QAbstractTableModel
Q_PROPERTY(QString collectionName MEMBER m_collectionName NOTIFY collectionNameChanged)
public:
enum class SourceFormat { Unknown, Json, Csv };
explicit SingleCollectionModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent) const override;
@@ -29,18 +29,21 @@ public:
Qt::Orientation orientation,
int role = Qt::DisplayRole) const override;
void setCollection(const ModelNode &collection);
void loadCollection(const ModelNode &sourceNode, const QString &collection);
signals:
void collectionNameChanged(const QString &collectionName);
private:
void updateCollectionName();
void setCollectionName(const QString &newCollectionName);
void setCollectionSourceFormat(SourceFormat sourceFormat);
void loadJsonCollection(const QString &source, const QString &collection);
void loadCsvCollection(const QString &source, const QString &collectionName);
QByteArrayList m_headers;
ModelNodes m_elements;
ModelNode m_collectionNode;
QStringList m_headers;
QList<QJsonObject> m_elements;
QString m_collectionName;
SourceFormat m_sourceFormat = SourceFormat::Unknown;
};
} // namespace QmlDesigner

View File

@@ -23,10 +23,11 @@
#include <formeditortoolbutton.h>
#include <documentmanager.h>
#include <qmldesignerplugin.h>
#include <viewmanager.h>
#include <actioneditor.h>
#include <documentmanager.h>
#include <model/modelutils.h>
#include <viewmanager.h>
#include <qmldesignerplugin.h>
#include <listmodeleditor/listmodeleditordialog.h>
#include <listmodeleditor/listmodeleditormodel.h>
@@ -419,7 +420,7 @@ public:
parentNode = selectionContext().currentSingleSelectedNode().parentProperty().parentModelNode();
if (!ModelNode::isThisOrAncestorLocked(parentNode)) {
if (!ModelUtils::isThisOrAncestorLocked(parentNode)) {
ActionTemplate *selectionAction = new ActionTemplate("SELECTION", {}, &ModelNodeOperations::select);
selectionAction->setParent(menu());
selectionAction->setText(QString(QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Parent")));
@@ -432,11 +433,9 @@ public:
}
}
for (const ModelNode &node : selectionContext().view()->allModelNodes()) {
if (node != selectionContext().currentSingleSelectedNode()
&& node != parentNode
&& contains(node, selectionContext().scenePosition())
&& !node.isRootNode()
&& !ModelNode::isThisOrAncestorLocked(node)) {
if (node != selectionContext().currentSingleSelectedNode() && node != parentNode
&& contains(node, selectionContext().scenePosition()) && !node.isRootNode()
&& !ModelUtils::isThisOrAncestorLocked(node)) {
selectionContext().setTargetNode(node);
QString what = QString(QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Select: %1")).arg(captionForModelNode(node));
ActionTemplate *selectionAction = new ActionTemplate("SELECT", what, &ModelNodeOperations::select);
@@ -741,19 +740,7 @@ public:
activeSignalHandlerGroup->addMenu(editSlotGroup);
}
ActionTemplate *openEditorAction = new ActionTemplate(
(propertyName + "OpenEditorId").toLatin1(),
QString(QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Open Connections Editor")),
[=](const SelectionContext &) {
signalHandler.parentModelNode().view()->executeInTransaction(
"ConnectionsModelNodeActionGroup::"
"openConnectionsEditor",
[signalHandler]() {
ActionEditor::invokeEditor(signalHandler, removeSignal);
});
});
activeSignalHandlerGroup->addAction(openEditorAction);
//add an action to open Connection Form from here:
ActionTemplate *removeSignalHandlerAction = new ActionTemplate(
(propertyName + "RemoveSignalHandlerId").toLatin1(),
@@ -822,25 +809,7 @@ public:
}
}
ActionTemplate *openEditorAction = new ActionTemplate(
(signalStr + "OpenEditorId").toLatin1(),
QString(QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Open Connections Editor")),
[=](const SelectionContext &) {
currentNode.view()->executeInTransaction(
"ConnectionsModelNodeActionGroup::"
"openConnectionsEditor",
[=]() {
ModelNode newConnectionNode = createNewConnection(currentNode);
SignalHandlerProperty newHandler = newConnectionNode.signalHandlerProperty(
prependSignal(signalStr).toLatin1());
newHandler.setSource(
QString("console.log(\"%1.%2\")").arg(currentNode.id(), signalStr));
ActionEditor::invokeEditor(newHandler, removeSignal, true);
});
});
newSignal->addAction(openEditorAction);
//add an action to open Connection Form from here
addConnection->addMenu(newSignal);
}
@@ -1270,9 +1239,9 @@ bool isPositioner(const SelectionContext &context)
bool layoutOptionVisible(const SelectionContext &context)
{
return selectionCanBeLayoutedAndQtQuickLayoutPossible(context)
|| singleSelectionAndInQtQuickLayout(context)
|| isLayout(context);
return (selectionCanBeLayoutedAndQtQuickLayoutPossible(context)
|| singleSelectionAndInQtQuickLayout(context) || isLayout(context))
&& !DesignerMcuManager::instance().isMCUProject();
}
bool positionOptionVisible(const SelectionContext &context)
@@ -1840,29 +1809,31 @@ void DesignerActionManager::createDefaultDesignerActions()
&isStackedContainerAndIndexCanBeIncreased,
&isStackedContainer));
addDesignerAction(new ModelNodeAction(
layoutRowLayoutCommandId,
layoutRowLayoutDisplayName,
Utils::Icon({{":/qmldesigner/icon/designeractions/images/row.png",
Utils::Theme::IconsBaseColor}}).icon(),
layoutRowLayoutToolTip,
layoutCategory,
QKeySequence("Ctrl+u"),
2,
&layoutRowLayout,
&selectionCanBeLayoutedAndQtQuickLayoutPossible));
addDesignerAction(
new ModelNodeAction(layoutRowLayoutCommandId,
layoutRowLayoutDisplayName,
Utils::Icon({{":/qmldesigner/icon/designeractions/images/row.png",
Utils::Theme::IconsBaseColor}})
.icon(),
layoutRowLayoutToolTip,
layoutCategory,
QKeySequence("Ctrl+u"),
2,
&layoutRowLayout,
&selectionCanBeLayoutedAndQtQuickLayoutPossibleAndNotMCU));
addDesignerAction(new ModelNodeAction(
layoutColumnLayoutCommandId,
layoutColumnLayoutDisplayName,
Utils::Icon({{":/qmldesigner/icon/designeractions/images/column.png",
Utils::Theme::IconsBaseColor}}).icon(),
layoutColumnLayoutToolTip,
layoutCategory,
QKeySequence("Ctrl+l"),
3,
&layoutColumnLayout,
&selectionCanBeLayoutedAndQtQuickLayoutPossible));
addDesignerAction(
new ModelNodeAction(layoutColumnLayoutCommandId,
layoutColumnLayoutDisplayName,
Utils::Icon({{":/qmldesigner/icon/designeractions/images/column.png",
Utils::Theme::IconsBaseColor}})
.icon(),
layoutColumnLayoutToolTip,
layoutCategory,
QKeySequence("Ctrl+l"),
3,
&layoutColumnLayout,
&selectionCanBeLayoutedAndQtQuickLayoutPossibleAndNotMCU));
addDesignerAction(new ModelNodeAction(
layoutGridLayoutCommandId,
@@ -1937,9 +1908,7 @@ void DesignerActionManager::createDefaultDesignerActions()
&addMouseAreaFillCheck,
&singleSelection));
const bool standaloneMode = QmlProjectManager::QmlProject::isQtDesignStudio();
if (!standaloneMode) {
if (!Core::ICore::isQtDesignStudio()) {
addDesignerAction(new ModelNodeContextMenuAction(goToImplementationCommandId,
goToImplementationDisplayName,
{},

View File

@@ -196,7 +196,7 @@ void LayoutInGridLayout::doIt()
static bool hasQtQuickLayoutImport(const SelectionContext &context)
{
if (context.view() && context.view()->model()) {
Import import = Import::createLibraryImport(QStringLiteral("QtQuick.Layouts"), QStringLiteral("1.0"));
Import import = Import::createLibraryImport(QStringLiteral("QtQuick.Layouts"), {});
return context.view()->model()->hasImport(import, true, true);
}
@@ -206,7 +206,7 @@ static bool hasQtQuickLayoutImport(const SelectionContext &context)
void LayoutInGridLayout::ensureLayoutImport(const SelectionContext &context)
{
if (!hasQtQuickLayoutImport(context)) {
Import layoutImport = Import::createLibraryImport("QtQuick.Layouts", "1.0");
Import layoutImport = Import::createLibraryImport("QtQuick.Layouts", {});
context.view()-> model()->changeImports({layoutImport}, {});
}
}

View File

@@ -521,13 +521,8 @@ void layoutFlowPositioner(const SelectionContext &selectionContext)
void layoutRowLayout(const SelectionContext &selectionContext)
{
try {
if (DesignerMcuManager::instance().isMCUProject()) {
layoutHelperFunction(selectionContext, "QtQuick.Row", compareByX);
}
else {
LayoutInGridLayout::ensureLayoutImport(selectionContext);
layoutHelperFunction(selectionContext, "QtQuick.Layouts.RowLayout", compareByX);
}
LayoutInGridLayout::ensureLayoutImport(selectionContext);
layoutHelperFunction(selectionContext, "QtQuick.Layouts.RowLayout", compareByX);
} catch (RewritingException &e) { //better safe than sorry
e.showException();
}
@@ -536,13 +531,8 @@ void layoutRowLayout(const SelectionContext &selectionContext)
void layoutColumnLayout(const SelectionContext &selectionContext)
{
try {
if (DesignerMcuManager::instance().isMCUProject()) {
layoutHelperFunction(selectionContext, "QtQuick.Column", compareByX);
}
else {
LayoutInGridLayout::ensureLayoutImport(selectionContext);
layoutHelperFunction(selectionContext, "QtQuick.Layouts.ColumnLayout", compareByY);
}
LayoutInGridLayout::ensureLayoutImport(selectionContext);
layoutHelperFunction(selectionContext, "QtQuick.Layouts.ColumnLayout", compareByY);
} catch (RewritingException &e) { //better safe than sorry
e.showException();
}
@@ -553,13 +543,8 @@ void layoutGridLayout(const SelectionContext &selectionContext)
try {
Q_ASSERT(!DesignerMcuManager::instance().isMCUProject()); //remove this line when grids are finally supported
if (DesignerMcuManager::instance().isMCUProject()) {
//qt for mcu doesn't support any grids yet
}
else {
LayoutInGridLayout::ensureLayoutImport(selectionContext);
LayoutInGridLayout::layout(selectionContext);
}
LayoutInGridLayout::ensureLayoutImport(selectionContext);
LayoutInGridLayout::layout(selectionContext);
} catch (RewritingException &e) { //better safe than sorry
e.showException();
}

View File

@@ -0,0 +1,324 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "propertycomponentgenerator.h"
#include <utils/algorithm.h>
#include <utils/environment.h>
#include <utils/set_algorithm.h>
#include <model.h>
#include <QFile>
#include <QString>
#include <memory>
using namespace Qt::StringLiterals;
namespace QmlDesigner {
namespace {
QmlJS::SimpleReaderNode::Ptr createTemplateConfiguration(const QString &propertyEditorResourcesPath)
{
QmlJS::SimpleReader reader;
const QString fileName = propertyEditorResourcesPath + u"/PropertyTemplates/TemplateTypes.qml";
auto templateConfiguration = reader.readFile(fileName);
if (!templateConfiguration)
qWarning().nospace() << "template definitions:" << reader.errors();
return templateConfiguration;
}
template<typename Type>
Type getProperty(const QmlJS::SimpleReaderNode *node, const QString &name)
{
if (auto property = node->property(name)) {
const auto &value = property.value;
if (value.type() == QVariant::List) {
auto list = value.toList();
if (list.size())
return list.front().value<Type>();
} else {
return property.value.value<Type>();
}
}
return {};
}
QString getContent(const QString &path)
{
QFile file{path};
if (file.open(QIODevice::ReadOnly))
return QString::fromUtf8(file.readAll());
return {};
}
QString generateComponentText(Utils::SmallStringView propertyName,
QStringView source,
Utils::SmallStringView typeName,
bool needsTypeArg)
{
QString underscoreName{propertyName};
underscoreName.replace('.', '_');
if (needsTypeArg)
return source.arg(QString{propertyName}, underscoreName, QString{typeName});
return source.arg(QString{propertyName}, underscoreName);
}
PropertyComponentGenerator::Property generate(const PropertyMetaInfo &property,
const PropertyComponentGenerator::Entry &entry)
{
auto propertyName = property.name();
auto component = generateComponentText(propertyName,
entry.propertyTemplate,
entry.typeName,
entry.needsTypeArg);
if (entry.separateSection)
return PropertyComponentGenerator::ComplexProperty{propertyName, component};
return PropertyComponentGenerator::BasicProperty{propertyName, component};
}
auto getRandomExportedName(const NodeMetaInfo &metaInfo)
{
const auto &names = metaInfo.allExportedTypeNames();
using Result = decltype(names.front().name);
if (!names.empty())
return names.front().name;
return Result{};
}
} // namespace
QString PropertyComponentGenerator::generateSubComponentText(Utils::SmallStringView propertyBaseName,
const PropertyMetaInfo &subProperty) const
{
auto propertyType = subProperty.propertyType();
if (auto entry = findEntry(propertyType)) {
auto propertyName = Utils::SmallString::join({propertyBaseName, subProperty.name()});
return generateComponentText(propertyName,
entry->propertyTemplate,
entry->typeName,
entry->needsTypeArg);
}
return {};
}
QString PropertyComponentGenerator::generateComplexComponentText(Utils::SmallStringView propertyName,
const NodeMetaInfo &propertyType) const
{
auto subProperties = propertyType.properties();
if (std::empty(subProperties))
return {};
auto propertyTypeName = getRandomExportedName(propertyType);
static QString templateText = QStringLiteral(
R"xy(
Section {
caption: %1 - %2
anchors.left: parent.left
anchors.right: parent.right
leftPadding: 8
rightPadding: 0
expanded: false
level: 1
SectionLayout {
)xy");
auto component = templateText.arg(QString{propertyName}, QString{propertyTypeName});
auto propertyBaseName = Utils::SmallString::join({propertyName, "."});
bool subPropertiesHaveContent = false;
for (const auto &subProperty : propertyType.properties()) {
auto subPropertyContent = generateSubComponentText(propertyBaseName, subProperty);
subPropertiesHaveContent = subPropertiesHaveContent || subPropertyContent.size();
component += subPropertyContent;
}
component += "}\n}\n"_L1;
if (subPropertiesHaveContent)
return component;
return {};
}
PropertyComponentGenerator::Property PropertyComponentGenerator::generateComplexComponent(
const PropertyMetaInfo &property, const NodeMetaInfo &propertyType) const
{
auto propertyName = property.name();
auto component = generateComplexComponentText(propertyName, propertyType);
if (component.isEmpty())
return {};
return ComplexProperty{propertyName, component};
}
namespace {
std::optional<PropertyComponentGenerator::Entry> createEntry(QmlJS::SimpleReaderNode *node,
Model *model,
const QString &templatesPath)
{
auto moduleName = getProperty<QByteArray>(node, "module");
if (moduleName.isEmpty())
return {};
auto module = model->module(moduleName);
auto typeName = getProperty<QByteArray>(node, "typeNames");
auto type = model->metaInfo(module, typeName);
if (!type)
return {};
auto path = getProperty<QString>(node, "sourceFile");
if (path.isEmpty())
return {};
auto content = getContent(templatesPath + path);
if (content.isEmpty())
return {};
bool needsTypeArg = getProperty<bool>(node, "needsTypeArg");
bool separateSection = getProperty<bool>(node, "separateSection");
return PropertyComponentGenerator::Entry{std::move(type),
std::move(typeName),
std::move(content),
separateSection,
needsTypeArg};
}
std::tuple<PropertyComponentGenerator::Entries, bool> createEntries(
QmlJS::SimpleReaderNode::Ptr templateConfiguration, Model *model, const QString &templatesPath)
{
bool hasInvalidTemplates = false;
PropertyComponentGenerator::Entries entries;
entries.reserve(32);
const auto &nodes = templateConfiguration->children();
for (const QmlJS::SimpleReaderNode::Ptr &node : nodes) {
if (auto entry = createEntry(node.get(), model, templatesPath))
entries.push_back(*entry);
else
hasInvalidTemplates = true;
}
return {entries, hasInvalidTemplates};
}
QStringList createImports(QmlJS::SimpleReaderNode *templateConfiguration)
{
auto property = templateConfiguration->property("imports");
return Utils::transform<QStringList>(property.value.toList(),
[](const auto &entry) { return entry.toString(); });
}
} // namespace
PropertyComponentGenerator::PropertyComponentGenerator(const QString &propertyEditorResourcesPath,
Model *model)
: m_templateConfiguration{createTemplateConfiguration(propertyEditorResourcesPath)}
, m_propertyTemplatesPath{propertyEditorResourcesPath + "/PropertyTemplates/"}
{
setModel(model);
m_imports = createImports(m_templateConfiguration.get());
}
PropertyComponentGenerator::Property PropertyComponentGenerator::create(const PropertyMetaInfo &property) const
{
auto propertyType = property.propertyType();
if (auto entry = findEntry(propertyType))
return generate(property, *entry);
if (property.isWritable() && property.isPointer())
return {};
return generateComplexComponent(property, propertyType);
}
void PropertyComponentGenerator::setModel(Model *model)
{
if (model && m_model && m_model->projectStorage() == model->projectStorage()) {
m_model = model;
return;
}
if (model) {
setEntries(m_templateConfiguration, model, m_propertyTemplatesPath);
} else {
m_entries.clear();
m_entryTypeIds.clear();
}
m_model = model;
}
namespace {
bool insect(const TypeIds &first, const TypeIds &second)
{
bool intersecting = false;
std::set_intersection(first.begin(),
first.end(),
second.begin(),
second.end(),
Utils::make_iterator([&](const auto &) { intersecting = true; }));
return intersecting;
}
} // namespace
void PropertyComponentGenerator::setEntries(QmlJS::SimpleReaderNode::Ptr templateConfiguration,
Model *model,
const QString &propertyTemplatesPath)
{
auto [entries, hasInvalidTemplates] = createEntries(templateConfiguration,
model,
propertyTemplatesPath);
m_entries = std::move(entries);
m_hasInvalidTemplates = hasInvalidTemplates;
m_entryTypeIds = Utils::transform<TypeIds>(m_entries,
[](const auto &entry) { return entry.type.id(); });
std::sort(m_entryTypeIds.begin(), m_entryTypeIds.end());
}
void PropertyComponentGenerator::refreshMetaInfos(const TypeIds &deletedTypeIds)
{
if (!insect(deletedTypeIds, m_entryTypeIds) && !m_hasInvalidTemplates)
return;
setEntries(m_templateConfiguration, m_model, m_propertyTemplatesPath);
}
const PropertyComponentGenerator::Entry *PropertyComponentGenerator::findEntry(const NodeMetaInfo &type) const
{
auto found = std::find_if(m_entries.begin(), m_entries.end(), [&](const auto &entry) {
return entry.type == type;
});
if (found != m_entries.end())
return std::addressof(*found);
return nullptr;
}
} // namespace QmlDesigner

View File

@@ -0,0 +1,68 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include "propertycomponentgeneratorinterface.h"
#include <modelfwd.h>
#include <nodemetainfo.h>
#include <qmljs/qmljssimplereader.h>
#include <QPointer>
#include <optional>
#include <variant>
#include <vector>
namespace QmlDesigner {
class PropertyComponentGenerator final : public PropertyComponentGeneratorInterface
{
public:
PropertyComponentGenerator(const QString &propertyEditorResourcesPath, Model *model);
Property create(const PropertyMetaInfo &property) const override;
struct Entry
{
NodeMetaInfo type;
Utils::SmallString typeName;
QString propertyTemplate;
bool separateSection = false;
bool needsTypeArg = false;
};
using Entries = std::vector<Entry>;
QStringList imports() const override { return m_imports; }
void setModel(Model *model);
void refreshMetaInfos(const TypeIds &deletedTypeIds);
private:
const Entry *findEntry(const NodeMetaInfo &type) const;
QString generateSubComponentText(Utils::SmallStringView propertyBaseName,
const PropertyMetaInfo &subProperty) const;
QString generateComplexComponentText(Utils::SmallStringView propertyName,
const NodeMetaInfo &propertyType) const;
Property generateComplexComponent(const PropertyMetaInfo &property,
const NodeMetaInfo &propertyType) const;
void setEntries(QmlJS::SimpleReaderNode::Ptr templateConfiguration,
Model *model,
const QString &propertyTemplatesPath);
private:
Entries m_entries;
TypeIds m_entryTypeIds;
QStringList m_imports;
QPointer<Model> m_model;
QmlJS::SimpleReaderNode::Ptr m_templateConfiguration;
QString m_propertyTemplatesPath;
bool m_hasInvalidTemplates = false;
};
} // namespace QmlDesigner

View File

@@ -0,0 +1,46 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <modelfwd.h>
#include <nodemetainfo.h>
#include <qmljs/qmljssimplereader.h>
#include <optional>
#include <variant>
#include <vector>
namespace QmlDesigner {
class PropertyComponentGeneratorInterface
{
public:
PropertyComponentGeneratorInterface() = default;
PropertyComponentGeneratorInterface(const PropertyComponentGeneratorInterface &) = delete;
PropertyComponentGeneratorInterface &operator=(const PropertyComponentGeneratorInterface &) = delete;
struct BasicProperty
{
Utils::SmallString propertyName;
QString component;
};
struct ComplexProperty
{
Utils::SmallString propertyName;
QString component;
};
using Property = std::variant<std::monostate, BasicProperty, ComplexProperty>;
virtual Property create(const PropertyMetaInfo &property) const = 0;
virtual QStringList imports() const = 0;
protected:
~PropertyComponentGeneratorInterface() = default;
};
} // namespace QmlDesigner

View File

@@ -0,0 +1,175 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "propertyeditorcomponentgenerator.h"
#include <utils/environment.h>
#include <utils/span.h>
#include <modelnode.h>
namespace QmlDesigner {
namespace {
using GeneratorProperty = PropertyComponentGeneratorInterface::Property;
using BasicProperty = PropertyComponentGeneratorInterface::BasicProperty;
using ComplexProperty = PropertyComponentGeneratorInterface::ComplexProperty;
using GeneratorProperties = std::vector<GeneratorProperty>;
QString createImports(const QStringList &imports)
{
QString importsContent;
importsContent.reserve(512);
for (const QString &import : imports) {
importsContent += import;
importsContent += '\n';
}
return importsContent;
}
QString componentButton(bool isComponent)
{
if (isComponent)
return "ComponentButton {}";
return {};
}
void createBasicPropertySections(QString &components,
Utils::span<GeneratorProperty> generatorProperties)
{
components += R"xy(
Column {
width: parent.width
leftPadding: 8
bottomPadding: 10
SectionLayout {)xy";
for (const auto &generatorProperty : generatorProperties)
components += std::get_if<BasicProperty>(&generatorProperty)->component;
components += R"xy(
}
})xy";
}
void createComplexPropertySections(QString &components,
Utils::span<GeneratorProperty> generatorProperties)
{
for (const auto &generatorProperty : generatorProperties)
components += std::get_if<ComplexProperty>(&generatorProperty)->component;
}
Utils::SmallStringView propertyName(const GeneratorProperty &property)
{
return std::visit(
[](const auto &p) -> Utils::SmallStringView {
if constexpr (!std::is_same_v<std::decay_t<decltype(p)>, std::monostate>)
return p.propertyName;
else
return {};
},
property);
}
PropertyMetaInfos getUnmangedProperties(const NodeMetaInfos &prototypes)
{
PropertyMetaInfos properties;
properties.reserve(128);
for (const auto &prototype : prototypes) {
if (prototype.propertyEditorPathId())
break;
auto localProperties = prototype.localProperties();
properties.insert(properties.end(), localProperties.begin(), localProperties.end());
}
return properties;
}
GeneratorProperties createSortedGeneratorProperties(
const PropertyMetaInfos &properties, const PropertyComponentGeneratorType &propertyGenerator)
{
GeneratorProperties generatorProperties;
generatorProperties.reserve(properties.size());
for (const auto &property : properties) {
auto generatedProperty = propertyGenerator.create(property);
if (!std::holds_alternative<std::monostate>(generatedProperty))
generatorProperties.push_back(std::move(generatedProperty));
}
std::sort(generatorProperties.begin(),
generatorProperties.end(),
[](const auto &first, const auto &second) {
// this is sensitive to the order of the variant types but the test should catch any error
return std::forward_as_tuple(first.index(), propertyName(first))
< std::forward_as_tuple(second.index(), propertyName(second));
});
return generatorProperties;
}
QString createPropertySections(const PropertyComponentGeneratorType &propertyGenerator,
const NodeMetaInfos &prototypeChain)
{
QString propertyComponents;
propertyComponents.reserve(100000);
auto generatorProperties = createSortedGeneratorProperties(getUnmangedProperties(prototypeChain),
propertyGenerator);
const auto begin = generatorProperties.begin();
const auto end = generatorProperties.end();
auto point = std::partition_point(begin, end, [](const auto &p) {
return !std::holds_alternative<ComplexProperty>(p);
});
if (begin != point)
createBasicPropertySections(propertyComponents, Utils::span<GeneratorProperty>{begin, point});
if (point != end)
createComplexPropertySections(propertyComponents, Utils::span<GeneratorProperty>{point, end});
return propertyComponents;
}
} // namespace
PropertyEditorComponentGenerator::PropertyEditorComponentGenerator(
const PropertyComponentGeneratorType &propertyGenerator)
: m_propertyGenerator{propertyGenerator}
{}
QString PropertyEditorComponentGenerator::create(const NodeMetaInfos &prototypeChain, bool isComponent)
{
return QString{R"xy(
%1
Column {
width: parent.width
%2
Section {
caption: "%3"
anchors.left: parent.left
anchors.right: parent.right
leftPadding: 0
rightPadding: 0
bottomPadding: 0
Column {
width: parent.width
%4
}
}
})xy"}
.arg(createImports(m_propertyGenerator.imports()),
componentButton(isComponent),
QObject::tr("Exposed Custom Properties"),
createPropertySections(m_propertyGenerator, prototypeChain));
}
} // namespace QmlDesigner

View File

@@ -0,0 +1,29 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include "propertycomponentgenerator.h"
#include <nodemetainfo.h>
namespace QmlDesigner {
#ifdef UNIT_TESTS
using PropertyComponentGeneratorType = PropertyComponentGeneratorInterface;
#else
using PropertyComponentGeneratorType = PropertyComponentGenerator;
#endif
class PropertyEditorComponentGenerator
{
public:
PropertyEditorComponentGenerator(const PropertyComponentGeneratorType &propertyGenerator);
[[nodiscard]] QString create(const NodeMetaInfos &prototypeChain, bool isComponent);
private:
const PropertyComponentGeneratorType &m_propertyGenerator;
};
} // namespace QmlDesigner

View File

@@ -31,6 +31,10 @@ public:
addTable,
add_medium,
add_small,
addcolumnleft_medium,
addcolumnright_medium,
addrowabove_medium,
addrowbelow_medium,
adsClose,
adsDetach,
adsDropDown,
@@ -83,6 +87,8 @@ public:
closeLink,
close_small,
code,
codeEditor_medium,
codeview_medium,
colorPopupClose,
colorSelection_medium,
columnsAndRows,
@@ -115,6 +121,8 @@ public:
deleteTable,
delete_medium,
delete_small,
deletecolumn_medium,
deleterow_medium,
designMode_large,
detach,
directionalLight_small,
@@ -133,7 +141,11 @@ public:
download,
downloadUnavailable,
downloadUpdate,
downloadcsv_large,
downloadcsv_medium,
downloaded,
downloadjson_large,
downloadjson_medium,
dragmarks,
duplicate_small,
edit,
@@ -281,6 +293,8 @@ public:
snapping_conf_medium,
snapping_medium,
snapping_small,
sortascending_medium,
sortdescending_medium,
sphere_medium,
sphere_small,
splitColumns,
@@ -337,6 +351,10 @@ public:
upDownSquare2,
updateAvailable_medium,
updateContent_medium,
uploadcsv_large,
uploadcsv_medium,
uploadjson_large,
uploadjson_medium,
visibilityOff,
visibilityOn,
visible_medium,

View File

@@ -16,7 +16,6 @@
#include <designeractionmanagerview.h>
#include <designmodewidget.h>
#include <edit3dview.h>
#include <effectmakerview.h>
#include <formeditorview.h>
#include <itemlibraryview.h>
#include <materialbrowserview.h>
@@ -56,7 +55,6 @@ public:
, contentLibraryView{externalDependencies}
, componentView{externalDependencies}
, edit3DView{externalDependencies}
, effectMakerView{externalDependencies}
, formEditorView{externalDependencies}
, textEditorView{externalDependencies}
, assetsLibraryView{externalDependencies}
@@ -79,7 +77,6 @@ public:
ContentLibraryView contentLibraryView;
ComponentView componentView;
Edit3DView edit3DView;
EffectMakerView effectMakerView;
FormEditorView formEditorView;
TextEditorView textEditorView;
AssetsLibraryView assetsLibraryView;
@@ -212,9 +209,6 @@ QList<AbstractView *> ViewManager::standardViews() const
.toBool())
list.append(&d->debugView);
if (qEnvironmentVariableIsSet("ENABLE_QDS_EFFECTMAKER"))
list.append(&d->effectMakerView);
if (qEnvironmentVariableIsSet("ENABLE_QDS_COLLECTIONVIEW"))
list.append(&d->collectionView);
@@ -393,9 +387,6 @@ QList<WidgetInfo> ViewManager::widgetInfos() const
widgetInfoList.append(d->textureEditorView.widgetInfo());
widgetInfoList.append(d->statesEditorView.widgetInfo());
if (qEnvironmentVariableIsSet("ENABLE_QDS_EFFECTMAKER"))
widgetInfoList.append(d->effectMakerView.widgetInfo());
if (qEnvironmentVariableIsSet("ENABLE_QDS_COLLECTIONVIEW"))
widgetInfoList.append(d->collectionView.widgetInfo());

View File

@@ -126,6 +126,7 @@ QWidget *ZoomAction::createWidget(QWidget *parent)
if (!m_combo && parentIsToolBar(parent)) {
m_combo = createZoomComboBox(parent);
m_combo->setProperty(Utils::StyleHelper::C_HIDE_BORDER, true);
m_combo->setProperty(Utils::StyleHelper::C_TOOLBAR_ACTIONWIDGET, true);
m_combo->setCurrentIndex(m_index);
m_combo->setToolTip(m_combo->currentText());

View File

@@ -3,8 +3,8 @@
#include "bindingmodel.h"
#include "bindingmodelitem.h"
#include "connectionview.h"
#include "connectioneditorutils.h"
#include "connectionview.h"
#include "modelfwd.h"
#include <bindingproperty.h>
@@ -16,6 +16,8 @@
#include <utils/qtcassert.h>
#include <QSignalBlocker>
namespace QmlDesigner {
BindingModel::BindingModel(ConnectionView *parent)
@@ -142,11 +144,15 @@ void BindingModel::setCurrentProperty(const AbstractProperty &property)
void BindingModel::updateItem(const BindingProperty &property)
{
if (auto *item = itemForProperty(property))
if (auto *item = itemForProperty(property)) {
item->updateProperty(property);
else
appendRow(new BindingModelItem(property));
} else {
ModelNode node = property.parentModelNode();
if (connectionView()->isSelectedModelNode(node)) {
appendRow(new BindingModelItem(property));
setCurrentProperty(property);
}
}
m_delegate->update(currentProperty(), m_connectionView);
}
@@ -250,15 +256,15 @@ BindingModelBackendDelegate::BindingModelBackendDelegate(BindingModel *parent)
, m_sourceNodeProperty()
{
connect(&m_sourceNode, &StudioQmlComboBoxBackend::activated, this, [this]() {
expressionChanged();
sourceNodeChanged();
});
connect(&m_sourceNodeProperty, &StudioQmlComboBoxBackend::activated, this, [this]() {
expressionChanged();
sourcePropertyNameChanged();
});
connect(&m_property, &StudioQmlComboBoxBackend::activated, this, [this]() {
propertyNameChanged();
targetPropertyNameChanged();
});
}
@@ -267,7 +273,7 @@ void BindingModelBackendDelegate::update(const BindingProperty &property, Abstra
if (!property.isValid())
return;
auto addName = [](QStringList&& list, const QString& name) {
auto addName = [](QStringList &&list, const QString &name) {
if (!list.contains(name))
list.prepend(name);
return std::move(list);
@@ -282,7 +288,8 @@ void BindingModelBackendDelegate::update(const BindingProperty &property, Abstra
m_sourceNode.setModel(sourceNodes);
m_sourceNode.setCurrentText(sourceNodeName);
auto sourceproperties = addName(availableSourceProperties(property, view), sourcePropertyName);
auto availableProperties = availableSourceProperties(sourceNodeName, property, view);
auto sourceproperties = addName(std::move(availableProperties), sourcePropertyName);
m_sourceNodeProperty.setModel(sourceproperties);
m_sourceNodeProperty.setCurrentText(sourcePropertyName);
@@ -316,14 +323,41 @@ StudioQmlComboBoxBackend *BindingModelBackendDelegate::sourceProperty()
return &m_sourceNodeProperty;
}
void BindingModelBackendDelegate::expressionChanged() const
void BindingModelBackendDelegate::sourceNodeChanged()
{
auto commit = [this]() {
BindingModel *model = qobject_cast<BindingModel *>(parent());
QTC_ASSERT(model, return);
ConnectionView *view = model->connectionView();
QTC_ASSERT(view, return);
QTC_ASSERT(view->isAttached(), return );
const QString sourceNode = m_sourceNode.currentText();
const QString sourceProperty = m_sourceNodeProperty.currentText();
BindingProperty targetProperty = model->currentProperty();
QStringList properties = availableSourceProperties(sourceNode, targetProperty, view);
if (!properties.contains(sourceProperty)) {
QSignalBlocker blocker(this);
properties.prepend("---");
m_sourceNodeProperty.setModel(properties);
m_sourceNodeProperty.setCurrentText({"---"});
}
sourcePropertyNameChanged();
}
void BindingModelBackendDelegate::sourcePropertyNameChanged() const
{
const QString sourceProperty = m_sourceNodeProperty.currentText();
if (sourceProperty.isEmpty() || sourceProperty == "---")
return;
auto commit = [this, sourceProperty]() {
BindingModel *model = qobject_cast<BindingModel *>(parent());
QTC_ASSERT(model, return);
const QString sourceNode = m_sourceNode.currentText();
const QString sourceProperty = m_sourceNodeProperty.currentText();
QString expression;
if (sourceProperty.isEmpty())
expression = sourceNode;
@@ -337,7 +371,7 @@ void BindingModelBackendDelegate::expressionChanged() const
callLater(commit);
}
void BindingModelBackendDelegate::propertyNameChanged() const
void BindingModelBackendDelegate::targetPropertyNameChanged() const
{
auto commit = [this]() {
BindingModel *model = qobject_cast<BindingModel *>(parent());

View File

@@ -86,8 +86,9 @@ public:
private:
QString targetNode() const;
void expressionChanged() const;
void propertyNameChanged() const;
void sourceNodeChanged();
void sourcePropertyNameChanged() const;
void targetPropertyNameChanged() const;
StudioQmlComboBoxBackend *property();
StudioQmlComboBoxBackend *sourceNode();

View File

@@ -760,14 +760,14 @@ QString ConnectionEditorEvaluator::getDisplayStringForType(const QString &statem
newDoc->parseJavaScript();
if (!newDoc->isParsedCorrectly())
return ConnectionEditorStatements::UNKNOWN_DISPLAY_NAME;
return ConnectionEditorStatements::CUSTOM_DISPLAY_NAME;
newDoc->ast()->accept(&evaluator);
const bool valid = evaluator.status() == ConnectionEditorEvaluator::Succeeded;
if (!valid)
return ConnectionEditorStatements::UNKNOWN_DISPLAY_NAME;
return ConnectionEditorStatements::CUSTOM_DISPLAY_NAME;
auto result = evaluator.resultNode();
@@ -967,11 +967,11 @@ bool ConnectionEditorEvaluator::visit(QmlJS::AST::FieldMemberExpression *fieldEx
bool ConnectionEditorEvaluator::visit(QmlJS::AST::CallExpression *callExpression)
{
if (d->isInIfCondition())
d->checkValidityAndReturn(false, "Functions are not allowd in the expressions");
return d->checkValidityAndReturn(false, "Functions are not allowd in the expressions");
MatchedStatement *currentStatement = d->currentStatement();
if (!currentStatement)
d->checkValidityAndReturn(false, "Invalid place to call an expression");
return d->checkValidityAndReturn(false, "Invalid place to call an expression");
if (ConnectionEditorStatements::isEmptyStatement(*currentStatement)) {
if (d->parentNodeStatus().childId() == 0) {

View File

@@ -21,8 +21,8 @@ inline constexpr char LOG_DISPLAY_NAME[] = QT_TRANSLATE_NOOP(
"QmlDesigner::ConnectionEditorStatements", "Print");
inline constexpr char EMPTY_DISPLAY_NAME[] = QT_TRANSLATE_NOOP(
"QmlDesigner::ConnectionEditorStatements", "Empty");
inline constexpr char UNKNOWN_DISPLAY_NAME[] = QT_TRANSLATE_NOOP(
"QmlDesigner::ConnectionEditorStatements", "Unknown");
inline constexpr char CUSTOM_DISPLAY_NAME[] = QT_TRANSLATE_NOOP(
"QmlDesigner::ConnectionEditorStatements", "Custom");
struct Variable;
struct MatchedFunction;

View File

@@ -2,7 +2,6 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "connectioneditorutils.h"
#include <QtCore/qvariant.h>
#include <abstractproperty.h>
#include <abstractview.h>
#include <bindingproperty.h>
@@ -58,61 +57,66 @@ PropertyName uniquePropertyName(const PropertyName &suggestion, const ModelNode
return {};
}
NodeMetaInfo dynamicTypeMetaInfo(const AbstractProperty& property)
NodeMetaInfo dynamicTypeNameToNodeMetaInfo(const TypeName &typeName, Model *model)
{
// Note: Uses old mechanism to create the NodeMetaInfo and supports
// only types we care about in the connection editor.
// TODO: Support all possible AbstractProperty types and move to the
// AbstractProperty class.
if (property.dynamicTypeName() == "bool")
return property.model()->boolMetaInfo();
else if (property.dynamicTypeName() == "int")
return property.model()->metaInfo("QML.int");
else if (property.dynamicTypeName() == "real")
return property.model()->metaInfo("QML.real");
else if (property.dynamicTypeName() == "color")
return property.model()->metaInfo("QML.color");
else if (property.dynamicTypeName() == "string")
return property.model()->metaInfo("QML.string");
else if (property.dynamicTypeName() == "url")
return property.model()->metaInfo("QML.url");
else if (property.dynamicTypeName() == "variant")
return property.model()->metaInfo("QML.variant");
if (typeName == "bool")
return model->boolMetaInfo();
else if (typeName == "int")
return model->metaInfo("QML.int");
else if (typeName == "real")
return model->metaInfo("QML.real");
else if (typeName == "color")
return model->metaInfo("QML.color");
else if (typeName == "string")
return model->metaInfo("QML.string");
else if (typeName == "url")
return model->metaInfo("QML.url");
else if (typeName == "variant")
return model->metaInfo("QML.variant");
else
qWarning() << __FUNCTION__ << " type " << property.dynamicTypeName() << "not found";
return { };
qWarning() << __FUNCTION__ << " type " << typeName << "not found";
return {};
}
bool metaInfoIsCompatibleUnsafe(const NodeMetaInfo &sourceType, const NodeMetaInfo &targetType)
NodeMetaInfo dynamicTypeMetaInfo(const AbstractProperty &property)
{
if (sourceType.isVariant())
return dynamicTypeNameToNodeMetaInfo(property.dynamicTypeName(), property.model());
}
bool metaInfoIsCompatibleUnsafe(const NodeMetaInfo &target, const NodeMetaInfo &source)
{
if (target.isVariant())
return true;
if (sourceType.isBool() && targetType.isBool())
if (target == source)
return true;
if (sourceType == targetType)
if (target.isBool() && source.isBool())
return true;
if (sourceType.isNumber() && targetType.isNumber())
if (target.isNumber() && source.isNumber())
return true;
if (sourceType.isString() && targetType.isString())
if (target.isString() && source.isString())
return true;
if (sourceType.isUrl() && targetType.isUrl())
if (target.isUrl() && source.isUrl())
return true;
if (sourceType.isColor() && targetType.isColor())
if (target.isColor() && source.isColor())
return true;
return false;
}
bool metaInfoIsCompatible(const NodeMetaInfo &sourceType, const PropertyMetaInfo &metaInfo)
bool metaInfoIsCompatible(const NodeMetaInfo &targetType, const PropertyMetaInfo &sourceInfo)
{
NodeMetaInfo targetType = metaInfo.propertyType();
return metaInfoIsCompatibleUnsafe(sourceType, targetType);
NodeMetaInfo sourceType = sourceInfo.propertyType();
return metaInfoIsCompatibleUnsafe(targetType, sourceType);
}
QVariant typeConvertVariant(const QVariant &variant, const QmlDesigner::TypeName &typeName)
@@ -194,7 +198,7 @@ void convertBindingToVariantProperty(const BindingProperty &property, const QVar
convertPropertyType(property, value);
}
bool isBindingExpression(const QVariant& value)
bool isBindingExpression(const QVariant &value)
{
if (value.metaType().id() != QMetaType::QString)
return false;
@@ -250,11 +254,30 @@ QString defaultExpressionForType(const TypeName &type)
return expression;
}
std::pair<QString, QString> splitExpression(const QString &expression)
{
// ### Todo from original code (getExpressionStrings):
// We assume no expressions yet
const QStringList stringList = expression.split(QLatin1String("."));
QString sourceNode = stringList.constFirst();
QString propertyName;
for (int i = 1; i < stringList.size(); ++i) {
propertyName += stringList.at(i);
if (i != stringList.size() - 1)
propertyName += QLatin1String(".");
}
if (propertyName.isEmpty())
std::swap(sourceNode, propertyName);
return {sourceNode, propertyName};
}
QStringList singletonsFromView(AbstractView *view)
{
RewriterView *rv = view->rewriterView();
if (!rv)
return { };
return {};
QStringList out;
for (const QmlTypeData &data : rv->getQMLTypes()) {
@@ -264,6 +287,29 @@ QStringList singletonsFromView(AbstractView *view)
return out;
}
std::vector<PropertyMetaInfo> propertiesFromSingleton(const QString &name, AbstractView *view)
{
Model *model = view->model();
QTC_ASSERT(model, return {});
if (NodeMetaInfo metaInfo = model->metaInfo(name.toUtf8()); metaInfo.isValid())
return metaInfo.properties();
return {};
}
QList<AbstractProperty> dynamicPropertiesFromNode(const ModelNode &node)
{
auto isDynamic = [](const AbstractProperty &p) { return p.isDynamic(); };
auto byName = [](const AbstractProperty &a, const AbstractProperty &b) {
return a.name() < b.name();
};
QList<AbstractProperty> dynamicProperties = Utils::filtered(node.properties(), isDynamic);
Utils::sort(dynamicProperties, byName);
return dynamicProperties;
}
QStringList availableSources(AbstractView *view)
{
QStringList sourceNodes;
@@ -295,7 +341,7 @@ QStringList availableTargetProperties(const BindingProperty &bindingProperty)
return writableProperties;
}
return { };
return {};
}
ModelNode getNodeByIdOrParent(AbstractView *view, const QString &id, const ModelNode &targetNode)
@@ -309,19 +355,17 @@ ModelNode getNodeByIdOrParent(AbstractView *view, const QString &id, const Model
return {};
}
QStringList availableSourceProperties(const BindingProperty &bindingProperty, AbstractView *view)
QStringList availableSourceProperties(const QString &id,
const BindingProperty &targetProperty,
AbstractView *view)
{
const QString expression = bindingProperty.expression();
const QStringList stringlist = expression.split(QLatin1String("."));
ModelNode modelNode = getNodeByIdOrParent(view, id, targetProperty.parentModelNode());
const QString &id = stringlist.constFirst();
ModelNode modelNode = getNodeByIdOrParent(view, id, bindingProperty.parentModelNode());
NodeMetaInfo type;
if (bindingProperty.isDynamic()) {
type = dynamicTypeMetaInfo(bindingProperty);
} else if (auto metaInfo = bindingProperty.parentModelNode().metaInfo(); metaInfo.isValid()) {
type = metaInfo.property(bindingProperty.name()).propertyType();
NodeMetaInfo targetType;
if (targetProperty.isDynamic()) {
targetType = dynamicTypeMetaInfo(targetProperty);
} else if (auto metaInfo = targetProperty.parentModelNode().metaInfo(); metaInfo.isValid()) {
targetType = metaInfo.property(targetProperty.name()).propertyType();
} else
qWarning() << __FUNCTION__ << " no meta info for target node";
@@ -329,23 +373,19 @@ QStringList availableSourceProperties(const BindingProperty &bindingProperty, Ab
if (!modelNode.isValid()) {
QStringList singletons = singletonsFromView(view);
if (singletons.contains(id)) {
Model *model = view->model();
QTC_ASSERT(model, return {});
if (NodeMetaInfo metaInfo = model->metaInfo(id.toUtf8()); metaInfo.isValid()) {
for (const auto &property : metaInfo.properties()) {
if (metaInfoIsCompatible(type, property))
possibleProperties.push_back(QString::fromUtf8(property.name()));
}
for (const auto &property : propertiesFromSingleton(id, view)) {
if (metaInfoIsCompatible(targetType, property))
possibleProperties.push_back(QString::fromUtf8(property.name()));
}
return possibleProperties;
}
qWarning() << __FUNCTION__ << " invalid model node";
qWarning() << __FUNCTION__ << " invalid model node: " << id;
return {};
}
}
auto isCompatible = [type](const AbstractProperty& other) {
auto isCompatible = [targetType](const AbstractProperty &other) {
auto otherType = dynamicTypeMetaInfo(other);
return metaInfoIsCompatibleUnsafe(type, otherType);
return metaInfoIsCompatibleUnsafe(targetType, otherType);
};
for (const VariantProperty &variantProperty : modelNode.variantProperties()) {
@@ -361,7 +401,7 @@ QStringList availableSourceProperties(const BindingProperty &bindingProperty, Ab
NodeMetaInfo metaInfo = modelNode.metaInfo();
if (metaInfo.isValid()) {
for (const auto &property : metaInfo.properties()) {
if (metaInfoIsCompatible(type, property) )
if (metaInfoIsCompatible(targetType, property))
possibleProperties.push_back(QString::fromUtf8(property.name()));
}
} else {
@@ -371,35 +411,4 @@ QStringList availableSourceProperties(const BindingProperty &bindingProperty, Ab
return possibleProperties;
}
QList<AbstractProperty> dynamicPropertiesFromNode(const ModelNode &node)
{
auto isDynamic = [](const AbstractProperty &p) { return p.isDynamic(); };
auto byName = [](const AbstractProperty &a, const AbstractProperty &b) {
return a.name() < b.name();
};
QList<AbstractProperty> dynamicProperties = Utils::filtered(node.properties(), isDynamic);
Utils::sort(dynamicProperties, byName);
return dynamicProperties;
}
std::pair<QString, QString> splitExpression(const QString &expression)
{
// ### Todo from original code (getExpressionStrings):
// We assume no expressions yet
const QStringList stringList = expression.split(QLatin1String("."));
QString sourceNode = stringList.constFirst();
QString propertyName;
for (int i = 1; i < stringList.size(); ++i) {
propertyName += stringList.at(i);
if (i != stringList.size() - 1)
propertyName += QLatin1String(".");
}
if (propertyName.isEmpty())
std::swap(sourceNode, propertyName);
return {sourceNode, propertyName};
}
} // namespace QmlDesigner

View File

@@ -24,21 +24,27 @@ void showErrorMessage(const QString &text);
QString idOrTypeName(const ModelNode &modelNode);
PropertyName uniquePropertyName(const PropertyName &suggestion, const ModelNode &modelNode);
NodeMetaInfo dynamicTypeMetaInfo(const AbstractProperty& property);
NodeMetaInfo dynamicTypeMetaInfo(const AbstractProperty &property);
NodeMetaInfo dynamicTypeNameToNodeMetaInfo(const TypeName &typeName, Model *model);
QVariant typeConvertVariant(const QVariant &variant, const QmlDesigner::TypeName &typeName);
void convertVariantToBindingProperty(const VariantProperty &property, const QVariant &value);
void convertBindingToVariantProperty(const BindingProperty &property, const QVariant &value);
bool isBindingExpression(const QVariant& value);
bool isBindingExpression(const QVariant &value);
bool isDynamicVariantPropertyType(const TypeName &type);
QVariant defaultValueForType(const TypeName &type);
QString defaultExpressionForType(const TypeName &type);
QStringList availableSources(AbstractView *view);
QStringList availableTargetProperties(const BindingProperty &bindingProperty);
QStringList availableSourceProperties(const BindingProperty &bindingProperty, AbstractView *view);
QList<AbstractProperty> dynamicPropertiesFromNode(const ModelNode &node);
std::pair<QString, QString> splitExpression(const QString &expression);
QStringList singletonsFromView(AbstractView *view);
std::vector<PropertyMetaInfo> propertiesFromSingleton(const QString &name, AbstractView *view);
QList<AbstractProperty> dynamicPropertiesFromNode(const ModelNode &node);
QStringList availableSources(AbstractView *view);
QStringList availableTargetProperties(const BindingProperty &bindingProperty);
QStringList availableSourceProperties(const QString &id,
const BindingProperty &targetProp,
AbstractView *view);
} // namespace QmlDesigner

View File

@@ -8,21 +8,25 @@
#include <bindingproperty.h>
#include <connectioneditorevaluator.h>
#include <exception.h>
#include <model/modelutils.h>
#include <nodeabstractproperty.h>
#include <nodelistproperty.h>
#include <nodemetainfo.h>
#include <qmldesignerconstants.h>
#include <qmldesignerplugin.h>
#include <plaintexteditmodifier.h>
#include <rewritertransaction.h>
#include <rewriterview.h>
#include <signalhandlerproperty.h>
#include <variantproperty.h>
#include <qmldesignerconstants.h>
#include <qmldesignerplugin.h>
#include <utils/qtcassert.h>
#include <QStandardItemModel>
#include <QMessageBox>
#include <QStandardItemModel>
#include <QTableView>
#include <QTextCursor>
#include <QTextDocument>
#include <QTimer>
namespace {
@@ -66,7 +70,7 @@ Qt::ItemFlags ConnectionModel::flags(const QModelIndex &modelIndex) const
const int internalId = data(index(modelIndex.row(), TargetModelNodeRow), UserRoles::InternalIdRole).toInt();
ModelNode modelNode = m_connectionView->modelNodeForInternalId(internalId);
if (modelNode.isValid() && ModelNode::isThisOrAncestorLocked(modelNode))
if (modelNode.isValid() && ModelUtils::isThisOrAncestorLocked(modelNode))
return Qt::ItemIsEnabled;
return Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled;
@@ -300,6 +304,46 @@ ModelNode ConnectionModel::getTargetNodeForConnection(const ModelNode &connectio
return result;
}
static QString addOnToSignalName(const QString &signal)
{
QString ret = signal;
ret[0] = ret.at(0).toUpper();
ret.prepend("on");
return ret;
}
static PropertyName getFirstSignalForTarget(const NodeMetaInfo &target)
{
PropertyName ret = "clicked";
if (!target.isValid())
return ret;
const auto signalNames = target.signalNames();
if (signalNames.isEmpty())
return ret;
const PropertyNameList priorityList = {"clicked",
"toggled",
"started",
"stopped",
"moved",
"valueChanged",
"visualPostionChanged",
"accepted",
"currentIndexChanged",
"activeFocusChanged"};
for (const auto &signal : priorityList) {
if (signalNames.contains(signal))
return signal;
}
ret = target.signalNames().first();
return ret;
}
void ConnectionModel::addConnection()
{
QmlDesignerPlugin::emitUsageStatistics(Constants::EVENT_CONNECTION_ADDED);
@@ -311,33 +355,46 @@ void ConnectionModel::addConnection()
NodeMetaInfo nodeMetaInfo = connectionView()->model()->qtQuickConnectionsMetaInfo();
if (nodeMetaInfo.isValid()) {
connectionView()->executeInTransaction("ConnectionModel::addConnection", [=, &rootModelNode](){
ModelNode newNode = connectionView()->createModelNode("QtQuick.Connections",
nodeMetaInfo.majorVersion(),
nodeMetaInfo.minorVersion());
QString source = "console.log(\"clicked\")";
ModelNode selectedNode = connectionView()->selectedModelNodes().constFirst();
const PropertyName signalHandlerName = addOnToSignalName(
QString::fromUtf8(getFirstSignalForTarget(
selectedNode.metaInfo())))
.toUtf8();
if (connectionView()->selectedModelNodes().size() == 1) {
ModelNode selectedNode = connectionView()->selectedModelNodes().constFirst();
if (QmlItemNode::isValidQmlItemNode(selectedNode))
selectedNode.nodeAbstractProperty("data").reparentHere(newNode);
else
rootModelNode.nodeAbstractProperty(rootModelNode.metaInfo().defaultPropertyName()).reparentHere(newNode);
connectionView()
->executeInTransaction("ConnectionModel::addConnection", [=, &rootModelNode]() {
ModelNode newNode = connectionView()
->createModelNode("QtQuick.Connections",
nodeMetaInfo.majorVersion(),
nodeMetaInfo.minorVersion());
QString source = "console.log(\"clicked\")";
if (QmlItemNode(selectedNode).isFlowActionArea() || QmlVisualNode(selectedNode).isFlowTransition())
source = selectedNode.validId() + ".trigger()";
if (connectionView()->selectedModelNodes().size() == 1) {
ModelNode selectedNode = connectionView()->selectedModelNodes().constFirst();
if (QmlItemNode::isValidQmlItemNode(selectedNode))
selectedNode.nodeAbstractProperty("data").reparentHere(newNode);
else
rootModelNode
.nodeAbstractProperty(rootModelNode.metaInfo().defaultPropertyName())
.reparentHere(newNode);
if (!connectionView()->selectedModelNodes().constFirst().id().isEmpty())
newNode.bindingProperty("target").setExpression(selectedNode.validId());
} else {
rootModelNode.nodeAbstractProperty(rootModelNode.metaInfo().defaultPropertyName()).reparentHere(newNode);
newNode.bindingProperty("target").setExpression(rootModelNode.validId());
}
if (QmlItemNode(selectedNode).isFlowActionArea()
|| QmlVisualNode(selectedNode).isFlowTransition())
source = selectedNode.validId() + ".trigger()";
newNode.signalHandlerProperty("onClicked").setSource(source);
if (!connectionView()->selectedModelNodes().constFirst().id().isEmpty())
newNode.bindingProperty("target").setExpression(selectedNode.validId());
} else {
rootModelNode
.nodeAbstractProperty(rootModelNode.metaInfo().defaultPropertyName())
.reparentHere(newNode);
newNode.bindingProperty("target").setExpression(rootModelNode.validId());
}
selectProperty(newNode.signalHandlerProperty("onClicked"));
});
newNode.signalHandlerProperty(signalHandlerName).setSource(source);
selectProperty(newNode.signalHandlerProperty(signalHandlerName));
});
}
}
}
@@ -363,7 +420,11 @@ void ConnectionModel::abstractPropertyChanged(const AbstractProperty &abstractPr
void ConnectionModel::deleteConnectionByRow(int currentRow)
{
SignalHandlerProperty targetSignal = signalHandlerPropertyForRow(currentRow);
QTC_ASSERT(targetSignal.isValid(), return );
SignalHandlerProperty selectedSignal = signalHandlerPropertyForRow(currentIndex());
const bool targetEqualsSelected = targetSignal == selectedSignal;
QTC_ASSERT(targetSignal.isValid(), return);
ModelNode node = targetSignal.parentModelNode();
QTC_ASSERT(node.isValid(), return );
@@ -374,6 +435,9 @@ void ConnectionModel::deleteConnectionByRow(int currentRow)
} else {
node.destroy();
}
if (!targetEqualsSelected)
selectProperty(selectedSignal);
}
void ConnectionModel::removeRowFromTable(const SignalHandlerProperty &property)
@@ -424,6 +488,17 @@ void ConnectionModel::selectProperty(const SignalHandlerProperty &property)
}
}
void ConnectionModel::nodeAboutToBeRemoved(const ModelNode &removedNode)
{
SignalHandlerProperty selectedSignal = signalHandlerPropertyForRow(currentIndex());
if (selectedSignal.isValid()) {
ModelNode targetNode = getTargetNodeForConnection(selectedSignal.parentModelNode());
if (targetNode == removedNode) {
emit m_delegate->popupShouldClose();
}
}
}
void ConnectionModel::handleException()
{
QMessageBox::warning(nullptr, tr("Error"), m_exceptionError);
@@ -645,7 +720,6 @@ QString generateDefaultStatement(ConnectionModelBackendDelegate::ActionType acti
void ConnectionModelBackendDelegate::changeActionType(ActionType actionType)
{
qDebug() << Q_FUNC_INFO << actionType;
QTC_ASSERT(actionType != ConnectionModelStatementDelegate::Custom, return );
ConnectionModel *model = qobject_cast<ConnectionModel *>(parent());
@@ -664,14 +738,19 @@ void ConnectionModelBackendDelegate::changeActionType(ActionType actionType)
ConnectionEditorStatements::MatchedStatement &okStatement
= ConnectionEditorStatements::okStatement(m_handler);
ConnectionEditorStatements::MatchedStatement &koStatement
= ConnectionEditorStatements::koStatement(m_handler);
koStatement = ConnectionEditorStatements::EmptyBlock();
//We expect a valid id on the root node
const QString validId = model->connectionView()->rootModelNode().validId();
QString statementSource = generateDefaultStatement(actionType, validId);
auto tempHandler = ConnectionEditorEvaluator::parseStatement(statementSource);
qDebug() << ConnectionEditorStatements::toString(tempHandler);
auto newOkStatement = ConnectionEditorStatements::okStatement(tempHandler);
qDebug() << "newOk" << statementSource;
QTC_ASSERT(!ConnectionEditorStatements::isEmptyStatement(newOkStatement), return );
okStatement = newOkStatement;
@@ -758,6 +837,14 @@ void ConnectionModelBackendDelegate::removeElse()
setupHandlerAndStatements();
}
void ConnectionModelBackendDelegate::setNewSource(const QString &newSource)
{
setSource(newSource);
commitNewSource(newSource);
setupHandlerAndStatements();
setupCondition();
}
int ConnectionModelBackendDelegate::currentRow() const
{
return m_currentRow;
@@ -773,14 +860,6 @@ QString removeOnFromSignalName(const QString &signal)
return ret;
}
QString addOnToSignalName(const QString &signal)
{
QString ret = signal;
ret[0] = ret.at(0).toUpper();
ret.prepend("on");
return ret;
}
void ConnectionModelBackendDelegate::setCurrentRow(int i)
{
if (m_currentRow == i)
@@ -796,6 +875,9 @@ void ConnectionModelBackendDelegate::update()
if (m_blockReflection)
return;
if (m_currentRow == -1)
return;
m_propertyTreeModel.resetModel();
m_propertyListProxyModel.setRowAndInternalId(0, internalRootIndex);
@@ -831,9 +913,6 @@ void ConnectionModelBackendDelegate::update()
setupHandlerAndStatements();
qDebug() << Q_FUNC_INFO << ConnectionEditorStatements::toString(m_handler)
<< ConnectionEditorStatements::toJavascript(m_handler);
setupCondition();
QTC_ASSERT(model, return );
@@ -897,6 +976,19 @@ ConditionListModel *ConnectionModelBackendDelegate::conditionListModel()
return &m_conditionListModel;
}
QString ConnectionModelBackendDelegate::indentedSource() const
{
if (m_source.isEmpty())
return {};
QTextDocument doc(m_source);
QTextCursor cursor(&doc);
IndentingTextEditModifier mod(&doc, cursor);
mod.indent(0, m_source.length() - 1);
return mod.text();
}
QString ConnectionModelBackendDelegate::source() const
{
return m_source;
@@ -1422,7 +1514,6 @@ void ConnectionModelStatementDelegate::setupCallFunction()
void ConnectionModelStatementDelegate::setupChangeState()
{
qDebug() << Q_FUNC_INFO;
QTC_ASSERT(std::holds_alternative<ConnectionEditorStatements::StateSet>(m_statement), return );
QTC_ASSERT(m_model->connectionView()->isAttached(), return );
@@ -1476,8 +1567,6 @@ void ConnectionModelStatementDelegate::setupStates()
const auto stateSet = std::get<ConnectionEditorStatements::StateSet>(m_statement);
qDebug() << Q_FUNC_INFO << stateSet.stateName;
const QString nodeId = m_stateTargets.currentText();
const ModelNode node = m_model->connectionView()->modelNodeForId(nodeId);
@@ -1734,20 +1823,16 @@ void ConditionListModel::command(const QString &string)
//TODO remove from prodcution code
QStringList list = string.split("%", Qt::SkipEmptyParts);
qDebug() << Q_FUNC_INFO << string << list.size();
if (list.size() < 2)
return;
if (list.size() == 2) {
if (list.first() == "A") {
qDebug() << "Append" << list.last();
appendToken(list.last());
} else if (list.first() == "R") {
bool ok = true;
int index = list.last().toInt(&ok);
qDebug() << "Remove" << index;
if (ok)
removeToken(index);
}
@@ -1760,15 +1845,12 @@ void ConditionListModel::command(const QString &string)
if (ok)
updateToken(index, list.last());
qDebug() << "Update" << index << list.last();
} else if (list.first() == "I") {
bool ok = true;
int index = list.at(1).toInt(&ok);
if (ok)
insertToken(index, list.last());
qDebug() << "Insert" << index << list.last();
}
}
}
@@ -2027,4 +2109,9 @@ ConnectionEditorStatements::ComparativeStatement ConditionListModel::toStatement
return {};
}
void QmlDesigner::ConnectionModel::modelAboutToBeDetached()
{
emit m_delegate->popupShouldClose();
}
} // namespace QmlDesigner

View File

@@ -71,6 +71,9 @@ public:
void selectProperty(const SignalHandlerProperty &property);
void nodeAboutToBeRemoved(const ModelNode &removedNode);
void modelAboutToBeDetached();
signals:
void currentIndexChanged();
@@ -266,6 +269,7 @@ class ConnectionModelBackendDelegate : public QObject
Q_PROPERTY(bool hasCondition READ hasCondition NOTIFY hasConditionChanged)
Q_PROPERTY(bool hasElse READ hasElse NOTIFY hasElseChanged)
Q_PROPERTY(QString source READ source NOTIFY sourceChanged)
Q_PROPERTY(QString indentedSource READ indentedSource NOTIFY sourceChanged)
Q_PROPERTY(PropertyTreeModel *propertyTreeModel READ propertyTreeModel CONSTANT)
Q_PROPERTY(PropertyListProxyModel *propertyListProxyModel READ propertyListProxyModel CONSTANT)
@@ -284,6 +288,8 @@ public:
Q_INVOKABLE void addElse();
Q_INVOKABLE void removeElse();
Q_INVOKABLE void setNewSource(const QString &newSource);
void setCurrentRow(int i);
void update();
@@ -293,6 +299,7 @@ signals:
void hasConditionChanged();
void hasElseChanged();
void sourceChanged();
void popupShouldClose();
private:
int currentRow() const;
@@ -306,6 +313,7 @@ private:
ConnectionModelStatementDelegate *okStatement();
ConnectionModelStatementDelegate *koStatement();
ConditionListModel *conditionListModel();
QString indentedSource() const;
QString source() const;
void setSource(const QString &source);

View File

@@ -169,6 +169,7 @@ void ConnectionView::modelAboutToBeDetached(Model *model)
bindingModel()->reset();
dynamicPropertiesModel()->reset();
connectionModel()->resetModel();
connectionModel()->modelAboutToBeDetached();
}
void ConnectionView::nodeCreated(const ModelNode & /*createdNode*/)
@@ -176,6 +177,11 @@ void ConnectionView::nodeCreated(const ModelNode & /*createdNode*/)
connectionModel()->resetModel();
}
void ConnectionView::nodeAboutToBeRemoved(const ModelNode &removedNode)
{
connectionModel()->nodeAboutToBeRemoved(removedNode);
}
void ConnectionView::nodeRemoved(const ModelNode & /*removedNode*/,
const NodeAbstractProperty & /*parentProperty*/,
AbstractView::PropertyChangeFlags /*propertyChange*/)

View File

@@ -39,6 +39,7 @@ public:
void modelAboutToBeDetached(Model *model) override;
void nodeCreated(const ModelNode &createdNode) override;
void nodeAboutToBeRemoved(const ModelNode &removedNode) override;
void nodeRemoved(const ModelNode &removedNode, const NodeAbstractProperty &parentProperty, PropertyChangeFlags propertyChange) override;
void nodeReparented(const ModelNode &node, const NodeAbstractProperty &newPropertyParent,
const NodeAbstractProperty &oldPropertyParent, AbstractView::PropertyChangeFlags propertyChange) override;

View File

@@ -151,10 +151,15 @@ void DynamicPropertiesModel::updateItem(const AbstractProperty &property)
if (!property.isDynamic())
return;
if (auto *item = itemForProperty(property))
if (auto *item = itemForProperty(property)) {
item->updateProperty(property);
else
addProperty(property);
} else {
ModelNode node = property.parentModelNode();
if (selectedNodes().contains(node)) {
addProperty(property);
setCurrentProperty(property);
}
}
}
void DynamicPropertiesModel::removeItem(const AbstractProperty &property)

View File

@@ -101,10 +101,45 @@ const std::vector<PropertyName> priorityListSignals = {"clicked",
"opacityChanged",
"rotationChanged"};
const std::vector<PropertyName> priorityListProperties
= {"opacity", "visible", "value", "x", "y", "width", "height", "rotation",
"color", "scale", "state", "enabled", "z", "text", "pressed", "containsMouse",
"checked", "hovered", "down", "clip", "parent", "from", "true", "focus"};
const std::vector<PropertyName> priorityListProperties = {"opacity",
"checked",
"hovered",
"visible",
"value",
"down",
"x",
"y",
"width",
"height",
"from",
"to",
"rotation",
"color",
"scale",
"state",
"enabled",
"z",
"text",
"pressed",
"containsMouse",
"down",
"clip",
"parent",
"radius",
"smooth",
"true",
"focus",
"border.width",
"border.color",
"eulerRotation.x",
"eulerRotation.y",
"eulerRotation.z",
"scale.x",
"scale.y",
"scale.z",
"position.x",
"position.y",
"position.z"};
const std::vector<PropertyName> priorityListSlots = {"toggle",
"increase",
@@ -335,6 +370,11 @@ int PropertyTreeModel::columnCount(const QModelIndex &) const
return 1;
}
void PropertyTreeModel::setIncludeDotPropertiesOnFirstLevel(bool b)
{
m_includeDotPropertiesOnFirstLevel = b;
}
void PropertyTreeModel::setPropertyType(PropertyTypes type)
{
if (m_type == type)
@@ -498,7 +538,6 @@ const std::vector<PropertyName> PropertyTreeModel::getDynamicProperties(
return propertyType == "url";
case ColorType:
return propertyType == "color";
case BoolType:
return propertyType == "bool";
default:
@@ -519,12 +558,8 @@ const std::vector<PropertyName> PropertyTreeModel::sortedAndFilteredPropertyName
const PropertyName name = metaInfo.name();
if (name.contains("."))
return false;
if (name.startsWith("icon."))
return false;
if (name.startsWith("transformOriginPoint."))
if (!m_includeDotPropertiesOnFirstLevel
&& name.contains("."))
return false;
return filterProperty(name, metaInfo, recursive);
@@ -721,6 +756,9 @@ bool PropertyTreeModel::filterProperty(const PropertyName &name,
const NodeMetaInfo propertyType = metaInfo.propertyType();
if (m_includeDotPropertiesOnFirstLevel && metaInfo.isPointer())
return false;
//We want to keep sub items with matching properties
if (!recursive && metaInfo.isPointer()
&& sortedAndFilteredPropertyNames(propertyType, true).size() > 0)
@@ -842,6 +880,8 @@ PropertyTreeModelDelegate::PropertyTreeModelDelegate(ConnectionView *parent) : m
connect(&m_idCombboBox, &StudioQmlComboBoxBackend::activated, this, [this]() {
handleIdChanged();
});
m_model.setIncludeDotPropertiesOnFirstLevel(true);
}
void PropertyTreeModelDelegate::setPropertyType(PropertyTreeModel::PropertyTypes type)

View File

@@ -65,6 +65,8 @@ public:
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
void setIncludeDotPropertiesOnFirstLevel(bool b);
struct DataCacheItem
{
ModelNode modelNode;
@@ -125,6 +127,7 @@ private:
PropertyTypes m_type = AllTypes;
QString m_filter;
mutable QHash<ModelNode, std::vector<PropertyName>> m_sortedAndFilteredPropertyNamesSignalsSlots;
bool m_includeDotPropertiesOnFirstLevel = false;
};
class PropertyListProxyModel : public QAbstractListModel

View File

@@ -11,6 +11,7 @@
#include "contentlibrarytexture.h"
#include "contentlibrarytexturesmodel.h"
#include "contentlibrarywidget.h"
#include "externaldependenciesinterface.h"
#include "nodelistproperty.h"
#include "qmldesignerconstants.h"
#include "qmlobjectnode.h"
@@ -223,6 +224,7 @@ void ContentLibraryView::modelAttached(Model *model)
const bool hasLibrary = materialLibraryNode().isValid();
m_widget->setHasMaterialLibrary(hasLibrary);
m_widget->setHasQuick3DImport(m_hasQuick3DImport);
m_widget->setIsQt6Project(externalDependencies().isQt6Project());
m_sceneId = model->active3DSceneId();

View File

@@ -681,6 +681,20 @@ void ContentLibraryWidget::setHasActive3DScene(bool b)
emit hasActive3DSceneChanged();
}
bool ContentLibraryWidget::isQt6Project() const
{
return m_isQt6Project;
}
void ContentLibraryWidget::setIsQt6Project(bool b)
{
if (m_isQt6Project == b)
return;
m_isQt6Project = b;
emit isQt6ProjectChanged();
}
void ContentLibraryWidget::reloadQmlSource()
{
const QString materialBrowserQmlPath = qmlSourcesPath() + "/ContentLibrary.qml";

View File

@@ -32,6 +32,7 @@ class ContentLibraryWidget : public QFrame
Q_PROPERTY(bool hasQuick3DImport READ hasQuick3DImport NOTIFY hasQuick3DImportChanged)
Q_PROPERTY(bool hasMaterialLibrary READ hasMaterialLibrary NOTIFY hasMaterialLibraryChanged)
Q_PROPERTY(bool hasActive3DScene READ hasActive3DScene WRITE setHasActive3DScene NOTIFY hasActive3DSceneChanged)
Q_PROPERTY(bool isQt6Project READ isQt6Project NOTIFY isQt6ProjectChanged)
// Needed for a workaround for a bug where after drag-n-dropping an item, the ScrollView scrolls to a random position
Q_PROPERTY(bool isDragging MEMBER m_isDragging NOTIFY isDraggingChanged)
@@ -53,6 +54,9 @@ public:
bool hasActive3DScene() const;
void setHasActive3DScene(bool b);
bool isQt6Project() const;
void setIsQt6Project(bool b);
Q_INVOKABLE void handleSearchFilterChanged(const QString &filterText);
void setMaterialsModel(QPointer<ContentLibraryMaterialsModel> newMaterialsModel);
@@ -83,6 +87,7 @@ signals:
void hasMaterialLibraryChanged();
void hasActive3DSceneChanged();
void isDraggingChanged();
void isQt6ProjectChanged();
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
@@ -121,6 +126,7 @@ private:
bool m_hasActive3DScene = false;
bool m_hasQuick3DImport = false;
bool m_isDragging = false;
bool m_isQt6Project = false;
QString m_baseUrl;
QString m_texturesUrl;
QString m_textureIconsUrl;

View File

@@ -120,7 +120,7 @@ void Edit3DView::updateActiveScene3D(const QVariantMap &sceneState)
const QString cameraFrustumKey = QStringLiteral("showCameraFrustum");
const QString particleEmitterKey = QStringLiteral("showParticleEmitter");
const QString particlesPlayKey = QStringLiteral("particlePlay");
const QString syncBgColorKey = QStringLiteral("syncBackgroundColor");
const QString syncEnvBgKey = QStringLiteral("syncEnvBackground");
if (sceneState.contains(sceneKey)) {
qint32 newActiveScene = sceneState[sceneKey].value<qint32>();
@@ -195,9 +195,11 @@ void Edit3DView::updateActiveScene3D(const QVariantMap &sceneState)
bool syncValue = false;
bool syncEnabled = false;
bool desiredSyncValue = false;
if (sceneState.contains(syncBgColorKey))
desiredSyncValue = sceneState[syncBgColorKey].toBool();
if (sceneState.contains(syncEnvBgKey))
desiredSyncValue = sceneState[syncEnvBgKey].toBool();
ModelNode checkNode = active3DSceneNode();
const bool activeSceneValid = checkNode.isValid();
while (checkNode.isValid()) {
if (checkNode.metaInfo().isQtQuick3DView3D()) {
syncValue = desiredSyncValue;
@@ -210,15 +212,15 @@ void Edit3DView::updateActiveScene3D(const QVariantMap &sceneState)
break;
}
if (syncValue != desiredSyncValue) {
if (activeSceneValid && syncValue != desiredSyncValue) {
// Update actual toolstate as well if we overrode it.
QTimer::singleShot(0, this, [this, syncValue]() {
emitView3DAction(View3DActionType::SyncBackgroundColor, syncValue);
emitView3DAction(View3DActionType::SyncEnvBackground, syncValue);
});
}
m_syncBackgroundColorAction->action()->setChecked(syncValue);
m_syncBackgroundColorAction->action()->setEnabled(syncEnabled);
m_syncEnvBackgroundAction->action()->setChecked(syncValue);
m_syncEnvBackgroundAction->action()->setEnabled(syncEnabled);
// Selection context change updates visible and enabled states
SelectionContext selectionContext(this);
@@ -449,23 +451,23 @@ QSize Edit3DView::canvasSize() const
return {};
}
void Edit3DView::createSelectBackgroundColorAction(QAction *syncBackgroundColorAction)
void Edit3DView::createSelectBackgroundColorAction(QAction *syncEnvBackgroundAction)
{
QString description = QCoreApplication::translate("SelectBackgroundColorAction",
"Select Background Color");
QString tooltip = QCoreApplication::translate("SelectBackgroundColorAction",
"Select a color for the background of the 3D view.");
auto operation = [this, syncBackgroundColorAction](const SelectionContext &) {
auto operation = [this, syncEnvBackgroundAction](const SelectionContext &) {
BackgroundColorSelection::showBackgroundColorSelectionWidget(
edit3DWidget(),
DesignerSettingsKey::EDIT3DVIEW_BACKGROUND_COLOR,
this,
edit3dBgColorProperty,
[this, syncBackgroundColorAction]() {
if (syncBackgroundColorAction->isChecked()) {
emitView3DAction(View3DActionType::SyncBackgroundColor, false);
syncBackgroundColorAction->setChecked(false);
[this, syncEnvBackgroundAction]() {
if (syncEnvBackgroundAction->isChecked()) {
emitView3DAction(View3DActionType::SyncEnvBackground, false);
syncEnvBackgroundAction->setChecked(false);
}
});
};
@@ -510,14 +512,14 @@ void Edit3DView::createGridColorSelectionAction()
tooltip);
}
void Edit3DView::createResetColorAction(QAction *syncBackgroundColorAction)
void Edit3DView::createResetColorAction(QAction *syncEnvBackgroundAction)
{
QString description = QCoreApplication::translate("ResetEdit3DColorsAction", "Reset Colors");
QString tooltip = QCoreApplication::translate("ResetEdit3DColorsAction",
"Reset the background color and the color of the "
"grid lines of the 3D view to the default values.");
auto operation = [this, syncBackgroundColorAction](const SelectionContext &) {
auto operation = [this, syncEnvBackgroundAction](const SelectionContext &) {
QList<QColor> bgColors = {QRgb(0x222222), QRgb(0x999999)};
Edit3DViewConfig::setColors(this, edit3dBgColorProperty, bgColors);
Edit3DViewConfig::saveColors(DesignerSettingsKey::EDIT3DVIEW_BACKGROUND_COLOR, bgColors);
@@ -526,9 +528,9 @@ void Edit3DView::createResetColorAction(QAction *syncBackgroundColorAction)
Edit3DViewConfig::setColors(this, edit3dGridColorProperty, {gridColor});
Edit3DViewConfig::saveColors(DesignerSettingsKey::EDIT3DVIEW_GRID_COLOR, {gridColor});
if (syncBackgroundColorAction->isChecked()) {
emitView3DAction(View3DActionType::SyncBackgroundColor, false);
syncBackgroundColorAction->setChecked(false);
if (syncEnvBackgroundAction->isChecked()) {
emitView3DAction(View3DActionType::SyncEnvBackground, false);
syncEnvBackgroundAction->setChecked(false);
}
};
@@ -545,17 +547,17 @@ void Edit3DView::createResetColorAction(QAction *syncBackgroundColorAction)
tooltip);
}
void Edit3DView::createSyncBackgroundColorAction()
void Edit3DView::createSyncEnvBackgroundAction()
{
QString description = QCoreApplication::translate("SyncEdit3DColorAction",
"Use Scene Environment Color");
QString tooltip = QCoreApplication::translate("SyncEdit3DColorAction",
QString description = QCoreApplication::translate("SyncEnvBackgroundAction",
"Use Scene Environment");
QString tooltip = QCoreApplication::translate("SyncEnvBackgroundAction",
"Sets the 3D view to use the Scene Environment "
"color as background color.");
"color or skybox as background color.");
m_syncBackgroundColorAction = std::make_unique<Edit3DAction>(
QmlDesigner::Constants::EDIT3D_EDIT_SYNC_BACKGROUND_COLOR,
View3DActionType::SyncBackgroundColor,
m_syncEnvBackgroundAction = std::make_unique<Edit3DAction>(
QmlDesigner::Constants::EDIT3D_EDIT_SYNC_ENV_BACKGROUND,
View3DActionType::SyncEnvBackground,
description,
QKeySequence(),
true,
@@ -589,9 +591,17 @@ QPoint Edit3DView::resolveToolbarPopupPos(Edit3DAction *action) const
const QList<QObject *> &objects = action->action()->associatedObjects();
for (QObject *obj : objects) {
if (auto button = qobject_cast<QToolButton *>(obj)) {
// Add small negative modifier to Y coordinate, so highlighted toolbar buttons don't
// peek from under the popup
pos = button->mapToGlobal(QPoint(0, -2));
if (auto toolBar = qobject_cast<QWidget *>(button->parent())) {
// If the button has not yet been shown (i.e. it starts under extension menu),
// the button x value will be zero.
if (button->x() >= toolBar->width() - button->width() || button->x() == 0) {
pos = toolBar->mapToGlobal(QPoint(toolBar->width() - button->width(), 4));
} else {
// Add small negative modifier to Y coordinate, so highlighted toolbar buttons don't
// peek from under the popup
pos = button->mapToGlobal(QPoint(0, -2));
}
}
break;
}
}
@@ -958,8 +968,15 @@ void Edit3DView::createEdit3DActions()
snapToggleTrigger);
SelectionContextOperation snapConfigTrigger = [this](const SelectionContext &) {
if (!m_snapConfiguration)
if (!m_snapConfiguration) {
m_snapConfiguration = new SnapConfiguration(this);
connect(m_snapConfiguration.data(), &SnapConfiguration::posIntChanged,
this, [this]() {
// Notify every change of position interval as that causes visible changes in grid
rootModelNode().setAuxiliaryData(edit3dSnapPosIntProperty,
m_snapConfiguration->posInt());
});
}
m_snapConfiguration->showConfigDialog(resolveToolbarPopupPos(m_snapConfigAction.get()));
};
@@ -1011,14 +1028,14 @@ void Edit3DView::createEdit3DActions()
m_visibilityToggleActions << m_showCameraFrustumAction.get();
m_visibilityToggleActions << m_showParticleEmitterAction.get();
createSyncBackgroundColorAction();
createSelectBackgroundColorAction(m_syncBackgroundColorAction->action());
createSyncEnvBackgroundAction();
createSelectBackgroundColorAction(m_syncEnvBackgroundAction->action());
createGridColorSelectionAction();
createResetColorAction(m_syncBackgroundColorAction->action());
createResetColorAction(m_syncEnvBackgroundAction->action());
m_backgroundColorActions << m_selectBackgroundColorAction.get();
m_backgroundColorActions << m_selectGridColorAction.get();
m_backgroundColorActions << m_syncBackgroundColorAction.get();
m_backgroundColorActions << m_syncEnvBackgroundAction.get();
m_backgroundColorActions << m_resetColorAction.get();
}

View File

@@ -101,10 +101,10 @@ private:
void showMaterialPropertiesView();
void updateAlignActionStates();
void createSelectBackgroundColorAction(QAction *syncBackgroundColorAction);
void createSelectBackgroundColorAction(QAction *syncEnvBackgroundAction);
void createGridColorSelectionAction();
void createResetColorAction(QAction *syncBackgroundColorAction);
void createSyncBackgroundColorAction();
void createResetColorAction(QAction *syncEnvBackgroundAction);
void createSyncEnvBackgroundAction();
void createSeekerSliderAction();
QPoint resolveToolbarPopupPos(Edit3DAction *action) const;
@@ -135,7 +135,7 @@ private:
std::unique_ptr<Edit3DAction> m_particlesPlayAction;
std::unique_ptr<Edit3DAction> m_particlesRestartAction;
std::unique_ptr<Edit3DParticleSeekerAction> m_seekerAction;
std::unique_ptr<Edit3DAction> m_syncBackgroundColorAction;
std::unique_ptr<Edit3DAction> m_syncEnvBackgroundAction;
std::unique_ptr<Edit3DAction> m_selectBackgroundColorAction;
std::unique_ptr<Edit3DAction> m_selectGridColorAction;
std::unique_ptr<Edit3DAction> m_resetColorAction;

View File

@@ -9,6 +9,7 @@
#include "edit3dtoolbarmenu.h"
#include "edit3dview.h"
#include "edit3dviewconfig.h"
#include "externaldependenciesinterface.h"
#include "materialutils.h"
#include "metainfo.h"
#include "modelnodeoperations.h"
@@ -24,6 +25,7 @@
#include <designeractionmanager.h>
#include <designermcumanager.h>
#include <import.h>
#include <model/modelutils.h>
#include <nodeinstanceview.h>
#include <seekerslider.h>
@@ -43,7 +45,8 @@
namespace QmlDesigner {
static inline QIcon contextIcon(const DesignerIcons::IconId &iconId) {
inline static QIcon contextIcon(const DesignerIcons::IconId &iconId)
{
return DesignerActionManager::instance().contextIcon(iconId);
};
@@ -168,28 +171,8 @@ Edit3DWidget::Edit3DWidget(Edit3DView *view)
createContextMenu();
m_mcuLabel = new QLabel(this);
m_mcuLabel->setText(tr("MCU project does not support Qt Quick 3D."));
m_mcuLabel->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
fillLayout->addWidget(m_mcuLabel.data());
// Onboarding label contains instructions for new users how to get 3D content into the project
m_onboardingLabel = new QLabel(this);
QString labelText =
tr("Your file does not import Qt Quick 3D.<br><br>"
"To create a 3D view, add the"
" <b>QtQuick3D</b>"
" module in the"
" <b>Components</b>"
" view or click"
" <a href=\"#add_import\"><span style=\"text-decoration:none;color:%1\">here</span></a>"
".<br><br>"
"To import 3D assets, select"
" <b>+</b>"
" in the"
" <b>Assets</b>"
" view.");
m_onboardingLabel->setText(labelText.arg(Utils::creatorTheme()->color(Utils::Theme::TextColorLink).name()));
m_onboardingLabel->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
connect(m_onboardingLabel, &QLabel::linkActivated, this, &Edit3DWidget::linkActivated);
fillLayout->addWidget(m_onboardingLabel.data());
@@ -276,17 +259,16 @@ void Edit3DWidget::createContextMenu()
m_contextMenu->addSeparator();
m_selectParentAction = m_contextMenu->addAction(
contextIcon(DesignerIcons::ParentIcon),
tr("Select Parent"), [&] {
ModelNode parentNode = ModelNode::lowestCommonAncestor(view()->selectedModelNodes());
if (!parentNode.isValid())
return;
contextIcon(DesignerIcons::ParentIcon), tr("Select Parent"), [&] {
ModelNode parentNode = ModelUtils::lowestCommonAncestor(view()->selectedModelNodes());
if (!parentNode.isValid())
return;
if (!parentNode.isRootNode() && view()->isSelectedModelNode(parentNode))
parentNode = parentNode.parentProperty().parentModelNode();
if (!parentNode.isRootNode() && view()->isSelectedModelNode(parentNode))
parentNode = parentNode.parentProperty().parentModelNode();
view()->setSelectedModelNode(parentNode);
});
view()->setSelectedModelNode(parentNode);
});
QAction *defaultToggleGroupAction = view()->edit3DAction(View3DActionType::SelectionModeToggle)->action();
m_toggleGroupAction = m_contextMenu->addAction(
@@ -310,12 +292,48 @@ bool Edit3DWidget::isSceneLocked() const
{
if (m_view && m_view->hasModelNodeForInternalId(m_canvas->activeScene())) {
ModelNode node = m_view->modelNodeForInternalId(m_canvas->activeScene());
if (ModelNode::isThisOrAncestorLocked(node))
if (ModelUtils::isThisOrAncestorLocked(node))
return true;
}
return false;
}
void Edit3DWidget::showOnboardingLabel()
{
QString text;
const DesignerMcuManager &mcuManager = DesignerMcuManager::instance();
if (mcuManager.isMCUProject()) {
const QStringList mcuAllowedList = mcuManager.allowedImports();
if (!mcuAllowedList.contains("QtQuick3d"))
text = tr("3D view is not supported in MCU projects.");
}
if (text.isEmpty()) {
if (m_view->externalDependencies().isQt6Project()) {
QString labelText =
tr("Your file does not import Qt Quick 3D.<br><br>"
"To create a 3D view, add the"
" <b>QtQuick3D</b>"
" module in the"
" <b>Components</b>"
" view or click"
" <a href=\"#add_import\"><span style=\"text-decoration:none;color:%1\">here</span></a>"
".<br><br>"
"To import 3D assets, select"
" <b>+</b>"
" in the"
" <b>Assets</b>"
" view.");
text = labelText.arg(Utils::creatorTheme()->color(Utils::Theme::TextColorLink).name());
} else {
text = tr("3D view is not supported in Qt5 projects.");
}
}
m_onboardingLabel->setText(text);
m_onboardingLabel->setVisible(true);
}
// Called by the view to update the "create" sub-menu when the Quick3D entries are ready.
void Edit3DWidget::updateCreateSubMenu(const QList<ItemLibraryDetails> &entriesList)
{
@@ -420,23 +438,10 @@ void Edit3DWidget::showCanvas(bool show)
}
m_canvas->setVisible(show);
if (show) {
if (show)
m_onboardingLabel->setVisible(false);
m_mcuLabel->setVisible(false);
} else {
bool quick3dAllowed = true;
const DesignerMcuManager &mcuManager = DesignerMcuManager::instance();
if (mcuManager.isMCUProject()) {
const QStringList mcuAllowedList = mcuManager.allowedImports();
if (!mcuAllowedList.contains("QtQuick3d"))
quick3dAllowed = false;
}
m_onboardingLabel->setVisible(quick3dAllowed);
m_mcuLabel->setVisible(!quick3dAllowed);
}
else
showOnboardingLabel();
}
QMenu *Edit3DWidget::visibilityTogglesMenu() const
@@ -521,7 +526,7 @@ void Edit3DWidget::dragEnterEvent(QDragEnterEvent *dragEnterEvent)
// Block all drags if scene root node is locked
if (m_view->hasModelNodeForInternalId(m_canvas->activeScene())) {
ModelNode node = m_view->modelNodeForInternalId(m_canvas->activeScene());
if (ModelNode::isThisOrAncestorLocked(node))
if (ModelUtils::isThisOrAncestorLocked(node))
return;
}

View File

@@ -66,6 +66,8 @@ private:
bool isPasteAvailable() const;
bool isSceneLocked() const;
void showOnboardingLabel();
QPointer<Edit3DView> m_edit3DView;
QPointer<Edit3DView> m_view;
QPointer<Edit3DCanvas> m_canvas;

Some files were not shown because too many files have changed in this diff Show More