From 96b0cda1c9e5d4420dc422ad46dd6aeeb8e769e6 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Tue, 15 Nov 2022 14:56:45 +0200 Subject: [PATCH 001/131] QmlDesigner: Don't write zero position properties at node creation Some types do not have x/y properties, so don't write them at node creation. Also, if the value would be the default zero, it is unnecessary to write them even for types that support them. Fixes: QDS-8304 Change-Id: I75218d599288cc79e3a390f68255cc6769df4083 Reviewed-by: Ali Kianian Reviewed-by: Reviewed-by: Mahmoud Badri --- .../designercore/model/qmlitemnode.cpp | 12 +++++--- .../designercore/model/qmlvisualnode.cpp | 29 ++++++++++++++----- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp b/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp index ca2bb3c5a51..95589335b98 100644 --- a/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp +++ b/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp @@ -69,8 +69,10 @@ QmlItemNode QmlItemNode::createQmlItemNodeFromImage(AbstractView *view, const QS auto doCreateQmlItemNodeFromImage = [=, &newQmlItemNode, &parentproperty]() { NodeMetaInfo metaInfo = view->model()->metaInfo("QtQuick.Image"); QList > propertyPairList; - propertyPairList.append({PropertyName("x"), QVariant(qRound(position.x()))}); - propertyPairList.append({PropertyName("y"), QVariant(qRound(position.y()))}); + if (const int intX = qRound(position.x())) + propertyPairList.append({PropertyName("x"), QVariant(intX)}); + if (const int intY = qRound(position.y())) + propertyPairList.append({PropertyName("y"), QVariant(intY)}); QString relativeImageName = imageName; @@ -131,8 +133,10 @@ QmlItemNode QmlItemNode::createQmlItemNodeFromFont(AbstractView *view, auto doCreateQmlItemNodeFromFont = [=, &newQmlItemNode, &parentproperty]() { NodeMetaInfo metaInfo = view->model()->metaInfo("QtQuick.Text"); QList> propertyPairList; - propertyPairList.append({PropertyName("x"), QVariant(qRound(position.x()))}); - propertyPairList.append({PropertyName("y"), QVariant(qRound(position.y()))}); + if (const int intX = qRound(position.x())) + propertyPairList.append({PropertyName("x"), QVariant(intX)}); + if (const int intY = qRound(position.y())) + propertyPairList.append({PropertyName("y"), QVariant(intY)}); propertyPairList.append({PropertyName("font.family"), QVariant(fontFamily)}); propertyPairList.append({PropertyName("font.pointSize"), 20}); propertyPairList.append({PropertyName("text"), QVariant(fontFamily)}); diff --git a/src/plugins/qmldesigner/designercore/model/qmlvisualnode.cpp b/src/plugins/qmldesigner/designercore/model/qmlvisualnode.cpp index 36edea4e9ea..9df8e9ecd7c 100644 --- a/src/plugins/qmldesigner/designercore/model/qmlvisualnode.cpp +++ b/src/plugins/qmldesigner/designercore/model/qmlvisualnode.cpp @@ -212,11 +212,16 @@ void QmlVisualNode::setPosition(const QmlVisualNode::Position &position) if (!isValid()) return; - setDoubleProperty("x", position.x()); - setDoubleProperty("y", position.y()); + if (!qFuzzyIsNull(position.x()) || modelNode().hasProperty("x")) + setDoubleProperty("x", position.x()); + if (!qFuzzyIsNull(position.y()) || modelNode().hasProperty("y")) + setDoubleProperty("y", position.y()); - if (position.is3D() && modelNode().metaInfo().isQtQuick3DNode()) + if (position.is3D() + && (!qFuzzyIsNull(position.z()) || modelNode().hasProperty("z")) + && modelNode().metaInfo().isQtQuick3DNode()) { setDoubleProperty("z", position.z()); + } } QmlVisualNode::Position QmlVisualNode::position() const @@ -566,11 +571,19 @@ QList > QmlVisualNode::Position::propertyPairList( { QList > propertyPairList; - propertyPairList.append({"x", QVariant(qRound(x()))}); - propertyPairList.append({"y", QVariant(qRound(y()))}); - - if (m_is3D) - propertyPairList.append({"z", QVariant(z())}); + if (m_is3D) { + if (!qFuzzyIsNull(x())) + propertyPairList.append({"x", QVariant{x()}}); + if (!qFuzzyIsNull(y())) + propertyPairList.append({"y", QVariant{y()}}); + if (!qFuzzyIsNull(z())) + propertyPairList.append({"z", QVariant{z()}}); + } else { + if (const int intX = qRound(x())) + propertyPairList.append({"x", QVariant(intX)}); + if (const int intY = qRound(y())) + propertyPairList.append({"y", QVariant(intY)}); + } return propertyPairList; } From 69461e4c68fee8eac734701b36694cac2acadfd9 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Mon, 14 Nov 2022 16:26:42 +0200 Subject: [PATCH 002/131] QmlDesigner: Generate material browser texture icons using provider Provider allows using special generation methods for hdr images, which QImage doesn't support. Task-number: QDS-8296 Change-Id: I829199264ff4a5eb677d65c908eacd1e20ad94e5 Reviewed-by: Mahmoud Badri Reviewed-by: --- .../materialBrowserQmlSource/TextureItem.qml | 2 +- .../assetslibrary/assetslibrary.qrc | 5 ++- .../assetslibrary/images/asset_default.png | Bin 312 -> 0 bytes .../assetslibrary/images/asset_default@2x.png | Bin 346 -> 0 bytes .../assetslibrary/images/assets_default.png | Bin 0 -> 1482 bytes .../images/assets_default@2x.png | Bin 0 -> 3316 bytes .../images/assets_default_128.png | Bin 0 -> 4699 bytes .../materialbrowsertexturesmodel.cpp | 2 +- .../materialbrowser/materialbrowserwidget.cpp | 36 +++++++++++++++++- .../materialbrowser/materialbrowserwidget.h | 2 + .../materialeditor/images/texture_default.png | Bin 0 -> 3219 bytes .../images/texture_default@2x.png | Bin 0 -> 7659 bytes .../materialeditor/materialeditor.qrc | 2 + 13 files changed, 43 insertions(+), 6 deletions(-) delete mode 100644 src/plugins/qmldesigner/components/assetslibrary/images/asset_default.png delete mode 100644 src/plugins/qmldesigner/components/assetslibrary/images/asset_default@2x.png create mode 100644 src/plugins/qmldesigner/components/assetslibrary/images/assets_default.png create mode 100644 src/plugins/qmldesigner/components/assetslibrary/images/assets_default@2x.png create mode 100644 src/plugins/qmldesigner/components/assetslibrary/images/assets_default_128.png create mode 100644 src/plugins/qmldesigner/components/materialeditor/images/texture_default.png create mode 100644 src/plugins/qmldesigner/components/materialeditor/images/texture_default@2x.png diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml index b5ef129a6c3..4f73722f692 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml @@ -37,7 +37,7 @@ Rectangle { } Image { - source: textureSource + source: "image://materialBrowserTex/" + textureSource sourceSize.width: root.width - 10 sourceSize.height: root.height - 10 anchors.centerIn: parent diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrary.qrc b/src/plugins/qmldesigner/components/assetslibrary/assetslibrary.qrc index f8cea612985..61d39d016db 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrary.qrc +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrary.qrc @@ -1,7 +1,5 @@ - images/asset_default.png - images/asset_default@2x.png images/asset_shader.png images/asset_shader@2x.png images/asset_shader_128.png @@ -12,5 +10,8 @@ images/asset_video@2x.png images/browse.png images/browse@2x.png + images/assets_default.png + images/assets_default@2x.png + images/assets_default_128.png diff --git a/src/plugins/qmldesigner/components/assetslibrary/images/asset_default.png b/src/plugins/qmldesigner/components/assetslibrary/images/asset_default.png deleted file mode 100644 index ef59d892791f9a91d37b5cd1330b7d78be415cdd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 312 zcmeAS@N?(olHy`uVBq!ia0y~yV2}V|4rT@hhU+WOo?>8N@C)z>ab;j&SV29|be7SF zfq_A?B*-tAfss$Zz{J$ddcw2?%k~^Nbn5iuXCFR({`R|z(?pYjfuX_E#W6%<;@pY1 zg_;a_Se2h%b8}H-`u*SjmA9;(pG@lfOK*H;&Fh+@EWjM7BBbt97hq{EA*Sf5GXb#;Uo|hl>OSjhpYP9k}ch@-;Xnw768@VMK2K tJB{x}<#+S>OOy+pza-m#Z2td&$>_O|=w`;v=?n}E44$rjF6*2UngG||i@pE= diff --git a/src/plugins/qmldesigner/components/assetslibrary/images/asset_default@2x.png b/src/plugins/qmldesigner/components/assetslibrary/images/asset_default@2x.png deleted file mode 100644 index 6540cc859cd93fbc9e4cb2fab46f642d3d615ec5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 346 zcmeAS@N?(olHy`uVBq!ia0y~yU@!n-4rT@hhJ-tuTNxM_Yyx~jTp1V`R*(yrN>jrb z7#M_0g8YIRxOsR51QpcuLgES_$qd(&r7#MbWx;Tb-9Df_?D0E1HXNj1W zi0G|9|JPe}S}ADG@IJk;I-zX>tI#CZmFo-{4}6h3{nFE+$8ODm1m3zQf1Y#9?kIj) z->miS=)VgG&Pz_ep>)KCkKgx4&j*Lwk9zbH?lg5PDhJrf&d+zh`FD?4lb@+g;)1K% zRjae(d>3=9mOu6{1-oD!M9glAU%qhuU1_tKyo-U3d9-Xye-0_z^ z1n%eRYwC8eb#d5zD9or1^>;b4T~yR1_#Bg5hYWje){VdayJpR30&Fz@E8#PimkNZc>tePMDDYN(6@_3%R53e_$d0*Ny zLFwcrNrnw-_fxICiziPukK^r=j}kT0;ceRn?PnZE z`qt(r^OPI1C7gL9AG?0Twp+#Dw-k8JF=B1dE&23aP*%UpsCt~&+`Ewo8 zS%+D;1H7MH`uZmLtjDeB9or@;F5bCSsCmkLG zdS5}&=i9DN7K=R@fBstBx|rUI_~K(J8fzDClba(}w>8R7m07!VvEE6~e#yWj@10ln z8D89*H~9nWf@PvB^`;kPKB_)mx7gEAB$IdPdeQJBQjL4cU;DmdpYJ(sN#EMJGjwGZ zD>&?zeq+6#&+HU~<(wtEw+6gpxlsS8?eyl^=W0|A;cxhE&$w<(kjR=MKjnCY@Q1ALVyEo8PJK<%e^;$z_hQG!C%(_BK8L<^b&;93 zpzdwf>dE;>bgMRYe-fSR_(b%WbQ^+UM~TZ46!`10n>^f}hH^5LEG`IGi! zZE3l&cUi2;UzMed45!q~r~dn$;PS9PYC$ve&-u%_&PZ)tSbW%l*)!_7TE>N2Xkcm7g_W@_~`t66m!eK|5>u))%{dR?alUX2d_sMPN85kHCJYD@<);T3K0RTyZ B^@0EZ literal 0 HcmV?d00001 diff --git a/src/plugins/qmldesigner/components/assetslibrary/images/assets_default@2x.png b/src/plugins/qmldesigner/components/assetslibrary/images/assets_default@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..e54039dcbde3d5f39c35145536b57878061bb525 GIT binary patch literal 3316 zcmeAS@N?(olHy`uVBq!ia0y~yU`POA4rT@hhO2JvTnr41c>z8lt_%zejEsy+mo8nl zY}xYV%i#oQc}b98FarY%3kwGa2M>>cfQX2Mgp7iMf{KcUj)8%R ziG_uYje~=WhmTJ{NJKn zy?*`r&6~Gx-@SYP{=>(QpFVy5{N>Bnuiw6X|Ni60&!4}3{r>&u&)>iQ{{8z%u;xSt z2CjXcE{-7?_j;vxokfoqUf+Gkdg6>D0$bM#B=;)01W%e(m)PYi&&YIe{gEtAsmn@+ z=i8GS@7CYHJM-qx9qnz^AMV||_wL-An)7$8-d|F$Uz*Q$Wt^aP@TNQ>K%^Sid=1t&poczD*lE~bkUq`rF51;bg+-I|bdvbVo;JHPMmn^;V ztS$Ym>Q(g_YYzN$WE1e@X9&5~=qAO!UEP1~OpBY^(F+)J49}RI`owYn>ow(%3&P77 z8usQS)D-=T@2{7$ssEJl?@+};kw-dio@*w}n%({-Pt@0s!D!9ZQ+-mqzx*v&f8xRE zvoj6fPn8XhcI`S*&bWau*pc61yD-DHRhbuVP8D~3&v@eAuMoqXN$#fUGsKU~Vo}&M z!|g%igqy!V^E3Y5`_`S`d|RQi|9Xvo(+k%HoS9m7({1y*qavP9W;%(_KQJl2*2eH& z-`>9;&n}m2Ke)z2DY&~bZb{cV76rG}8KR$0&8?du_fF5>H1)eMxBjIEYq@VNzfjb( z%lJ>>SzTRM#;*-eejT}0$8q?)w7T8uT~;2mD}MBGOr9jeq;;Wa^FnYsev zcQ;C&TPWgMXkj?9qpVKxV)g4a%hXNo-{mR%ziI}v>XkJ4l=DWDg;_qbuNLiH^=xuP zU1VvC-PUJ9Qm0#gor~g8OilDyTqfY@GjWx=oC;sO45!^5YvYc@*Hg+*%`-YDv~@yK z#inH}3bkR{vwqyGclz-%R`8<}*MqYk)|~L3?so3R$)t6{4NE<)iA=YdD{xmrc-1Bk zv(t+Ee(b#wDw;6&l#v*~weA6732VT5%_`a&#{al;Zg4z{Z1rDwDj(an;V?ryt zG?SsOPai{r+kD@s4-2Mx<`~XCDi9Z=lh*h&eD}&jJn6V`o46Z#>8St*8Ef)(eq2w|&XUPWJqbasqkbm+n6ET)#y^D!JVG#{We^ zVOGK+6^4}`=FXV&H?&Xdh}gp;!Tp!S{;4=}asEBCT43JaWzsz zf~UG?xSixrGAlcp66-NZYF=#h?5i0$zdR2m6>Po4ZT->G&B1l@))~h_kJ{OPH~RYa z?)-lW>m&A?*XEn^-u#>WcuTtgr`W#D7e8E6H)dFr_V;ITz?Zkye_Pk*{hu+t-#UJq z+P^@xEy`XDdkl7l{d1V9RsYg+Nly>czxj4AE0=9Azm?c`q<`m3)5;_A8CR?7|E)ZA zk?n7BZ@AyBmfeerPE9etze!C$F(<0?qU#sG`2G9$&fatT-RAmw!>cbc?r9o7RaG;) zw`*tW7lou7kLGaxzunodcP`GZK5fl2i|ge|y9Il-Z@<=xI&*PrxZj!AC5I!;lPB@6 zpUmI6uUgH%L&R#+I*Zq3TiG)2uRdw=ut)y=+Sxk7HnTW(T%UGPD?nZ0>9H@*=A7Fc z)6Chy7d?mfzwe)8b`Ft&o)JYRJ4DN#l}JoGEH?GnqRZQtT#)&C&S>X}XfBMVv`G-Cou74(?D);8k!wo+BU9QI|6&!ecxcBeR z$CEbv60|LU$F;V8?M%rdhuH6LbzQHi>Z5vprox_;QwmbKkJzq#|L4sE21S`5uLR#P zy=(rt4sAB~wbp4fr7|e|Z>WFgUwz^dmsC4L2$NCN>V~hyn`#!<#;$c*`(Dz?N1$Zw zq94t-q^!)pIDI-PpO0PmH77 zAS(Cd7yrGxxy8*6YBMj--P-!a{9*oT-I@F^51wAQZrhS)QA=Z9Uttp{FMqhhdV8eB zSsk^X>i@o#bi5R35|R7eKCM{b|BP$?9=>@@T9y1-T62%hVE*`iN^u&S(AyUqT60-W z)!*5BlgaP7+(aE&aa)F;J?stp!&Aju>_5-(UYVhlvsdG#{-x!r6FipASUPn@!=JOC ziYxYJqy?;zH;_!eJgcSo$@8mcA0JIr<7ha4?|xKF!`Id6ItyPcc$2tA;j7{+{Rijg zc!mG>+GOH*uD!W%#C7ST z&=>q)Eo0zBvBM z_0+xn-^BX!^&hM=rY_yON1Ney{gp{l9)cQDH_wNgM=$4EU{n7|aKDN~-L21aHQX$@ zS6MOHX>D3P({Am`(t?F2Cm&n*M`mx@^$UFGn*YeF{F1)E=F#FanG)6IduDUq`f{w$ z(4|$^-%R<|`D?Fp4z1pE&w7#2t1p4wO)2Y~7xv4qeBz-KwZ!yidtYCrvY6lW;uWuE zJlt4ooUa*j_TFZ@CmGLPgt}L4o%S-ByLVMWaDdHsjcFbZIwsXJ8&@a?O?fK+GMVMP z19MgBpTiSR&DzjBa{)1DY_n+e|?COWi#+O;F?^sF5F(tB6e&MwkF5wxx5?Zns1 zGA%w`za4(kt#wIidJCA&u6FB7tMU;I>lIj z#u77^ZK0nJX8xO`yK8B;;;t=gPIE5Xvc^SBxzVBS*LCmHOBJ4$>`YYIe!X%{dED`u zQyvy|ky6c7{nN@Wg#28!y6Ebw<;(c(SAD*JC7EZ!^XNBrVaJv%554{L;O4JeL-#x= z&-Kl_c1gCZPMN=$xl`oZzIfB(kLA*gSB|QGj%<^-YW+*&{fD#Mza^~SKV59gc`#l5 z%F32=EG7>MDjZAYR~(fLn&QlJQD&w_*D`+HBR6c57WB(4NbLW$!?owx2d5Wv{4_Fi zYVKyXU!U*W!Tzg-BV5DU-DJs{l>v%TEf*3GA3Slf!=h-3$}DG{2@gZAPn#Y-?Pb)) z`aip!lrO#KD*G^Z-dyXjQ?`H3q;jiliobf?p~I8$Ri9r^7ANQ4Ko;%TzI*rX{re9eKYsfB74?+JRPDsuR)d^=-HS(Nnk1m`FY%}k#t=d1;$S9lfII=|ZE zC9EVY%&9pkufFEHsrCIeDeVdKbzjC+TU%Sd|9eg%`9%GRzZ33zN82Pmk~DglWpy;X zBj|PocVN!^`cI8pc>jw?FOAJQb9mc>e~;ep`E|6J)h15D+Is(jybt9%C#JUkywm)< zcJVup$Uo~Yzx}ax_WnbCbKbPQe}6gg$KQML)2pp(!gH>^edfKR^mFmgoBnlAIV<9R zp8eIoc2!M)?~U`%uiQ;GU%q-ilZEe`3HuPRW0sp-pZi3Yx>dR-%5R^zwJF+l{@QQicSLl)>P%Sm z?2wUqt{k)cvDw5ziC0 z#YGNJk1RR9?Dz+n9-+v^TNo2MY{@_ySZGY`WVUx_#}={X2Hv-@4WGj_2<^%Iu%IpMQzXuBg#2 zRP=My|FpPRQ94$6*_2+j`KQCCJ!<R`*EBUeqebi^5M3V zVaw+w=il-E#;!8O)p>p+3@ z<1^kY@A)BnDBZ!BXEK-XZQF2*O`TH z-rE;(dDA&2#V4!fo0TS>`yirX9k62IhkK?o?*6(ac5cb`y+Ou8JO@)Ylu2#vZ2XA(0;&T@_mrNb7mZY+FT_2u&XdFu~roH%3K@jc({zz**- zylS4$oQ{5ax7lh-t8vjHQQwU_rg!_>u4m_8c2w?Y;1)CM#^me0kBZE0u-t4hTq?^| z&cV*%^O05c+}VC-j*WMX+|Jb1oa^jOaR2w)ij&jkgOA$NGKGmYJo)TvDh|Kl-f%Qp zF|4-2Oj=fG@vR-gQ8`cKxObXooV}Rz@n%BD95;LKwi9mo9p8RGQI=1ztrL!&@ZrIm z<^LG(p9xI-(hthqP7Y_F*-V~Rh#{Nu#*+xuTz*1J~EzpL1p8GLh{_@M(#@)J5# z|I8JfUsPnC5!HK2Wv2D+XYw1&*<~194I~T%{w+xqIj3>#bRw6<9INf`&)G(+dhX_I zc5%LZPmcFqBZpwKB!~FYtI0<%xU z_7*$6ck3MVU)#O>xuS0=-{PgU$}>8J6+S=UiOiq+#+gGl^}wnlD%Q&X<9{ERf78y< zjs3v{lRdA${s@$Ha>)ICk$Z)M`uPnkDu%bY4Hcy?O8;wXUoTN$z`}R%Z%zf1-Kv9M z7Mwg-5b1j3<1r<}&a$7sML87LS32)J{Vz}FTj9n_vHHiNO?&2^T(GP2!Sly!Hm+ut z-n7twb-(Y;!tYH!MzPM|787wnz;Zkf-dF8^kMOL6YiaYAtRg*5mCp)gxNHi^FI}&v)!&9Kh^N(q& z?uMBS30=8G$0Mu4n;P#OC~-d{b@Bb)S3LJtKUHA4;=tO>CTih7o&RLcL*CqUR*O_W z_WUmua*N_#_MEf%6uy6+tGJKbZU^Q|jhXecngT+-mRW>N+#}}jq}=qx$#bhGtvk&; ztK9bD;a;hfiwVzHoDkXgK*pafK4Gd@%L0)#D?k5eG>q20vgpT~W4je+|9++OOF}}A z&0S!b)298$K00V!ZoQ)2nzHlj(XtuK>gTlvm76ebcV@Y`J8tsx)l)iL$_-Rn(vQ#B zFzL&js{)7ltL&!*lOo7%szLwR_MH)h4PX~Wq(__r?WQK+`q-(ylGN{PEhaf7~kLL*LHv2e7RUW|IHTR z{dRL*FRR^OrO?zT(0b{}oeN2MFH#RUe36hV|CHA5{a|mp|NJ-K+gAEq*lq7y_12k5 zXohg;9p>pSkGMUwmL8PUy#Mmk;qrYmw@7`syH-=_>UtAJjY*CIZJfzv$4?4;UF+p{ zbp4O>XRb|7I=JB0|ALRAsz2OrCQmWf%4YLS4r32H^;KpXyS2lvs8@Rr9s2gzah+h~ z?SIAGibZy4G+g;Oh*7yc92yiPe$K^}Zv>3POzqy-X{J0rH7D>^Rsz=nk>ZJ5uQeYuZJOPx8o4Gt z&9-H4L3r}s?bUwXsRhULPPP;~ub8BH-!RVC5HmtvjyseAXUsxG(tersdwc zNlZe!`KN7o^y@(3L-u9YsyA(WS;_Ef?&l`f&}ElTxv$Dwc~&Pa`IhSQ-+TT-7RL#wyM1j>#H1#{4_dT&$6v=A6Z+4x3|;KWyKbG8*FPx)2;qw!$a=c3t%XWlJ7qqSy2${&X>Z9yf6 zA00Z=SNveMZgcmvfVBsbDz5xDd2w*>V^G#&=ym1h?LqbL{>6U)O`5&YBbG8u01Uaqif>e>YQNZaq%j zf1>%SyxK$du!K(DAj$jnZO8BANt&EpWhCD*tLdY;sufp}fTqRq&`sh;_AU+qUcla9^W;5YSNGl8bu%$+gRjoDr_wk7@$LQ7|CpVLf6;;c5_Jtf z<3p>%UtPKJVncQBw*?3IU49?`WiMzi@RWfezx^?LTA9$|oZnsN-K5QT>-5cDYxsxZ z#{BBD%^#Sj-@fH8-z8u0Y5s=nx7uGLj6ObPn#lZgW6+UX)GFhsOGs-!$dvcz<|8Xhj}C*^>58)# zuax<(^4DI%{(;S8R+GDqKN@Z7?%Us3>e84mpcB;fKkv)rrpn*{m47szX+Hdcc~`v- zOBI*U=4P{Bm+Srn#yI`AZ!ZI!@P+^K$N6J! zCA*wr{LyIeufTmyeXslsA-Nxs9zXlf{O5kaJ}KXZ!~VfBd#_LRTjN-4BBhlt`iU?3 z|8VcK?nmtR*76}E{cA5&m!Nh;-tqsxfa?Ng4-dLc z?@rqt^}nxAzTk8I=0DO~#h4c^SnRMMbg@Xi9Cuwp+hc$I|7l8k%8%HYSQmF)p6@v6 z|KdmNhkoR4*?MJR8+72BVN+~vPAr_={&2R;7Lc!IgD zSbcwi${&Ygx$OV_xnzwP?FBY9@XlkE{(MaNA-lyV|HthM%9ZL4xa7Vm*dnidb@2ms zp>=o9)<;&%Xpv9Q@$TSX((kH=D#|6NZ=ut z#Uu6tOS?+7Gq?KK?F}A^ayJ>xJs4PNdEm^~iVw_-jP0G9M z>ues6gDW2&L`@cu`^z~0aQ%e&oCG)@8XIroQA6Tk>fBPEw7u)Rp5AlaI*XjS;xt4$am%Hze zHz&Ook7wHc;_JNMYJdOqe0kshmgP@F-Q&VHx}LQ>+)g`Nr<(tIbdKYG?3RLXyX7C4 zC&wALd{@0Jl~VU})25(9p>N-C+;`l1lzU3p(fwTZ6XG+rJYJu1s=I4(bKI$|*ZTHL zb-%B^^)Xz*v~Gb`{JyQLnkO#O3j6KGy7cGPdyirxE48m>eG;up_;c5<=Hz8RDdsa; z`~PNNHklff(mg#-{Q>)f+&dpXioac3rEM`^rjy}N^akPf_8*OZpRWmYOq}G2X zaP2Ew{{Ij2!tQ*z{oHx0Hvh@+rm*dM1bP0l+XkK Dd&fjc literal 0 HcmV?d00001 diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp index b39bdbb1f94..15613960da1 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp @@ -35,7 +35,7 @@ QVariant MaterialBrowserTexturesModel::data(const QModelIndex &index, int role) QByteArray roleName = roleNames().value(role); if (roleName == "textureSource") { QString source = m_textureList.at(index.row()).variantProperty("source").value().toString(); - return QUrl::fromLocalFile(DocumentManager::currentResourcePath().path() + '/' + source); + return QVariant(DocumentManager::currentResourcePath().path() + '/' + source); } if (roleName == "textureVisible") diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp index d9a3539d249..75aad7bd7af 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -88,6 +89,33 @@ public: } }; +class TextureImageProvider : public QQuickImageProvider +{ +public: + TextureImageProvider() : QQuickImageProvider(Pixmap) {} + + QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override + { + QPixmap pixmap; + const QString suffix = id.split('.').last().toLower(); + if (suffix == "hdr") + pixmap = HdrImage{id}.toPixmap(); + else + pixmap = Utils::StyleHelper::dpiSpecificImageFile(id); + + if (pixmap.isNull()) + pixmap = Utils::StyleHelper::dpiSpecificImageFile(":/materialeditor/images/texture_default.png"); + + if (size) + *size = pixmap.size(); + + if (requestedSize.isValid()) + return pixmap.scaled(requestedSize, Qt::KeepAspectRatio); + + return pixmap; + } +}; + bool MaterialBrowserWidget::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::FocusOut) { @@ -119,8 +147,9 @@ bool MaterialBrowserWidget::eventFilter(QObject *obj, QEvent *event) QString iconPath = QLatin1String("%1/%2") .arg(DocumentManager::currentResourcePath().path(), m_textureToDrag.variantProperty("source").value().toString()); - - model->startDrag(mimeData, QPixmap(iconPath).scaled({128, 128})); + model->startDrag(mimeData, + m_textureImageProvider->requestPixmap(iconPath, nullptr, + {128, 128})); } m_materialToDrag = {}; m_textureToDrag = {}; @@ -140,6 +169,7 @@ MaterialBrowserWidget::MaterialBrowserWidget(MaterialBrowserView *view) , m_materialBrowserTexturesModel(new MaterialBrowserTexturesModel(this)) , m_quickWidget(new QQuickWidget(this)) , m_previewImageProvider(new PreviewImageProvider()) + , m_textureImageProvider(new TextureImageProvider()) { setWindowTitle(tr("Material Browser", "Title of material browser widget")); setMinimumWidth(120); @@ -160,6 +190,8 @@ MaterialBrowserWidget::MaterialBrowserWidget(MaterialBrowserView *view) }); m_quickWidget->engine()->addImageProvider("materialBrowser", m_previewImageProvider); + m_quickWidget->engine()->addImageProvider("materialBrowserTex", m_textureImageProvider); + Theme::setupTheme(m_quickWidget->engine()); m_quickWidget->installEventFilter(this); diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h index d1c0ef7c8a6..8e868a69864 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h @@ -28,6 +28,7 @@ class MaterialBrowserView; class MaterialBrowserModel; class MaterialBrowserTexturesModel; class PreviewImageProvider; +class TextureImageProvider; class MaterialBrowserWidget : public QFrame { @@ -70,6 +71,7 @@ private: QShortcut *m_qmlSourceUpdateShortcut = nullptr; PreviewImageProvider *m_previewImageProvider = nullptr; + TextureImageProvider *m_textureImageProvider = nullptr; Core::IContext *m_context = nullptr; QString m_filterText; diff --git a/src/plugins/qmldesigner/components/materialeditor/images/texture_default.png b/src/plugins/qmldesigner/components/materialeditor/images/texture_default.png new file mode 100644 index 0000000000000000000000000000000000000000..70e85cc2ebae18303e61189afb839ab646e11207 GIT binary patch literal 3219 zcmeAS@N?(olHy`uVBq!ia0y~yV2A=?4rT@h2L7^*1`G_0{Q*89t_%zejEs!SmMvSp zd^v<%x^yXoft5rPMB`!_f@T^r7nj~;FaS3VCIE6IeiM*ok+tJuFI%<@O$!g&z?Ph{^G^USFc~ce)Hz-+qduDy?_7V!^e-GK7IcDbT`I*}o7`O~QT^vIqT5}_Non_ob@9(s}vpYe!MZ-5l>CRLWrI3}O zi!^$-n)oVzTr3jQc(^OqCn#4-MdF<8j+yp%D(~m(-`#!t_T8D4pPzlNd!Jn1eIdi_ z1-JZ-l==208fTNGV-CpOxRhhIRbEcawYoO_`Q*8C@5=ssdx=s0)v@b$TqT2EtL-hz zYhQGy+wdrd^`FPa`=piA_Oz%E$KWF6Qw&P{}{zUjy!}G?zUCzr(eIiY|I4;dRS0XjL+j@mZCbyAViFQMs zxNCpVH7kp$+|x_~w@JAwJ~v8N+{6{ytXI_kPQYisb_0*tlI0EsKiS{s%FD^VyYn`d zb5BrX!%{92t1i(Op>hHTtxlX$54X48t6;=C?_ZhMwCpVHxH}87m=zoxE?V(EYi3r` zk>%Sv-zGA5#?|0$8-Jb59)H-R+}qvb@xq|sT$VJJ+_KrO)$-H+ z6*+c_Oq>6F+TlF!2i3LK^TS^o?Fs0!=8@gWG)s^nvs*It&YQ_o&i$_lJu&OQRe90# z&8p8$9WC^PJA55EKJVg8^SStO*T+D&^6FI@GA&Qpcb+~RG}q|Ftw|1E zP9|L4{4=iS|6J`o5vo1iGnbUII!@hd*t<#c<*(Q0joWAE9o+Nk>KpUzI%OFi8x=%T zR_^sGdMvl&%uI`4HF0Ni&qs%SIjc4A|D8uItlIvChnA^-{1xr8kCpBFvB;j+hr&}s z0`Ht!bl))lzQil>L0yE%CmWRpG|kxFcDeSA*kLFX~h4FkODP_MFj@ z3*|*ARywVJxsqQmmhgXWD&#rgvfaG}vBw3Tneye-Nt`>xg?RUU*Ew2$zW3vUQ}>gt zV#^fuI{c#|0@;_{(a@W0(%5?Ju1)ZN(^$EZ`G1_lQnn}mTbV7CzqR2+-#J5-q9=>} zF8@FCTt0s55@EIXsTO^^O}~enFI+7#c5}{@(^wJo^4>Fvp#I5r4@8o=jm($S{(K!j_0`kH z)1C#A%eU%XJ8r}7exYEY&N=s|#|n1DSurPXRG7$rc(POCN5?SD<~y584}EistKS+I zbt%wz+eW^NN)f#J_ZCiU*3^H>`G3>7h*M2D$Ar(GShSLFqw3Y>y+^Bdukc8pdvd$h zk(P~e+Gm^=I&>}Eb5-Pzl+n}O5#H+4)|Pfy?CL68vvlLqP3L`|Ma1QWDDN#@Q5P)r zGsaE+4EIZw(gR+|1DooQcYY z<7-$L_r0Fa1+ORV@(+mqcjFAlX2ok#6`n;_yJ~yxG1zQeaAZ>ASAM;dK^LFfD@4mV z1o+P3nsn#%g=@84nIB(7CI6II+iVl)z|q_D`I_o#yMklB-+Nw4=l|dBzv1x3uQCs( z_h0P&o;Jn)W>b&bwVSV4WbWSU`SXM`f648laD{hsFE*r2k#Q(5n_T@PCZq>77*`R8hdZj4nl3t`h$u^B$HA1W@Q+OwDa%Bz;G`fl`z;qQy6;GVb2%{`!*)0|NhFo;_LQ3{ADjKc z%JM)~)A4L=^%X2X_gJh>wR7v2%jW!9;OuY6kTY|Z5{vLT>xFD{d|b{uzv0vIS`(Zy zSMlz#eUl$=zj*lOq(z?s;^qll6MCI8_n5A(;>QDzw*TC&Gcnvc%+JXDO1&t*r-D;g zlB>bWnYSW0{o^paUgOUv@cy>2iTy+4fI{S6aHypV#i^PD zOJB|1xl{VbLf`(p8{2c-K3Bud(FKlRs9UTJiGCJxee5eTADAL{-mR`FE1( zO_8nNr>*yxcD8-_lj7%)Znud(qV_Umu?f_1xvcy8n`by|;7T lO^CiYvlj{ literal 0 HcmV?d00001 diff --git a/src/plugins/qmldesigner/components/materialeditor/images/texture_default@2x.png b/src/plugins/qmldesigner/components/materialeditor/images/texture_default@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a27efb275d39b149b901835c57ad93c750a686e3 GIT binary patch literal 7659 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 literal 0 HcmV?d00001 diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditor.qrc b/src/plugins/qmldesigner/components/materialeditor/materialeditor.qrc index 4ed3c769175..872452df1a0 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditor.qrc +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditor.qrc @@ -1,5 +1,7 @@ images/defaultmaterialpreview.png + images/texture_default.png + images/texture_default@2x.png From f6d878f037ebbc7dc011cb7f067841b9db0d47ff Mon Sep 17 00:00:00 2001 From: Pranta Dastider Date: Thu, 17 Nov 2022 12:41:08 +0100 Subject: [PATCH 003/131] QmlDesigner: Update the wrong Timeline option text A text was not similar in the document related to Timeline option. This patch solves the issue. Fixes: QDS-8263 Change-Id: I34cdb99b6e60d8a0f91baa02d0660ac9e6981523 Reviewed-by: Leena Miettinen --- doc/qtdesignstudio/examples/doc/progressbar.qdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/qtdesignstudio/examples/doc/progressbar.qdoc b/doc/qtdesignstudio/examples/doc/progressbar.qdoc index db027155d9f..6ce9ff1562c 100644 --- a/doc/qtdesignstudio/examples/doc/progressbar.qdoc +++ b/doc/qtdesignstudio/examples/doc/progressbar.qdoc @@ -108,7 +108,7 @@ We will now copy the color animation from the text label to the indicator. First, we right-click the text component in the \uicontrol Timeline view to - open a context menu and select \uicontrol {Copy All Keyframes from Item} to + open a context menu and select \uicontrol {Copy All Keyframes} to copy the keyframe values we specified for the text label. Next, we select the indicator in the \uicontrol Navigator, and then select From 56fa49d8e6cd7ac378deee3ec1fc95d041172d01 Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Tue, 15 Nov 2022 21:36:09 +0200 Subject: [PATCH 004/131] QmlDesigner: Allow drag-n-drop a texture to the 3D Editor Task-number: QDS-8207 Change-Id: I58bf2857e2ae1830b1dc48f352088c424d4e7c0d Reviewed-by: Miikka Heikkinen --- .../ChooseMaterialProperty.qml | 153 ++++++++++++++++++ .../components/edit3d/edit3dview.cpp | 139 ++++++++++++++-- .../components/edit3d/edit3dview.h | 17 +- .../components/edit3d/edit3dwidget.cpp | 21 ++- .../materialbrowser/materialbrowserwidget.cpp | 2 +- .../materialeditor/materialeditorview.cpp | 2 +- 6 files changed, 312 insertions(+), 22 deletions(-) create mode 100644 share/qtcreator/qmldesigner/materialBrowserQmlSource/ChooseMaterialProperty.qml diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/ChooseMaterialProperty.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/ChooseMaterialProperty.qml new file mode 100644 index 00000000000..d9135d1d39d --- /dev/null +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/ChooseMaterialProperty.qml @@ -0,0 +1,153 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuickDesignerTheme +import HelperWidgets +import StudioControls as StudioControls +import StudioTheme as StudioTheme + +Rectangle { + id: root + + color: StudioTheme.Values.themePanelBackground + + Column { + id: col + padding: 5 + spacing: 5 + + Row { + spacing: 5 + + Column { + spacing: 5 + + Text { + text: qsTr("Select material:") + font.bold: true + font.pixelSize: StudioTheme.Values.myFontSize + color: StudioTheme.Values.themeTextColor + } + + ListView { + id: materialsListView + + width: root.width * .5 - 5 + height: root.height - 60 + focus: true + clip: true + boundsBehavior: Flickable.StopAtBounds + ScrollBar.vertical: StudioControls.ScrollBar { + visible: materialsListView.height < materialsListView.contentHeight + } + model: materialsModel + delegate: Rectangle { + width: materialsListView.width + height: 20 + color: ListView.isCurrentItem ? StudioTheme.Values.themeTextSelectionColor + : "transparent" + + function id() { + return modelData.match(/\((.*)\)/).pop() + } + + Text { + text: modelData + anchors.verticalCenter: parent.verticalCenter + font.pixelSize: StudioTheme.Values.myFontSize + color: StudioTheme.Values.themeTextColor + leftPadding: 5 + } + + MouseArea { + anchors.fill: parent + + onClicked: { + materialsListView.currentIndex = index + rootView.updatePropsModel(id()) + } + } + } + } + } + + Column { + spacing: 5 + Text { + text: qsTr("Select property:") + font.bold: true + font.pixelSize: StudioTheme.Values.myFontSize + color: StudioTheme.Values.themeTextColor + } + + ListView { + id: propertiesListView + + width: root.width * .5 - 5 + height: root.height - 60 + focus: true + clip: true + boundsBehavior: Flickable.StopAtBounds + ScrollBar.vertical: StudioControls.ScrollBar { + visible: propertiesListView.height < propertiesListView.contentHeight + } + model: propertiesModel + delegate: Rectangle { + width: propertiesListView.width + height: 20 + color: ListView.isCurrentItem ? StudioTheme.Values.themeTextSelectionColor + : "transparent" + + function propName() { + return modelData + } + + Text { + text: modelData + anchors.verticalCenter: parent.verticalCenter + font.pixelSize: StudioTheme.Values.myFontSize + color: StudioTheme.Values.themeTextColor + leftPadding: 5 + } + + MouseArea { + anchors.fill: parent + + onClicked: { + propertiesListView.currentIndex = index + } + } + } + } + } + } + + Row { + spacing: 5 + anchors.right: parent.right + anchors.rightMargin: 10 + + Button { + text: qsTr("Cancel") + + onClicked: { + rootView.closeChooseMatPropsView() + } + } + + Button { + text: qsTr("Apply") + + onClicked: { + let matId = materialsListView.currentItem.id() + let prop = propertiesListView.currentItem.propName() + + rootView.applyTextureToMaterial(matId, prop) + } + } + } + } +} diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp index 6098e49fbcf..2eedb474d98 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp @@ -2,33 +2,48 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 #include "edit3dview.h" + #include "backgroundcolorselection.h" +#include "bindingproperty.h" +#include "designersettings.h" +#include "designmodecontext.h" #include "edit3dactions.h" #include "edit3dcanvas.h" #include "edit3dviewconfig.h" #include "edit3dwidget.h" +#include "materialbrowserwidget.h" #include "metainfo.h" #include "nodehints.h" +#include "nodeinstanceview.h" +#include "qmldesignerconstants.h" +#include "qmldesignericons.h" +#include "qmldesignerplugin.h" #include "seekerslider.h" +#include "variantproperty.h" #include #include -#include -#include -#include -#include -#include -#include -#include -#include +#include #include #include +#include +#include +#include #include namespace QmlDesigner { +static QString propertyEditorResourcesPath() +{ +#ifdef SHARE_QML_PATH + if (qEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE")) + return QLatin1String(SHARE_QML_PATH) + "/propertyEditorQmlSources"; +#endif + return Core::ICore::resourcePath("qmldesigner/propertyEditorQmlSources").toString(); +} + Edit3DView::Edit3DView(ExternalDependenciesInterface &externalDependencies) : AbstractView{externalDependencies} { @@ -261,6 +276,24 @@ void Edit3DView::customNotification([[maybe_unused]] const AbstractView *view, resetPuppet(); } +bool Edit3DView::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::KeyPress) { + QKeyEvent *keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Escape) { + if (obj == m_chooseMatPropsView) + m_chooseMatPropsView->close(); + } + } else if (event->type() == QEvent::Close) { + if (obj == m_chooseMatPropsView) { + m_droppedModelNode = {}; + m_chooseMatPropsView->deleteLater(); + } + } + + return AbstractView::eventFilter(obj, event); +} + /** * @brief Get node at position from puppet process * @@ -278,15 +311,64 @@ void Edit3DView::nodeAtPosReady(const ModelNode &modelNode, const QVector3D &pos setSelectedModelNode(modelNode); m_edit3DWidget->showContextMenu(m_contextMenuPos, modelNode, pos3d); } else if (m_nodeAtPosReqType == NodeAtPosReqType::MaterialDrop) { - const bool isModel = modelNode.metaInfo().isQtQuick3DModel(); - if (m_droppedMaterial.isValid() && modelNode.isValid() && isModel) { + bool isModel = modelNode.metaInfo().isQtQuick3DModel(); + if (m_droppedModelNode.isValid() && modelNode.isValid() && isModel) { executeInTransaction(__FUNCTION__, [&] { - assignMaterialTo3dModel(modelNode, m_droppedMaterial); + assignMaterialTo3dModel(modelNode, m_droppedModelNode); }); } } else if (m_nodeAtPosReqType == NodeAtPosReqType::BundleMaterialDrop) { emitCustomNotification("drop_bundle_material", {modelNode}); // To ContentLibraryView + } else if (m_nodeAtPosReqType == NodeAtPosReqType::TextureDrop) { + if (m_droppedModelNode.isValid() && modelNode.isValid() && modelNode.metaInfo().isQtQuick3DModel()) { + // get model's material list + BindingProperty matsProp = modelNode.bindingProperty("materials"); + QList materials; + if (hasId(matsProp.expression())) + materials.append(modelNodeForId(matsProp.expression())); + else + materials = matsProp.resolveToModelNodeList(); + + if (materials.size() > 0) { + m_textureModels.clear(); + QStringList materialsModel; + for (const ModelNode &mat : std::as_const(materials)) { + QString matName = mat.variantProperty("objectName").value().toString(); + materialsModel.append(QLatin1String("%1 (%2)").arg(matName, mat.id())); + QList texProps; + for (const PropertyMetaInfo &p : mat.metaInfo().properties()) { + if (p.propertyType().isQtQuick3DTexture()) + texProps.append(p.name()); + } + m_textureModels.insert(mat.id(), texProps); + } + + QString path = MaterialBrowserWidget::qmlSourcesPath() + "/ChooseMaterialProperty.qml"; + + m_chooseMatPropsView = new QQuickView; + m_chooseMatPropsView->setTitle(tr("Select a material property")); + m_chooseMatPropsView->setResizeMode(QQuickView::SizeRootObjectToView); + m_chooseMatPropsView->setMinimumSize({150, 100}); + m_chooseMatPropsView->setMaximumSize({600, 400}); + m_chooseMatPropsView->setWidth(450); + m_chooseMatPropsView->setHeight(300); + m_chooseMatPropsView->setFlags(Qt::Widget); + m_chooseMatPropsView->setModality(Qt::ApplicationModal); + m_chooseMatPropsView->engine()->addImportPath(propertyEditorResourcesPath() + "/imports"); + m_chooseMatPropsView->rootContext()->setContextProperties({ + {"rootView", QVariant::fromValue(this)}, + {"materialsModel", QVariant::fromValue(materialsModel)}, + {"propertiesModel", QVariant::fromValue(m_textureModels.value(materials.at(0).id()))}, + }); + m_chooseMatPropsView->setSource(QUrl::fromLocalFile(path)); + m_chooseMatPropsView->installEventFilter(this); + m_chooseMatPropsView->show(); + } + } } + + if (m_nodeAtPosReqType != NodeAtPosReqType::TextureDrop) + m_droppedModelNode = {}; m_nodeAtPosReqType = NodeAtPosReqType::None; } @@ -842,7 +924,7 @@ void Edit3DView::startContextMenu(const QPoint &pos) void Edit3DView::dropMaterial(const ModelNode &matNode, const QPointF &pos) { m_nodeAtPosReqType = NodeAtPosReqType::MaterialDrop; - m_droppedMaterial = matNode; + m_droppedModelNode = matNode; emitView3DAction(View3DActionType::GetNodeAtPos, pos); } @@ -852,4 +934,37 @@ void Edit3DView::dropBundleMaterial(const QPointF &pos) emitView3DAction(View3DActionType::GetNodeAtPos, pos); } +void Edit3DView::dropTexture(const ModelNode &textureNode, const QPointF &pos) +{ + m_nodeAtPosReqType = NodeAtPosReqType::TextureDrop; + m_droppedModelNode = textureNode; + emitView3DAction(View3DActionType::GetNodeAtPos, pos); +} + +void Edit3DView::updatePropsModel(const QString &matId) +{ + m_chooseMatPropsView->rootContext()->setContextProperty("propertiesModel", + QVariant::fromValue(m_textureModels.value(matId))); +} + +void Edit3DView::applyTextureToMaterial(const QString &matId, const QString &propName) +{ + QTC_ASSERT(m_droppedModelNode.isValid(), return); + + ModelNode mat = modelNodeForId(matId); + QTC_ASSERT(mat.isValid(), return); + + BindingProperty texProp = mat.bindingProperty(propName.toLatin1()); + QTC_ASSERT(texProp.isValid(), return); + + texProp.setExpression(m_droppedModelNode.id()); + + closeChooseMatPropsView(); +} + +void Edit3DView::closeChooseMatPropsView() +{ + m_chooseMatPropsView->close(); +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.h b/src/plugins/qmldesigner/components/edit3d/edit3dview.h index eaa881d5b1e..be510e45660 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.h +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.h @@ -8,14 +8,16 @@ #include #include +#include #include #include #include #include QT_BEGIN_NAMESPACE -class QInputEvent; class QAction; +class QInputEvent; +class QQuickView; QT_END_NAMESPACE namespace QmlDesigner { @@ -62,6 +64,14 @@ public: void startContextMenu(const QPoint &pos); void dropMaterial(const ModelNode &matNode, const QPointF &pos); void dropBundleMaterial(const QPointF &pos); + void dropTexture(const ModelNode &textureNode, const QPointF &pos); + + Q_INVOKABLE void updatePropsModel(const QString &matId); + Q_INVOKABLE void applyTextureToMaterial(const QString &matId, const QString &propName); + Q_INVOKABLE void closeChooseMatPropsView(); + +protected: + bool eventFilter(QObject *obj, QEvent *event) override; private slots: void onEntriesChanged(); @@ -70,6 +80,7 @@ private: enum class NodeAtPosReqType { BundleMaterialDrop, MaterialDrop, + TextureDrop, ContextMenu, None }; @@ -112,10 +123,12 @@ private: SeekerSlider *m_seeker = nullptr; int particlemode; ModelCache m_canvasCache; - ModelNode m_droppedMaterial; + ModelNode m_droppedModelNode; NodeAtPosReqType m_nodeAtPosReqType; QPoint m_contextMenuPos; QTimer m_compressionTimer; + QPointer m_chooseMatPropsView; + QHash> m_textureModels; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp index e2da1c48135..8261301e1ee 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp @@ -427,7 +427,8 @@ void Edit3DWidget::dragEnterEvent(QDragEnterEvent *dragEnterEvent) ->viewManager().designerActionManager(); if (actionManager.externalDragHasSupportedAssets(dragEnterEvent->mimeData()) || dragEnterEvent->mimeData()->hasFormat(Constants::MIME_TYPE_MATERIAL) - || dragEnterEvent->mimeData()->hasFormat(Constants::MIME_TYPE_BUNDLE_MATERIAL)) { + || dragEnterEvent->mimeData()->hasFormat(Constants::MIME_TYPE_BUNDLE_MATERIAL) + || dragEnterEvent->mimeData()->hasFormat(Constants::MIME_TYPE_TEXTURE)) { dragEnterEvent->acceptProposedAction(); } } @@ -436,15 +437,23 @@ void Edit3DWidget::dropEvent(QDropEvent *dropEvent) { const QPointF pos = m_canvas->mapFrom(this, dropEvent->position()); - // handle dropping materials - if (dropEvent->mimeData()->hasFormat(Constants::MIME_TYPE_MATERIAL)) { - QByteArray data = dropEvent->mimeData()->data(Constants::MIME_TYPE_MATERIAL); + // handle dropping materials and textures + if (dropEvent->mimeData()->hasFormat(Constants::MIME_TYPE_MATERIAL) + || dropEvent->mimeData()->hasFormat(Constants::MIME_TYPE_TEXTURE)) { + bool isMaterial = dropEvent->mimeData()->hasFormat(Constants::MIME_TYPE_MATERIAL); + QByteArray data = dropEvent->mimeData()->data(isMaterial + ? QString::fromLatin1(Constants::MIME_TYPE_MATERIAL) + : QString::fromLatin1(Constants::MIME_TYPE_TEXTURE)); QDataStream stream(data); qint32 internalId; stream >> internalId; - if (ModelNode matNode = m_view->modelNodeForInternalId(internalId)) - m_view->dropMaterial(matNode, pos); + if (ModelNode dropNode = m_view->modelNodeForInternalId(internalId)) { + if (isMaterial) + m_view->dropMaterial(dropNode, pos); + else + m_view->dropTexture(dropNode, pos); + } return; } diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp index 75aad7bd7af..0cac19c4988 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp @@ -134,7 +134,7 @@ bool MaterialBrowserWidget::eventFilter(QObject *obj, QEvent *event) QByteArray data; QMimeData *mimeData = new QMimeData; QDataStream stream(&data, QIODevice::WriteOnly); - stream << m_materialToDrag.internalId(); + stream << (isMaterial ? m_materialToDrag.internalId() : m_textureToDrag.internalId()); mimeData->setData(isMaterial ? QString::fromLatin1(Constants::MIME_TYPE_MATERIAL) : QString::fromLatin1(Constants::MIME_TYPE_TEXTURE), data); diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp index b1594f80f93..97d93bb7b8f 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp @@ -1149,7 +1149,7 @@ bool MaterialEditorView::eventFilter(QObject *obj, QEvent *event) if (m_qmlBackEnd && m_qmlBackEnd->widget() == obj) QMetaObject::invokeMethod(m_qmlBackEnd->widget()->rootObject(), "closeContextMenu"); } - return QObject::eventFilter(obj, event); + return AbstractView::eventFilter(obj, event); } void MaterialEditorView::reloadQml() From 04864bd0d1fa26b323c84ae16b25833b1923df4d Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Wed, 16 Nov 2022 16:11:41 +0100 Subject: [PATCH 005/131] QmlDesigner: Show message when generating package Task-number: QDS-6825 Change-Id: I4bc826789de51778f7e9ad1314f9b0e029c7949f Reviewed-by: Thomas Hartmann --- src/plugins/qmldesigner/generateresource.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/plugins/qmldesigner/generateresource.cpp b/src/plugins/qmldesigner/generateresource.cpp index dce45fab5e9..4e319f2c1c1 100644 --- a/src/plugins/qmldesigner/generateresource.cpp +++ b/src/plugins/qmldesigner/generateresource.cpp @@ -3,12 +3,13 @@ #include -#include #include +#include #include #include -#include #include +#include +#include #include #include @@ -549,6 +550,13 @@ void GenerateResource::generateMenuEntry(QObject *parent) return; } } + + Core::AsynchronousMessageBox::information( + QCoreApplication::translate("QmlDesigner::GenerateResource", + "Success"), + QCoreApplication::translate("QmlDesigner::GenerateResource", + "Successfully generated deployable package\n %1") + .arg(resourceFileName.toString())); }); menu->addAction(cmd, Core::Constants::G_FILE_EXPORT); menu->addAction(cmd2, Core::Constants::G_FILE_EXPORT); From 409f7f226fd4b5dd99441bf466394d4ecde1441e Mon Sep 17 00:00:00 2001 From: Tim Jenssen Date: Wed, 16 Nov 2022 12:10:06 +0100 Subject: [PATCH 006/131] deployqtHelper_mac.sh: make sdktool optional Silents the warning about not existing sdktool in QtDesignStudio build. Change-Id: Ia8e3f341936f4b20d340f9c66a5da3d01f14bccf Reviewed-by: Reviewed-by: Burak Hancerli Reviewed-by: Eike Ziller --- scripts/deployqtHelper_mac.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/deployqtHelper_mac.sh b/scripts/deployqtHelper_mac.sh index cc09b80ad67..4109e6f31db 100755 --- a/scripts/deployqtHelper_mac.sh +++ b/scripts/deployqtHelper_mac.sh @@ -126,11 +126,15 @@ if [ ! -d "$app_path/Contents/Frameworks/QtCore.framework" ]; then if [ -f "$qml2puppetapp" ]; then qml2puppetArgument="-executable=$qml2puppetapp" fi + sdktoolapp="$libexec_path/sdktool" + if [ -f "$sdktoolapp" ]; then + sdktoolArgument="-executable=$sdktoolapp" + fi "$bin_src/macdeployqt" "$app_path" \ "-executable=$app_path/Contents/MacOS/qtdiag" \ "-executable=$libexec_path/qtpromaker" \ - "-executable=$libexec_path/sdktool" \ + "$sdktoolArgument" \ "-executable=$libexec_path/ios/iostool" \ "-executable=$libexec_path/buildoutputparser" \ "-executable=$libexec_path/cpaster" \ From 1f7383096c74d09cba342e2fc3de9e2f42a6200b Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Wed, 16 Nov 2022 14:09:55 +0100 Subject: [PATCH 007/131] QmlDesigner: Add more output to debug view Change-Id: I305778e71eda6675fc63daba58763dbc570ad722 Reviewed-by: Tim Jenssen --- .../components/debugview/debugview.cpp | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/plugins/qmldesigner/components/debugview/debugview.cpp b/src/plugins/qmldesigner/components/debugview/debugview.cpp index 286e1000f3e..527a8644569 100644 --- a/src/plugins/qmldesigner/components/debugview/debugview.cpp +++ b/src/plugins/qmldesigner/components/debugview/debugview.cpp @@ -247,6 +247,17 @@ QTextStream &operator<<(QTextStream &stream, AuxiliaryDataType type) } } // namespace +static QString rectFToString(const QRectF &rect) +{ + return QString::number(rect.x()) + " " + QString::number(rect.y()) + " " + + QString::number(rect.width()) + " " + QString::number(rect.height()); +} + +static QString sizeToString(const QSize &size) +{ + return QString::number(size.width()) + " " + QString::number(size.height()); +} + void DebugView::selectedNodesChanged(const QList &selectedNodes /*selectedNodeList*/, const QList & /*lastSelectedNodeList*/) { @@ -266,6 +277,10 @@ void DebugView::selectedNodesChanged(const QList &selectedNodes /*sel message << lineBreak; + message << lineBreak; + message << "metaInfo valid: " << selectedNode.metaInfo().isValid(); + message << lineBreak; + if (selectedNode.metaInfo().isValid()) { for (const NodeMetaInfo &metaInfo : selectedNode.metaInfo().classHierarchy()) message << metaInfo.typeName() << lineBreak; @@ -289,6 +304,42 @@ void DebugView::selectedNodesChanged(const QList &selectedNodes /*sel message << name << " "; message << lineBreak; + message << "Is QtQuickItem: " + << selectedNode.metaInfo().isBasedOn(model()->qtQuickItemMetaInfo()); + } + + message << lineBreak; + message << "Is item or window: " << QmlItemNode::isItemOrWindow(selectedNode); + message << lineBreak; + + message << lineBreak; + message << "Is graphical item: " << selectedNode.metaInfo().isGraphicalItem(); + message << lineBreak; + + message << lineBreak; + message << "Is valid object node: " << QmlItemNode::isValidQmlObjectNode(selectedNode); + message << lineBreak; + + message << "version: " + << QString::number(selectedNode.metaInfo().majorVersion()) + "." + + QString::number(selectedNode.metaInfo().minorVersion()); + + message << lineBreak; + + QmlItemNode itemNode(selectedNode); + if (itemNode.isValid()) { + message << lineBreak; + message << "Item Node"; + message << lineBreak; + message << "BR "; + message << rectFToString(itemNode.instanceBoundingRect()); + message << lineBreak; + message << "BR content "; + message << rectFToString(itemNode.instanceContentItemBoundingRect()); + message << lineBreak; + message << "PM "; + message << sizeToString(itemNode.instanceRenderPixmap().size()); + message << lineBreak; } auto auxiliaryData = selectedNode.auxiliaryData(); From 9880d9a33a0ae23d5262a4498b38a4517d2ae34b Mon Sep 17 00:00:00 2001 From: Ali Kianian Date: Thu, 17 Nov 2022 17:41:04 +0200 Subject: [PATCH 008/131] QmlDesigner: Less delay in opening context menu of 3D editor A comment line is prepended to the content of the clipboard data in order to mark it as a 3D content. So, we might check if the content of the clipboard is suitable for being pasted in the 3D editor or not. Task-number: QDS-8347 Change-Id: Id4319c31ffeb3f43fd032500f92a7cb7c21910eb Reviewed-by: Miikka Heikkinen Reviewed-by: Mahmoud Badri Reviewed-by: Thomas Hartmann --- .../components/edit3d/edit3dwidget.cpp | 31 ++----------------- .../integration/designdocumentview.cpp | 21 ++++++++++++- .../qmldesigner/qmldesignerconstants.h | 3 ++ 3 files changed, 26 insertions(+), 29 deletions(-) diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp index 8261301e1ee..59aba18bd62 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp @@ -30,6 +30,8 @@ #include #include +#include +#include #include #include @@ -235,34 +237,7 @@ void Edit3DWidget::createContextMenu() bool Edit3DWidget::isPasteAvailable() const { - if (TimelineActions::clipboardContainsKeyframes()) - return false; - - auto pasteModel(DesignDocumentView::pasteToModel(view()->externalDependencies())); - if (!pasteModel) - return false; - - DesignDocumentView docView{view()->externalDependencies()}; - pasteModel->attachView(&docView); - auto rootNode = docView.rootModelNode(); - - if (rootNode.type() == "empty") - return false; - - QList allNodes; - if (rootNode.id() == "__multi__selection__") - allNodes << rootNode.directSubModelNodes(); - else - allNodes << rootNode; - - bool hasNon3DNode = std::any_of(allNodes.begin(), allNodes.end(), [](const ModelNode &node) { - return !node.metaInfo().isQtQuick3DNode(); - }); - - if (hasNon3DNode) - return false; - - return true; + return QApplication::clipboard()->text().startsWith(Constants::HEADER_3DPASTE_CONTENT); } // Called by the view to update the "create" sub-menu when the Quick3D entries are ready. diff --git a/src/plugins/qmldesigner/components/integration/designdocumentview.cpp b/src/plugins/qmldesigner/components/integration/designdocumentview.cpp index d2f63010f6d..5513ca1664d 100644 --- a/src/plugins/qmldesigner/components/integration/designdocumentview.cpp +++ b/src/plugins/qmldesigner/components/integration/designdocumentview.cpp @@ -9,8 +9,10 @@ #include #include "designdocument.h" +#include #include #include +#include #include #include @@ -18,6 +20,7 @@ #include #include +#include #include namespace QmlDesigner { @@ -80,6 +83,18 @@ void DesignDocumentView::fromClipboard() // } } +static bool hasOnly3DNodes(const ModelNode &node) +{ + if (node.id() == "__multi__selection__") { + bool hasNon3DNode = Utils::anyOf(node.directSubModelNodes(), [](const ModelNode &node) { + return !node.metaInfo().isQtQuick3DNode(); + }); + + return !hasNon3DNode; + } + return node.metaInfo().isQtQuick3DNode(); +} + QString DesignDocumentView::toText() const { auto outputModel = Model::create("QtQuick.Rectangle", 1, 0, model()); @@ -110,7 +125,11 @@ QString DesignDocumentView::toText() const ModelNode rewriterNode(rewriterView->rootModelNode()); rewriterView->writeAuxiliaryData(); - return rewriterView->extractText({rewriterNode}).value(rewriterNode) + rewriterView->getRawAuxiliaryData(); + + QString paste3DHeader = hasOnly3DNodes(rewriterNode) ? QString(Constants::HEADER_3DPASTE_CONTENT) : QString(); + return paste3DHeader + + rewriterView->extractText({rewriterNode}).value(rewriterNode) + + rewriterView->getRawAuxiliaryData(); //get the text of the root item without imports } diff --git a/src/plugins/qmldesigner/qmldesignerconstants.h b/src/plugins/qmldesigner/qmldesignerconstants.h index 7b32ea297b9..61cc2cf316b 100644 --- a/src/plugins/qmldesigner/qmldesignerconstants.h +++ b/src/plugins/qmldesigner/qmldesignerconstants.h @@ -125,6 +125,9 @@ const char EVENT_INSIGHT_TIME[] = "insight"; const char PROPERTY_EDITOR_CLASSNAME_PROPERTY[] = "__classNamePrivateInternal"; +// Copy/Paste Headers +const char HEADER_3DPASTE_CONTENT[] = "// __QmlDesigner.Editor3D.Paste__ \n"; + namespace Internal { enum { debug = 0 }; } From 815dd39e478378b01c3489d9ecd2e85dedb75b48 Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Thu, 17 Nov 2022 12:54:19 +0200 Subject: [PATCH 009/131] QmlDesigner: Implement Texture Editor view Fixes: QDS-8209 Change-Id: Ief0c9f56da79841c745595024dbcd9219072b681 Reviewed-by: Miikka Heikkinen --- .../materialBrowserQmlSource/TextureItem.qml | 1 + .../MaterialEditorTopSection.qml | 1 + .../EmptyTextureEditorPane.qml | 50 + .../TextureEditorPane.qml | 65 ++ .../TextureEditorToolBar.qml | 69 ++ .../TextureEditorTopSection.qml | 45 + src/plugins/qmldesigner/CMakeLists.txt | 12 + .../components/componentcore/viewmanager.cpp | 5 + .../components/edit3d/edit3dview.cpp | 99 +- .../components/edit3d/edit3dview.h | 1 + .../materialbrowsertexturesmodel.cpp | 14 + .../materialbrowsertexturesmodel.h | 7 + .../materialbrowser/materialbrowserview.cpp | 24 +- .../materialbrowser/materialbrowserwidget.cpp | 2 +- .../materialeditor/images/texture_default.png | Bin 3219 -> 0 bytes .../images/texture_default@2x.png | Bin 7659 -> 0 bytes .../materialeditor/materialeditor.qrc | 2 - .../materialeditor/materialeditorview.cpp | 6 + .../textureeditor/images/texture_default.png | Bin 0 -> 5883 bytes .../images/texture_default@2x.png | Bin 0 -> 15348 bytes .../textureeditor/textureeditor.qrc | 6 + .../textureeditorcontextobject.cpp | 324 +++++++ .../textureeditorcontextobject.h | 156 ++++ ...xtureeditordynamicpropertiesproxymodel.cpp | 22 + ...textureeditordynamicpropertiesproxymodel.h | 16 + .../textureeditor/textureeditorqmlbackend.cpp | 310 +++++++ .../textureeditor/textureeditorqmlbackend.h | 69 ++ .../textureeditortransaction.cpp | 48 + .../textureeditor/textureeditortransaction.h | 31 + .../textureeditor/textureeditorview.cpp | 866 ++++++++++++++++++ .../textureeditor/textureeditorview.h | 127 +++ .../qmldesigner/qmldesignerconstants.h | 1 + 32 files changed, 2324 insertions(+), 55 deletions(-) create mode 100644 share/qtcreator/qmldesigner/textureEditorQmlSource/EmptyTextureEditorPane.qml create mode 100644 share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorPane.qml create mode 100644 share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorToolBar.qml create mode 100644 share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorTopSection.qml delete mode 100644 src/plugins/qmldesigner/components/materialeditor/images/texture_default.png delete mode 100644 src/plugins/qmldesigner/components/materialeditor/images/texture_default@2x.png create mode 100644 src/plugins/qmldesigner/components/textureeditor/images/texture_default.png create mode 100644 src/plugins/qmldesigner/components/textureeditor/images/texture_default@2x.png create mode 100644 src/plugins/qmldesigner/components/textureeditor/textureeditor.qrc create mode 100644 src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.cpp create mode 100644 src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.h create mode 100644 src/plugins/qmldesigner/components/textureeditor/textureeditordynamicpropertiesproxymodel.cpp create mode 100644 src/plugins/qmldesigner/components/textureeditor/textureeditordynamicpropertiesproxymodel.h create mode 100644 src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.cpp create mode 100644 src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.h create mode 100644 src/plugins/qmldesigner/components/textureeditor/textureeditortransaction.cpp create mode 100644 src/plugins/qmldesigner/components/textureeditor/textureeditortransaction.h create mode 100644 src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp create mode 100644 src/plugins/qmldesigner/components/textureeditor/textureeditorview.h diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml index 4f73722f692..b0861aa882e 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml @@ -42,5 +42,6 @@ Rectangle { sourceSize.height: root.height - 10 anchors.centerIn: parent cache: false + smooth: true } } diff --git a/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorTopSection.qml b/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorTopSection.qml index 73e9865c194..b98f7293f3b 100644 --- a/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorTopSection.qml +++ b/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorTopSection.qml @@ -140,6 +140,7 @@ Column { anchors.centerIn: parent source: "image://materialEditor/preview" cache: false + smooth: true } } diff --git a/share/qtcreator/qmldesigner/textureEditorQmlSource/EmptyTextureEditorPane.qml b/share/qtcreator/qmldesigner/textureEditorQmlSource/EmptyTextureEditorPane.qml new file mode 100644 index 00000000000..b6527e082d7 --- /dev/null +++ b/share/qtcreator/qmldesigner/textureEditorQmlSource/EmptyTextureEditorPane.qml @@ -0,0 +1,50 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuickDesignerTheme 1.0 +import HelperWidgets 2.0 +import StudioTheme 1.0 as StudioTheme + +PropertyEditorPane { + id: root + + signal toolBarAction(int action) + + // Called from C++, dummy method to avoid warnings + function closeContextMenu() {} + + Column { + id: col + + TextureEditorToolBar { + width: root.width + + onToolBarAction: (action) => root.toolBarAction(action) + } + + Item { + width: root.width - 2 * col.padding + height: 150 + + Text { + text: { + if (!hasQuick3DImport) + qsTr("To use Texture Editor, first add the QtQuick3D module in the Components view.") + else if (!hasMaterialLibrary) + qsTr("Texture Editor is disabled inside a non-visual component.") + else + qsTr("There are no textures in this project.
Select '+' to create one.") + } + textFormat: Text.RichText + color: StudioTheme.Values.themeTextColor + font.pixelSize: StudioTheme.Values.mediumFontSize + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WordWrap + width: root.width + anchors.centerIn: parent + } + } + } +} diff --git a/share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorPane.qml b/share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorPane.qml new file mode 100644 index 00000000000..30e21478226 --- /dev/null +++ b/share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorPane.qml @@ -0,0 +1,65 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +import QtQuick 2.15 +import QtQuickDesignerTheme 1.0 +import HelperWidgets 2.0 + +PropertyEditorPane { + id: itemPane + + signal toolBarAction(int action) + + // invoked from C++ to refresh material preview image + function refreshPreview() + { + topSection.refreshPreview() + } + + // Called also from C++ to close context menu on focus out + function closeContextMenu() + { + // Nothing + } + + TextureEditorTopSection { + id: topSection + + onToolBarAction: (action) => itemPane.toolBarAction(action) + } + + Item { width: 1; height: 10 } + + DynamicPropertiesSection { + propertiesModel: TextureEditorDynamicPropertiesModel {} + } + + Loader { + id: specificsTwo + + property string theSource: specificQmlData + + anchors.left: parent.left + anchors.right: parent.right + visible: theSource !== "" + sourceComponent: specificQmlComponent + + onTheSourceChanged: { + active = false + active = true + } + } + + Item { + width: 1 + height: 10 + visible: specificsTwo.visible + } + + Loader { + id: specificsOne + anchors.left: parent.left + anchors.right: parent.right + source: specificsUrl + } +} diff --git a/share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorToolBar.qml b/share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorToolBar.qml new file mode 100644 index 00000000000..91b46c5da47 --- /dev/null +++ b/share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorToolBar.qml @@ -0,0 +1,69 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +import QtQuick 2.15 +import QtQuickDesignerTheme 1.0 +import HelperWidgets 2.0 +import StudioTheme 1.0 as StudioTheme +import ToolBarAction 1.0 + +Rectangle { + id: root + + color: StudioTheme.Values.themeSectionHeadBackground + width: row.width + height: 40 + + signal toolBarAction(int action) + + Row { + id: row + + anchors.verticalCenter: parent.verticalCenter + leftPadding: 6 + + IconButton { + icon: StudioTheme.Constants.applyMaterialToSelected + + normalColor: StudioTheme.Values.themeSectionHeadBackground + iconSize: StudioTheme.Values.bigIconFontSize + buttonSize: root.height + enabled: hasTexture && hasModelSelection && hasQuick3DImport && hasMaterialLibrary + onClicked: root.toolBarAction(ToolBarAction.ApplyToSelected) + tooltip: qsTr("Apply texture to selected model's material.") + } + + IconButton { + icon: StudioTheme.Constants.newMaterial + + normalColor: StudioTheme.Values.themeSectionHeadBackground + iconSize: StudioTheme.Values.bigIconFontSize + buttonSize: root.height + enabled: hasQuick3DImport && hasMaterialLibrary + onClicked: root.toolBarAction(ToolBarAction.AddNewTexture) + tooltip: qsTr("Create new texture.") + } + + IconButton { + icon: StudioTheme.Constants.deleteMaterial + + normalColor: StudioTheme.Values.themeSectionHeadBackground + iconSize: StudioTheme.Values.bigIconFontSize + buttonSize: root.height + enabled: hasTexture && hasQuick3DImport && hasMaterialLibrary + onClicked: root.toolBarAction(ToolBarAction.DeleteCurrentTexture) + tooltip: qsTr("Delete current texture.") + } + + IconButton { + icon: StudioTheme.Constants.openMaterialBrowser + + normalColor: StudioTheme.Values.themeSectionHeadBackground + iconSize: StudioTheme.Values.bigIconFontSize + buttonSize: root.height + enabled: hasQuick3DImport && hasMaterialLibrary + onClicked: root.toolBarAction(ToolBarAction.OpenMaterialBrowser) + tooltip: qsTr("Open material browser.") + } + } +} diff --git a/share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorTopSection.qml b/share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorTopSection.qml new file mode 100644 index 00000000000..287278d9548 --- /dev/null +++ b/share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorTopSection.qml @@ -0,0 +1,45 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +import QtQuick + +Column { + id: root + + signal toolBarAction(int action) + + function refreshPreview() + { + texturePreview.source = "" + texturePreview.source = "image://textureEditor/" + backendValues.source.valueToString + } + + anchors.left: parent.left + anchors.right: parent.right + + TextureEditorToolBar { + width: root.width + + onToolBarAction: (action) => root.toolBarAction(action) + } + + Item { width: 1; height: 10 } // spacer + + Rectangle { + id: previewRect + anchors.horizontalCenter: parent.horizontalCenter + width: 152 + height: 152 + color: "#000000" + + Image { + id: texturePreview + + sourceSize.width: 150 + sourceSize.height: 150 + anchors.centerIn: parent + source: "image://textureEditor/" + backendValues.source.valueToString + cache: false + } + } +} diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 4ef26740a80..c2c3c941b2b 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -410,6 +410,7 @@ add_qtc_plugin(QmlDesigner ${CMAKE_CURRENT_LIST_DIR}/components/itemlibrary ${CMAKE_CURRENT_LIST_DIR}/components/materialbrowser ${CMAKE_CURRENT_LIST_DIR}/components/materialeditor + ${CMAKE_CURRENT_LIST_DIR}/components/textureeditor ${CMAKE_CURRENT_LIST_DIR}/components/navigator ${CMAKE_CURRENT_LIST_DIR}/components/propertyeditor ${CMAKE_CURRENT_LIST_DIR}/components/stateseditor @@ -814,6 +815,17 @@ extend_qtc_plugin(QmlDesigner materialeditor.qrc ) +extend_qtc_plugin(QmlDesigner + SOURCES_PREFIX components/textureeditor + SOURCES + textureeditorcontextobject.cpp textureeditorcontextobject.h + textureeditordynamicpropertiesproxymodel.cpp textureeditordynamicpropertiesproxymodel.h + textureeditorqmlbackend.cpp textureeditorqmlbackend.h + textureeditortransaction.cpp textureeditortransaction.h + textureeditorview.cpp textureeditorview.h + textureeditor.qrc +) + extend_qtc_plugin(QmlDesigner SOURCES_PREFIX components/materialbrowser SOURCES diff --git a/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp b/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp index 5a06b2b1e83..08ee673ff16 100644 --- a/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -69,6 +70,7 @@ public: , propertyEditorView(imageCache, externalDependencies) , materialEditorView{externalDependencies} , materialBrowserView{externalDependencies} + , textureEditorView{externalDependencies} , statesEditorView{externalDependencies} , newStatesEditorView{externalDependencies} {} @@ -90,6 +92,7 @@ public: PropertyEditorView propertyEditorView; MaterialEditorView materialEditorView; MaterialBrowserView materialBrowserView; + TextureEditorView textureEditorView; StatesEditorView statesEditorView; Experimental::StatesEditorView newStatesEditorView; @@ -219,6 +222,7 @@ QList ViewManager::standardViews() const &d->contentLibraryView, &d->materialEditorView, &d->materialBrowserView, + &d->textureEditorView, &d->statesEditorView, &d->newStatesEditorView, // TODO &d->designerActionManagerView}; @@ -400,6 +404,7 @@ QList ViewManager::widgetInfos() const widgetInfoList.append(d->contentLibraryView.widgetInfo()); widgetInfoList.append(d->materialEditorView.widgetInfo()); widgetInfoList.append(d->materialBrowserView.widgetInfo()); + widgetInfoList.append(d->textureEditorView.widgetInfo()); if (useOldStatesEditor()) widgetInfoList.append(d->statesEditorView.widgetInfo()); else diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp index 2eedb474d98..b41977e9cdc 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp @@ -274,6 +274,8 @@ void Edit3DView::customNotification([[maybe_unused]] const AbstractView *view, { if (identifier == "asset_import_update") resetPuppet(); + else if (identifier == "apply_texture_to_model3D") + applyTextureToModel3D(nodeList.at(0), nodeList.at(1)); } bool Edit3DView::eventFilter(QObject *obj, QEvent *event) @@ -320,51 +322,7 @@ void Edit3DView::nodeAtPosReady(const ModelNode &modelNode, const QVector3D &pos } else if (m_nodeAtPosReqType == NodeAtPosReqType::BundleMaterialDrop) { emitCustomNotification("drop_bundle_material", {modelNode}); // To ContentLibraryView } else if (m_nodeAtPosReqType == NodeAtPosReqType::TextureDrop) { - if (m_droppedModelNode.isValid() && modelNode.isValid() && modelNode.metaInfo().isQtQuick3DModel()) { - // get model's material list - BindingProperty matsProp = modelNode.bindingProperty("materials"); - QList materials; - if (hasId(matsProp.expression())) - materials.append(modelNodeForId(matsProp.expression())); - else - materials = matsProp.resolveToModelNodeList(); - - if (materials.size() > 0) { - m_textureModels.clear(); - QStringList materialsModel; - for (const ModelNode &mat : std::as_const(materials)) { - QString matName = mat.variantProperty("objectName").value().toString(); - materialsModel.append(QLatin1String("%1 (%2)").arg(matName, mat.id())); - QList texProps; - for (const PropertyMetaInfo &p : mat.metaInfo().properties()) { - if (p.propertyType().isQtQuick3DTexture()) - texProps.append(p.name()); - } - m_textureModels.insert(mat.id(), texProps); - } - - QString path = MaterialBrowserWidget::qmlSourcesPath() + "/ChooseMaterialProperty.qml"; - - m_chooseMatPropsView = new QQuickView; - m_chooseMatPropsView->setTitle(tr("Select a material property")); - m_chooseMatPropsView->setResizeMode(QQuickView::SizeRootObjectToView); - m_chooseMatPropsView->setMinimumSize({150, 100}); - m_chooseMatPropsView->setMaximumSize({600, 400}); - m_chooseMatPropsView->setWidth(450); - m_chooseMatPropsView->setHeight(300); - m_chooseMatPropsView->setFlags(Qt::Widget); - m_chooseMatPropsView->setModality(Qt::ApplicationModal); - m_chooseMatPropsView->engine()->addImportPath(propertyEditorResourcesPath() + "/imports"); - m_chooseMatPropsView->rootContext()->setContextProperties({ - {"rootView", QVariant::fromValue(this)}, - {"materialsModel", QVariant::fromValue(materialsModel)}, - {"propertiesModel", QVariant::fromValue(m_textureModels.value(materials.at(0).id()))}, - }); - m_chooseMatPropsView->setSource(QUrl::fromLocalFile(path)); - m_chooseMatPropsView->installEventFilter(this); - m_chooseMatPropsView->show(); - } - } + applyTextureToModel3D(modelNode, m_droppedModelNode); } if (m_nodeAtPosReqType != NodeAtPosReqType::TextureDrop) @@ -372,6 +330,57 @@ void Edit3DView::nodeAtPosReady(const ModelNode &modelNode, const QVector3D &pos m_nodeAtPosReqType = NodeAtPosReqType::None; } +void Edit3DView::applyTextureToModel3D(const ModelNode &model3D, const ModelNode &texture) +{ + if (!texture.isValid() || !model3D.isValid() || !model3D.metaInfo().isQtQuick3DModel()) + return; + + m_droppedModelNode = texture; + + // get model's material list + BindingProperty matsProp = model3D.bindingProperty("materials"); + QList materials; + if (hasId(matsProp.expression())) + materials.append(modelNodeForId(matsProp.expression())); + else + materials = matsProp.resolveToModelNodeList(); + + if (materials.size() > 0) { + m_textureModels.clear(); + QStringList materialsModel; + for (const ModelNode &mat : std::as_const(materials)) { + QString matName = mat.variantProperty("objectName").value().toString(); + materialsModel.append(QLatin1String("%1 (%2)").arg(matName, mat.id())); + QList texProps; + for (const PropertyMetaInfo &p : mat.metaInfo().properties()) { + if (p.propertyType().isQtQuick3DTexture()) + texProps.append(p.name()); + } + m_textureModels.insert(mat.id(), texProps); + } + + QString path = MaterialBrowserWidget::qmlSourcesPath() + "/ChooseMaterialProperty.qml"; + + m_chooseMatPropsView = new QQuickView; + m_chooseMatPropsView->setTitle(tr("Select a material property")); + m_chooseMatPropsView->setResizeMode(QQuickView::SizeRootObjectToView); + m_chooseMatPropsView->setMinimumSize({150, 100}); + m_chooseMatPropsView->setMaximumSize({600, 400}); + m_chooseMatPropsView->setWidth(450); + m_chooseMatPropsView->setHeight(300); + m_chooseMatPropsView->setFlags(Qt::Widget); + m_chooseMatPropsView->setModality(Qt::ApplicationModal); + m_chooseMatPropsView->engine()->addImportPath(propertyEditorResourcesPath() + "/imports"); + m_chooseMatPropsView->rootContext()->setContextProperties({ + {"rootView", QVariant::fromValue(this)}, + {"materialsModel", QVariant::fromValue(materialsModel)}, + {"propertiesModel", QVariant::fromValue(m_textureModels.value(materials.at(0).id()))}, + }); + m_chooseMatPropsView->setSource(QUrl::fromLocalFile(path)); + m_chooseMatPropsView->installEventFilter(this); + m_chooseMatPropsView->show(); + } +} void Edit3DView::sendInputEvent(QInputEvent *e) const { if (nodeInstanceView()) diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.h b/src/plugins/qmldesigner/components/edit3d/edit3dview.h index be510e45660..2f542c0e714 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.h +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.h @@ -65,6 +65,7 @@ public: void dropMaterial(const ModelNode &matNode, const QPointF &pos); void dropBundleMaterial(const QPointF &pos); void dropTexture(const ModelNode &textureNode, const QPointF &pos); + void applyTextureToModel3D(const ModelNode &model3D, const ModelNode &texture); Q_INVOKABLE void updatePropsModel(const QString &matId); Q_INVOKABLE void applyTextureToMaterial(const QString &matId, const QString &propName); diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp index 15613960da1..772620cd04e 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp @@ -180,6 +180,20 @@ ModelNode MaterialBrowserTexturesModel::textureAt(int idx) const return {}; } +bool MaterialBrowserTexturesModel::hasSingleModelSelection() const +{ + return m_hasSingleModelSelection; +} + +void MaterialBrowserTexturesModel::setHasSingleModelSelection(bool b) +{ + if (b == m_hasSingleModelSelection) + return; + + m_hasSingleModelSelection = b; + emit hasSingleModelSelectionChanged(); +} + void MaterialBrowserTexturesModel::resetModel() { beginResetModel(); diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h index 4b5d1bd55bf..f23ca1e48ac 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h @@ -16,6 +16,8 @@ class MaterialBrowserTexturesModel : public QAbstractListModel Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged) Q_PROPERTY(int selectedIndex MEMBER m_selectedIndex NOTIFY selectedIndexChanged) + Q_PROPERTY(bool hasSingleModelSelection READ hasSingleModelSelection + WRITE setHasSingleModelSelection NOTIFY hasSingleModelSelectionChanged) public: MaterialBrowserTexturesModel(QObject *parent = nullptr); @@ -35,6 +37,9 @@ public: int textureIndex(const ModelNode &material) const; ModelNode textureAt(int idx) const; + bool hasSingleModelSelection() const; + void setHasSingleModelSelection(bool b); + void resetModel(); Q_INVOKABLE void selectTexture(int idx, bool force = false); @@ -43,6 +48,7 @@ public: signals: void isEmptyChanged(); + void hasSingleModelSelectionChanged(); void materialSectionsChanged(); void selectedIndexChanged(int idx); void duplicateTextureTriggered(const QmlDesigner::ModelNode &material); @@ -58,6 +64,7 @@ private: int m_selectedIndex = 0; bool m_isEmpty = true; + bool m_hasSingleModelSelection = false; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp index 0f05ed0b39b..5238ae0ed3f 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp @@ -49,9 +49,9 @@ WidgetInfo MaterialBrowserView::widgetInfo() auto matEditorContext = new Internal::MaterialBrowserContext(m_widget.data()); Core::ICore::addContextObject(matEditorContext); - MaterialBrowserModel *matBrowserModel = m_widget->materialBrowserModel().data(); // custom notifications below are sent to the MaterialEditor + MaterialBrowserModel *matBrowserModel = m_widget->materialBrowserModel().data(); connect(matBrowserModel, &MaterialBrowserModel::selectedIndexChanged, this, [&] (int idx) { ModelNode matNode = m_widget->materialBrowserModel()->materialAt(idx); @@ -139,6 +139,13 @@ WidgetInfo MaterialBrowserView::widgetInfo() } }); }); + + // custom notifications below are sent to the TextureEditor + MaterialBrowserTexturesModel *texturesModel = m_widget->materialBrowserTexturesModel().data(); + connect(texturesModel, &MaterialBrowserTexturesModel::selectedIndexChanged, this, [&] (int idx) { + ModelNode texNode = m_widget->materialBrowserTexturesModel()->textureAt(idx); + emitCustomNotification("selected_texture_changed", {texNode}, {}); + }); } return createWidgetInfo(m_widget.data(), @@ -230,6 +237,7 @@ void MaterialBrowserView::selectedNodesChanged(const QList &selectedN }); m_widget->materialBrowserModel()->setHasModelSelection(!m_selectedModels.isEmpty()); + m_widget->materialBrowserTexturesModel()->setHasSingleModelSelection(m_selectedModels.size() == 1); // the logic below selects the material of the first selected model if auto selection is on if (!m_autoSelectModelMaterial) @@ -299,20 +307,21 @@ void MaterialBrowserView::nodeReparented(const ModelNode &node, void MaterialBrowserView::nodeAboutToBeRemoved(const ModelNode &removedNode) { - // removing the material editor node + // removing the material lib node if (removedNode.id() == Constants::MATERIAL_LIB_ID) { m_widget->materialBrowserModel()->setMaterials({}, m_hasQuick3DImport); m_widget->clearPreviewCache(); return; } - // not a material under the material editor - if (!isMaterial(removedNode) - || removedNode.parentProperty().parentModelNode().id() != Constants::MATERIAL_LIB_ID) { + // not under the material lib + if (removedNode.parentProperty().parentModelNode().id() != Constants::MATERIAL_LIB_ID) return; - } - m_widget->materialBrowserModel()->removeMaterial(removedNode); + if (isMaterial(removedNode)) + m_widget->materialBrowserModel()->removeMaterial(removedNode); + else if (isTexture(removedNode)) + m_widget->materialBrowserTexturesModel()->removeTexture(removedNode); } void MaterialBrowserView::nodeRemoved([[maybe_unused]] const ModelNode &removedNode, @@ -323,6 +332,7 @@ void MaterialBrowserView::nodeRemoved([[maybe_unused]] const ModelNode &removedN return; m_widget->materialBrowserModel()->updateSelectedMaterial(); + m_widget->materialBrowserTexturesModel()->updateSelectedTexture(); } void QmlDesigner::MaterialBrowserView::loadPropertyGroups() diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp index 0cac19c4988..fe2a9f06ab5 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp @@ -104,7 +104,7 @@ public: pixmap = Utils::StyleHelper::dpiSpecificImageFile(id); if (pixmap.isNull()) - pixmap = Utils::StyleHelper::dpiSpecificImageFile(":/materialeditor/images/texture_default.png"); + pixmap = Utils::StyleHelper::dpiSpecificImageFile(":/textureeditor/images/texture_default.png"); if (size) *size = pixmap.size(); diff --git a/src/plugins/qmldesigner/components/materialeditor/images/texture_default.png b/src/plugins/qmldesigner/components/materialeditor/images/texture_default.png deleted file mode 100644 index 70e85cc2ebae18303e61189afb839ab646e11207..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3219 zcmeAS@N?(olHy`uVBq!ia0y~yV2A=?4rT@h2L7^*1`G_0{Q*89t_%zejEs!SmMvSp zd^v<%x^yXoft5rPMB`!_f@T^r7nj~;FaS3VCIE6IeiM*ok+tJuFI%<@O$!g&z?Ph{^G^USFc~ce)Hz-+qduDy?_7V!^e-GK7IcDbT`I*}o7`O~QT^vIqT5}_Non_ob@9(s}vpYe!MZ-5l>CRLWrI3}O zi!^$-n)oVzTr3jQc(^OqCn#4-MdF<8j+yp%D(~m(-`#!t_T8D4pPzlNd!Jn1eIdi_ z1-JZ-l==208fTNGV-CpOxRhhIRbEcawYoO_`Q*8C@5=ssdx=s0)v@b$TqT2EtL-hz zYhQGy+wdrd^`FPa`=piA_Oz%E$KWF6Qw&P{}{zUjy!}G?zUCzr(eIiY|I4;dRS0XjL+j@mZCbyAViFQMs zxNCpVH7kp$+|x_~w@JAwJ~v8N+{6{ytXI_kPQYisb_0*tlI0EsKiS{s%FD^VyYn`d zb5BrX!%{92t1i(Op>hHTtxlX$54X48t6;=C?_ZhMwCpVHxH}87m=zoxE?V(EYi3r` zk>%Sv-zGA5#?|0$8-Jb59)H-R+}qvb@xq|sT$VJJ+_KrO)$-H+ z6*+c_Oq>6F+TlF!2i3LK^TS^o?Fs0!=8@gWG)s^nvs*It&YQ_o&i$_lJu&OQRe90# z&8p8$9WC^PJA55EKJVg8^SStO*T+D&^6FI@GA&Qpcb+~RG}q|Ftw|1E zP9|L4{4=iS|6J`o5vo1iGnbUII!@hd*t<#c<*(Q0joWAE9o+Nk>KpUzI%OFi8x=%T zR_^sGdMvl&%uI`4HF0Ni&qs%SIjc4A|D8uItlIvChnA^-{1xr8kCpBFvB;j+hr&}s z0`Ht!bl))lzQil>L0yE%CmWRpG|kxFcDeSA*kLFX~h4FkODP_MFj@ z3*|*ARywVJxsqQmmhgXWD&#rgvfaG}vBw3Tneye-Nt`>xg?RUU*Ew2$zW3vUQ}>gt zV#^fuI{c#|0@;_{(a@W0(%5?Ju1)ZN(^$EZ`G1_lQnn}mTbV7CzqR2+-#J5-q9=>} zF8@FCTt0s55@EIXsTO^^O}~enFI+7#c5}{@(^wJo^4>Fvp#I5r4@8o=jm($S{(K!j_0`kH z)1C#A%eU%XJ8r}7exYEY&N=s|#|n1DSurPXRG7$rc(POCN5?SD<~y584}EistKS+I zbt%wz+eW^NN)f#J_ZCiU*3^H>`G3>7h*M2D$Ar(GShSLFqw3Y>y+^Bdukc8pdvd$h zk(P~e+Gm^=I&>}Eb5-Pzl+n}O5#H+4)|Pfy?CL68vvlLqP3L`|Ma1QWDDN#@Q5P)r zGsaE+4EIZw(gR+|1DooQcYY z<7-$L_r0Fa1+ORV@(+mqcjFAlX2ok#6`n;_yJ~yxG1zQeaAZ>ASAM;dK^LFfD@4mV z1o+P3nsn#%g=@84nIB(7CI6II+iVl)z|q_D`I_o#yMklB-+Nw4=l|dBzv1x3uQCs( z_h0P&o;Jn)W>b&bwVSV4WbWSU`SXM`f648laD{hsFE*r2k#Q(5n_T@PCZq>77*`R8hdZj4nl3t`h$u^B$HA1W@Q+OwDa%Bz;G`fl`z;qQy6;GVb2%{`!*)0|NhFo;_LQ3{ADjKc z%JM)~)A4L=^%X2X_gJh>wR7v2%jW!9;OuY6kTY|Z5{vLT>xFD{d|b{uzv0vIS`(Zy zSMlz#eUl$=zj*lOq(z?s;^qll6MCI8_n5A(;>QDzw*TC&Gcnvc%+JXDO1&t*r-D;g zlB>bWnYSW0{o^paUgOUv@cy>2iTy+4fI{S6aHypV#i^PD zOJB|1xl{VbLf`(p8{2c-K3Bud(FKlRs9UTJiGCJxee5eTADAL{-mR`FE1( zO_8nNr>*yxcD8-_lj7%)Znud(qV_Umu?f_1xvcy8n`by|;7T lO^CiYvlj{ diff --git a/src/plugins/qmldesigner/components/materialeditor/images/texture_default@2x.png b/src/plugins/qmldesigner/components/materialeditor/images/texture_default@2x.png deleted file mode 100644 index a27efb275d39b149b901835c57ad93c750a686e3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7659 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 diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditor.qrc b/src/plugins/qmldesigner/components/materialeditor/materialeditor.qrc index 872452df1a0..4ed3c769175 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditor.qrc +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditor.qrc @@ -1,7 +1,5 @@ images/defaultmaterialpreview.png - images/texture_default.png - images/texture_default@2x.png diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp index 97d93bb7b8f..379ccc411ac 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp @@ -825,6 +825,12 @@ void MaterialEditorView::variantPropertiesChanged(const QList & changed = true; } + if (!changed && node.metaInfo().isQtQuick3DTexture() + && m_selectedMaterial.bindingProperties().size() > 0) { + // update preview when editing texture properties if the material has binding properties + changed = true; + } + dynamicPropertiesModel()->dispatchPropertyChanges(property); } if (changed) diff --git a/src/plugins/qmldesigner/components/textureeditor/images/texture_default.png b/src/plugins/qmldesigner/components/textureeditor/images/texture_default.png new file mode 100644 index 0000000000000000000000000000000000000000..32ad4e3eda62887f6faea7a26c394b03493cd519 GIT binary patch literal 5883 zcmeAS@N?(olHy`uVBq!ia0y~yV3-EN9Lx+14BYn{FEB7LE(!1nab;j&U}R)mwrtt* z<;x-D(xpprG4P8+WGN)!wl4z%xU0xB7EL>{;b>C$%_m7CR>g2ZOa?Bi;VK16!Xp?M z7&=RW{DK)6SXekXczAdO1O!AxL?k3+WMmW+6jW3+G<0+f3`|TcENpBX99&#HJbZit z0zx7pB4QE}Qc^N9a&ig^N=hm!YU&#rnw#6(yZiemPo6$~`s~^B=PzEoeEI6t>(_7I zynXxb-MjbiKYaN3@zbZzpTB(h`t|F#Z{NTF`0?ZC&!4}3{rdg;&!4}4|NT2o^YTvy z29a7%7srr_TW4dqlVz@!?n_@!e!Ls6C#-N5w%5||6`pR=$FXGn35S@+ z>ym_(-b{@>$-2Z#bdz$J^>_dDcaQIUo@>2(lgzf(jeB-~wky8({7(KlMd!T2`OBhK z$zN^%{nGBx{QvUP3ufGx^Ph0|(X;}A_q^VBS9+-`NImv?r@2|9V=v~&C>AKm%pALy36$U z1pn!?c~9TUeO+_LQ#|QV?Ap+nwPt*#iyfKF40ryzk&u`AXC?P4nZ2Brfz=HUci1l8 zDckaN-S&q&OurwPHq-5f%FZB@#VkL6-aq_i-eDXReA& zTe0rv%T9HL2^H%VBPPnO<+^pM>04W_<94}}o~>TmpW3%aoqBce$D5zcS+6}_Wqe+~ z{h*L?71*fE6XiR;x8TYNUGe|ZUl#33eI{p+Ue49I&d_H0{Ud@BO+`E}T3#ETTV^41 z{j%#NF$<+PZ{KV*bHB^=Z|9cVte#Q_wkvO)=OI|sGGj~I^GvUxdkH2puYP}|cYBKZ z>EF8Rqx*DD>a?((^y*P8&HgJ;Abu{pLC8+g*2k;;keGYe2Fd4sd-Dt|3y?ct@`?wX-m_T6Nj$+#rE|^$3FG8Nr8`iSi0t^ zOl)JGd}?#v#@ToBw6{%SWSrqv__X=ozk>+@)7sg8&tJ1=*M)|h*+zROU$zVsTdFWu zb90~GyG8du#o4`x5>fi28aj7pMfUFvM@-(IytdoB!Y6OXlnx^{(WV1G0z&5s%ypMg zD?jC!u-x~h;Ffvi_BAnS6BkF>?GJN!o4BAZ;U|;-fj3JmCJMDhY(H@NxX96+!A!hq zxif3acW+sIC}rnQH|IH?tCD+8Xidz#l9#Y67hns8&rgQqv=9tu9HUYGh;<6`lL7n5)Af4jEc%;V>| zgwnWee%OuygK zx09V>n6~!p^Hsj9E`{pNZBV;Xcew8KUCoN0?Plf6U;gaecfa4{{r>-_$|r3V-n#A! zTi~kwnMD&s=4SQD*m3&UEc2`b+5WI%J|@xb#`4v zHP$-|nI=xm6V+I#c2_9cBPCMTrKj_a#;pH;rrs~ESj>EqFMjWDPmA~crz>@)yzqz$ zdeG#-a1DjCd~cx zZi@ECw}%#Vo2|9JcggO555mo59LK*I?NPlE*Ypg#N?VIvF z)~u4+>fS>C(%x6H?&zD=z~sWMB6azO%BjE89#7*vyYhkGWlR04%N2jjX9!&Xa@;5O zd;gchZjMyjL%JTn(m%QM8f@Gc^yapNu;I5qC$c{`v!1D(+$|CnZ~dk0qT^g+wQx5l z*TWK1ZmeqN-#J5KNBGWQkqiENvTCm^nDh45b@BO?%l1WdUloY(m}zn$vT3! z@}@QSyX>2~Ac}YD29Kum=W9;Q|Nq1^HbQ!C(!{J*Z%f{78dJ}^c`&~^L-@VIj{PmYOIA(ae%UNQ$nSp9ah`vU z>gAJ`8mC^~9vf*;;az04*4Nb9;KlEj&9W!qT0mo>PII2Y&x@SRg|h(LQ$`%|F!A|wFbKWJ~QtYg#UgVFWnI{YdedU zUu5pYJ#3}lmV7#C#Np%oBHixio=l_D=4`62X$MoA%l5B66RhDsSM1W!da-#sR+Vj^ za*d<9lF9u0_Kobzyvj0mxUgTSeI(r}do}Q+bnJ@46Gjj0%qNAIU*O)z8@gj*c+C>K z9SK(^2aBye`&GI8#@@*0sdZ_xGZYptO7y*wxM_!9O8KkP;SY0q%Iw?M#XS?a>-GBk zn|mSotB=X9s0cZ)9qFoZBIHA3QoK%P*3tJnwl~PSnUwCYJNWeK(}S;9ygjv9SZkYK z$rZclChk-1RqIZ3e$?;%q;79h!no&?#|)QASynTy>DIq`=9I+#^q;V|`vcXE3Lk!@ zXVC{8J6Obi-TP^;F=gxGLlVU=50o3!883~B*ts~sckZmj$%ZYZkJ!Y2A2hNIQFs3w zX7T3kvK7;^LMBE=eSiLLpGQiGSI^=7Ev3P_!adRv9b!@s?n&&=NPm0rXZ62j{_6zO zE+<5M{{QmqhCiXqx0daCq?Ds7$WnPo;9d1{8;x84^&UPrRkm<;#<7^MJAHpX(OURr zQqq=xs#_k({S{4P(~6iVW!=7sH#X(Nr`;Aino_oYj(c%t?S|JM{bwh{GQTWm8grH3xv?jra5=7Wb#503rOXv<{Z(ML0jsxc;v|qU8PG zX^jiH&Ny9~o7&R&N^_0Lf!s|lT?db+3gqUVe0RT9V&y4!d)+CY_HEfE%Wj>opCp;g z@UJ0pR>}I74TgfNUfIqyDX!O^BB3UHa5lTmhHLSam3AJlt&c9{x3lir6kpGIsC&yr z4r$L@BBk^D4xCPY`nh3uzn*pI%{BW^U$s4*{K#MKZrt8~->T*7{`^do68Zb%jY#TZ z)t^ngOs#%_lJ%*bg#|~ee_Ec{9y0gjnSC4P@xG37l$Tkj_w;MRvjsg_`xcsbn(BB| zS3YXAKAowMdRQamj?5Ivrl#N1u18;JV@yn(>~@T`CrJJ2&eeXO-)(Tw|36{xYrjV3 zwhL`@_kG&ExF+H5)|vIcKYdwY6veBbbl+Qi#)YEN=laV6_A(x3HRBAdZYsF>^Xb0@ zE;}5r`s^)DlvwOdo2yo}EpvNe8WZ#L$w!4C(;siX z-T%M9wdzAtiL<>v>*i~mDyla!pQ$aCe%5pEYIIWD!$aCz&h4rD`nLY}gX{J?i$3~H z_Lv&HN=NjrS>(SyMxSVpfXdZeM$*y`PUJQ(wpVCyW8QUgiL1&%mr8%r7B`DMjFxU$ zLB~u!C_cY%yY=MmbyZu=NW`AJ!ftJRM>_Rf_mzMZ?=CK168Tu+hNA#1|$eEWk_*B@kWo%YUoTBgPc2hmdNX+0}{D{&|<+2wvgFQ|6i(Wh#g z{ENN&_pN%^bmpVulYko?YYt|YNX=S$T@Q4tLR-?MS;_{ zzic|6GNZlfv&QQ`Kiv-Bb+nAgbgG)0CnO{-&241--Qe|HC!q`_3p9^wLS?5ly zV6@gryLsguPtcn3KTQXvjND8o`MkPuq{jQGrfJ1OZ_OT-fXLK4M~=U|W81{|!!fnr zD&WI`xmS0Ee)Z$tVtdL^>*j_XPaZ`?Pk5x_^5Uq5*`jH9$njv!p!x{7Kph>O4;04 z&=$67e!{e!f<`(g{wnNw#d=bwmD^%QU0tHwu-j zWMaP_6Lpka)}GO++_(GruSkB;sDDPGrHkTk&E@)X{GG^*WvQC;3qx9WZ4FO3aUkj{ z*QNJ&WK|yj{*t3?b4dEPpncpywmo%{g#u<=A6b6ret%jq^~IssukIx@&|MPvR?}7ZUU3+%gvF$%P@8HWxi?w!6?26x`d}LwzvHVIa{anpI*P;@O zA|7e{3A>_wJ#Uj=Q^WmLKaR3hKKc9C_|f)1kE>UGC~e>BG^uCf58aDp%fo}E_CI|h zp=n*U{O6h=jTrgP`&_r=&i(w7qkKHyvbyKRm#+BpZTGo^?rizF zTVPH6l3NqF_HV67pRh~gobiv|+x>3+*4|b13*1sT*;YwC{t*2_{nX?~+wXLJf7|%x;3{YJ zdGEPdwuYw&@T8qwbp999$XPq@2pM(X-lZ__)K zo#8%LEB=Hn&`w*UekgzDgY_0C*H(zGyLFdg7gvLc!RA=C!!`w$he5&gTlmp-p5u=C zzpnmt$=g2P=EjC_jo>92%`uAWi%aDC??>G`E}k{7@4k}1=;FfX2Xp7W&#qhPxAKEl zz}JvJVNa&pO4fC+6v~}$_)FnuYVWC{rnwLRVwsn+Vkmq_LbPwX}ig6*Y*(j z82$YBC$*6EyuRD`f}bj#+j;Edhv@t_%3JTPg1ztYle-+Im|n#T?<2!Af6 zel-7Q%~zEly5AOEyZwOU&$J(*^TpjhypV0|c3#-T%k)V{Pik?~gx5=QQ_)n?F1LVEeAxliYi4f5n6>`n>t!_IoxpT6Nqx zA2=$fKHR?O|EI%MqOqa=$2Ohriz`|5W37DtvhRZZi+KBv<*(dP%UBe6PExJ&{;oZ$ zsdsvx_aDu7wXEWkc*{&xb{kmJ>8r>|^bbd-12v>BriD zi`PFyt9^g-<5~4LJO3}j*S5A*9ZmTVy|V1whiJFnWT7PUE4Ld({<64p-B17Z>&My| zCwrQBuf`W2cM?%#^|`)b%D$7=59fzYy>9U*jPIn9g#XD8(K-Fsy6)B6{<(I^@P}^e z`9srxX8+Kg?|xh|`C8Zi>OW#if-fZgguP&HIabnf`#=8=-I~T~p1SV08;^Wkn9o?} z{ipU*{C%!JVcYdRIX}O?-EjX?;fZONzN^~y9x!jc|LU7#-`kvbv-1V^vzAIdU;WGW zYCgyQ)}ODUq7@5Y%s!G|_+R_S?=_b!E$gzk@80lXZ>zZ2pGo`TIQIX!&i&B+jtKuF z+YPqwcJ9&rT)%ei!?XH7>VG|Xwb`0)f9Sj1MkHh)>k`Pa>N zQCi`;4Htjts<60OUzws~7MQU4<&#a4Z0_gF)qboMELQJ3n9pebX2(r|tHQ5#^Cdoz zP|w}`!K7-D+iLX`ndbYOEL3Cu1_nw=YPWZ-{Z%vPvj4;FUuq^EZh3U#V{}%ML-yMp zH{ayUIvA*@8ge@)%zjBlN3q4LlOG!vO4e!be)EG<#P$1!ox+oK&KzprmDZkRX8*9t z^{v3O!)HE3UrU>;aI+@Lwp-Zr)lb!2x0#n*C5~*__V2*WFP)Ng+VdakZ$5bIwn#*0?o?UzPtUmPr-A!91>vE2MJ^N+v(cjzeaOHnv z&-uCe(8}mnOTH9$>u)?N?Jg@bZT7#pTb{jn^JdQP_jZR4t$x_QeaHDNuj*~gyO%HF zK66qsOSQIb|MNrJ@0h*I^Igg1^z}s6kF_;b?UNsF_p|#y&8znx%d)-4ZBiRsjxaDV OFnGH9xvXK2O}ofn*_6uKs2fxu0d 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 literal 0 HcmV?d00001 diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditor.qrc b/src/plugins/qmldesigner/components/textureeditor/textureeditor.qrc new file mode 100644 index 00000000000..680fe7d82b0 --- /dev/null +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditor.qrc @@ -0,0 +1,6 @@ + + + images/texture_default.png + images/texture_default@2x.png + + diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.cpp b/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.cpp new file mode 100644 index 00000000000..1bfc1c6c6b1 --- /dev/null +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.cpp @@ -0,0 +1,324 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "textureeditorcontextobject.h" + +#include "abstractview.h" +#include "documentmanager.h" +#include "model.h" +#include "qmldesignerplugin.h" +#include "qmlobjectnode.h" +#include "qmltimeline.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +namespace QmlDesigner { + +TextureEditorContextObject::TextureEditorContextObject(QQmlContext *context, QObject *parent) + : QObject(parent) + , m_qmlContext(context) +{ + qmlRegisterUncreatableType("ToolBarAction", 1, 0, "ToolBarAction", "Enum type"); +} + +QQmlComponent *TextureEditorContextObject::specificQmlComponent() +{ + if (m_specificQmlComponent) + return m_specificQmlComponent; + + m_specificQmlComponent = new QQmlComponent(m_qmlContext->engine(), this); + m_specificQmlComponent->setData(m_specificQmlData.toUtf8(), QUrl::fromLocalFile("specifics.qml")); + + return m_specificQmlComponent; +} + +QString TextureEditorContextObject::convertColorToString(const QVariant &color) +{ + QString colorString; + QColor theColor; + if (color.canConvert(QVariant::Color)) { + theColor = color.value(); + } else if (color.canConvert(QVariant::Vector3D)) { + auto vec = color.value(); + theColor = QColor::fromRgbF(vec.x(), vec.y(), vec.z()); + } + + colorString = theColor.name(); + + if (theColor.alpha() != 255) { + QString hexAlpha = QString("%1").arg(theColor.alpha(), 2, 16, QLatin1Char('0')); + colorString.remove(0, 1); + colorString.prepend(hexAlpha); + colorString.prepend(QStringLiteral("#")); + } + return colorString; +} + +// TODO: this method is used by the ColorEditor helper widget, check if at all needed? +QColor TextureEditorContextObject::colorFromString(const QString &colorString) +{ + return colorString; +} + +void TextureEditorContextObject::insertKeyframe(const QString &propertyName) +{ + QTC_ASSERT(m_model && m_model->rewriterView(), return); + QTC_ASSERT(m_selectedTexture.isValid(), return); + + // Ideally we should not missuse the rewriterView + // If we add more code here we have to forward the material editor view + RewriterView *rewriterView = m_model->rewriterView(); + + QmlTimeline timeline = rewriterView->currentTimeline(); + + QTC_ASSERT(timeline.isValid(), return); + + rewriterView->executeInTransaction("TextureEditorContextObject::insertKeyframe", [&] { + timeline.insertKeyframe(m_selectedTexture, propertyName.toUtf8()); + }); +} + +int TextureEditorContextObject::majorVersion() const +{ + return m_majorVersion; +} + +void TextureEditorContextObject::setMajorVersion(int majorVersion) +{ + if (m_majorVersion == majorVersion) + return; + + m_majorVersion = majorVersion; + + emit majorVersionChanged(); +} + +bool TextureEditorContextObject::hasActiveTimeline() const +{ + return m_hasActiveTimeline; +} + +void TextureEditorContextObject::setHasActiveTimeline(bool b) +{ + if (b == m_hasActiveTimeline) + return; + + m_hasActiveTimeline = b; + emit hasActiveTimelineChanged(); +} + +bool TextureEditorContextObject::hasQuick3DImport() const +{ + return m_hasQuick3DImport; +} + +void TextureEditorContextObject::setHasQuick3DImport(bool b) +{ + if (b == m_hasQuick3DImport) + return; + + m_hasQuick3DImport = b; + emit hasQuick3DImportChanged(); +} + +bool TextureEditorContextObject::hasMaterialLibrary() const +{ + return m_hasMaterialLibrary; +} + +void TextureEditorContextObject::setHasMaterialLibrary(bool b) +{ + if (b == m_hasMaterialLibrary) + return; + + m_hasMaterialLibrary = b; + emit hasMaterialLibraryChanged(); +} + +bool TextureEditorContextObject::hasModelSelection() const +{ + return m_hasModelSelection; +} + +void TextureEditorContextObject::setHasModelSelection(bool b) +{ + if (b == m_hasModelSelection) + return; + + m_hasModelSelection = b; + emit hasModelSelectionChanged(); +} + +void TextureEditorContextObject::setSelectedMaterial(const ModelNode &matNode) +{ + m_selectedTexture = matNode; +} + +void TextureEditorContextObject::setSpecificsUrl(const QUrl &newSpecificsUrl) +{ + if (newSpecificsUrl == m_specificsUrl) + return; + + m_specificsUrl = newSpecificsUrl; + emit specificsUrlChanged(); +} + +void TextureEditorContextObject::setSpecificQmlData(const QString &newSpecificQmlData) +{ + if (newSpecificQmlData == m_specificQmlData) + return; + + m_specificQmlData = newSpecificQmlData; + + delete m_specificQmlComponent; + m_specificQmlComponent = nullptr; + + emit specificQmlComponentChanged(); + emit specificQmlDataChanged(); +} + +void TextureEditorContextObject::setStateName(const QString &newStateName) +{ + if (newStateName == m_stateName) + return; + + m_stateName = newStateName; + emit stateNameChanged(); +} + +void TextureEditorContextObject::setAllStateNames(const QStringList &allStates) +{ + if (allStates == m_allStateNames) + return; + + m_allStateNames = allStates; + emit allStateNamesChanged(); +} + +void TextureEditorContextObject::setIsBaseState(bool newIsBaseState) +{ + if (newIsBaseState == m_isBaseState) + return; + + m_isBaseState = newIsBaseState; + emit isBaseStateChanged(); +} + +void TextureEditorContextObject::setSelectionChanged(bool newSelectionChanged) +{ + if (newSelectionChanged == m_selectionChanged) + return; + + m_selectionChanged = newSelectionChanged; + emit selectionChangedChanged(); +} + +void TextureEditorContextObject::setBackendValues(QQmlPropertyMap *newBackendValues) +{ + if (newBackendValues == m_backendValues) + return; + + m_backendValues = newBackendValues; + emit backendValuesChanged(); +} + +void TextureEditorContextObject::setModel(Model *model) +{ + m_model = model; +} + +void TextureEditorContextObject::triggerSelectionChanged() +{ + setSelectionChanged(!m_selectionChanged); +} + +void TextureEditorContextObject::setHasAliasExport(bool hasAliasExport) +{ + if (m_aliasExport == hasAliasExport) + return; + + m_aliasExport = hasAliasExport; + emit hasAliasExportChanged(); +} + +void TextureEditorContextObject::hideCursor() +{ + if (QApplication::overrideCursor()) + return; + + QApplication::setOverrideCursor(QCursor(Qt::BlankCursor)); + + if (QWidget *w = QApplication::activeWindow()) + m_lastPos = QCursor::pos(w->screen()); +} + +void TextureEditorContextObject::restoreCursor() +{ + if (!QApplication::overrideCursor()) + return; + + QApplication::restoreOverrideCursor(); + + if (QWidget *w = QApplication::activeWindow()) + QCursor::setPos(w->screen(), m_lastPos); +} + +void TextureEditorContextObject::holdCursorInPlace() +{ + if (!QApplication::overrideCursor()) + return; + + if (QWidget *w = QApplication::activeWindow()) + QCursor::setPos(w->screen(), m_lastPos); +} + +int TextureEditorContextObject::devicePixelRatio() +{ + if (QWidget *w = QApplication::activeWindow()) + return w->devicePixelRatio(); + + return 1; +} + +QStringList TextureEditorContextObject::allStatesForId(const QString &id) +{ + if (m_model && m_model->rewriterView()) { + const QmlObjectNode node = m_model->rewriterView()->modelNodeForId(id); + if (node.isValid()) + return node.allStateNames(); + } + + return {}; +} + +bool TextureEditorContextObject::isBlocked(const QString &propName) const +{ + if (!m_selectedTexture.isValid()) + return false; + + if (!m_model || !m_model->rewriterView()) + return false; + + if (QmlObjectNode(m_selectedTexture).isBlocked(propName.toUtf8())) + return true; + + return false; +} + +void TextureEditorContextObject::goIntoComponent() +{ + QTC_ASSERT(m_model, return); + DocumentManager::goIntoComponent(m_selectedTexture); +} + +} // QmlDesigner diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.h b/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.h new file mode 100644 index 00000000000..c2537589a95 --- /dev/null +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.h @@ -0,0 +1,156 @@ +// Copyright (C) 2022 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 + +#include +#include +#include +#include +#include +#include +#include + +namespace QmlDesigner { + +class Model; + +class TextureEditorContextObject : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QUrl specificsUrl READ specificsUrl WRITE setSpecificsUrl NOTIFY specificsUrlChanged) + Q_PROPERTY(QString specificQmlData READ specificQmlData WRITE setSpecificQmlData NOTIFY specificQmlDataChanged) + Q_PROPERTY(QQmlComponent *specificQmlComponent READ specificQmlComponent NOTIFY specificQmlComponentChanged) + + Q_PROPERTY(QString stateName READ stateName WRITE setStateName NOTIFY stateNameChanged) + Q_PROPERTY(QStringList allStateNames READ allStateNames WRITE setAllStateNames NOTIFY allStateNamesChanged) + + Q_PROPERTY(bool isBaseState READ isBaseState WRITE setIsBaseState NOTIFY isBaseStateChanged) + Q_PROPERTY(bool selectionChanged READ selectionChanged WRITE setSelectionChanged NOTIFY selectionChangedChanged) + + Q_PROPERTY(int majorVersion READ majorVersion WRITE setMajorVersion NOTIFY majorVersionChanged) + + Q_PROPERTY(bool hasAliasExport READ hasAliasExport NOTIFY hasAliasExportChanged) + Q_PROPERTY(bool hasActiveTimeline READ hasActiveTimeline NOTIFY hasActiveTimelineChanged) + Q_PROPERTY(bool hasQuick3DImport READ hasQuick3DImport WRITE setHasQuick3DImport NOTIFY hasQuick3DImportChanged) + Q_PROPERTY(bool hasModelSelection READ hasModelSelection WRITE setHasModelSelection NOTIFY hasModelSelectionChanged) + Q_PROPERTY(bool hasMaterialLibrary READ hasMaterialLibrary WRITE setHasMaterialLibrary NOTIFY hasMaterialLibraryChanged) + + Q_PROPERTY(QQmlPropertyMap *backendValues READ backendValues WRITE setBackendValues NOTIFY backendValuesChanged) + +public: + TextureEditorContextObject(QQmlContext *context, QObject *parent = nullptr); + + QUrl specificsUrl() const { return m_specificsUrl; } + QString specificQmlData() const {return m_specificQmlData; } + QQmlComponent *specificQmlComponent(); + QString stateName() const { return m_stateName; } + QStringList allStateNames() const { return m_allStateNames; } + + bool isBaseState() const { return m_isBaseState; } + bool selectionChanged() const { return m_selectionChanged; } + + QQmlPropertyMap *backendValues() const { return m_backendValues; } + + Q_INVOKABLE QString convertColorToString(const QVariant &color); + Q_INVOKABLE QColor colorFromString(const QString &colorString); + + Q_INVOKABLE void insertKeyframe(const QString &propertyName); + + Q_INVOKABLE void hideCursor(); + Q_INVOKABLE void restoreCursor(); + Q_INVOKABLE void holdCursorInPlace(); + + Q_INVOKABLE int devicePixelRatio(); + + Q_INVOKABLE QStringList allStatesForId(const QString &id); + + Q_INVOKABLE bool isBlocked(const QString &propName) const; + Q_INVOKABLE void goIntoComponent(); + + enum ToolBarAction { + ApplyToSelected, + AddNewTexture, + DeleteCurrentTexture, + OpenMaterialBrowser + }; + Q_ENUM(ToolBarAction) + + int majorVersion() const; + void setMajorVersion(int majorVersion); + + bool hasActiveTimeline() const; + void setHasActiveTimeline(bool b); + + bool hasQuick3DImport() const; + void setHasQuick3DImport(bool b); + + bool hasMaterialLibrary() const; + void setHasMaterialLibrary(bool b); + + bool hasModelSelection() const; + void setHasModelSelection(bool b); + + bool hasAliasExport() const { return m_aliasExport; } + + void setSelectedMaterial(const ModelNode &matNode); + + void setSpecificsUrl(const QUrl &newSpecificsUrl); + void setSpecificQmlData(const QString &newSpecificQmlData); + void setStateName(const QString &newStateName); + void setAllStateNames(const QStringList &allStates); + void setIsBaseState(bool newIsBaseState); + void setSelectionChanged(bool newSelectionChanged); + void setBackendValues(QQmlPropertyMap *newBackendValues); + void setModel(Model *model); + + void triggerSelectionChanged(); + void setHasAliasExport(bool hasAliasExport); + +signals: + void specificsUrlChanged(); + void specificQmlDataChanged(); + void specificQmlComponentChanged(); + void stateNameChanged(); + void allStateNamesChanged(); + void isBaseStateChanged(); + void selectionChangedChanged(); + void backendValuesChanged(); + void majorVersionChanged(); + void hasAliasExportChanged(); + void hasActiveTimelineChanged(); + void hasQuick3DImportChanged(); + void hasMaterialLibraryChanged(); + void hasModelSelectionChanged(); + +private: + QUrl m_specificsUrl; + QString m_specificQmlData; + QQmlComponent *m_specificQmlComponent = nullptr; + QQmlContext *m_qmlContext = nullptr; + + QString m_stateName; + QStringList m_allStateNames; + + int m_majorVersion = 1; + + QQmlPropertyMap *m_backendValues = nullptr; + Model *m_model = nullptr; + + QPoint m_lastPos; + + bool m_isBaseState = false; + bool m_selectionChanged = false; + bool m_aliasExport = false; + bool m_hasActiveTimeline = false; + bool m_hasQuick3DImport = false; + bool m_hasMaterialLibrary = false; + bool m_hasModelSelection = false; + + ModelNode m_selectedTexture; +}; + +} // QmlDesigner diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditordynamicpropertiesproxymodel.cpp b/src/plugins/qmldesigner/components/textureeditor/textureeditordynamicpropertiesproxymodel.cpp new file mode 100644 index 00000000000..e6f9c64c740 --- /dev/null +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditordynamicpropertiesproxymodel.cpp @@ -0,0 +1,22 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "textureeditordynamicpropertiesproxymodel.h" + +#include "dynamicpropertiesmodel.h" +#include "textureeditorview.h" + +using namespace QmlDesigner; + +TextureEditorDynamicPropertiesProxyModel::TextureEditorDynamicPropertiesProxyModel(QObject *parent) + : DynamicPropertiesProxyModel(parent) +{ + if (TextureEditorView::instance()) + initModel(TextureEditorView::instance()->dynamicPropertiesModel()); +} + +void TextureEditorDynamicPropertiesProxyModel::registerDeclarativeType() +{ + DynamicPropertiesProxyModel::registerDeclarativeType(); + qmlRegisterType("HelperWidgets", 2, 0, "TextureEditorDynamicPropertiesModel"); +} diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditordynamicpropertiesproxymodel.h b/src/plugins/qmldesigner/components/textureeditor/textureeditordynamicpropertiesproxymodel.h new file mode 100644 index 00000000000..5cd81b751f2 --- /dev/null +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditordynamicpropertiesproxymodel.h @@ -0,0 +1,16 @@ +// Copyright (C) 2022 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 "dynamicpropertiesproxymodel.h" + +class TextureEditorDynamicPropertiesProxyModel : public DynamicPropertiesProxyModel +{ + Q_OBJECT + +public: + explicit TextureEditorDynamicPropertiesProxyModel(QObject *parent = nullptr); + + static void registerDeclarativeType(); +}; diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.cpp b/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.cpp new file mode 100644 index 00000000000..b385afe4f37 --- /dev/null +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.cpp @@ -0,0 +1,310 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "textureeditorqmlbackend.h" + +#include "bindingproperty.h" +#include "documentmanager.h" +#include "nodemetainfo.h" +#include "propertyeditorvalue.h" +#include "qmldesignerconstants.h" +#include "qmlobjectnode.h" +#include "qmltimeline.h" +#include "textureeditortransaction.h" +#include "textureeditorcontextobject.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +static QObject *variantToQObject(const QVariant &value) +{ + if (value.userType() == QMetaType::QObjectStar || value.userType() > QMetaType::User) + return *(QObject **)value.constData(); + + return nullptr; +} + +namespace QmlDesigner { + +class TextureEditorImageProvider : public QQuickImageProvider +{ + QPixmap m_previewPixmap; + +public: + TextureEditorImageProvider() + : QQuickImageProvider(Pixmap) {} + + QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override + { + QPixmap pixmap; + const QString suffix = id.split('.').last().toLower(); + const QString path = DocumentManager::currentResourcePath().path() + '/' + id; + if (suffix == "hdr") + pixmap = HdrImage{path}.toPixmap(); + else + pixmap = Utils::StyleHelper::dpiSpecificImageFile(path); + + if (pixmap.isNull()) + pixmap = Utils::StyleHelper::dpiSpecificImageFile(":/textureeditor/images/texture_default.png"); + + if (size) + *size = pixmap.size(); + + if (requestedSize.isValid()) + return pixmap.scaled(requestedSize, Qt::KeepAspectRatio); + + return pixmap; + } +}; + +TextureEditorQmlBackend::TextureEditorQmlBackend(TextureEditorView *textureEditor) + : m_view(new QQuickWidget) + , m_textureEditorTransaction(new TextureEditorTransaction(textureEditor)) + , m_contextObject(new TextureEditorContextObject(m_view->rootContext())) + , m_textureEditorImageProvider(new TextureEditorImageProvider()) +{ + m_view->setResizeMode(QQuickWidget::SizeRootObjectToView); + m_view->engine()->addImportPath(propertyEditorResourcesPath() + "/imports"); + m_view->engine()->addImageProvider("textureEditor", m_textureEditorImageProvider); + m_contextObject->setBackendValues(&m_backendValuesPropertyMap); + m_contextObject->setModel(textureEditor->model()); + context()->setContextObject(m_contextObject.data()); + + QObject::connect(&m_backendValuesPropertyMap, &DesignerPropertyMap::valueChanged, + textureEditor, &TextureEditorView::changeValue); +} + +TextureEditorQmlBackend::~TextureEditorQmlBackend() +{ +} + +PropertyName TextureEditorQmlBackend::auxNamePostFix(const PropertyName &propertyName) +{ + return propertyName + "__AUX"; +} + +void TextureEditorQmlBackend::createPropertyEditorValue(const QmlObjectNode &qmlObjectNode, + const PropertyName &name, + const QVariant &value, + TextureEditorView *textureEditor) +{ + PropertyName propertyName(name); + propertyName.replace('.', '_'); + auto valueObject = qobject_cast(variantToQObject(backendValuesPropertyMap().value(QString::fromUtf8(propertyName)))); + if (!valueObject) { + valueObject = new PropertyEditorValue(&backendValuesPropertyMap()); + QObject::connect(valueObject, &PropertyEditorValue::valueChanged, &backendValuesPropertyMap(), &DesignerPropertyMap::valueChanged); + QObject::connect(valueObject, &PropertyEditorValue::expressionChanged, textureEditor, &TextureEditorView::changeExpression); + QObject::connect(valueObject, &PropertyEditorValue::exportPropertyAsAliasRequested, textureEditor, &TextureEditorView::exportPropertyAsAlias); + QObject::connect(valueObject, &PropertyEditorValue::removeAliasExportRequested, textureEditor, &TextureEditorView::removeAliasExport); + backendValuesPropertyMap().insert(QString::fromUtf8(propertyName), QVariant::fromValue(valueObject)); + } + valueObject->setName(name); + valueObject->setModelNode(qmlObjectNode); + + if (qmlObjectNode.propertyAffectedByCurrentState(name) && !(qmlObjectNode.modelNode().property(name).isBindingProperty())) + valueObject->setValue(qmlObjectNode.modelValue(name)); + else + valueObject->setValue(value); + + if (propertyName != "id" && qmlObjectNode.currentState().isBaseState() + && qmlObjectNode.modelNode().property(propertyName).isBindingProperty()) { + valueObject->setExpression(qmlObjectNode.modelNode().bindingProperty(propertyName).expression()); + } else { + if (qmlObjectNode.hasBindingProperty(name)) + valueObject->setExpression(qmlObjectNode.expression(name)); + else + valueObject->setExpression(qmlObjectNode.instanceValue(name).toString()); + } +} + +void TextureEditorQmlBackend::setValue(const QmlObjectNode &, const PropertyName &name, const QVariant &value) +{ + // Vector*D values need to be split into their subcomponents + if (value.type() == QVariant::Vector2D) { + const char *suffix[2] = {"_x", "_y"}; + auto vecValue = value.value(); + for (int i = 0; i < 2; ++i) { + PropertyName subPropName(name.size() + 2, '\0'); + subPropName.replace(0, name.size(), name); + subPropName.replace(name.size(), 2, suffix[i]); + auto propertyValue = qobject_cast(variantToQObject(m_backendValuesPropertyMap.value(QString::fromUtf8(subPropName)))); + if (propertyValue) + propertyValue->setValue(QVariant(vecValue[i])); + } + } else if (value.type() == QVariant::Vector3D) { + const char *suffix[3] = {"_x", "_y", "_z"}; + auto vecValue = value.value(); + for (int i = 0; i < 3; ++i) { + PropertyName subPropName(name.size() + 2, '\0'); + subPropName.replace(0, name.size(), name); + subPropName.replace(name.size(), 2, suffix[i]); + auto propertyValue = qobject_cast(variantToQObject(m_backendValuesPropertyMap.value(QString::fromUtf8(subPropName)))); + if (propertyValue) + propertyValue->setValue(QVariant(vecValue[i])); + } + } else if (value.type() == QVariant::Vector4D) { + const char *suffix[4] = {"_x", "_y", "_z", "_w"}; + auto vecValue = value.value(); + for (int i = 0; i < 4; ++i) { + PropertyName subPropName(name.size() + 2, '\0'); + subPropName.replace(0, name.size(), name); + subPropName.replace(name.size(), 2, suffix[i]); + auto propertyValue = qobject_cast( + variantToQObject(m_backendValuesPropertyMap.value(QString::fromUtf8(subPropName)))); + if (propertyValue) + propertyValue->setValue(QVariant(vecValue[i])); + } + } else { + PropertyName propertyName = name; + propertyName.replace('.', '_'); + auto propertyValue = qobject_cast(variantToQObject(m_backendValuesPropertyMap.value(QString::fromUtf8(propertyName)))); + if (propertyValue) + propertyValue->setValue(value); + } +} + +QQmlContext *TextureEditorQmlBackend::context() const +{ + return m_view->rootContext(); +} + +TextureEditorContextObject *TextureEditorQmlBackend::contextObject() const +{ + return m_contextObject.data(); +} + +QQuickWidget *TextureEditorQmlBackend::widget() const +{ + return m_view; +} + +void TextureEditorQmlBackend::setSource(const QUrl &url) +{ + m_view->setSource(url); +} + +Internal::QmlAnchorBindingProxy &TextureEditorQmlBackend::backendAnchorBinding() +{ + return m_backendAnchorBinding; +} + +DesignerPropertyMap &TextureEditorQmlBackend::backendValuesPropertyMap() +{ + return m_backendValuesPropertyMap; +} + +TextureEditorTransaction *TextureEditorQmlBackend::textureEditorTransaction() const +{ + return m_textureEditorTransaction.data(); +} + +PropertyEditorValue *TextureEditorQmlBackend::propertyValueForName(const QString &propertyName) +{ + return qobject_cast(variantToQObject(backendValuesPropertyMap().value(propertyName))); +} + +void TextureEditorQmlBackend::setup(const QmlObjectNode &selectedTextureNode, const QString &stateName, + const QUrl &qmlSpecificsFile, TextureEditorView *textureEditor) +{ + if (selectedTextureNode.isValid()) { + m_contextObject->setModel(textureEditor->model()); + + for (const auto &property : selectedTextureNode.modelNode().metaInfo().properties()) { + createPropertyEditorValue(selectedTextureNode, + property.name(), + selectedTextureNode.instanceValue(property.name()), + textureEditor); + } + + // model node + m_backendModelNode.setup(selectedTextureNode.modelNode()); + context()->setContextProperty("modelNodeBackend", &m_backendModelNode); + context()->setContextProperty("hasTexture", QVariant(true)); + + // className + auto valueObject = qobject_cast(variantToQObject( + m_backendValuesPropertyMap.value(Constants::PROPERTY_EDITOR_CLASSNAME_PROPERTY))); + if (!valueObject) + valueObject = new PropertyEditorValue(&m_backendValuesPropertyMap); + valueObject->setName(Constants::PROPERTY_EDITOR_CLASSNAME_PROPERTY); + valueObject->setModelNode(selectedTextureNode.modelNode()); + valueObject->setValue(m_backendModelNode.simplifiedTypeName()); + QObject::connect(valueObject, + &PropertyEditorValue::valueChanged, + &backendValuesPropertyMap(), + &DesignerPropertyMap::valueChanged); + m_backendValuesPropertyMap.insert(Constants::PROPERTY_EDITOR_CLASSNAME_PROPERTY, + QVariant::fromValue(valueObject)); + + // anchors + m_backendAnchorBinding.setup(selectedTextureNode.modelNode()); + context()->setContextProperties( + QVector{ + {{"anchorBackend"}, QVariant::fromValue(&m_backendAnchorBinding)}, + {{"transaction"}, QVariant::fromValue(m_textureEditorTransaction.data())} + } + ); + + contextObject()->setSpecificsUrl(qmlSpecificsFile); + contextObject()->setStateName(stateName); + + QStringList stateNames = selectedTextureNode.allStateNames(); + stateNames.prepend("base state"); + contextObject()->setAllStateNames(stateNames); + contextObject()->setSelectedMaterial(selectedTextureNode); + contextObject()->setIsBaseState(selectedTextureNode.isInBaseState()); + contextObject()->setHasAliasExport(selectedTextureNode.isAliasExported()); + contextObject()->setHasActiveTimeline(QmlTimeline::hasActiveTimeline(selectedTextureNode.view())); + + contextObject()->setSelectionChanged(false); + + NodeMetaInfo metaInfo = selectedTextureNode.modelNode().metaInfo(); + contextObject()->setMajorVersion(metaInfo.isValid() ? metaInfo.majorVersion() : -1); + } else { + context()->setContextProperty("hasTexture", QVariant(false)); + } +} + +QString TextureEditorQmlBackend::propertyEditorResourcesPath() +{ +#ifdef SHARE_QML_PATH + if (Utils::qtcEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE")) + return QLatin1String(SHARE_QML_PATH) + "/propertyEditorQmlSources"; +#endif + return Core::ICore::resourcePath("qmldesigner/propertyEditorQmlSources").toString(); +} + +void TextureEditorQmlBackend::emitSelectionToBeChanged() +{ + m_backendModelNode.emitSelectionToBeChanged(); +} + +void TextureEditorQmlBackend::emitSelectionChanged() +{ + m_backendModelNode.emitSelectionChanged(); +} + +void TextureEditorQmlBackend::setValueforAuxiliaryProperties(const QmlObjectNode &qmlObjectNode, + AuxiliaryDataKeyView key) +{ + const PropertyName propertyName = auxNamePostFix(PropertyName(key.name)); + setValue(qmlObjectNode, propertyName, qmlObjectNode.modelNode().auxiliaryDataWithDefault(key)); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.h b/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.h new file mode 100644 index 00000000000..fab25c6d586 --- /dev/null +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.h @@ -0,0 +1,69 @@ +// Copyright (C) 2022 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 "designerpropertymap.h" +#include "qmlanchorbindingproxy.h" +#include "qmlmodelnodeproxy.h" + +#include + +class PropertyEditorValue; + +QT_BEGIN_NAMESPACE +class QQuickWidget; +QT_END_NAMESPACE + +namespace QmlDesigner { + +class TextureEditorContextObject; +class TextureEditorImageProvider; +class TextureEditorTransaction; +class TextureEditorView; + +class TextureEditorQmlBackend +{ + Q_DISABLE_COPY(TextureEditorQmlBackend) + +public: + TextureEditorQmlBackend(TextureEditorView *materialEditor); + ~TextureEditorQmlBackend(); + + void setup(const QmlObjectNode &selectedTextureNode, const QString &stateName, const QUrl &qmlSpecificsFile, + TextureEditorView *textureEditor); + void setValue(const QmlObjectNode &fxObjectNode, const PropertyName &name, const QVariant &value); + + QQmlContext *context() const; + TextureEditorContextObject *contextObject() const; + QQuickWidget *widget() const; + void setSource(const QUrl &url); + Internal::QmlAnchorBindingProxy &backendAnchorBinding(); + DesignerPropertyMap &backendValuesPropertyMap(); + TextureEditorTransaction *textureEditorTransaction() const; + + PropertyEditorValue *propertyValueForName(const QString &propertyName); + + static QString propertyEditorResourcesPath(); + + void emitSelectionToBeChanged(); + void emitSelectionChanged(); + + void setValueforAuxiliaryProperties(const QmlObjectNode &qmlObjectNode, AuxiliaryDataKeyView key); + +private: + void createPropertyEditorValue(const QmlObjectNode &qmlObjectNode, + const PropertyName &name, const QVariant &value, + TextureEditorView *textureEditor); + PropertyName auxNamePostFix(const PropertyName &propertyName); + + QQuickWidget *m_view = nullptr; + Internal::QmlAnchorBindingProxy m_backendAnchorBinding; + QmlModelNodeProxy m_backendModelNode; + DesignerPropertyMap m_backendValuesPropertyMap; + QScopedPointer m_textureEditorTransaction; + QScopedPointer m_contextObject; + TextureEditorImageProvider *m_textureEditorImageProvider = nullptr; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditortransaction.cpp b/src/plugins/qmldesigner/components/textureeditor/textureeditortransaction.cpp new file mode 100644 index 00000000000..91ca4efb40a --- /dev/null +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditortransaction.cpp @@ -0,0 +1,48 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "textureeditortransaction.h" + +#include + +namespace QmlDesigner { + +TextureEditorTransaction::TextureEditorTransaction(TextureEditorView *textureEditor) + : QObject(textureEditor), + m_textureEditor(textureEditor) +{ +} + +void TextureEditorTransaction::start() +{ + if (!m_textureEditor->model()) + return; + if (m_rewriterTransaction.isValid()) + m_rewriterTransaction.commit(); + m_rewriterTransaction = m_textureEditor->beginRewriterTransaction(QByteArrayLiteral("MaterialEditorTransaction::start")); + m_timerId = startTimer(10000); +} + +void TextureEditorTransaction::end() +{ + if (m_rewriterTransaction.isValid() && m_textureEditor->model()) { + killTimer(m_timerId); + m_rewriterTransaction.commit(); + } +} + +bool TextureEditorTransaction::active() const +{ + return m_rewriterTransaction.isValid(); +} + +void TextureEditorTransaction::timerEvent(QTimerEvent *timerEvent) +{ + if (timerEvent->timerId() != m_timerId) + return; + killTimer(timerEvent->timerId()); + if (m_rewriterTransaction.isValid()) + m_rewriterTransaction.commit(); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditortransaction.h b/src/plugins/qmldesigner/components/textureeditor/textureeditortransaction.h new file mode 100644 index 00000000000..6c543aebcbc --- /dev/null +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditortransaction.h @@ -0,0 +1,31 @@ +// Copyright (C) 2022 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 "textureeditorview.h" + +namespace QmlDesigner { + +class TextureEditorTransaction : public QObject +{ + Q_OBJECT + +public: + TextureEditorTransaction(TextureEditorView *textureEditor); + + Q_INVOKABLE void start(); + Q_INVOKABLE void end(); + + Q_INVOKABLE bool active() const; + +protected: + void timerEvent(QTimerEvent *event) override; + +private: + TextureEditorView *m_textureEditor = nullptr; + RewriterTransaction m_rewriterTransaction; + int m_timerId = -1; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp b/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp new file mode 100644 index 00000000000..a753c1d03a6 --- /dev/null +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp @@ -0,0 +1,866 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "textureeditorview.h" + +#include "textureeditorqmlbackend.h" +#include "textureeditorcontextobject.h" +#include "textureeditordynamicpropertiesproxymodel.h" +#include "propertyeditorvalue.h" +#include "textureeditortransaction.h" +#include "assetslibrarywidget.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace QmlDesigner { + +TextureEditorView::TextureEditorView(ExternalDependenciesInterface &externalDependencies) + : AbstractView{externalDependencies} + , m_stackedWidget(new QStackedWidget) + , m_dynamicPropertiesModel(new Internal::DynamicPropertiesModel(true, this)) +{ + m_updateShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_F12), m_stackedWidget); + connect(m_updateShortcut, &QShortcut::activated, this, &TextureEditorView::reloadQml); + + m_ensureMatLibTimer.callOnTimeout([this] { + if (model() && model()->rewriterView() && !model()->rewriterView()->hasIncompleteTypeInformation() + && model()->rewriterView()->errors().isEmpty()) { + ensureMaterialLibraryNode(); + if (m_qmlBackEnd && m_qmlBackEnd->contextObject()) + m_qmlBackEnd->contextObject()->setHasMaterialLibrary(materialLibraryNode().isValid()); + m_ensureMatLibTimer.stop(); + } + }); + + m_stackedWidget->setStyleSheet(Theme::replaceCssColors( + QString::fromUtf8(Utils::FileReader::fetchQrc(":/qmldesigner/stylesheet.css")))); + m_stackedWidget->setMinimumWidth(250); + QmlDesignerPlugin::trackWidgetFocusTime(m_stackedWidget, Constants::EVENT_TEXTUREEDITOR_TIME); + + TextureEditorDynamicPropertiesProxyModel::registerDeclarativeType(); +} + +TextureEditorView::~TextureEditorView() +{ + qDeleteAll(m_qmlBackendHash); +} + +// from texture editor to model +void TextureEditorView::changeValue(const QString &name) +{ + PropertyName propertyName = name.toUtf8(); + + if (propertyName.isNull() || locked() || noValidSelection() || propertyName == "id" + || propertyName == Constants::PROPERTY_EDITOR_CLASSNAME_PROPERTY) { + return; + } + + PropertyName underscoreName(propertyName); + underscoreName.replace('.', '_'); + PropertyEditorValue *value = m_qmlBackEnd->propertyValueForName(QString::fromLatin1(underscoreName)); + + if (!value) + return; + + if (propertyName.endsWith("__AUX")) { + commitAuxValueToModel(propertyName, value->value()); + return; + } + + const NodeMetaInfo metaInfo = m_selectedTexture.metaInfo(); + + QVariant castedValue; + + if (auto property = metaInfo.property(propertyName)) { + castedValue = property.castedValue(value->value()); + } else { + qWarning() << __FUNCTION__ << propertyName << "cannot be casted (metainfo)"; + return; + } + + if (value->value().isValid() && !castedValue.isValid()) { + qWarning() << __FUNCTION__ << propertyName << "not properly casted (metainfo)"; + return; + } + + bool propertyTypeUrl = false; + + if (auto property = metaInfo.property(propertyName)) { + if (property.propertyType().isUrl()) { + // turn absolute local file paths into relative paths + propertyTypeUrl = true; + QString filePath = castedValue.toUrl().toString(); + QFileInfo fi(filePath); + if (fi.exists() && fi.isAbsolute()) { + QDir fileDir(QFileInfo(model()->fileUrl().toLocalFile()).absolutePath()); + castedValue = QUrl(fileDir.relativeFilePath(filePath)); + } + } + } + + if (name == "state" && castedValue.toString() == "base state") + castedValue = ""; + + if (castedValue.type() == QVariant::Color) { + QColor color = castedValue.value(); + QColor newColor = QColor(color.name()); + newColor.setAlpha(color.alpha()); + castedValue = QVariant(newColor); + } + + if (!value->value().isValid() || (propertyTypeUrl && value->value().toString().isEmpty())) { // reset + removePropertyFromModel(propertyName); + } else { + // QVector*D(0, 0, 0) detects as null variant though it is valid value + if (castedValue.isValid() + && (!castedValue.isNull() || castedValue.type() == QVariant::Vector2D + || castedValue.type() == QVariant::Vector3D + || castedValue.type() == QVariant::Vector4D)) { + commitVariantValueToModel(propertyName, castedValue); + } + } +} + +static bool isTrueFalseLiteral(const QString &expression) +{ + return (expression.compare("false", Qt::CaseInsensitive) == 0) + || (expression.compare("true", Qt::CaseInsensitive) == 0); +} + +void TextureEditorView::changeExpression(const QString &propertyName) +{ + PropertyName name = propertyName.toUtf8(); + + if (name.isNull() || locked() || noValidSelection()) + return; + + executeInTransaction("TextureEditorView::changeExpression", [this, name] { + PropertyName underscoreName(name); + underscoreName.replace('.', '_'); + + QmlObjectNode qmlObjectNode(m_selectedTexture); + PropertyEditorValue *value = m_qmlBackEnd->propertyValueForName(QString::fromLatin1(underscoreName)); + + if (!value) { + qWarning() << __FUNCTION__ << "no value for " << underscoreName; + return; + } + + if (auto property = m_selectedTexture.metaInfo().property(name)) { + auto propertyTypeName = property.propertyType().typeName(); + if (propertyTypeName == "QColor") { + if (QColor(value->expression().remove('"')).isValid()) { + qmlObjectNode.setVariantProperty(name, QColor(value->expression().remove('"'))); + return; + } + } else if (propertyTypeName == "bool") { + if (isTrueFalseLiteral(value->expression())) { + if (value->expression().compare("true", Qt::CaseInsensitive) == 0) + qmlObjectNode.setVariantProperty(name, true); + else + qmlObjectNode.setVariantProperty(name, false); + return; + } + } else if (propertyTypeName == "int") { + bool ok; + int intValue = value->expression().toInt(&ok); + if (ok) { + qmlObjectNode.setVariantProperty(name, intValue); + return; + } + } else if (propertyTypeName == "qreal") { + bool ok; + qreal realValue = value->expression().toDouble(&ok); + if (ok) { + qmlObjectNode.setVariantProperty(name, realValue); + return; + } + } else if (propertyTypeName == "QVariant") { + bool ok; + qreal realValue = value->expression().toDouble(&ok); + if (ok) { + qmlObjectNode.setVariantProperty(name, realValue); + return; + } else if (isTrueFalseLiteral(value->expression())) { + if (value->expression().compare("true", Qt::CaseInsensitive) == 0) + qmlObjectNode.setVariantProperty(name, true); + else + qmlObjectNode.setVariantProperty(name, false); + return; + } + } + } + + if (value->expression().isEmpty()) { + value->resetValue(); + return; + } + + if (qmlObjectNode.expression(name) != value->expression() || !qmlObjectNode.propertyAffectedByCurrentState(name)) + qmlObjectNode.setBindingProperty(name, value->expression()); + }); +} + +void TextureEditorView::exportPropertyAsAlias(const QString &name) +{ + if (name.isNull() || locked() || noValidSelection()) + return; + + executeInTransaction("TextureEditorView::exportPopertyAsAlias", [this, name] { + const QString id = m_selectedTexture.validId(); + QString upperCasePropertyName = name; + upperCasePropertyName.replace(0, 1, upperCasePropertyName.at(0).toUpper()); + QString aliasName = id + upperCasePropertyName; + aliasName.replace(".", ""); //remove all dots + + PropertyName propertyName = aliasName.toUtf8(); + if (rootModelNode().hasProperty(propertyName)) { + Core::AsynchronousMessageBox::warning(tr("Cannot Export Property as Alias"), + tr("Property %1 does already exist for root component.").arg(aliasName)); + return; + } + rootModelNode().bindingProperty(propertyName).setDynamicTypeNameAndExpression("alias", id + "." + name); + }); +} + +void TextureEditorView::removeAliasExport(const QString &name) +{ + if (name.isNull() || locked() || noValidSelection()) + return; + + executeInTransaction("TextureEditorView::removeAliasExport", [this, name] { + const QString id = m_selectedTexture.validId(); + + const QList bindingProps = rootModelNode().bindingProperties(); + for (const BindingProperty &property : bindingProps) { + if (property.expression() == (id + "." + name)) { + rootModelNode().removeProperty(property.name()); + break; + } + } + }); +} + +bool TextureEditorView::locked() const +{ + return m_locked; +} + +void TextureEditorView::currentTimelineChanged(const ModelNode &) +{ + m_qmlBackEnd->contextObject()->setHasActiveTimeline(QmlTimeline::hasActiveTimeline(this)); +} + +Internal::DynamicPropertiesModel *TextureEditorView::dynamicPropertiesModel() const +{ + return m_dynamicPropertiesModel; +} + +TextureEditorView *TextureEditorView::instance() +{ + static TextureEditorView *s_instance = nullptr; + + if (s_instance) + return s_instance; + + const auto views = QmlDesignerPlugin::instance()->viewManager().views(); + for (auto *view : views) { + TextureEditorView *myView = qobject_cast(view); + if (myView) + s_instance = myView; + } + + QTC_ASSERT(s_instance, return nullptr); + return s_instance; +} + +void TextureEditorView::timerEvent(QTimerEvent *timerEvent) +{ + if (m_timerId == timerEvent->timerId()) + resetView(); +} + +void TextureEditorView::resetView() +{ + if (!model()) + return; + + m_locked = true; + + if (m_timerId) + killTimer(m_timerId); + + setupQmlBackend(); + + if (m_qmlBackEnd) + m_qmlBackEnd->emitSelectionChanged(); + + m_locked = false; + + if (m_timerId) + m_timerId = 0; +} + +// static +QString TextureEditorView::textureEditorResourcesPath() +{ +#ifdef SHARE_QML_PATH + if (Utils::qtcEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE")) + return QLatin1String(SHARE_QML_PATH) + "/textureEditorQmlSource"; +#endif + return Core::ICore::resourcePath("qmldesigner/textureEditorQmlSource").toString(); +} + +void TextureEditorView::applyTextureToSelectedModel(const ModelNode &texture) +{ + if (!m_selectedModel.isValid()) + return; + + QTC_ASSERT(texture.isValid(), return); + + emitCustomNotification("apply_texture_to_model3D", {m_selectedModel, m_selectedTexture}); +} + +void TextureEditorView::handleToolBarAction(int action) +{ + QTC_ASSERT(m_hasQuick3DImport, return); + + switch (action) { + case TextureEditorContextObject::ApplyToSelected: { + applyTextureToSelectedModel(m_selectedTexture); + break; + } + + case TextureEditorContextObject::AddNewTexture: { + if (!model()) + break; + executeInTransaction("TextureEditorView:handleToolBarAction", [&] { + ModelNode matLib = materialLibraryNode(); + if (!matLib.isValid()) + return; + + NodeMetaInfo metaInfo = model()->metaInfo("QtQuick3D.Texture"); + ModelNode newTextureNode = createModelNode("QtQuick3D.Texture", metaInfo.majorVersion(), + metaInfo.minorVersion()); + newTextureNode.validId(); + matLib.defaultNodeListProperty().reparentHere(newTextureNode); + }); + break; + } + + case TextureEditorContextObject::DeleteCurrentTexture: { + if (m_selectedTexture.isValid()) + m_selectedTexture.destroy(); + break; + } + + case TextureEditorContextObject::OpenMaterialBrowser: { + QmlDesignerPlugin::instance()->mainWidget()->showDockWidget("MaterialBrowser", true); + break; + } + } +} + +void TextureEditorView::setupQmlBackend() +{ + QUrl qmlPaneUrl; + QUrl qmlSpecificsUrl; + QString specificQmlData; + + if (m_selectedTexture.isValid() && m_hasQuick3DImport && (materialLibraryNode().isValid() || m_hasTextureRoot)) { + qmlPaneUrl = QUrl::fromLocalFile(textureEditorResourcesPath() + "/TextureEditorPane.qml"); + + TypeName diffClassName; + if (NodeMetaInfo metaInfo = m_selectedTexture.metaInfo()) { + diffClassName = metaInfo.typeName(); + for (const NodeMetaInfo &metaInfo : metaInfo.classHierarchy()) { + if (PropertyEditorQmlBackend::checkIfUrlExists(qmlSpecificsUrl)) + break; + qmlSpecificsUrl = PropertyEditorQmlBackend::getQmlFileUrl(metaInfo.typeName() + + "Specifics", metaInfo); + diffClassName = metaInfo.typeName(); + } + + if (diffClassName != m_selectedTexture.type()) { + specificQmlData = PropertyEditorQmlBackend::templateGeneration(metaInfo, + model()->metaInfo( + diffClassName), + m_selectedTexture); + } + } + } else { + qmlPaneUrl = QUrl::fromLocalFile(textureEditorResourcesPath() + "/EmptyTextureEditorPane.qml"); + } + + TextureEditorQmlBackend *currentQmlBackend = m_qmlBackendHash.value(qmlPaneUrl.toString()); + + QString currentStateName = currentState().isBaseState() ? currentState().name() : "invalid state"; + + if (!currentQmlBackend) { + currentQmlBackend = new TextureEditorQmlBackend(this); + + m_stackedWidget->addWidget(currentQmlBackend->widget()); + m_qmlBackendHash.insert(qmlPaneUrl.toString(), currentQmlBackend); + + currentQmlBackend->setup(m_selectedTexture, currentStateName, qmlSpecificsUrl, this); + + currentQmlBackend->setSource(qmlPaneUrl); + + QObject *rootObj = currentQmlBackend->widget()->rootObject(); + QObject::connect(rootObj, SIGNAL(toolBarAction(int)), this, SLOT(handleToolBarAction(int))); + } else { + currentQmlBackend->setup(m_selectedTexture, currentStateName, qmlSpecificsUrl, this); + } + + currentQmlBackend->widget()->installEventFilter(this); + currentQmlBackend->contextObject()->setHasQuick3DImport(m_hasQuick3DImport); + currentQmlBackend->contextObject()->setHasMaterialLibrary(materialLibraryNode().isValid()); + currentQmlBackend->contextObject()->setSpecificQmlData(specificQmlData); + + m_qmlBackEnd = currentQmlBackend; + + if (m_hasTextureRoot) + m_dynamicPropertiesModel->setSelectedNode(m_selectedTexture); + else + m_dynamicPropertiesModel->reset(); + + m_stackedWidget->setCurrentWidget(m_qmlBackEnd->widget()); +} + +void TextureEditorView::commitVariantValueToModel(const PropertyName &propertyName, const QVariant &value) +{ + m_locked = true; + executeInTransaction("TextureEditorView:commitVariantValueToModel", [&] { + QmlObjectNode(m_selectedTexture).setVariantProperty(propertyName, value); + }); + m_locked = false; +} + +void TextureEditorView::commitAuxValueToModel(const PropertyName &propertyName, const QVariant &value) +{ + m_locked = true; + + PropertyName name = propertyName; + name.chop(5); + + try { + if (value.isValid()) + m_selectedTexture.setAuxiliaryData(AuxiliaryDataType::Document, name, value); + else + m_selectedTexture.removeAuxiliaryData(AuxiliaryDataType::Document, name); + } + catch (const Exception &e) { + e.showException(); + } + m_locked = false; +} + +void TextureEditorView::removePropertyFromModel(const PropertyName &propertyName) +{ + m_locked = true; + executeInTransaction("MaterialEditorView:removePropertyFromModel", [&] { + QmlObjectNode(m_selectedTexture).removeProperty(propertyName); + }); + m_locked = false; +} + +bool TextureEditorView::noValidSelection() const +{ + QTC_ASSERT(m_qmlBackEnd, return true); + return !QmlObjectNode::isValidQmlObjectNode(m_selectedTexture); +} + +void TextureEditorView::modelAttached(Model *model) +{ + AbstractView::modelAttached(model); + + m_locked = true; + + m_hasQuick3DImport = model->hasImport("QtQuick3D"); + m_hasTextureRoot = rootModelNode().metaInfo().isQtQuick3DTexture(); + + if (m_hasTextureRoot) { + m_selectedTexture = rootModelNode(); + } else if (m_hasQuick3DImport) { + // Creating the material library node on model attach causes errors as long as the type + // information is not complete yet, so we keep checking until type info is complete. + m_ensureMatLibTimer.start(500); + } + + if (!m_setupCompleted) { + reloadQml(); + m_setupCompleted = true; + } + resetView(); + + m_locked = false; +} + +void TextureEditorView::modelAboutToBeDetached(Model *model) +{ + AbstractView::modelAboutToBeDetached(model); + m_dynamicPropertiesModel->reset(); + m_qmlBackEnd->textureEditorTransaction()->end(); + m_qmlBackEnd->contextObject()->setHasMaterialLibrary(false); +} + +void TextureEditorView::propertiesRemoved(const QList &propertyList) +{ + if (noValidSelection()) + return; + + for (const AbstractProperty &property : propertyList) { + ModelNode node(property.parentModelNode()); + + if (node.isRootNode()) + m_qmlBackEnd->contextObject()->setHasAliasExport(QmlObjectNode(m_selectedTexture).isAliasExported()); + + if (node == m_selectedTexture || QmlObjectNode(m_selectedTexture).propertyChangeForCurrentState() == node) { + setValue(m_selectedTexture, property.name(), QmlObjectNode(m_selectedTexture).instanceValue(property.name())); + } + + dynamicPropertiesModel()->dispatchPropertyChanges(property); + } +} + +void TextureEditorView::variantPropertiesChanged(const QList &propertyList, PropertyChangeFlags /*propertyChange*/) +{ + if (noValidSelection()) + return; + + for (const VariantProperty &property : propertyList) { + ModelNode node(property.parentModelNode()); + if (node == m_selectedTexture || QmlObjectNode(m_selectedTexture).propertyChangeForCurrentState() == node) { + if (property.isDynamic()) + m_dynamicPropertiesModel->variantPropertyChanged(property); + if (m_selectedTexture.property(property.name()).isBindingProperty()) + setValue(m_selectedTexture, property.name(), QmlObjectNode(m_selectedTexture).instanceValue(property.name())); + else + setValue(m_selectedTexture, property.name(), QmlObjectNode(m_selectedTexture).modelValue(property.name())); + } + + dynamicPropertiesModel()->dispatchPropertyChanges(property); + } +} + +void TextureEditorView::bindingPropertiesChanged(const QList &propertyList, PropertyChangeFlags /*propertyChange*/) +{ + if (noValidSelection()) + return; + + for (const BindingProperty &property : propertyList) { + ModelNode node(property.parentModelNode()); + + if (property.isAliasExport()) + m_qmlBackEnd->contextObject()->setHasAliasExport(QmlObjectNode(m_selectedTexture).isAliasExported()); + + if (node == m_selectedTexture || QmlObjectNode(m_selectedTexture).propertyChangeForCurrentState() == node) { + if (property.isDynamic()) + m_dynamicPropertiesModel->bindingPropertyChanged(property); + if (QmlObjectNode(m_selectedTexture).modelNode().property(property.name()).isBindingProperty()) + setValue(m_selectedTexture, property.name(), QmlObjectNode(m_selectedTexture).instanceValue(property.name())); + else + setValue(m_selectedTexture, property.name(), QmlObjectNode(m_selectedTexture).modelValue(property.name())); + } + + dynamicPropertiesModel()->dispatchPropertyChanges(property); + } +} + +void TextureEditorView::auxiliaryDataChanged(const ModelNode &node, + AuxiliaryDataKeyView key, + const QVariant &) +{ + + if (noValidSelection() || !node.isSelected()) + return; + + m_qmlBackEnd->setValueforAuxiliaryProperties(m_selectedTexture, key); +} + +void TextureEditorView::propertiesAboutToBeRemoved(const QList &propertyList) +{ + for (const auto &property : propertyList) { + if (property.isBindingProperty()) + m_dynamicPropertiesModel->bindingRemoved(property.toBindingProperty()); + else if (property.isVariantProperty()) + m_dynamicPropertiesModel->variantRemoved(property.toVariantProperty()); + } +} + +void TextureEditorView::nodeReparented(const ModelNode &node, + const NodeAbstractProperty &newPropertyParent, + const NodeAbstractProperty &oldPropertyParent, + PropertyChangeFlags propertyChange) +{ + if (node.id() == Constants::MATERIAL_LIB_ID && m_qmlBackEnd && m_qmlBackEnd->contextObject()) + m_qmlBackEnd->contextObject()->setHasMaterialLibrary(true); +} + +void TextureEditorView::nodeAboutToBeRemoved(const ModelNode &removedNode) +{ + if (removedNode.id() == Constants::MATERIAL_LIB_ID && m_qmlBackEnd && m_qmlBackEnd->contextObject()) + m_qmlBackEnd->contextObject()->setHasMaterialLibrary(false); +} + +bool TextureEditorView::hasWidget() const +{ + return true; +} + +WidgetInfo TextureEditorView::widgetInfo() +{ + return createWidgetInfo(m_stackedWidget, + "TextureEditor", + WidgetInfo::RightPane, + 0, + tr("Texture Editor")); +} + +void TextureEditorView::selectedNodesChanged(const QList &selectedNodeList, + [[maybe_unused]] const QList &lastSelectedNodeList) +{ + m_selectedModel = {}; + + if (selectedNodeList.size() == 1 && selectedNodeList.at(0).metaInfo().isQtQuick3DModel()) + m_selectedModel = selectedNodeList.at(0); + + m_qmlBackEnd->contextObject()->setHasModelSelection(m_selectedModel.isValid()); +} + +void TextureEditorView::currentStateChanged(const ModelNode &node) +{ + QmlModelState newQmlModelState(node); + Q_ASSERT(newQmlModelState.isValid()); + resetView(); +} + +void TextureEditorView::instancePropertyChanged(const QList> &propertyList) +{ + if (!m_selectedTexture.isValid() || !m_qmlBackEnd) + return; + + m_locked = true; + + for (const QPair &propertyPair : propertyList) { + const ModelNode modelNode = propertyPair.first; + const QmlObjectNode qmlObjectNode(modelNode); + const PropertyName propertyName = propertyPair.second; + + if (qmlObjectNode.isValid() && modelNode == m_selectedTexture && qmlObjectNode.currentState().isValid()) { + const AbstractProperty property = modelNode.property(propertyName); + if (!modelNode.hasProperty(propertyName) || modelNode.property(property.name()).isBindingProperty()) + setValue(modelNode, property.name(), qmlObjectNode.instanceValue(property.name())); + else + setValue(modelNode, property.name(), qmlObjectNode.modelValue(property.name())); + } + } + + m_locked = false; +} + +void TextureEditorView::importsChanged([[maybe_unused]] const QList &addedImports, + [[maybe_unused]] const QList &removedImports) +{ + m_hasQuick3DImport = model()->hasImport("QtQuick3D"); + m_qmlBackEnd->contextObject()->setHasQuick3DImport(m_hasQuick3DImport); + + if (m_hasQuick3DImport) + m_ensureMatLibTimer.start(500); + + resetView(); +} + +void TextureEditorView::duplicateTexture(const ModelNode &texture) +{ + QTC_ASSERT(texture.isValid(), return); + + if (!model()) + return; + + TypeName matType = texture.type(); + QmlObjectNode sourceTexture(texture); + ModelNode duplicateTextureNode; + QList dynamicProps; + + executeInTransaction(__FUNCTION__, [&] { + ModelNode matLib = materialLibraryNode(); + if (!matLib.isValid()) + return; + + // create the duplicate texture + NodeMetaInfo metaInfo = model()->metaInfo(matType); + QmlObjectNode duplicateTex = createModelNode(matType, metaInfo.majorVersion(), metaInfo.minorVersion()); + + duplicateTextureNode = duplicateTex .modelNode(); + duplicateTextureNode.validId(); + + // sync properties. Only the base state is duplicated. + const QList props = texture.properties(); + for (const AbstractProperty &prop : props) { + if (prop.name() == "objectName" || prop.name() == "data") + continue; + + if (prop.isVariantProperty()) { + if (prop.isDynamic()) { + dynamicProps.append(prop); + } else { + duplicateTextureNode.variantProperty(prop.name()) + .setValue(prop.toVariantProperty().value()); + } + } else if (prop.isBindingProperty()) { + if (prop.isDynamic()) { + dynamicProps.append(prop); + } else { + duplicateTextureNode.bindingProperty(prop.name()) + .setExpression(prop.toBindingProperty().expression()); + } + } + } + + matLib.defaultNodeListProperty().reparentHere(duplicateTex); + }); + + // For some reason, creating dynamic properties in the same transaction doesn't work, so + // let's do it in separate transaction. + // TODO: Fix the issue and merge transactions (QDS-8094) + if (!dynamicProps.isEmpty()) { + executeInTransaction(__FUNCTION__, [&] { + for (const AbstractProperty &prop : std::as_const(dynamicProps)) { + if (prop.isVariantProperty()) { + duplicateTextureNode.variantProperty(prop.name()) + .setDynamicTypeNameAndValue(prop.dynamicTypeName(), + prop.toVariantProperty().value()); + } else if (prop.isBindingProperty()) { + duplicateTextureNode.bindingProperty(prop.name()) + .setDynamicTypeNameAndExpression(prop.dynamicTypeName(), + prop.toBindingProperty().expression()); + } + } + }); + } +} + +void TextureEditorView::customNotification([[maybe_unused]] const AbstractView *view, + const QString &identifier, + const QList &nodeList, + const QList &data) +{ + if (identifier == "selected_texture_changed") { + if (!m_hasTextureRoot) { + m_selectedTexture = nodeList.first(); + m_dynamicPropertiesModel->setSelectedNode(m_selectedTexture); + QTimer::singleShot(0, this, &TextureEditorView::resetView); + } + } else if (identifier == "apply_texture_to_selected_triggered") { + applyTextureToSelectedModel(nodeList.first()); + } else if (identifier == "add_new_texture") { + handleToolBarAction(TextureEditorContextObject::AddNewTexture); + } else if (identifier == "duplicate_texture") { + duplicateTexture(nodeList.first()); + } +} + +void QmlDesigner::TextureEditorView::highlightSupportedProperties(bool highlight) +{ + if (!m_selectedTexture.isValid()) + return; + + DesignerPropertyMap &propMap = m_qmlBackEnd->backendValuesPropertyMap(); + const QStringList propNames = propMap.keys(); + NodeMetaInfo metaInfo = m_selectedTexture.metaInfo(); + QTC_ASSERT(metaInfo.isValid(), return); + + for (const QString &propName : propNames) { + if (metaInfo.property(propName.toUtf8()).propertyType().isQtQuick3DTexture()) { // TODO: support dropping to texture source + QObject *propEditorValObj = propMap.value(propName).value(); + PropertyEditorValue *propEditorVal = qobject_cast(propEditorValObj); + propEditorVal->setHasActiveDrag(highlight); + } + } +} + +void TextureEditorView::dragStarted(QMimeData *mimeData) +{ + if (!mimeData->hasFormat(Constants::MIME_TYPE_ASSETS)) + return; + + const QString assetPath = QString::fromUtf8(mimeData->data(Constants::MIME_TYPE_ASSETS)).split(',')[0]; + QString assetType = AssetsLibraryWidget::getAssetTypeAndData(assetPath).first; + + if (assetType != Constants::MIME_TYPE_ASSET_IMAGE) // currently only image assets have dnd-supported properties + return; + + highlightSupportedProperties(); +} + +void TextureEditorView::dragEnded() +{ + highlightSupportedProperties(false); +} + +// from model to texture editor +void TextureEditorView::setValue(const QmlObjectNode &qmlObjectNode, const PropertyName &name, const QVariant &value) +{ + m_locked = true; + m_qmlBackEnd->setValue(qmlObjectNode, name, value); + m_locked = false; +} + +bool TextureEditorView::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::FocusOut) { + if (m_qmlBackEnd && m_qmlBackEnd->widget() == obj) + QMetaObject::invokeMethod(m_qmlBackEnd->widget()->rootObject(), "closeContextMenu"); + } + return QObject::eventFilter(obj, event); +} + +void TextureEditorView::reloadQml() +{ + m_qmlBackendHash.clear(); + while (QWidget *widget = m_stackedWidget->widget(0)) { + m_stackedWidget->removeWidget(widget); + delete widget; + } + m_qmlBackEnd = nullptr; + + resetView(); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorview.h b/src/plugins/qmldesigner/components/textureeditor/textureeditorview.h new file mode 100644 index 00000000000..8e5c175e853 --- /dev/null +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorview.h @@ -0,0 +1,127 @@ +// Copyright (C) 2022 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 +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE +class QShortcut; +class QStackedWidget; +class QTimer; +class QColorDialog; +QT_END_NAMESPACE + +namespace QmlDesigner { + +class ModelNode; +class TextureEditorQmlBackend; + +namespace Internal { +class DynamicPropertiesModel; +} + +class TextureEditorView : public AbstractView +{ + Q_OBJECT + +public: + TextureEditorView(ExternalDependenciesInterface &externalDependencies); + ~TextureEditorView() override; + + bool hasWidget() const override; + WidgetInfo widgetInfo() override; + + void selectedNodesChanged(const QList &selectedNodeList, + const QList &lastSelectedNodeList) override; + + void propertiesRemoved(const QList &propertyList) override; + + void modelAttached(Model *model) override; + void modelAboutToBeDetached(Model *model) override; + + void variantPropertiesChanged(const QList &propertyList, PropertyChangeFlags propertyChange) override; + void bindingPropertiesChanged(const QList &propertyList, PropertyChangeFlags propertyChange) override; + void auxiliaryDataChanged(const ModelNode &node, + AuxiliaryDataKeyView key, + const QVariant &data) override; + void propertiesAboutToBeRemoved(const QList &propertyList) override; + void nodeReparented(const ModelNode &node, const NodeAbstractProperty &newPropertyParent, + const NodeAbstractProperty &oldPropertyParent, + AbstractView::PropertyChangeFlags propertyChange) override; + void nodeAboutToBeRemoved(const ModelNode &removedNode) override; + + void resetView(); + void currentStateChanged(const ModelNode &node) override; + void instancePropertyChanged(const QList > &propertyList) override; + + void importsChanged(const QList &addedImports, const QList &removedImports) override; + void customNotification(const AbstractView *view, const QString &identifier, + const QList &nodeList, const QList &data) override; + + void dragStarted(QMimeData *mimeData) override; + void dragEnded() override; + + void changeValue(const QString &name); + void changeExpression(const QString &name); + void exportPropertyAsAlias(const QString &name); + void removeAliasExport(const QString &name); + + bool locked() const; + + void currentTimelineChanged(const ModelNode &node) override; + + Internal::DynamicPropertiesModel *dynamicPropertiesModel() const; + + static TextureEditorView *instance(); + +public slots: + void handleToolBarAction(int action); + +protected: + void timerEvent(QTimerEvent *event) override; + void setValue(const QmlObjectNode &fxObjectNode, const PropertyName &name, const QVariant &value); + bool eventFilter(QObject *obj, QEvent *event) override; + +private: + static QString textureEditorResourcesPath(); + + void reloadQml(); + void highlightSupportedProperties(bool highlight = true); + + void applyTextureToSelectedModel(const ModelNode &texture); + + void setupQmlBackend(); + + void commitVariantValueToModel(const PropertyName &propertyName, const QVariant &value); + void commitAuxValueToModel(const PropertyName &propertyName, const QVariant &value); + void removePropertyFromModel(const PropertyName &propertyName); + void duplicateTexture(const ModelNode &texture); + + bool noValidSelection() const; + + ModelNode m_selectedTexture; + QTimer m_ensureMatLibTimer; + QShortcut *m_updateShortcut = nullptr; + int m_timerId = 0; + QStackedWidget *m_stackedWidget = nullptr; + ModelNode m_selectedModel; + QHash m_qmlBackendHash; + TextureEditorQmlBackend *m_qmlBackEnd = nullptr; + bool m_locked = false; + bool m_setupCompleted = false; + bool m_hasQuick3DImport = false; + bool m_hasTextureRoot = false; + bool m_initializingPreviewData = false; + + QPointer m_colorDialog; + QPointer m_itemLibraryInfo; + Internal::DynamicPropertiesModel *m_dynamicPropertiesModel = nullptr; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/qmldesignerconstants.h b/src/plugins/qmldesigner/qmldesignerconstants.h index 61cc2cf316b..b178bf1837b 100644 --- a/src/plugins/qmldesigner/qmldesignerconstants.h +++ b/src/plugins/qmldesigner/qmldesignerconstants.h @@ -113,6 +113,7 @@ const char EVENT_TRANSITIONEDITOR_TIME[] = "transitionEditor"; const char EVENT_CURVEDITOR_TIME[] = "curveEditor"; const char EVENT_STATESEDITOR_TIME[] = "statesEditor"; const char EVENT_TEXTEDITOR_TIME[] = "textEditor"; +const char EVENT_TEXTUREEDITOR_TIME[] = "textureEditor"; const char EVENT_PROPERTYEDITOR_TIME[] = "propertyEditor"; const char EVENT_ASSETSLIBRARY_TIME[] = "assetsLibrary"; const char EVENT_ITEMLIBRARY_TIME[] = "itemLibrary"; From ab7b8052961a66d698af43cbc1a4083fdc2c31a1 Mon Sep 17 00:00:00 2001 From: Pranta Dastider Date: Wed, 16 Nov 2022 17:19:00 +0100 Subject: [PATCH 010/131] QmlDesigner: Update Qt Installation instruction This update the Qt installation instructions with a bit more example. Also removes the outdated part with qmake. Fixes: QDS-8189 Change-Id: I9cfaeaff30585dc6ef65bb1ddedb48c1e5d13805 Reviewed-by: Mats Honkamaa Reviewed-by: Thomas Hartmann --- .../qtquick-from-qmlproject-to-pro.qdoc | 70 +++++-------------- 1 file changed, 19 insertions(+), 51 deletions(-) diff --git a/doc/qtcreator/src/qtquick/qtquick-from-qmlproject-to-pro.qdoc b/doc/qtcreator/src/qtquick/qtquick-from-qmlproject-to-pro.qdoc index 78f5e8398df..1e6956a449a 100644 --- a/doc/qtcreator/src/qtquick/qtquick-from-qmlproject-to-pro.qdoc +++ b/doc/qtcreator/src/qtquick/qtquick-from-qmlproject-to-pro.qdoc @@ -155,56 +155,24 @@ {Qt Code Review}. For example: - \badcode - git clone https://code.qt.io/qt-labs/qtquickdesigner-components.git - \endcode + \list 1 + \li Clone the module repository. + \badcode + git clone https://code.qt.io/qt-labs/qtquickdesigner-components.git + \endcode - Then use qmake from your Qt installation to build the module and to add it - to your Qt. Switch to the directory that contains the sources (usually, - qtquickdesigner-components), make sure you checkout the qmake branch, and enter - the following commands: - - \badcode - \qmake -r - make - make install - \endcode - - On Windows, use the \c nmake and \c {nmake install} commands instead. - - If you prefer CMake instead and you want to benefit from the QML compilation, - then you can checkout the dev branch instead. CMake is only supported since Qt 6.2. - Enter the following commands: - - \badcode - mkdir build - cd build - cmake -GNinja -DCMAKE_INSTALL_PREFIX= - cmake --build . - cmake --install . - \endcode - - \section1 Adding Qt Quick Timeline Module to Qt Installations - - \note You only need to do this if your Qt version is older than 5.14. - - Check out the \l{Qt Quick Timeline} module from - \l{https://codereview.qt-project.org/#/admin/projects/qt/qtquicktimeline} - {Qt Code Review}. - - For example: - \badcode - git clone "https://codereview.qt-project.org/qt/qtquicktimeline" - \endcode - - To use qmake, you need to check out a branch or tag that contains the - qmake configuration files. - - For example: - \badcode - git checkout v5.15.2 - \endcode - - Then build the module and add it to your Qt as described in the previous - section. + \li Install the Qt Quick Designer Components module. + Enter the following commands: + \badcode + mkdir build + cd build + cmake -GNinja -DCMAKE_INSTALL_PREFIX= + cmake --build . + cmake --install . + \endcode + \note Here, \e and \e + needs to be replaced with the real location on your local drive. For example, + \e can be something like \e /Qt/6.3.0/msvc2019_64 + and \e like this \e ../qtquickdesigner-components/ + \endlist */ From b6c2b08555f5ab883601ddb0ca5aa41d067f0269 Mon Sep 17 00:00:00 2001 From: Amr Essam Date: Fri, 18 Nov 2022 14:42:00 +0200 Subject: [PATCH 011/131] QmlDesigner: Change icons to original ones for effects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task-number: QDS-8353 Change-Id: I9a1fcdb7da7c0fd9d079720d3b3fafde1df384de Reviewed-by: Alessandro Portale Reviewed-by: Tomi Korpipää Reviewed-by: Miikka Heikkinen --- .../assetslibrary/assetslibrary.qrc | 6 ++++++ .../assetslibraryiconprovider.cpp | 3 ++- .../images/asset_effectClass.png | Bin 0 -> 682 bytes .../images/asset_effectClass@2x.png | Bin 0 -> 1260 bytes .../images/asset_effectClass_128.png | Bin 0 -> 2450 bytes .../images/asset_effectExported.png | Bin 0 -> 701 bytes .../images/asset_effectExported@2x.png | Bin 0 -> 1518 bytes .../images/asset_effectExported_128.png | Bin 0 -> 3150 bytes .../componentcore/modelnodeoperations.cpp | 18 +++++++++++++++++- .../componentcore/modelnodeoperations.h | 1 + .../designercore/model/qmlitemnode.cpp | 12 ++++++++++-- 11 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 src/plugins/qmldesigner/components/assetslibrary/images/asset_effectClass.png create mode 100644 src/plugins/qmldesigner/components/assetslibrary/images/asset_effectClass@2x.png create mode 100644 src/plugins/qmldesigner/components/assetslibrary/images/asset_effectClass_128.png create mode 100644 src/plugins/qmldesigner/components/assetslibrary/images/asset_effectExported.png create mode 100644 src/plugins/qmldesigner/components/assetslibrary/images/asset_effectExported@2x.png create mode 100644 src/plugins/qmldesigner/components/assetslibrary/images/asset_effectExported_128.png diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrary.qrc b/src/plugins/qmldesigner/components/assetslibrary/assetslibrary.qrc index 61d39d016db..26b4250d0ab 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrary.qrc +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrary.qrc @@ -8,10 +8,16 @@ images/asset_sound_128.png images/asset_video.png images/asset_video@2x.png + images/asset_effectClass.png + images/asset_effectClass@2x.png + images/asset_effectExported.png + images/asset_effectExported@2x.png images/browse.png images/browse@2x.png images/assets_default.png images/assets_default@2x.png images/assets_default_128.png + images/asset_effectClass_128.png + images/asset_effectExported_128.png diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.cpp index 7d878185378..90f3b011ff8 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.cpp @@ -3,6 +3,7 @@ #include "assetslibraryiconprovider.h" #include "assetslibrarymodel.h" +#include "modelnodeoperations.h" #include #include @@ -37,7 +38,7 @@ QPixmap AssetsLibraryIconProvider::requestPixmap(const QString &id, QSize *size, else if (AssetsLibraryModel::supportedVideoSuffixes().contains(suffix)) type = "video"; else if (AssetsLibraryModel::supportedEffectMakerSuffixes().contains(suffix)) - type = "default"; + type = QmlDesigner::ModelNodeOperations::getEffectIcon(id); QString pathTemplate = QString(":/AssetsLibrary/images/asset_%1%2.png").arg(type); QString path = pathTemplate.arg('_' + QString::number(requestedSize.width())); diff --git a/src/plugins/qmldesigner/components/assetslibrary/images/asset_effectClass.png b/src/plugins/qmldesigner/components/assetslibrary/images/asset_effectClass.png new file mode 100644 index 0000000000000000000000000000000000000000..1ce785cbfd2b566c4b41d192970107f75f582594 GIT binary patch literal 682 zcmeAS@N?(olHy`uVBq!ia0y~yV2}V|4rT@hhU+WOo?>8Nlnw9+ab;j&`2YX^icx?q z1TLjCK2&DsqcH*ed0;^f(jSFYW-dGEoKr_WxzeEsI# z`wySK{QULjukD53iVO@4_dH!3Lo_BPCmdmvI59DSfmty0_XdM%Q$aNYeyxM+lVl~t zm!3bVmj6aDWw8>+RD;jF8$%vVy;91(F)S(lLD&|KkEYDtM~)t?oS{)*U{IiayLI=A z8C5K|-43ca9JuQdckhI<$rXkSUyDsF9*cKLY~IUrA+LlpcY%Z-r*PmJp{N?Ca}la@ zk`~u)@QOHk)YT(-VgTdBe}`rW1r_-fZn_bBlzG!^RUw^+0?lt?!qOr?3%Gl{C|b4a zfa;Mie`2ogka+M>aluR0DH|?)_;O9irHkJ{z?_4_U`j*LiJsy&3=F1UeU=#2tAAu* PU|{fc^>bP0l+XkK2RdOY literal 0 HcmV?d00001 diff --git a/src/plugins/qmldesigner/components/assetslibrary/images/asset_effectClass@2x.png b/src/plugins/qmldesigner/components/assetslibrary/images/asset_effectClass@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a602bdb4ad9f0dd6bb4e4ed9861c868f3fe5c0f7 GIT binary patch literal 1260 zcmeAS@N?(olHy`uVBq!ia0y~yU@!n-4rT@hhJ-tuTNxM_-v{`FxH2#>{Qv)d#V8mV zAz<>j%$$LNVM$4lUoZm$BNH%_<$gxwW&z!$-@$%IhH*ej( zbNBv(Cr_U}fARA5o44;ieEj_7>$mSee*XIX=kGt>?=r0n3``uJE{-7{$CDFO1Pyq^ zd3jbecwXCF@$8L|zNMN8=Nc&&nak>WjZL1Cev2whKSUqd-Y9a-{YCMomu=T{m^N7p z>9TIfdT@oCS(~9r%wBQ^Bj;O(e~WGv)cT3=`f%AT-P~vt{Mly8+fB=vBV#wR$q4u^ zIu|*)fvNb^pD8mQO3i9e;y(R1G~$5QjW3I>8<~u`WLgxeW{B3D5@T?k^-xNIuXA&Q z^)$y@+l5~2J8IgmhRU|JUbJrSaOV)Gajw_BrVpwBIGyc3@L|% zI@b+i)-K$u{VXFgiq_3mnQ*%|mYL&>&d0EzFIuxWQ_j7MnRJ5ZV!u1{nN6z%V#|K3 z9^^Ta*q`J3&ch5Od3sZ*1 z4f-pVtS))zsuz8fDQaW(gKgTZ)4bn0KAN#~d)6D35?&p)%mbU!yqc`~9?d&zk}fxi zZKubA=g*$b-28ZTz>gR$MX9t(bEcD7<#kS`0?f@73DPe9?i*MHL&YRksQS#CwW2ZZ is3FhluDQ#k7<2*^e8LX4$TBc6FnGH9xvXy6l@c;k+6{BD@ z1crVHd^w?Zoq>VjT}hB%FarZ46Eh1d8#@Ol7dH>V zvWlvjx`w8nub+QFP;f|SSVUx0bWChq zd_rPUa!P7idPZhec1~VFVNr2OX<2ziWmR=eZC!msV{=PuTYE=mS9ecuU;l(jlc!9b zHhsp-S###jo4;V;qQy&=E?d50<*GI7)^FIjY4et?+qUo6xoh{Hz55RwJaqWT(PPI? zoIG{<%-M4nE?&BP<+v*#~fzIy%U?Ys9MK7RWA?L)`XdfAa%#YO3EJ;WcrW$@(g?pq1GoS9|+@x2>;omQObba67Rv zkAKzXdv8{4eZFiNbA)gAHCy+se@;!|U{w)4bZ@SDfasn(0a@q#c-akJr|Zkj3BRc4315#%rR{ zaSK^*wFq!Nl2LeYuS($T_V(KcPs%>A{VWx7(w#dwwcttQ^J9Bg%zS)zBVT?Z6W@c= zcYd+0&3VZ`!ND}rtnQ<*i0bcborn$APB(Tx)>u`T|NY6whs6i7uIw?h4PvYou--P; zF|abfOk$l`{+%zF3&NX}?07*3lU=uLp(*zGV>DVPNURm_Pf#h7@+Mpz6Jv;o^$A zy>kCltQlC&eQ7`OV6DR8w8hix#H(#9BqXLB`hD^BS#I+aMGO|Fzb{BC13V8I}vEO46r5aarWlZ;Dy-QwBLlsSwO7*ndQrwmz1$CQ}=z{NMU$sozHT*_3~B5ivs`LvC*{FLG4@q~v}ZH%uaMH`CeUpipf@L6WtG6vhl;fL4*x2j9xJes`|t&nHZU=@JTg;geIr=>=koVuvgRvaG3f1Y^)P*UH;(%dw?xF;<@U=a zF-N>nS1c8sclPTOg(6l~{|B4Gr&u3Y@l@+l%KcXyb(5}5=upjFe1cI+^~CF{vgO{B zHg>sf{Gc(DPr(0f;~tL1F>~LPy0Gzvdz3Nczwla;s$hQ0MR`*SoMc4MKK4Ew(_2j5fw2;e+J#+lE zvgPI2PdrnazOeoC)67lt0yclr31jCtp6kLj-@CMGOLXALlxv-VLHA=CW3GLR6pVT3 z^>9{@*>uSrUa!)N_gcKzv|_4xhGm-P-!$JA2HELfyHpoXyP}b*8+%QZ`8ubl+~L4$ zmrgP+n6yjK!lgg*TF`@ri$~ugidJ}O%gn&=Vl7kEyxK<&3=9kmp00i_>zopr0Hp{2 AY5)KL literal 0 HcmV?d00001 diff --git a/src/plugins/qmldesigner/components/assetslibrary/images/asset_effectExported.png b/src/plugins/qmldesigner/components/assetslibrary/images/asset_effectExported.png new file mode 100644 index 0000000000000000000000000000000000000000..fac52d0f7c5f6f1aff4b6ded06fa84bd78d65b7f GIT binary patch literal 701 zcmeAS@N?(olHy`uVBq!ia0y~yV2}V|4mJh`h6m-gKNuL8T0C7GLp(aSUiIymS}MZ! z;r>}kcAXc^9=;Rp6qbrPg@i5cR`U84!Fexu#yK~R)&(64ZizT{H!afFxYe@7f?4<0 zwt`S+!OfZ;oka(FW=(K-;cWCY_v5^sd+rs!@q5uYz54!V`{$MOivP8L{x)k_VPI}Z z)0rAA%K{gz_y{^4fRec7UUwZYA^Z%us{Uw=K<|L-oZ zSvpH?&m^%K3l{1amp^{kapUIM+*!|Zn$E;Dp54J6U06Q#pKRa5eWz?YzTSxVCt1_) z|Dn`nPNagw77gR;$$ari;&q26?@&`Rdw9KX$Inl(y?-sbs=G4Qt+LzgI`gO8wXlAF zzO65>WqGd;$veKSV}@+k>;om76-fb(X?X`k=1H^ZS8qKq^;Y?ZTLl3we|n!AhR(=k zy?ptye3FcRxM=o9KkMJKKV+~!_ubQ>d~3Hw%lxh7AH8k!AExZTKR;^5%>yiQ*6qJs z#RS!jJ~wvlm;9dW%Qj4*e)7!l z9CG!t&eqhbMfU85kHC NJYD@<);T3K0RZU>Rh9q% literal 0 HcmV?d00001 diff --git a/src/plugins/qmldesigner/components/assetslibrary/images/asset_effectExported@2x.png b/src/plugins/qmldesigner/components/assetslibrary/images/asset_effectExported@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..ab6a5f29407e40a9ff6a1e9b4526d624651a9bbd GIT binary patch literal 1518 zcmeAS@N?(olHy`uVBq!ia0y~yU@!n-4mJh`hH$2z?F{IYHP;vQC%HG^%Dk0R#*eoTmQ8Zr2QP=Q@{wK!^ z2DevS$Z-0x;F7qe{2DjIYfd%Y+f}!bqt79guZqPG*OKfcgpZT zPR;F~_UQM_or_Li$(23kckax$-We^9^A8l&ux@{#Uz#JbzPICH$B*jYEoWkyPFsK2 zCBmQj>~yuR#g$Lb99PVZ^05b`dlcDZvg zd#>Tm^i>XNdWT#qviHVyu*RN<5Ak_4{p;rlmp_{izP74y|2T7A;ENfeC(aZX6gt0n zxKFL@xZYzf+rIC5w+B>GSFXx(nm)Hs0^P(YQ8rLTJQ&x6N|<*VTkQW}0)U z@p_$2*w38cGw)h6H3N!gSQjilX(&4{S7+1KogTc^^+7p1JJUB9De;Bun*34i@$Ah~ z%gZBPE9_hP<7mXY`;&G@YZda^-QV$l^GDg6ZF7Coa)0EoKjha?|89AQ?dnYV_4D&X zmWG*$?2^b2F#BNkN6Z!r9eI5TV{I=b&jjLW@{+sY0UHYX0^CnGv7qsZ|6uw(dE^m(W{typ)^!Tlb zu*izt_1Sy(?v{MC%qflUadNDjeBc@Ob!X2M|9&bhcs|K_@7}GaEZV*aM|6vJ9lNaS zurIGj?3PH% z!`~;t-C6hajpZG-*xq0MpfjDD1igLMJi1l5#Ib(!D{)`esGp~Erp!!UG4biAGcgNplzg5tT}yrU zo{xJc$0Y9zEB+7^v*^t~&mHw)CM!BRoV~vNOwr$UCTFjKeboByjXS^YiPrj9^;q<( zg26N9y%T13XWz)_+bUvVtn}}e)O@X}GfY#SeZ6r*#KhP?+)C*7>#g}`b~5eYx_{=( z@2Msab$4)ge|Y@T$XaggQQa=#I;YI#cUE)oPwx7+vcN{<#TBQ_#P=mLW%*g>z5DoP z^-Rlpt256+8l!gD-)Mf4^~c88I6uSs#}0Sp zw(#u2|JOU0&1U+&%U42VRcDQtW~};?*qJ*`jP2K&9DJ}}ZJXTj3tRQ4%q*Ak-M6f%~~M8Xq!;e%QX%_I}^{#4Yo;%w{ZIZu~jtvGvbga!Y1U5!!T<`|s>Wd%n9K z3BDX#o$$8wSk6z`F5Ug|ck9Hg8sxRqHtoDWJye}1q*3kFdAIkWeoyCiy|h_17-7#J8lUHx3vIVCg!047TFzyJUM literal 0 HcmV?d00001 diff --git a/src/plugins/qmldesigner/components/assetslibrary/images/asset_effectExported_128.png b/src/plugins/qmldesigner/components/assetslibrary/images/asset_effectExported_128.png new file mode 100644 index 0000000000000000000000000000000000000000..07960893e87da8263604f7bb5f8bbeb9b2b7ce7a GIT binary patch literal 3150 zcmeAS@N?(olHy`uVBq!ia0y~yU}ykg4rT@hhA$5${$yZadJ*6g;>y6l@c;k+6{BD@ z1cr48=xfZI$-uzyy(Gvln1O+jiJ66!jh%y&i<^g+k6%DgNLWNvOk6@zN?Jx%PF_J# zNm)fzO!O_Xt#nsK-!_&*#$Jft4ATTI6Bs4rC zGAcSIHZDFPF)2AEH7z|OGb=kcFTbF$sJNuGth}PKs=B7OuD+qMskx=Kt-YhOtGlPS zuYba%$y26In?7Uatl4ws&YQnr;iAP$mM&YqV&$sUYu2t?zhUF1&D*x`*tu)>p1u3_ zA2@jE@R6g(j-NPr>hzhj=gwcacRJbd){$~I}^yTZf?>~P2`u*qcztyu|GBYr6$a%UrhE&{oJ3HG!Bvhn9y)0b7#bHT<%ZJ8U z3pzTIyiz)BCh=TW5O&mYJsTu=Q6Nyjvu1&f%Z3#K9+jFu92C3-L{&oql!N5DG`bpW z%HH2kNV_@r_}sZS@7`Q0KgZs>`25avyLWev?=0p&5#6kFLf`-h{%2+gbnCf%KXlC^ zBZjs}`HuA_VcWS_U$Hx^a$NfV!@m2PsthI@73Gc@Iw&zbh&=M+f_d%!r)Qqs^JVH| z3}aaN#Ocdw<+Dr7A9F2OyF}!Gg^YuZ%X@~ZOR^ih4?Zia(AG5PlJjINbGR}~_V4?& zR<))Zcb8fwZ9{j%f&Vrp$BFeocN=ffH-ZAvZ+}XqIylC>Rfb)d~iw%mD?;1TWllbN-@ahKF zs?W?KnOh!9Ied1Ue>-VKRv^D9LrFy;PlL|8&#!ocZ){^+!BWiAFo*r@tO-e<-CjJ- zp7htffG6SVZvLRG0{(#Zmv#Qi=d2Uw%SiES&e?6O_Vdisu3Og{k8j_*{%EC#n%K>Y z4>Al71&SVNd2#vee$R6!J@36*TlhGUIiR|(AdY*^E~RU?=g2k$O8A!lHrKu2 zCUtYIqSke<$zI(pWS6f?_j2XV#KrC@p6o|1PUfG% zAhY{qXdSDx-f5$4~nbsF-oyxoAa(6Kr2L-JKFEEYrr=lX{cEkb%wM4P#YpQ><{p zWi?I(#!ykCN{RcttPazXR$nt&_Up#;-7H!Smd`5qCN~E&@HPBtyeG0@w)3vDnhfeU zug>Rv!6382T3n4^Tt%Cm@qp0myKg13W?#9$q_BTs`Bk641$<(Pk&_teoVtr&WPS8a zuUyA#A9sWE1*2M${>J5hd~!Z?vA(}}jt3Fc9sYm9jyKs~ZPz@i=WI~4 zkz+ly?9-hea~boDdrSj%%X~asz;NwGk(h9e%H}QWywCA=usGO=qmc z7}+OqO%c#!c%;w&r{AI>tdC)WXD9EQ@BJ1G2Sgp}Lz16v%KClrvQC^T--0G*hLwAa zW7E>t9bnwS70TEkea`sKwU%WQjHh>xEi zHnS%DVETQ}xWanz*Nl%UUYA(48)Rh|INmwL2p~yx!R@Pp6=zVy9{2h zmt?Sel9E4zS?Xgj!~LhzzcIf4$-{71YA3^t8=`&E(=YDucq=&n<%e6rCm0!o49gNW ze|oEXsC{Ao%>MV{c25`>tbbmPtLvL_E9&(bwMNGenR)%282-*bCNsH1d&-A9MvS*b z`nH?Lg$pHIlDqWq-rM&+Pfjat>{=BraiAbp^Wu`1?Jv1fE`AVOwl9n!;l?E6BmG-s zGuPF)Ualyq<#zqVctc)UPO78p@0TgwDMq*B&Pp9nYT4qrfB#{pw(M&wV>Ty6M=U$yn0QZGxgO}of+LqlD_$w1+|O zY7U*Oex1PZHdJLn$H_N;G$&j*QS6mD`L6Qqpg9#edo`zBTKTwmUfE8eXTLK}KlFMy zC+PCp&<)4K^US86?>CCvx~<(~=2Blnvm)z2z4Z|fCMw?KES*>MSo?v~%Oh#E|1W>t tYuj%e{!%ln{)N3>J3|{o8$-ZP{)jC9s@P-MEes3{44$rjF6*2UngD0%M_>Q| literal 0 HcmV?d00001 diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp index 27a18a09b56..9edf0869e74 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp +++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp @@ -1651,7 +1651,7 @@ void openEffectMaker(const QString &filePath) Utils::FilePath effectPath = Utils::FilePath::fromString(filePath); QStringList arguments; arguments << filePath; - if (effectPath.fileContents()) + if (effectPath.fileContents()->isEmpty()) arguments << "--create"; arguments << "--exportpath" << effectResPath.toString(); @@ -1684,6 +1684,22 @@ Utils::FilePath getEffectsDirectory() return effectsPath; } +QString getEffectIcon(const QString &effectPath) +{ + const ProjectExplorer::Target *target = ProjectExplorer::ProjectTree::currentTarget(); + if (!target) { + qWarning() << __FUNCTION__ << "No project open"; + return QString(); + } + + Utils::FilePath projectPath = target->project()->projectDirectory(); + QString effectName = QFileInfo(effectPath).baseName(); + QString effectResDir = "asset_imports/Effects/" + effectName; + Utils::FilePath effectResPath = projectPath.resolvePath(effectResDir + "/" + effectName + ".qml"); + + return effectResPath.exists() ? QString("effectExported") : QString("effectClass"); +} + } // namespace ModelNodeOperations } //QmlDesigner diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h index c883edc8f03..279e18af974 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h +++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h @@ -80,6 +80,7 @@ void updateImported3DAsset(const SelectionContext &selectionContext); QMLDESIGNERCORE_EXPORT Utils::FilePath getEffectsDirectory(); void openEffectMaker(const QString &filePath); +QString getEffectIcon(const QString &effectPath); // ModelNodePreviewImageOperations QVariant previewImageDataForGenericNode(const ModelNode &modelNode); diff --git a/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp b/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp index 95589335b98..b1a526bd6a8 100644 --- a/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp +++ b/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp @@ -185,8 +185,16 @@ void QmlItemNode::createQmlItemNodeForEffect(AbstractView *view, TypeName type(effectName.toUtf8()); newQmlItemNode = QmlItemNode(view->createModelNode(type, 1, 0)); NodeAbstractProperty parentProperty = layerEffect - ? parentNode.nodeAbstractProperty("layer.effect") - : parentNode.defaultNodeAbstractProperty(); + ? parentNode.nodeAbstractProperty("layer.effect") + : parentNode.defaultNodeAbstractProperty(); + + if (layerEffect) { + if (!parentProperty .isEmpty()) { //already contains a node + ModelNode oldEffect = parentProperty.toNodeProperty().modelNode(); + QmlObjectNode(oldEffect).destroy(); + } + } + parentProperty.reparentHere(newQmlItemNode); if (!layerEffect) { From 9e48f63a07669b383da1a05f5e9563aa1baa3afc Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Mon, 21 Nov 2022 14:18:11 +0200 Subject: [PATCH 012/131] QmlDesigner: Remove one unused variable warning from ItemsLibrary Change-Id: I1446879250dd99d27d3e84fc7b133e35603472a9 Reviewed-by: hjk --- .../qmldesigner/components/itemlibrary/itemlibraryview.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp index 813b021b85d..3e56c6a0e6d 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp @@ -148,6 +148,8 @@ void ItemLibraryView::updateImport3DSupport(const QVariantMap &supportMap) AddResourceOperation import3DModelOperation = [this](const QStringList &fileNames, const QString &defaultDir, bool showDialog) -> AddFilesResult { + Q_UNUSED(showDialog) + auto importDlg = new ItemLibraryAssetImportDialog(fileNames, defaultDir, m_importableExtensions3DMap, m_importOptions3DMap, {}, {}, From 88ce27736fa6324aeeebd30bc79a6d7ffe097d82 Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Mon, 21 Nov 2022 14:17:00 +0200 Subject: [PATCH 013/131] QmlDesigner: Add separate + buttons for material browser sections Fixes: QDS-8343 Change-Id: Id986820c857df241cf25e55416832f189c28bfe0 Reviewed-by: Miikka Heikkinen --- .../MaterialBrowser.qml | 214 ++++++++++-------- .../materialbrowsertexturesmodel.cpp | 5 + .../materialbrowsertexturesmodel.h | 2 + .../materialbrowser/materialbrowserview.cpp | 21 +- 4 files changed, 143 insertions(+), 99 deletions(-) diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml index 7849dfd9269..d93f236fede 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml @@ -95,24 +95,12 @@ Item { StudioControls.SearchBox { id: searchBox - width: root.width - addMaterialButton.width + width: root.width onSearchChanged: (searchText) => { rootView.handleSearchFilterChanged(searchText) } } - - IconButton { - id: addMaterialButton - - tooltip: qsTr("Add a material.") - - icon: StudioTheme.Constants.plus - anchors.verticalCenter: parent.verticalCenter - buttonSize: searchBox.height - onClicked: materialBrowserModel.addNewMaterial() - enabled: materialBrowserModel.hasQuick3DImport - } } Text { @@ -145,116 +133,154 @@ Item { interactive: !ctxMenu.opened Column { - Section { - id: materialsSection - + Item { width: root.width - caption: qsTr("Materials") - dropEnabled: true + height: materialsSection.height - onDropEnter: (drag) => { - drag.accepted = drag.formats[0] === "application/vnd.qtdesignstudio.bundlematerial" - materialsSection.highlight = drag.accepted - } + Section { + id: materialsSection - onDropExit: { - materialsSection.highlight = false - } + width: root.width + caption: qsTr("Materials") + dropEnabled: true - onDrop: { - materialsSection.highlight = false - rootView.acceptBundleMaterialDrop() - } + onDropEnter: (drag) => { + drag.accepted = drag.formats[0] === "application/vnd.qtdesignstudio.bundlematerial" + materialsSection.highlight = drag.accepted + } - Grid { - id: grid + onDropExit: { + materialsSection.highlight = false + } - width: scrollView.width - leftPadding: 5 - rightPadding: 5 - bottomPadding: 5 - columns: root.width / root.cellWidth + onDrop: { + materialsSection.highlight = false + rootView.acceptBundleMaterialDrop() + } - Repeater { - id: materialRepeater + Grid { + id: grid - model: materialBrowserModel - delegate: MaterialItem { - width: root.cellWidth - height: root.cellHeight + width: scrollView.width + leftPadding: 5 + rightPadding: 5 + bottomPadding: 5 + columns: root.width / root.cellWidth - onShowContextMenu: { - ctxMenu.popupMenu(this, model) + Repeater { + id: materialRepeater + + model: materialBrowserModel + delegate: MaterialItem { + width: root.cellWidth + height: root.cellHeight + + onShowContextMenu: { + ctxMenu.popupMenu(this, model) + } } } } + + Text { + text: qsTr("No match found."); + color: StudioTheme.Values.themeTextColor + font.pixelSize: StudioTheme.Values.baseFontSize + leftPadding: 10 + visible: materialBrowserModel.isEmpty && !searchBox.isEmpty() && !materialBrowserModel.hasMaterialRoot + } + + Text { + text:qsTr("There are no materials in this project.
Select '+' to create one.") + visible: materialBrowserModel.isEmpty && searchBox.isEmpty() + textFormat: Text.RichText + color: StudioTheme.Values.themeTextColor + font.pixelSize: StudioTheme.Values.mediumFontSize + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WordWrap + width: root.width + } } - Text { - text: qsTr("No match found."); - color: StudioTheme.Values.themeTextColor - font.pixelSize: StudioTheme.Values.baseFontSize - leftPadding: 10 - visible: materialBrowserModel.isEmpty && !searchBox.isEmpty() && !materialBrowserModel.hasMaterialRoot - } + IconButton { + id: addMaterialButton - Text { - text:qsTr("There are no materials in this project.
Select '+' to create one.") - visible: materialBrowserModel.isEmpty && searchBox.isEmpty() - textFormat: Text.RichText - color: StudioTheme.Values.themeTextColor - font.pixelSize: StudioTheme.Values.mediumFontSize - horizontalAlignment: Text.AlignHCenter - wrapMode: Text.WordWrap - width: root.width + tooltip: qsTr("Add a material.") + + anchors.right: parent.right + anchors.rightMargin: scrollView.verticalScrollBarVisible ? 10 : 0 + icon: StudioTheme.Constants.plus + normalColor: "transparent" + buttonSize: StudioTheme.Values.sectionHeadHeight + onClicked: materialBrowserModel.addNewMaterial() + enabled: materialBrowserModel.hasQuick3DImport } } - Section { - id: texturesSection - + Item { width: root.width - caption: qsTr("Textures") + height: texturesSection.height - Grid { - width: scrollView.width - leftPadding: 5 - rightPadding: 5 - bottomPadding: 5 - columns: root.width / root.cellWidth + Section { + id: texturesSection - Repeater { - id: texturesRepeater + width: root.width + caption: qsTr("Textures") - model: materialBrowserTexturesModel - delegate: TextureItem { - width: root.cellWidth - height: root.cellWidth + Grid { + width: scrollView.width + leftPadding: 5 + rightPadding: 5 + bottomPadding: 5 + columns: root.width / root.cellWidth - onShowContextMenu: { -// ctxMenuTexture.popupMenu(this, model) // TODO: implement textures context menu + Repeater { + id: texturesRepeater + + model: materialBrowserTexturesModel + delegate: TextureItem { + width: root.cellWidth + height: root.cellWidth + + onShowContextMenu: { + // ctxMenuTexture.popupMenu(this, model) // TODO: implement textures context menu + } } } } + + Text { + text: qsTr("No match found."); + color: StudioTheme.Values.themeTextColor + font.pixelSize: StudioTheme.Values.baseFontSize + leftPadding: 10 + visible: materialBrowserModel.isEmpty && !searchBox.isEmpty() && !materialBrowserModel.hasMaterialRoot + } + + Text { + text:qsTr("There are no texture in this project.") + visible: materialBrowserTexturesModel.isEmpty && searchBox.isEmpty() + textFormat: Text.RichText + color: StudioTheme.Values.themeTextColor + font.pixelSize: StudioTheme.Values.mediumFontSize + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WordWrap + width: root.width + } } - Text { - text: qsTr("No match found."); - color: StudioTheme.Values.themeTextColor - font.pixelSize: StudioTheme.Values.baseFontSize - leftPadding: 10 - visible: materialBrowserModel.isEmpty && !searchBox.isEmpty() && !materialBrowserModel.hasMaterialRoot - } + IconButton { + id: addTextureButton - Text { - text:qsTr("There are no texture in this project.") - visible: materialBrowserTexturesModel.isEmpty && searchBox.isEmpty() - textFormat: Text.RichText - color: StudioTheme.Values.themeTextColor - font.pixelSize: StudioTheme.Values.mediumFontSize - horizontalAlignment: Text.AlignHCenter - wrapMode: Text.WordWrap - width: root.width + tooltip: qsTr("Add a texture.") + + anchors.right: parent.right + anchors.rightMargin: scrollView.verticalScrollBarVisible ? 10 : 0 + icon: StudioTheme.Constants.plus + normalColor: "transparent" + buttonSize: StudioTheme.Values.sectionHeadHeight + onClicked: materialBrowserTexturesModel.addNewTexture() + enabled: materialBrowserModel.hasQuick3DImport } } } diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp index 772620cd04e..827fadcab6d 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp @@ -154,6 +154,11 @@ void MaterialBrowserTexturesModel::removeTexture(const ModelNode &texture) } } +void MaterialBrowserTexturesModel::addNewTexture() +{ + emit addNewTextureTriggered(); +} + void MaterialBrowserTexturesModel::deleteSelectedTexture() { deleteTexture(m_selectedIndex); diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h index f23ca1e48ac..afff53e6ae3 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h @@ -43,6 +43,7 @@ public: void resetModel(); Q_INVOKABLE void selectTexture(int idx, bool force = false); + Q_INVOKABLE void addNewTexture(); Q_INVOKABLE void duplicateTexture(int idx); Q_INVOKABLE void deleteTexture(int idx); @@ -52,6 +53,7 @@ signals: void materialSectionsChanged(); void selectedIndexChanged(int idx); void duplicateTextureTriggered(const QmlDesigner::ModelNode &material); + void addNewTextureTriggered(); private: bool isTextureVisible(int idx) const; diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp index 5238ae0ed3f..b7f493c6154 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp @@ -146,6 +146,10 @@ WidgetInfo MaterialBrowserView::widgetInfo() ModelNode texNode = m_widget->materialBrowserTexturesModel()->textureAt(idx); emitCustomNotification("selected_texture_changed", {texNode}, {}); }); + + connect(texturesModel, &MaterialBrowserTexturesModel::addNewTextureTriggered, this, [&] { + emitCustomNotification("add_new_texture"); + }); } return createWidgetInfo(m_widget.data(), @@ -290,18 +294,25 @@ void MaterialBrowserView::nodeReparented(const ModelNode &node, ModelNode newParentNode = newPropertyParent.parentModelNode(); ModelNode oldParentNode = oldPropertyParent.parentModelNode(); - bool matAdded = newParentNode.id() == Constants::MATERIAL_LIB_ID; - bool matRemoved = oldParentNode.id() == Constants::MATERIAL_LIB_ID; + bool added = newParentNode.id() == Constants::MATERIAL_LIB_ID; + bool removed = oldParentNode.id() == Constants::MATERIAL_LIB_ID; - if (matAdded || matRemoved) { - if (matAdded && !m_puppetResetPending) { + if (!added && !removed) + return; + + refreshModel(removed); + + if (isMaterial(node)) { + if (added && !m_puppetResetPending) { // Workaround to fix various material issues all likely caused by QTBUG-103316 resetPuppet(); m_puppetResetPending = true; } - refreshModel(!matAdded); int idx = m_widget->materialBrowserModel()->materialIndex(node); m_widget->materialBrowserModel()->selectMaterial(idx); + } else { // is texture + int idx = m_widget->materialBrowserTexturesModel()->textureIndex(node); + m_widget->materialBrowserTexturesModel()->selectTexture(idx); } } From 4b6b81cd76ec341aa5a83d3284ab5c71f84f16be Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Mon, 21 Nov 2022 16:12:54 +0200 Subject: [PATCH 014/131] QmlDesigner: Fix content library material unimport Change-Id: I3737682b578cdd991ca761da92cfdcc5506b3629 Reviewed-by: Miikka Heikkinen --- .../qmldesigner/contentLibraryQmlSource/ContentLibrary.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibrary.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibrary.qml index bbe9641419e..8d699308478 100644 --- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibrary.qml +++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibrary.qml @@ -94,8 +94,8 @@ Item { searchBox: searchBox onUnimport: (bundleMat) => { - unimportBundleMaterialDialog.targetBundleMaterial = bundleMat - unimportBundleMaterialDialog.open() + confirmUnimportDialog.targetBundleMaterial = bundleMat + confirmUnimportDialog.open() } } From a04f1590de94405d1ddb8543d0c2e58f28bc8127 Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Mon, 21 Nov 2022 16:45:03 +0200 Subject: [PATCH 015/131] QmlDesigner: Update texture preview when source change Fixes: QDS-8386 Change-Id: Ifaa6effc040398452b022080257bbacbfb14efdf Reviewed-by: Miikka Heikkinen --- .../materialbrowser/materialbrowsertexturesmodel.cpp | 7 +++++++ .../materialbrowser/materialbrowsertexturesmodel.h | 1 + .../components/materialbrowser/materialbrowserview.cpp | 2 ++ 3 files changed, 10 insertions(+) diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp index 827fadcab6d..66762114322 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp @@ -164,6 +164,13 @@ void MaterialBrowserTexturesModel::deleteSelectedTexture() deleteTexture(m_selectedIndex); } +void MaterialBrowserTexturesModel::updateTextureSource(const ModelNode &texture) +{ + int idx = textureIndex(texture); + if (idx != -1) + emit dataChanged(index(idx, 0), index(idx, 0), {roleNames().key("textureSource")}); +} + void MaterialBrowserTexturesModel::updateSelectedTexture() { selectTexture(m_selectedIndex, true); diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h index afff53e6ae3..3d02884cae3 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h @@ -34,6 +34,7 @@ public: void removeTexture(const ModelNode &texture); void deleteSelectedTexture(); void updateSelectedTexture(); + void updateTextureSource(const ModelNode &texture); int textureIndex(const ModelNode &material) const; ModelNode textureAt(int idx) const; diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp index b7f493c6154..5af32bce600 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp @@ -279,6 +279,8 @@ void MaterialBrowserView::variantPropertiesChanged(const QList if (isMaterial(node) && property.name() == "objectName") m_widget->materialBrowserModel()->updateMaterialName(node); + else if (isTexture(node) && property.name() == "source") + m_widget->materialBrowserTexturesModel()->updateTextureSource(node); } } From 6d620429c6e1438c9b479479570a409fc65a0ca5 Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Mon, 21 Nov 2022 21:10:49 +0200 Subject: [PATCH 016/131] QmlDesigner: Fix Material/Texture Editors toolbars issues Make sure each toolbar's enum is registered under a different URI. Change-Id: I7613b4018b62188ba3fba5f651d064ada8173066 Reviewed-by: Reviewed-by: Miikka Heikkinen --- .../materialEditorQmlSources/MaterialEditorToolBar.qml | 2 +- .../qmldesigner/textureEditorQmlSource/TextureEditorToolBar.qml | 2 +- .../components/materialeditor/materialeditorcontextobject.cpp | 2 +- .../components/textureeditor/textureeditorcontextobject.cpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorToolBar.qml b/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorToolBar.qml index 065a1a6ba4d..0a1b97f34bb 100644 --- a/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorToolBar.qml +++ b/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorToolBar.qml @@ -5,7 +5,7 @@ import QtQuick 2.15 import QtQuickDesignerTheme 1.0 import HelperWidgets 2.0 import StudioTheme 1.0 as StudioTheme -import ToolBarAction 1.0 +import MaterialToolBarAction 1.0 Rectangle { id: root diff --git a/share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorToolBar.qml b/share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorToolBar.qml index 91b46c5da47..ed40b038a72 100644 --- a/share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorToolBar.qml +++ b/share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorToolBar.qml @@ -5,7 +5,7 @@ import QtQuick 2.15 import QtQuickDesignerTheme 1.0 import HelperWidgets 2.0 import StudioTheme 1.0 as StudioTheme -import ToolBarAction 1.0 +import TextureToolBarAction 1.0 Rectangle { id: root diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.cpp index 7566bd60bcc..57e6eab9e25 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.cpp +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.cpp @@ -30,7 +30,7 @@ MaterialEditorContextObject::MaterialEditorContextObject(QQmlContext *context, Q : QObject(parent) , m_qmlContext(context) { - qmlRegisterUncreatableType("ToolBarAction", 1, 0, "ToolBarAction", "Enum type"); + qmlRegisterUncreatableType("MaterialToolBarAction", 1, 0, "ToolBarAction", "Enum type"); } QQmlComponent *MaterialEditorContextObject::specificQmlComponent() diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.cpp b/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.cpp index 1bfc1c6c6b1..017661bfcfc 100644 --- a/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.cpp +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.cpp @@ -28,7 +28,7 @@ TextureEditorContextObject::TextureEditorContextObject(QQmlContext *context, QOb : QObject(parent) , m_qmlContext(context) { - qmlRegisterUncreatableType("ToolBarAction", 1, 0, "ToolBarAction", "Enum type"); + qmlRegisterUncreatableType("TextureToolBarAction", 1, 0, "ToolBarAction", "Enum type"); } QQmlComponent *TextureEditorContextObject::specificQmlComponent() From f70cdf52c4d58051bb1c65eb01779a63e2dc003b Mon Sep 17 00:00:00 2001 From: Amr Essam Date: Tue, 22 Nov 2022 11:08:04 +0200 Subject: [PATCH 017/131] QmlDesigner: Fix created effect cannot be used Create effect node needed to be added to a transaction There are other fixes to the effectmakerplugin Task-number: QDS-8152 Change-Id: I37eac416e7664ae4a28a0a3362f98080ca29fd17 Reviewed-by: Thomas Hartmann --- .../components/formeditor/dragtool.cpp | 1 - .../designercore/model/qmlitemnode.cpp | 56 ++++++++++--------- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/src/plugins/qmldesigner/components/formeditor/dragtool.cpp b/src/plugins/qmldesigner/components/formeditor/dragtool.cpp index fe345024182..ce27128cb35 100644 --- a/src/plugins/qmldesigner/components/formeditor/dragtool.cpp +++ b/src/plugins/qmldesigner/components/formeditor/dragtool.cpp @@ -269,7 +269,6 @@ void DragTool::dropEvent(const QList &itemList, QGraphicsSceneD QmlItemNode::createQmlItemNodeForEffect(view(), parentQmlItemNode, effectName); view()->setSelectedModelNodes({parentQmlItemNode}); - view()->resetPuppet(); commitTransaction(); } diff --git a/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp b/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp index b1a526bd6a8..46618eed503 100644 --- a/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp +++ b/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp @@ -172,37 +172,39 @@ void QmlItemNode::createQmlItemNodeForEffect(AbstractView *view, { QmlItemNode newQmlItemNode; - const bool layerEffect = useLayerEffect(); + view->executeInTransaction("QmlItemNode::createQmlItemNodeForEffect", [=, &newQmlItemNode, &parentNode]() { + const bool layerEffect = useLayerEffect(); - QmlDesigner::Import import = Import::createLibraryImport("Effects." + effectName, "1.0"); - try { - if (!view->model()->hasImport(import, true, true)) - view->model()->changeImports({import}, {}); - } catch (const Exception &) { - QTC_ASSERT(false, return); - } - - TypeName type(effectName.toUtf8()); - newQmlItemNode = QmlItemNode(view->createModelNode(type, 1, 0)); - NodeAbstractProperty parentProperty = layerEffect - ? parentNode.nodeAbstractProperty("layer.effect") - : parentNode.defaultNodeAbstractProperty(); - - if (layerEffect) { - if (!parentProperty .isEmpty()) { //already contains a node - ModelNode oldEffect = parentProperty.toNodeProperty().modelNode(); - QmlObjectNode(oldEffect).destroy(); + QmlDesigner::Import import = Import::createLibraryImport("Effects." + effectName, "1.0"); + try { + if (!view->model()->hasImport(import, true, true)) + view->model()->changeImports({import}, {}); + } catch (const Exception &) { + QTC_ASSERT(false, return); } - } - parentProperty.reparentHere(newQmlItemNode); + TypeName type(effectName.toUtf8()); + newQmlItemNode = QmlItemNode(view->createModelNode(type, -1, -1)); + NodeAbstractProperty parentProperty = layerEffect + ? parentNode.nodeAbstractProperty("layer.effect") + : parentNode.defaultNodeAbstractProperty(); - if (!layerEffect) { - newQmlItemNode.modelNode().bindingProperty("source").setExpression("parent"); - newQmlItemNode.modelNode().bindingProperty("anchors.fill").setExpression("parent"); - } else { - parentNode.modelNode().variantProperty("layer.enabled").setValue(true); - } + if (layerEffect) { + if (!parentProperty .isEmpty()) { //already contains a node + ModelNode oldEffect = parentProperty.toNodeProperty().modelNode(); + QmlObjectNode(oldEffect).destroy(); + } + } + + parentProperty.reparentHere(newQmlItemNode); + + if (!layerEffect) { + newQmlItemNode.modelNode().bindingProperty("source").setExpression("parent"); + newQmlItemNode.modelNode().bindingProperty("anchors.fill").setExpression("parent"); + } else { + parentNode.modelNode().variantProperty("layer.enabled").setValue(true); + } + }); } bool QmlItemNode::isValid() const From e3a817ec77f653100bf29389464af8ac8706d8e3 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Thu, 17 Nov 2022 17:52:26 +0200 Subject: [PATCH 018/131] QmlDesigner: Hide or disable material browser and editor if no library If material library is missing, material browser and editor UI should be disabled, except for material editor main pane in case of root material node. Change-Id: I3d2bd545de0649fb90d3fe1f751d46b7c7054bbf Reviewed-by: Mahmoud Badri --- .../MaterialBrowser.qml | 26 +++++++++---------- .../EmptyMaterialEditorPane.qml | 10 +++++-- .../MaterialEditorToolBar.qml | 8 +++--- .../materialbrowser/materialbrowsermodel.cpp | 12 ++++----- .../materialbrowser/materialbrowsermodel.h | 10 +++---- .../materialbrowser/materialbrowserview.cpp | 9 +++++-- .../materialeditorcontextobject.cpp | 12 ++++----- .../materialeditorcontextobject.h | 10 +++---- .../materialeditor/materialeditorview.cpp | 22 ++++++++++++++-- .../materialeditor/materialeditorview.h | 4 +++ .../designercore/model/abstractview.cpp | 5 +++- .../designercore/include/nodemetainfo.h | 1 + 12 files changed, 83 insertions(+), 46 deletions(-) diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml index d93f236fede..54476c3be43 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml @@ -12,6 +12,8 @@ Item { readonly property int cellWidth: 100 readonly property int cellHeight: 120 + readonly property bool enableUiElements: materialBrowserModel.hasMaterialLibrary + && materialBrowserModel.hasQuick3DImport property var currMaterialItem: null @@ -54,16 +56,14 @@ Item { acceptedButtons: Qt.RightButton onClicked: (mouse) => { - if (materialBrowserModel.hasMaterialRoot || !materialBrowserModel.hasQuick3DImport) + if (!root.enableUiElements) return; var matsSecBottom = mapFromItem(materialsSection, 0, materialsSection.y).y + materialsSection.height; - if (!materialBrowserModel.hasMaterialRoot && materialBrowserModel.hasQuick3DImport - && mouse.y < matsSecBottom) { + if (mouse.y < matsSecBottom) ctxMenu.popupMenu() - } } } @@ -90,7 +90,7 @@ Item { Row { width: root.width - enabled: !materialBrowserModel.hasMaterialRoot && materialBrowserModel.hasQuick3DImport + enabled: root.enableUiElements StudioControls.SearchBox { id: searchBox @@ -105,10 +105,10 @@ Item { Text { text: { - if (materialBrowserModel.hasMaterialRoot) - qsTr("Material Browser is disabled inside a material component.") - else if (!materialBrowserModel.hasQuick3DImport) + if (!materialBrowserModel.hasQuick3DImport) qsTr("To use Material Browser, first add the QtQuick3D module in the Components view.") + else if (!materialBrowserModel.hasMaterialLibrary) + qsTr("Material Browser is disabled inside a non-visual component.") else "" } @@ -129,7 +129,7 @@ Item { width: root.width height: root.height - searchBox.height clip: true - visible: materialBrowserModel.hasQuick3DImport && !materialBrowserModel.hasMaterialRoot + visible: root.enableUiElements interactive: !ctxMenu.opened Column { @@ -187,7 +187,7 @@ Item { color: StudioTheme.Values.themeTextColor font.pixelSize: StudioTheme.Values.baseFontSize leftPadding: 10 - visible: materialBrowserModel.isEmpty && !searchBox.isEmpty() && !materialBrowserModel.hasMaterialRoot + visible: materialBrowserModel.isEmpty && !searchBox.isEmpty() && materialBrowserModel.hasMaterialLibrary } Text { @@ -213,7 +213,7 @@ Item { normalColor: "transparent" buttonSize: StudioTheme.Values.sectionHeadHeight onClicked: materialBrowserModel.addNewMaterial() - enabled: materialBrowserModel.hasQuick3DImport + enabled: root.enableUiElements } } @@ -254,7 +254,7 @@ Item { color: StudioTheme.Values.themeTextColor font.pixelSize: StudioTheme.Values.baseFontSize leftPadding: 10 - visible: materialBrowserModel.isEmpty && !searchBox.isEmpty() && !materialBrowserModel.hasMaterialRoot + visible: materialBrowserModel.isEmpty && !searchBox.isEmpty() && materialBrowserModel.hasMaterialLibrary } Text { @@ -280,7 +280,7 @@ Item { normalColor: "transparent" buttonSize: StudioTheme.Values.sectionHeadHeight onClicked: materialBrowserTexturesModel.addNewTexture() - enabled: materialBrowserModel.hasQuick3DImport + enabled: root.enableUiElements } } } diff --git a/share/qtcreator/qmldesigner/materialEditorQmlSources/EmptyMaterialEditorPane.qml b/share/qtcreator/qmldesigner/materialEditorQmlSources/EmptyMaterialEditorPane.qml index 5b9044ac4c2..304279b63af 100644 --- a/share/qtcreator/qmldesigner/materialEditorQmlSources/EmptyMaterialEditorPane.qml +++ b/share/qtcreator/qmldesigner/materialEditorQmlSources/EmptyMaterialEditorPane.qml @@ -32,8 +32,14 @@ PropertyEditorPane { height: 150 Text { - text: hasQuick3DImport ? qsTr("There are no materials in this project.
Select '+' to create one.") - : qsTr("To use Material Editor, first add the QtQuick3D module in the Components view.") + text: { + if (!hasQuick3DImport) + qsTr("To use Material Editor, first add the QtQuick3D module in the Components view.") + else if (!hasMaterialLibrary) + qsTr("Material Editor is disabled inside a non-visual component.") + else + qsTr("There are no materials in this project.
Select '+' to create one.") + } textFormat: Text.RichText color: StudioTheme.Values.themeTextColor font.pixelSize: StudioTheme.Values.mediumFontSize diff --git a/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorToolBar.qml b/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorToolBar.qml index 0a1b97f34bb..25edbf2d083 100644 --- a/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorToolBar.qml +++ b/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorToolBar.qml @@ -28,7 +28,7 @@ Rectangle { normalColor: StudioTheme.Values.themeSectionHeadBackground iconSize: StudioTheme.Values.bigIconFontSize buttonSize: root.height - enabled: hasMaterial && hasModelSelection && hasQuick3DImport && !hasMaterialRoot + enabled: hasMaterial && hasModelSelection && hasQuick3DImport && hasMaterialLibrary onClicked: root.toolBarAction(ToolBarAction.ApplyToSelected) tooltip: qsTr("Apply material to selected model.") } @@ -39,7 +39,7 @@ Rectangle { normalColor: StudioTheme.Values.themeSectionHeadBackground iconSize: StudioTheme.Values.bigIconFontSize buttonSize: root.height - enabled: hasQuick3DImport && !hasMaterialRoot + enabled: hasQuick3DImport && hasMaterialLibrary onClicked: root.toolBarAction(ToolBarAction.AddNewMaterial) tooltip: qsTr("Create new material.") } @@ -50,7 +50,7 @@ Rectangle { normalColor: StudioTheme.Values.themeSectionHeadBackground iconSize: StudioTheme.Values.bigIconFontSize buttonSize: root.height - enabled: hasMaterial && hasQuick3DImport && !hasMaterialRoot + enabled: hasMaterial && hasQuick3DImport && hasMaterialLibrary onClicked: root.toolBarAction(ToolBarAction.DeleteCurrentMaterial) tooltip: qsTr("Delete current material.") } @@ -61,7 +61,7 @@ Rectangle { normalColor: StudioTheme.Values.themeSectionHeadBackground iconSize: StudioTheme.Values.bigIconFontSize buttonSize: root.height - enabled: hasMaterial && hasQuick3DImport && !hasMaterialRoot + enabled: hasMaterial && hasQuick3DImport && hasMaterialLibrary onClicked: root.toolBarAction(ToolBarAction.OpenMaterialBrowser) tooltip: qsTr("Open material browser.") } diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp index ea2e1e47089..210b62d227e 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp @@ -171,18 +171,18 @@ void MaterialBrowserModel::setHasModelSelection(bool b) emit hasModelSelectionChanged(); } -bool MaterialBrowserModel::hasMaterialRoot() const +bool MaterialBrowserModel::hasMaterialLibrary() const { - return m_hasMaterialRoot; + return m_hasMaterialLibrary; } -void MaterialBrowserModel::setHasMaterialRoot(bool b) +void MaterialBrowserModel::setHasMaterialLibrary(bool b) { - if (m_hasMaterialRoot == b) + if (m_hasMaterialLibrary == b) return; - m_hasMaterialRoot = b; - emit hasMaterialRootChanged(); + m_hasMaterialLibrary = b; + emit hasMaterialLibraryChanged(); } QString MaterialBrowserModel::copiedMaterialType() const diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h index f850aaedb01..ca8c5cad74f 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h @@ -21,7 +21,7 @@ class MaterialBrowserModel : public QAbstractListModel Q_PROPERTY(int selectedIndex MEMBER m_selectedIndex NOTIFY selectedIndexChanged) Q_PROPERTY(bool hasQuick3DImport READ hasQuick3DImport WRITE setHasQuick3DImport NOTIFY hasQuick3DImportChanged) Q_PROPERTY(bool hasModelSelection READ hasModelSelection WRITE setHasModelSelection NOTIFY hasModelSelectionChanged) - Q_PROPERTY(bool hasMaterialRoot READ hasMaterialRoot WRITE setHasMaterialRoot NOTIFY hasMaterialRootChanged) + Q_PROPERTY(bool hasMaterialLibrary READ hasMaterialLibrary WRITE setHasMaterialLibrary NOTIFY hasMaterialLibraryChanged) Q_PROPERTY(QString copiedMaterialType READ copiedMaterialType WRITE setCopiedMaterialType NOTIFY copiedMaterialTypeChanged) Q_PROPERTY(QStringList defaultMaterialSections MEMBER m_defaultMaterialSections NOTIFY materialSectionsChanged) Q_PROPERTY(QStringList principledMaterialSections MEMBER m_principledMaterialSections NOTIFY materialSectionsChanged) @@ -44,8 +44,8 @@ public: bool hasModelSelection() const; void setHasModelSelection(bool b); - bool hasMaterialRoot() const; - void setHasMaterialRoot(bool b); + bool hasMaterialLibrary() const; + void setHasMaterialLibrary(bool b); QString copiedMaterialType() const; void setCopiedMaterialType(const QString &matType); @@ -87,7 +87,7 @@ signals: void isEmptyChanged(); void hasQuick3DImportChanged(); void hasModelSelectionChanged(); - void hasMaterialRootChanged(); + void hasMaterialLibraryChanged(); void copiedMaterialTypeChanged(); void materialSectionsChanged(); void selectedIndexChanged(int idx); @@ -119,7 +119,7 @@ private: bool m_isEmpty = true; bool m_hasQuick3DImport = false; bool m_hasModelSelection = false; - bool m_hasMaterialRoot = false; + bool m_hasMaterialLibrary = false; bool m_allPropsCopied = true; QString m_copiedMaterialType; }; diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp index 5af32bce600..635e9938154 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp @@ -164,8 +164,7 @@ void MaterialBrowserView::modelAttached(Model *model) AbstractView::modelAttached(model); m_widget->clearSearchFilter(); - m_widget->materialBrowserModel()->setHasMaterialRoot( - rootModelNode().metaInfo().isQtQuick3DMaterial()); + m_widget->materialBrowserModel()->setHasMaterialLibrary(false); m_hasQuick3DImport = model->hasImport("QtQuick3D"); // Project load is already very busy and may even trigger puppet reset, so let's wait a moment @@ -198,6 +197,7 @@ void MaterialBrowserView::refreshModel(bool updateImages) m_widget->clearSearchFilter(); m_widget->materialBrowserModel()->setMaterials(materials, m_hasQuick3DImport); m_widget->materialBrowserTexturesModel()->setTextures(textures); + m_widget->materialBrowserModel()->setHasMaterialLibrary(matLib.isValid()); if (updateImages) { for (const ModelNode &node : std::as_const(materials)) @@ -223,6 +223,7 @@ bool MaterialBrowserView::isTexture(const ModelNode &node) const void MaterialBrowserView::modelAboutToBeDetached(Model *model) { m_widget->materialBrowserModel()->setMaterials({}, m_hasQuick3DImport); + m_widget->materialBrowserModel()->setHasMaterialLibrary(false); m_widget->clearPreviewCache(); if (m_propertyGroupsLoaded) { @@ -291,6 +292,9 @@ void MaterialBrowserView::nodeReparented(const ModelNode &node, { Q_UNUSED(propertyChange) + if (node.id() == Constants::MATERIAL_LIB_ID) + m_widget->materialBrowserModel()->setHasMaterialLibrary(true); + if (!isMaterial(node) && !isTexture(node)) return; @@ -323,6 +327,7 @@ void MaterialBrowserView::nodeAboutToBeRemoved(const ModelNode &removedNode) // removing the material lib node if (removedNode.id() == Constants::MATERIAL_LIB_ID) { m_widget->materialBrowserModel()->setMaterials({}, m_hasQuick3DImport); + m_widget->materialBrowserModel()->setHasMaterialLibrary(false); m_widget->clearPreviewCache(); return; } diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.cpp index 57e6eab9e25..3f788e7f3fd 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.cpp +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.cpp @@ -227,18 +227,18 @@ void MaterialEditorContextObject::setHasQuick3DImport(bool b) emit hasQuick3DImportChanged(); } -bool MaterialEditorContextObject::hasMaterialRoot() const +bool MaterialEditorContextObject::hasMaterialLibrary() const { - return m_hasMaterialRoot; + return m_hasMaterialLibrary; } -void MaterialEditorContextObject::setHasMaterialRoot(bool b) +void MaterialEditorContextObject::setHasMaterialLibrary(bool b) { - if (b == m_hasMaterialRoot) + if (b == m_hasMaterialLibrary) return; - m_hasMaterialRoot = b; - emit hasMaterialRootChanged(); + m_hasMaterialLibrary = b; + emit hasMaterialLibraryChanged(); } bool MaterialEditorContextObject::hasModelSelection() const diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.h b/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.h index af155511338..39aeff83326 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.h +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.h @@ -38,7 +38,7 @@ class MaterialEditorContextObject : public QObject Q_PROPERTY(bool hasActiveTimeline READ hasActiveTimeline NOTIFY hasActiveTimelineChanged) Q_PROPERTY(bool hasQuick3DImport READ hasQuick3DImport WRITE setHasQuick3DImport NOTIFY hasQuick3DImportChanged) Q_PROPERTY(bool hasModelSelection READ hasModelSelection WRITE setHasModelSelection NOTIFY hasModelSelectionChanged) - Q_PROPERTY(bool hasMaterialRoot READ hasMaterialRoot WRITE setHasMaterialRoot NOTIFY hasMaterialRootChanged) + Q_PROPERTY(bool hasMaterialLibrary READ hasMaterialLibrary WRITE setHasMaterialLibrary NOTIFY hasMaterialLibraryChanged) Q_PROPERTY(QQmlPropertyMap *backendValues READ backendValues WRITE setBackendValues NOTIFY backendValuesChanged) @@ -93,8 +93,8 @@ public: bool hasQuick3DImport() const; void setHasQuick3DImport(bool b); - bool hasMaterialRoot() const; - void setHasMaterialRoot(bool b); + bool hasMaterialLibrary() const; + void setHasMaterialLibrary(bool b); bool hasModelSelection() const; void setHasModelSelection(bool b); @@ -132,7 +132,7 @@ signals: void hasAliasExportChanged(); void hasActiveTimelineChanged(); void hasQuick3DImportChanged(); - void hasMaterialRootChanged(); + void hasMaterialLibraryChanged(); void hasModelSelectionChanged(); private: @@ -161,7 +161,7 @@ private: bool m_aliasExport = false; bool m_hasActiveTimeline = false; bool m_hasQuick3DImport = false; - bool m_hasMaterialRoot = false; + bool m_hasMaterialLibrary = false; bool m_hasModelSelection = false; ModelNode m_selectedMaterial; diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp index 379ccc411ac..e93f99cb7d2 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp @@ -61,6 +61,8 @@ MaterialEditorView::MaterialEditorView(ExternalDependenciesInterface &externalDe if (model() && model()->rewriterView() && !model()->rewriterView()->hasIncompleteTypeInformation() && model()->rewriterView()->errors().isEmpty()) { ensureMaterialLibraryNode(); + if (m_qmlBackEnd && m_qmlBackEnd->contextObject()) + m_qmlBackEnd->contextObject()->setHasMaterialLibrary(materialLibraryNode().isValid()); m_ensureMatLibTimer.stop(); } }); @@ -534,7 +536,7 @@ void MaterialEditorView::setupQmlBackend() QString specificQmlData; QString currentTypeName; - if (m_selectedMaterial.isValid() && m_hasQuick3DImport) { + if (m_selectedMaterial.isValid() && m_hasQuick3DImport && (materialLibraryNode().isValid() || m_hasMaterialRoot)) { qmlPaneUrl = QUrl::fromLocalFile(materialEditorResourcesPath() + "/MaterialEditorPane.qml"); TypeName diffClassName; @@ -587,7 +589,7 @@ void MaterialEditorView::setupQmlBackend() currentQmlBackend->widget()->installEventFilter(this); currentQmlBackend->contextObject()->setHasQuick3DImport(m_hasQuick3DImport); - currentQmlBackend->contextObject()->setHasMaterialRoot(m_hasMaterialRoot); + currentQmlBackend->contextObject()->setHasMaterialLibrary(materialLibraryNode().isValid()); currentQmlBackend->contextObject()->setSpecificQmlData(specificQmlData); currentQmlBackend->contextObject()->setCurrentType(currentTypeName); @@ -781,6 +783,7 @@ void MaterialEditorView::modelAboutToBeDetached(Model *model) AbstractView::modelAboutToBeDetached(model); m_dynamicPropertiesModel->reset(); m_qmlBackEnd->materialEditorTransaction()->end(); + m_qmlBackEnd->contextObject()->setHasMaterialLibrary(false); } void MaterialEditorView::propertiesRemoved(const QList &propertyList) @@ -1103,6 +1106,21 @@ void MaterialEditorView::customNotification([[maybe_unused]] const AbstractView } } +void MaterialEditorView::nodeReparented(const ModelNode &node, + const NodeAbstractProperty &newPropertyParent, + const NodeAbstractProperty &oldPropertyParent, + PropertyChangeFlags propertyChange) +{ + if (node.id() == Constants::MATERIAL_LIB_ID && m_qmlBackEnd && m_qmlBackEnd->contextObject()) + m_qmlBackEnd->contextObject()->setHasMaterialLibrary(true); +} + +void MaterialEditorView::nodeAboutToBeRemoved(const ModelNode &removedNode) +{ + if (removedNode.id() == Constants::MATERIAL_LIB_ID && m_qmlBackEnd && m_qmlBackEnd->contextObject()) + m_qmlBackEnd->contextObject()->setHasMaterialLibrary(false); +} + void QmlDesigner::MaterialEditorView::highlightSupportedProperties(bool highlight) { if (!m_selectedMaterial.isValid()) diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.h b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.h index 5c0a59d3a0e..c0880c738bc 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.h +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.h @@ -62,6 +62,10 @@ public: void importsChanged(const QList &addedImports, const QList &removedImports) override; void customNotification(const AbstractView *view, const QString &identifier, const QList &nodeList, const QList &data) override; + void nodeReparented(const ModelNode &node, const NodeAbstractProperty &newPropertyParent, + const NodeAbstractProperty &oldPropertyParent, + AbstractView::PropertyChangeFlags propertyChange) override; + void nodeAboutToBeRemoved(const ModelNode &removedNode) override; void dragStarted(QMimeData *mimeData) override; void dragEnded() override; diff --git a/src/plugins/qmldesigner/designercore/model/abstractview.cpp b/src/plugins/qmldesigner/designercore/model/abstractview.cpp index 8f937f97958..fe5d5a18ca8 100644 --- a/src/plugins/qmldesigner/designercore/model/abstractview.cpp +++ b/src/plugins/qmldesigner/designercore/model/abstractview.cpp @@ -806,8 +806,11 @@ void AbstractView::changeRootNodeType(const TypeName &type, int majorVersion, in void AbstractView::ensureMaterialLibraryNode() { ModelNode matLib = modelNodeForId(Constants::MATERIAL_LIB_ID); - if (matLib.isValid() || rootModelNode().metaInfo().isQtQuick3DMaterial()) + if (matLib.isValid() + || (!rootModelNode().metaInfo().isQtQuick3DNode() + && !rootModelNode().metaInfo().isQtQuickItem())) { return; + } executeInTransaction(__FUNCTION__, [&] { // Create material library node diff --git a/tests/unit/mockup/qmldesigner/designercore/include/nodemetainfo.h b/tests/unit/mockup/qmldesigner/designercore/include/nodemetainfo.h index 9a350961032..f898f752152 100644 --- a/tests/unit/mockup/qmldesigner/designercore/include/nodemetainfo.h +++ b/tests/unit/mockup/qmldesigner/designercore/include/nodemetainfo.h @@ -76,6 +76,7 @@ public: bool isQtQuick3DModel() const { return {}; } bool isQtQuick3DMaterial() const { return {}; } bool isQtQuickLoader() const { return {}; } + bool isQtQuickItem() const { return {}; } QString importDirectoryPath() const { return {}; } From ac1af9a58211e6efc2b265c79ff192dc03e16ec2 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Thu, 17 Nov 2022 15:46:11 +0200 Subject: [PATCH 019/131] Implement texture context menu Apply to selected model/material, delete, duplicate, and create new options are available in the menu. Fixes: QDS-8342 Change-Id: Ib9bdc1738500a87361000bcd3e89403e3b8ccef8 Reviewed-by: Mahmoud Badri --- .../ChooseMaterialProperty.qml | 2 +- .../MaterialBrowser.qml | 11 +- .../TextureBrowserContextMenu.qml | 55 +++++++ .../components/edit3d/edit3dview.cpp | 105 +------------ .../components/edit3d/edit3dview.h | 12 +- .../materialbrowser/materialbrowsermodel.cpp | 7 + .../materialbrowser/materialbrowsermodel.h | 3 +- .../materialbrowsertexturesmodel.cpp | 24 ++- .../materialbrowsertexturesmodel.h | 7 +- .../materialbrowser/materialbrowserview.cpp | 143 +++++++++++++++++- .../materialbrowser/materialbrowserview.h | 17 +++ .../textureeditor/textureeditorview.cpp | 2 +- 12 files changed, 264 insertions(+), 124 deletions(-) create mode 100644 share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureBrowserContextMenu.qml diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/ChooseMaterialProperty.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/ChooseMaterialProperty.qml index d9135d1d39d..189b211707b 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/ChooseMaterialProperty.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/ChooseMaterialProperty.qml @@ -145,7 +145,7 @@ Rectangle { let matId = materialsListView.currentItem.id() let prop = propertiesListView.currentItem.propName() - rootView.applyTextureToMaterial(matId, prop) + rootView.applyTextureToProperty(matId, prop) } } } diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml index 54476c3be43..8b1acabe9e9 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml @@ -21,6 +21,7 @@ Item { function closeContextMenu() { ctxMenu.close() + ctxMenuTextures.close() } // Called from C++ to refresh a preview material after it changes @@ -64,6 +65,8 @@ Item { if (mouse.y < matsSecBottom) ctxMenu.popupMenu() + else + ctxMenuTextures.popupMenu() } } @@ -83,6 +86,10 @@ Item { id: ctxMenu } + TextureBrowserContextMenu { + id: ctxMenuTextures + } + Column { id: col y: 5 @@ -130,7 +137,7 @@ Item { height: root.height - searchBox.height clip: true visible: root.enableUiElements - interactive: !ctxMenu.opened + interactive: !ctxMenu.opened && !ctxMenuTextures.opened Column { Item { @@ -243,7 +250,7 @@ Item { height: root.cellWidth onShowContextMenu: { - // ctxMenuTexture.popupMenu(this, model) // TODO: implement textures context menu + ctxMenuTextures.popupMenu(model) } } } diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureBrowserContextMenu.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureBrowserContextMenu.qml new file mode 100644 index 00000000000..5081356b29f --- /dev/null +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureBrowserContextMenu.qml @@ -0,0 +1,55 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +import QtQuick +import HelperWidgets +import StudioControls as StudioControls +import StudioTheme as StudioTheme + +StudioControls.Menu { + id: root + + property var targetTexture: null + property int copiedTextureInternalId: -1 + + function popupMenu(targetTexture = null) + { + this.targetTexture = targetTexture + popup() + } + + closePolicy: StudioControls.Menu.CloseOnEscape | StudioControls.Menu.CloseOnPressOutside + + StudioControls.MenuItem { + text: qsTr("Apply to selected model") + enabled: root.targetTexture && materialBrowserTexturesModel.hasSingleModelSelection + onTriggered: materialBrowserTexturesModel.applyToSelectedModel(root.targetTexture.textureInternalId) + } + + StudioControls.MenuItem { + text: qsTr("Apply to selected material") + enabled: root.targetTexture && materialBrowserModel.selectedIndex >= 0 + onTriggered: materialBrowserTexturesModel.applyToSelectedMaterial(root.targetTexture.textureInternalId) + } + + StudioControls.MenuSeparator {} + + StudioControls.MenuItem { + text: qsTr("Duplicate") + enabled: root.targetTexture + onTriggered: materialBrowserTexturesModel.duplicateTexture(materialBrowserTexturesModel.selectedIndex) + } + + StudioControls.MenuItem { + text: qsTr("Delete") + enabled: root.targetTexture + onTriggered: materialBrowserTexturesModel.deleteTexture(materialBrowserTexturesModel.selectedIndex) + } + + StudioControls.MenuSeparator {} + + StudioControls.MenuItem { + text: qsTr("Create New Texture") + onTriggered: materialBrowserTexturesModel.addNewTexture() + } +} diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp index b41977e9cdc..d8c2f7678e1 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp @@ -28,9 +28,6 @@ #include #include -#include -#include -#include #include namespace QmlDesigner { @@ -274,26 +271,6 @@ void Edit3DView::customNotification([[maybe_unused]] const AbstractView *view, { if (identifier == "asset_import_update") resetPuppet(); - else if (identifier == "apply_texture_to_model3D") - applyTextureToModel3D(nodeList.at(0), nodeList.at(1)); -} - -bool Edit3DView::eventFilter(QObject *obj, QEvent *event) -{ - if (event->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast(event); - if (keyEvent->key() == Qt::Key_Escape) { - if (obj == m_chooseMatPropsView) - m_chooseMatPropsView->close(); - } - } else if (event->type() == QEvent::Close) { - if (obj == m_chooseMatPropsView) { - m_droppedModelNode = {}; - m_chooseMatPropsView->deleteLater(); - } - } - - return AbstractView::eventFilter(obj, event); } /** @@ -322,65 +299,13 @@ void Edit3DView::nodeAtPosReady(const ModelNode &modelNode, const QVector3D &pos } else if (m_nodeAtPosReqType == NodeAtPosReqType::BundleMaterialDrop) { emitCustomNotification("drop_bundle_material", {modelNode}); // To ContentLibraryView } else if (m_nodeAtPosReqType == NodeAtPosReqType::TextureDrop) { - applyTextureToModel3D(modelNode, m_droppedModelNode); + emitCustomNotification("apply_texture_to_model3D", {modelNode, m_droppedModelNode}); } - if (m_nodeAtPosReqType != NodeAtPosReqType::TextureDrop) - m_droppedModelNode = {}; + m_droppedModelNode = {}; m_nodeAtPosReqType = NodeAtPosReqType::None; } -void Edit3DView::applyTextureToModel3D(const ModelNode &model3D, const ModelNode &texture) -{ - if (!texture.isValid() || !model3D.isValid() || !model3D.metaInfo().isQtQuick3DModel()) - return; - - m_droppedModelNode = texture; - - // get model's material list - BindingProperty matsProp = model3D.bindingProperty("materials"); - QList materials; - if (hasId(matsProp.expression())) - materials.append(modelNodeForId(matsProp.expression())); - else - materials = matsProp.resolveToModelNodeList(); - - if (materials.size() > 0) { - m_textureModels.clear(); - QStringList materialsModel; - for (const ModelNode &mat : std::as_const(materials)) { - QString matName = mat.variantProperty("objectName").value().toString(); - materialsModel.append(QLatin1String("%1 (%2)").arg(matName, mat.id())); - QList texProps; - for (const PropertyMetaInfo &p : mat.metaInfo().properties()) { - if (p.propertyType().isQtQuick3DTexture()) - texProps.append(p.name()); - } - m_textureModels.insert(mat.id(), texProps); - } - - QString path = MaterialBrowserWidget::qmlSourcesPath() + "/ChooseMaterialProperty.qml"; - - m_chooseMatPropsView = new QQuickView; - m_chooseMatPropsView->setTitle(tr("Select a material property")); - m_chooseMatPropsView->setResizeMode(QQuickView::SizeRootObjectToView); - m_chooseMatPropsView->setMinimumSize({150, 100}); - m_chooseMatPropsView->setMaximumSize({600, 400}); - m_chooseMatPropsView->setWidth(450); - m_chooseMatPropsView->setHeight(300); - m_chooseMatPropsView->setFlags(Qt::Widget); - m_chooseMatPropsView->setModality(Qt::ApplicationModal); - m_chooseMatPropsView->engine()->addImportPath(propertyEditorResourcesPath() + "/imports"); - m_chooseMatPropsView->rootContext()->setContextProperties({ - {"rootView", QVariant::fromValue(this)}, - {"materialsModel", QVariant::fromValue(materialsModel)}, - {"propertiesModel", QVariant::fromValue(m_textureModels.value(materials.at(0).id()))}, - }); - m_chooseMatPropsView->setSource(QUrl::fromLocalFile(path)); - m_chooseMatPropsView->installEventFilter(this); - m_chooseMatPropsView->show(); - } -} void Edit3DView::sendInputEvent(QInputEvent *e) const { if (nodeInstanceView()) @@ -950,30 +875,4 @@ void Edit3DView::dropTexture(const ModelNode &textureNode, const QPointF &pos) emitView3DAction(View3DActionType::GetNodeAtPos, pos); } -void Edit3DView::updatePropsModel(const QString &matId) -{ - m_chooseMatPropsView->rootContext()->setContextProperty("propertiesModel", - QVariant::fromValue(m_textureModels.value(matId))); -} - -void Edit3DView::applyTextureToMaterial(const QString &matId, const QString &propName) -{ - QTC_ASSERT(m_droppedModelNode.isValid(), return); - - ModelNode mat = modelNodeForId(matId); - QTC_ASSERT(mat.isValid(), return); - - BindingProperty texProp = mat.bindingProperty(propName.toLatin1()); - QTC_ASSERT(texProp.isValid(), return); - - texProp.setExpression(m_droppedModelNode.id()); - - closeChooseMatPropsView(); -} - -void Edit3DView::closeChooseMatPropsView() -{ - m_chooseMatPropsView->close(); -} - } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.h b/src/plugins/qmldesigner/components/edit3d/edit3dview.h index 2f542c0e714..a14399e1c70 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.h +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.h @@ -17,7 +17,6 @@ QT_BEGIN_NAMESPACE class QAction; class QInputEvent; -class QQuickView; QT_END_NAMESPACE namespace QmlDesigner { @@ -65,14 +64,6 @@ public: void dropMaterial(const ModelNode &matNode, const QPointF &pos); void dropBundleMaterial(const QPointF &pos); void dropTexture(const ModelNode &textureNode, const QPointF &pos); - void applyTextureToModel3D(const ModelNode &model3D, const ModelNode &texture); - - Q_INVOKABLE void updatePropsModel(const QString &matId); - Q_INVOKABLE void applyTextureToMaterial(const QString &matId, const QString &propName); - Q_INVOKABLE void closeChooseMatPropsView(); - -protected: - bool eventFilter(QObject *obj, QEvent *event) override; private slots: void onEntriesChanged(); @@ -89,6 +80,7 @@ private: void createEdit3DWidget(); void checkImports(); void handleEntriesChanged(); + void showMaterialPropertiesView(); Edit3DAction *createSelectBackgroundColorAction(QAction *syncBackgroundColorAction); Edit3DAction *createGridColorSelectionAction(); @@ -128,8 +120,6 @@ private: NodeAtPosReqType m_nodeAtPosReqType; QPoint m_contextMenuPos; QTimer m_compressionTimer; - QPointer m_chooseMatPropsView; - QHash> m_textureModels; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp index 210b62d227e..2250dacf80f 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp @@ -315,6 +315,13 @@ ModelNode MaterialBrowserModel::materialAt(int idx) const return {}; } +ModelNode MaterialBrowserModel::selectedMaterial() const +{ + if (isValidIndex(m_selectedIndex)) + return m_materialList[m_selectedIndex]; + return {}; +} + void MaterialBrowserModel::resetModel() { beginResetModel(); diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h index ca8c5cad74f..d836bc0070a 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h @@ -58,6 +58,7 @@ public: void updateSelectedMaterial(); int materialIndex(const ModelNode &material) const; ModelNode materialAt(int idx) const; + ModelNode selectedMaterial() const; bool loadPropertyGroups(const QString &path); void unloadPropertyGroups(); @@ -115,7 +116,7 @@ private: QHash m_materialIndexHash; // internalId -> index QJsonObject m_propertyGroupsObj; - int m_selectedIndex = 0; + int m_selectedIndex = -1; bool m_isEmpty = true; bool m_hasQuick3DImport = false; bool m_hasModelSelection = false; diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp index 66762114322..b7851157ee4 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp @@ -44,6 +44,9 @@ QVariant MaterialBrowserTexturesModel::data(const QModelIndex &index, int role) if (roleName == "hasDynamicProperties") return !m_textureList.at(index.row()).dynamicProperties().isEmpty(); + if (roleName == "textureInternalId") + return m_textureList.at(index.row()).internalId(); + return {}; } @@ -67,7 +70,8 @@ QHash MaterialBrowserTexturesModel::roleNames() const static const QHash roles { {Qt::UserRole + 1, "textureSource"}, {Qt::UserRole + 2, "textureVisible"}, - {Qt::UserRole + 3, "hasDynamicProperties"} + {Qt::UserRole + 3, "hasDynamicProperties"}, + {Qt::UserRole + 4, "textureInternalId"} }; return roles; } @@ -242,4 +246,22 @@ void MaterialBrowserTexturesModel::deleteTexture(int idx) } } +void MaterialBrowserTexturesModel::applyToSelectedMaterial(qint64 internalId) +{ + int idx = m_textureIndexHash.value(internalId); + if (idx != -1) { + ModelNode tex = m_textureList.at(idx); + emit applyToSelectedMaterialTriggered(tex); + } +} + +void MaterialBrowserTexturesModel::applyToSelectedModel(qint64 internalId) +{ + int idx = m_textureIndexHash.value(internalId); + if (idx != -1) { + ModelNode tex = m_textureList.at(idx); + emit applyToSelectedModelTriggered(tex); + } +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h index 3d02884cae3..3f5a59ea54f 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h @@ -47,13 +47,16 @@ public: Q_INVOKABLE void addNewTexture(); Q_INVOKABLE void duplicateTexture(int idx); Q_INVOKABLE void deleteTexture(int idx); + Q_INVOKABLE void applyToSelectedMaterial(qint64 internalId); + Q_INVOKABLE void applyToSelectedModel(qint64 internalId); signals: void isEmptyChanged(); void hasSingleModelSelectionChanged(); - void materialSectionsChanged(); void selectedIndexChanged(int idx); - void duplicateTextureTriggered(const QmlDesigner::ModelNode &material); + void duplicateTextureTriggered(const QmlDesigner::ModelNode &texture); + void applyToSelectedMaterialTriggered(const QmlDesigner::ModelNode &texture); + void applyToSelectedModelTriggered(const QmlDesigner::ModelNode &texture); void addNewTextureTriggered(); private: diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp index 635e9938154..5599cd585f9 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp @@ -12,20 +12,35 @@ #include "qmlobjectnode.h" #include "variantproperty.h" -#include #include #include #include #include #include -#include +#include + +#include +#include + +#include +#include #include +#include #include #include namespace QmlDesigner { +static QString propertyEditorResourcesPath() +{ +#ifdef SHARE_QML_PATH + if (qEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE")) + return QLatin1String(SHARE_QML_PATH) + "/propertyEditorQmlSources"; +#endif + return Core::ICore::resourcePath("qmldesigner/propertyEditorQmlSources").toString(); +} + MaterialBrowserView::MaterialBrowserView(ExternalDependenciesInterface &externalDependencies) : AbstractView{externalDependencies} { @@ -146,6 +161,25 @@ WidgetInfo MaterialBrowserView::widgetInfo() ModelNode texNode = m_widget->materialBrowserTexturesModel()->textureAt(idx); emitCustomNotification("selected_texture_changed", {texNode}, {}); }); + connect(texturesModel, &MaterialBrowserTexturesModel::duplicateTextureTriggered, this, + [&] (const ModelNode &texture) { + emitCustomNotification("duplicate_texture", {texture}); + }); + + connect(texturesModel, &MaterialBrowserTexturesModel::applyToSelectedMaterialTriggered, this, + [&] (const ModelNode &texture) { + if (!m_widget) + return; + const ModelNode material = m_widget->materialBrowserModel()->selectedMaterial(); + applyTextureToMaterial({material}, texture); + }); + + connect(texturesModel, &MaterialBrowserTexturesModel::applyToSelectedModelTriggered, this, + [&] (const ModelNode &texture) { + if (m_selectedModels.size() != 1) + return; + applyTextureToModel3D(m_selectedModels[0], texture); + }); connect(texturesModel, &MaterialBrowserTexturesModel::addNewTextureTriggered, this, [&] { emitCustomNotification("add_new_texture"); @@ -406,6 +440,10 @@ void MaterialBrowserView::customNotification(const AbstractView *view, }); } else if (identifier == "delete_selected_material") { m_widget->materialBrowserModel()->deleteSelectedMaterial(); + } else if (identifier == "apply_texture_to_model3D") { + applyTextureToModel3D(nodeList.at(0), nodeList.at(1)); + } else if (identifier == "apply_texture_to_material") { + applyTextureToMaterial({nodeList.at(0)}, nodeList.at(1)); } } @@ -443,4 +481,105 @@ void MaterialBrowserView::instancePropertyChanged(const QList materials; + if (hasId(matsProp.expression())) + materials.append(modelNodeForId(matsProp.expression())); + else + materials = matsProp.resolveToModelNodeList(); + + applyTextureToMaterial(materials, texture); +} + +void MaterialBrowserView::applyTextureToMaterial(const QList &materials, + const ModelNode &texture) +{ + if (materials.size() > 0) { + m_appliedTextureId = texture.id(); + m_textureModels.clear(); + QStringList materialsModel; + for (const ModelNode &mat : std::as_const(materials)) { + QString matName = mat.variantProperty("objectName").value().toString(); + materialsModel.append(QLatin1String("%1 (%2)").arg(matName, mat.id())); + QList texProps; + for (const PropertyMetaInfo &p : mat.metaInfo().properties()) { + if (p.propertyType().isQtQuick3DTexture()) + texProps.append(p.name()); + } + m_textureModels.insert(mat.id(), texProps); + } + + QString path = MaterialBrowserWidget::qmlSourcesPath() + "/ChooseMaterialProperty.qml"; + + m_chooseMatPropsView = new QQuickView; + m_chooseMatPropsView->setTitle(tr("Select a material property")); + m_chooseMatPropsView->setResizeMode(QQuickView::SizeRootObjectToView); + m_chooseMatPropsView->setMinimumSize({150, 100}); + m_chooseMatPropsView->setMaximumSize({600, 400}); + m_chooseMatPropsView->setWidth(450); + m_chooseMatPropsView->setHeight(300); + m_chooseMatPropsView->setFlags(Qt::Widget); + m_chooseMatPropsView->setModality(Qt::ApplicationModal); + m_chooseMatPropsView->engine()->addImportPath(propertyEditorResourcesPath() + "/imports"); + m_chooseMatPropsView->rootContext()->setContextProperties({ + {"rootView", QVariant::fromValue(this)}, + {"materialsModel", QVariant::fromValue(materialsModel)}, + {"propertiesModel", QVariant::fromValue(m_textureModels.value(materials.at(0).id()))}, + }); + m_chooseMatPropsView->setSource(QUrl::fromLocalFile(path)); + m_chooseMatPropsView->installEventFilter(this); + m_chooseMatPropsView->show(); + } +} + +void MaterialBrowserView::updatePropsModel(const QString &matId) +{ + m_chooseMatPropsView->rootContext()->setContextProperty("propertiesModel", + QVariant::fromValue(m_textureModels.value(matId))); +} + +void MaterialBrowserView::applyTextureToProperty(const QString &matId, const QString &propName) +{ + QTC_ASSERT(!m_appliedTextureId.isEmpty(), return); + + ModelNode mat = modelNodeForId(matId); + QTC_ASSERT(mat.isValid(), return); + + BindingProperty texProp = mat.bindingProperty(propName.toLatin1()); + QTC_ASSERT(texProp.isValid(), return); + + QmlObjectNode qmlObjNode(mat); + qmlObjNode.setBindingProperty(propName.toLatin1(), m_appliedTextureId); + + closeChooseMatPropsView(); +} + +void MaterialBrowserView::closeChooseMatPropsView() +{ + m_chooseMatPropsView->close(); +} + +bool MaterialBrowserView::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::KeyPress) { + QKeyEvent *keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Escape) { + if (obj == m_chooseMatPropsView) + m_chooseMatPropsView->close(); + } + } else if (event->type() == QEvent::Close) { + if (obj == m_chooseMatPropsView) { + m_appliedTextureId.clear(); + m_chooseMatPropsView->deleteLater(); + } + } + + return AbstractView::eventFilter(obj, event); +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h index f59cb2dbbcd..1ba219ed2a3 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h @@ -9,6 +9,10 @@ #include #include +QT_BEGIN_NAMESPACE +class QQuickView; +QT_END_NAMESPACE + namespace QmlDesigner { class MaterialBrowserWidget; @@ -43,6 +47,16 @@ public: void instancesCompleted(const QVector &completedNodeList) override; void instancePropertyChanged(const QList > &propertyList) override; + void applyTextureToModel3D(const ModelNode &model3D, const ModelNode &texture); + void applyTextureToMaterial(const QList &materials, const ModelNode &texture); + + Q_INVOKABLE void updatePropsModel(const QString &matId); + Q_INVOKABLE void applyTextureToProperty(const QString &matId, const QString &propName); + Q_INVOKABLE void closeChooseMatPropsView(); + +protected: + bool eventFilter(QObject *obj, QEvent *event) override; + private: void refreshModel(bool updateImages); bool isMaterial(const ModelNode &node) const; @@ -60,6 +74,9 @@ private: QTimer m_previewTimer; QSet m_previewRequests; + QPointer m_chooseMatPropsView; + QHash> m_textureModels; + QString m_appliedTextureId; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp b/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp index a753c1d03a6..4464818de7d 100644 --- a/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp @@ -787,7 +787,7 @@ void TextureEditorView::customNotification([[maybe_unused]] const AbstractView * m_dynamicPropertiesModel->setSelectedNode(m_selectedTexture); QTimer::singleShot(0, this, &TextureEditorView::resetView); } - } else if (identifier == "apply_texture_to_selected_triggered") { + } else if (identifier == "apply_texture_to_selected_model") { applyTextureToSelectedModel(nodeList.first()); } else if (identifier == "add_new_texture") { handleToolBarAction(TextureEditorContextObject::AddNewTexture); From c960d75b33a55f42431590c81fdf85fa960a1756 Mon Sep 17 00:00:00 2001 From: Pranta Dastider Date: Mon, 21 Nov 2022 14:47:26 +0100 Subject: [PATCH 020/131] QmlDesigner: Update Property binding instructions The property binidng instruction was a little outdated hence, had some mismatch with the current procedure. This patch updates it to the correct state removing the irrelevant text. Also, put the texts in more structured format for better readability. Fixes: QDS-8261 Change-Id: I809af7ef6bd33c25f571a81f2c4f1ff294139c25 Reviewed-by: Leena Miettinen Reviewed-by: Thomas Hartmann --- .../src/components/qtquick-positioning.qdoc | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/doc/qtdesignstudio/src/components/qtquick-positioning.qdoc b/doc/qtdesignstudio/src/components/qtquick-positioning.qdoc index 54857d91222..5ea37b040c0 100644 --- a/doc/qtdesignstudio/src/components/qtquick-positioning.qdoc +++ b/doc/qtdesignstudio/src/components/qtquick-positioning.qdoc @@ -44,23 +44,19 @@ Property bindings are created implicitly whenever a property is assigned a JavaScript expression. To set JavaScript expressions as values of properties - in the \l Properties view, select the \inlineimage icons/action-icon.png - (\uicontrol Actions) menu next to a property, and then select - \uicontrol {Set Binding}. + in the \l Properties view: + \list 1 + \li Select the \inlineimage icons/action-icon.png + (\uicontrol Actions) menu next to a property, and then select + \uicontrol {Set Binding}. - \image qmldesigner-set-expression.png "Actions menu" + \image qmldesigner-set-expression.png "Actions menu" - In \uicontrol {Binding Editor}, select a component and a property from - lists of available components and their properties. + \li In \uicontrol {Binding Editor}, select a component and a property from + lists of available components and their properties. - \image qmldesigner-binding-editor.png "Binding Editor" - - Alternatively, start typing a - string and press \key Ctrl+Space to display a list of properties, IDs, and - code snippets. When you enter a period (.) after a property name, a list of - available values is displayed. Press \key Enter to accept the first - suggestion in the list and to complete the code. For more information, see - \l{Completing Code}. + \image qmldesigner-binding-editor.png "Binding Editor" + \endlist When a binding is set, the \uicontrol Actions menu icon changes to \inlineimage icons/action-icon-binding.png From 83fa44afd4588f0539d5a25f3205a732883b6d3d Mon Sep 17 00:00:00 2001 From: Tim Jenssen Date: Tue, 22 Nov 2022 13:16:10 +0100 Subject: [PATCH 021/131] qml2puppet: qt5 puppet fixes Change-Id: I2fd809c6646944b06b497f0587178e2dcd92c4a1 Reviewed-by: Thomas Hartmann --- .../qml2puppet/iconrenderer/iconrenderer.cpp | 4 ++-- .../instances/qt5nodeinstanceclientproxy.cpp | 6 +++++- .../qmlprivategate/qmlprivategate.cpp | 18 +++++++++++------- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/tools/qml2puppet/qml2puppet/iconrenderer/iconrenderer.cpp b/src/tools/qml2puppet/qml2puppet/iconrenderer/iconrenderer.cpp index 453fa24473e..f672f4e2f55 100644 --- a/src/tools/qml2puppet/qml2puppet/iconrenderer/iconrenderer.cpp +++ b/src/tools/qml2puppet/qml2puppet/iconrenderer/iconrenderer.cpp @@ -129,7 +129,7 @@ void IconRenderer::setupRender() void IconRenderer::startCreateIcon() { #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) - m_designerSupport.refFromEffectItem(m_containerItem, false); + m_designerSupport->refFromEffectItem(m_containerItem, false); #endif QQuickDesignerSupportItems::disableNativeTextRendering(m_containerItem); @@ -202,7 +202,7 @@ void IconRenderer::render(const QString &fileName) QRect rect(QPoint(), m_contentItem->size().toSize()); QImage renderImage; #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) - renderImage = m_designerSupport.renderImageForItem(m_containerItem, rect, rect.size()); + renderImage = m_designerSupport->renderImageForItem(m_containerItem, rect, rect.size()); #else m_renderControl->polishItems(); m_renderControl->beginFrame(); diff --git a/src/tools/qml2puppet/qml2puppet/instances/qt5nodeinstanceclientproxy.cpp b/src/tools/qml2puppet/qml2puppet/instances/qt5nodeinstanceclientproxy.cpp index bc0f7821620..e1029b3dda0 100644 --- a/src/tools/qml2puppet/qml2puppet/instances/qt5nodeinstanceclientproxy.cpp +++ b/src/tools/qml2puppet/qml2puppet/instances/qt5nodeinstanceclientproxy.cpp @@ -14,6 +14,10 @@ #include "qt5testnodeinstanceserver.h" #include "quickitemnodeinstance.h" +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + #include +#endif + #if defined(Q_OS_UNIX) #include #elif defined(Q_OS_WIN) @@ -41,7 +45,7 @@ Qt5NodeInstanceClientProxy::Qt5NodeInstanceClientProxy(QObject *parent) : Internal::QuickItemNodeInstance::enableUnifiedRenderPath(true); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) else - DesignerSupport::activateDesignerWindowManager(); + QQuickDesignerSupport::activateDesignerWindowManager(); #endif if (QCoreApplication::arguments().at(1) == QLatin1String("--readcapturedstream")) { diff --git a/src/tools/qml2puppet/qmlprivategate/qmlprivategate.cpp b/src/tools/qml2puppet/qmlprivategate/qmlprivategate.cpp index 5d36ff8755b..b3e9af82ddd 100644 --- a/src/tools/qml2puppet/qmlprivategate/qmlprivategate.cpp +++ b/src/tools/qml2puppet/qmlprivategate/qmlprivategate.cpp @@ -41,6 +41,10 @@ #include #endif +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + #include +#endif + namespace QmlDesigner { namespace Internal { @@ -52,10 +56,10 @@ bool isPropertyBlackListed(const QmlDesigner::PropertyName &propertyName) return QQuickDesignerSupportProperties::isPropertyBlackListed(propertyName); } -#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) && (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) static void addToPropertyNameListIfNotBlackListed( - PropertyNameList *propertyNameList, const QQuickQQuickDesignerSupport::PropertyName &propertyName) + PropertyNameList *propertyNameList, const QQuickDesignerSupport::PropertyName &propertyName) { if (!QQuickDesignerSupportProperties::isPropertyBlackListed(propertyName)) propertyNameList->append(propertyName); @@ -66,7 +70,7 @@ PropertyNameList allPropertyNamesInline(QObject *object, QObjectList *inspectedObjects = nullptr, int depth = 0) { - QQuickQQuickDesignerSupport::PropertyNameList propertyNameList; + QQuickDesignerSupport::PropertyNameList propertyNameList; QObjectList localObjectList; @@ -100,7 +104,7 @@ PropertyNameList allPropertyNamesInline(QObject *object, propertyNameList.append( allPropertyNamesInline(childObject, baseName - + QQuickQQuickDesignerSupport::PropertyName( + + QQuickDesignerSupport::PropertyName( metaProperty.name()) + '.', inspectedObjects, @@ -110,18 +114,18 @@ PropertyNameList allPropertyNamesInline(QObject *object, = QQmlGadgetPtrWrapper::instance(qmlEngine(object), metaProperty.userType())) { valueType->setValue(metaProperty.read(object)); propertyNameList.append(baseName - + QQuickQQuickDesignerSupport::PropertyName(metaProperty.name())); + + QQuickDesignerSupport::PropertyName(metaProperty.name())); propertyNameList.append( allPropertyNamesInline(valueType, baseName - + QQuickQQuickDesignerSupport::PropertyName(metaProperty.name()) + + QQuickDesignerSupport::PropertyName(metaProperty.name()) + '.', inspectedObjects, depth + 1)); } else { addToPropertyNameListIfNotBlackListed(&propertyNameList, baseName - + QQuickQQuickDesignerSupport::PropertyName( + + QQuickDesignerSupport::PropertyName( metaProperty.name())); } } From 53526e4d4bee772e98ce48b83c37bcfd53cf7241 Mon Sep 17 00:00:00 2001 From: hjk Date: Fri, 18 Nov 2022 10:20:18 +0100 Subject: [PATCH 022/131] FilePath: Backport some pathView() changes After the change to a single-string representation, the QString construction for path() is expensive for the comparison operators and simple convienience functions. Change-Id: I643c7115d3ad52f971d1692230b6eab82645b810 Reviewed-by: Tim Jenssen --- src/libs/utils/filepath.cpp | 13 +++++++++---- src/libs/utils/filepath.h | 1 + 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/libs/utils/filepath.cpp b/src/libs/utils/filepath.cpp index 55d5ca89a07..4c90f0e6931 100644 --- a/src/libs/utils/filepath.cpp +++ b/src/libs/utils/filepath.cpp @@ -341,6 +341,11 @@ QStringView FilePath::host() const return QStringView{m_data}.mid(m_pathLen + m_schemeLen, m_hostLen); } +QStringView FilePath::pathView() const +{ + return QStringView{m_data}.left(m_pathLen); +} + QString FilePath::path() const { if (m_data.startsWith("/./")) @@ -869,7 +874,7 @@ QVariant FilePath::toVariant() const bool FilePath::operator==(const FilePath &other) const { - return QString::compare(path(), other.path(), caseSensitivity()) == 0 + return pathView().compare(other.pathView(), caseSensitivity()) == 0 && host() == other.host() && scheme() == other.scheme(); } @@ -881,7 +886,7 @@ bool FilePath::operator!=(const FilePath &other) const bool FilePath::operator<(const FilePath &other) const { - const int cmp = QString::compare(path(), other.path(), caseSensitivity()); + const int cmp = pathView().compare(other.pathView(), caseSensitivity()); if (cmp != 0) return cmp < 0; if (host() != other.host()) @@ -930,7 +935,7 @@ bool FilePath::isChildOf(const FilePath &s) const /// \returns whether path() startsWith \a s bool FilePath::startsWith(const QString &s) const { - return path().startsWith(s, caseSensitivity()); + return pathView().startsWith(s, caseSensitivity()); } /*! @@ -939,7 +944,7 @@ bool FilePath::startsWith(const QString &s) const */ bool FilePath::endsWith(const QString &s) const { - return path().endsWith(s, caseSensitivity()); + return pathView().endsWith(s, caseSensitivity()); } /*! diff --git a/src/libs/utils/filepath.h b/src/libs/utils/filepath.h index f7ba8631bcb..d0fb810e465 100644 --- a/src/libs/utils/filepath.h +++ b/src/libs/utils/filepath.h @@ -74,6 +74,7 @@ public: QStringView scheme() const; QStringView host() const; + QStringView pathView() const; QString path() const; void setParts(const QStringView scheme, const QStringView host, const QStringView path); From a575cb4f46b8f24ef0a5cc15702a15020016d343 Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Tue, 22 Nov 2022 17:19:12 +0200 Subject: [PATCH 023/131] QmlDesigner: Implement drag-n-drop a texture to material editor Fixes: QDS-8339 Change-Id: Iea61c175525fbef25e763454f53e9bcc28847fe2 Reviewed-by: Miikka Heikkinen Reviewed-by: Reviewed-by: Thomas Hartmann --- .../imports/HelperWidgets/ComboBox.qml | 6 +- .../components/edit3d/edit3dwidget.cpp | 6 +- .../materialbrowser/materialbrowserwidget.cpp | 12 ++-- .../materialeditor/materialeditorview.cpp | 17 ++--- .../propertyeditor/propertyeditorvalue.cpp | 62 +++++++++---------- .../propertyeditor/propertyeditorvalue.h | 2 +- 6 files changed, 49 insertions(+), 56 deletions(-) diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ComboBox.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ComboBox.qml index de53533b352..61f41d8b9c3 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ComboBox.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ComboBox.qml @@ -59,10 +59,10 @@ StudioControls.ComboBox { anchors.fill: parent - property string assetPath: "" + property string dropData: "" onEntered: (drag) => { - dropArea.assetPath = drag.getDataAsString(drag.keys[0]).split(",")[0] + dropArea.dropData = drag.getDataAsString(drag.keys[0]).split(",")[0] drag.accepted = comboBox.backendValue !== undefined && comboBox.backendValue.hasActiveDrag comboBox.hasActiveHoverDrag = drag.accepted } @@ -70,7 +70,7 @@ StudioControls.ComboBox { onExited: comboBox.hasActiveHoverDrag = false onDropped: { - comboBox.backendValue.commitDrop(dropArea.assetPath) + comboBox.backendValue.commitDrop(dropArea.dropData) comboBox.hasActiveHoverDrag = false } diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp index 59aba18bd62..d939d4bf50d 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp @@ -419,11 +419,7 @@ void Edit3DWidget::dropEvent(QDropEvent *dropEvent) QByteArray data = dropEvent->mimeData()->data(isMaterial ? QString::fromLatin1(Constants::MIME_TYPE_MATERIAL) : QString::fromLatin1(Constants::MIME_TYPE_TEXTURE)); - QDataStream stream(data); - qint32 internalId; - stream >> internalId; - - if (ModelNode dropNode = m_view->modelNodeForInternalId(internalId)) { + if (ModelNode dropNode = m_view->modelNodeForInternalId(data.toInt())) { if (isMaterial) m_view->dropMaterial(dropNode, pos); else diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp index fe2a9f06ab5..d411d6b825c 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp @@ -131,19 +131,17 @@ bool MaterialBrowserWidget::eventFilter(QObject *obj, QEvent *event) QMouseEvent *me = static_cast(event); if ((me->globalPos() - m_dragStartPoint).manhattanLength() > 20) { bool isMaterial = m_materialToDrag.isValid(); - QByteArray data; QMimeData *mimeData = new QMimeData; - QDataStream stream(&data, QIODevice::WriteOnly); - stream << (isMaterial ? m_materialToDrag.internalId() : m_textureToDrag.internalId()); - mimeData->setData(isMaterial ? QString::fromLatin1(Constants::MIME_TYPE_MATERIAL) - : QString::fromLatin1(Constants::MIME_TYPE_TEXTURE), - data); - mimeData->removeFormat("text/plain"); + QByteArray internalId; if (isMaterial) { + internalId.setNum(m_materialToDrag.internalId()); + mimeData->setData(Constants::MIME_TYPE_MATERIAL, internalId); model->startDrag(mimeData, m_previewImageProvider->requestPixmap( QString::number(m_materialToDrag.internalId()), nullptr, {128, 128})); } else { + internalId.setNum(m_textureToDrag.internalId()); + mimeData->setData(Constants::MIME_TYPE_TEXTURE, internalId); QString iconPath = QLatin1String("%1/%2") .arg(DocumentManager::currentResourcePath().path(), m_textureToDrag.variantProperty("source").value().toString()); diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp index e93f99cb7d2..00352c50cf5 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp @@ -1142,16 +1142,17 @@ void QmlDesigner::MaterialEditorView::highlightSupportedProperties(bool highligh void MaterialEditorView::dragStarted(QMimeData *mimeData) { - if (!mimeData->hasFormat(Constants::MIME_TYPE_ASSETS)) - return; + if (mimeData->hasFormat(Constants::MIME_TYPE_ASSETS)) { + const QString assetPath = QString::fromUtf8(mimeData->data(Constants::MIME_TYPE_ASSETS)).split(',')[0]; + QString assetType = AssetsLibraryWidget::getAssetTypeAndData(assetPath).first; - const QString assetPath = QString::fromUtf8(mimeData->data(Constants::MIME_TYPE_ASSETS)).split(',')[0]; - QString assetType = AssetsLibraryWidget::getAssetTypeAndData(assetPath).first; + if (assetType != Constants::MIME_TYPE_ASSET_IMAGE) // currently only image assets have dnd-supported properties + return; - if (assetType != Constants::MIME_TYPE_ASSET_IMAGE) // currently only image assets have dnd-supported properties - return; - - highlightSupportedProperties(); + highlightSupportedProperties(); + } else if (mimeData->hasFormat(Constants::MIME_TYPE_TEXTURE)) { + highlightSupportedProperties(); + } } void MaterialEditorView::dragEnded() diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp index 45eb5b69b4c..28ee93cbb2d 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp @@ -2,28 +2,23 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 #include "propertyeditorvalue.h" -#include "variantproperty.h" -#include "documentmanager.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "abstractview.h" +#include "bindingproperty.h" +#include "designermcumanager.h" +#include "documentmanager.h" +#include "nodelistproperty.h" +#include "nodemetainfo.h" +#include "nodeproperty.h" +#include "qmlitemnode.h" +#include "qmlobjectnode.h" +#include "variantproperty.h" #include #include -#include #include -#include - -//using namespace QmlDesigner; +#include PropertyEditorValue::PropertyEditorValue(QObject *parent) : QObject(parent), @@ -498,27 +493,30 @@ bool PropertyEditorValue::idListReplace(int idx, const QString &value) return true; } -void PropertyEditorValue::commitDrop(const QString &path) +void PropertyEditorValue::commitDrop(const QString &dropData) { if (m_modelNode.metaInfo().isQtQuick3DMaterial() && m_modelNode.metaInfo().property(m_name).propertyType().isQtQuick3DTexture()) { - // create a texture node - QmlDesigner::NodeMetaInfo metaInfo = m_modelNode.view()->model()->metaInfo("QtQuick3D.Texture"); - QmlDesigner::ModelNode texture = m_modelNode.view()->createModelNode("QtQuick3D.Texture", - metaInfo.majorVersion(), - metaInfo.minorVersion()); - texture.validId(); - m_modelNode.view()->rootModelNode().defaultNodeListProperty().reparentHere(texture); - // TODO: group textures under 1 node (just like materials) + m_modelNode.view()->executeInTransaction(__FUNCTION__, [&] { + QmlDesigner::ModelNode texture = m_modelNode.view()->modelNodeForInternalId(dropData.toInt()); + if (!texture || !texture.metaInfo().isQtQuick3DTexture()) { + // create a texture node + QmlDesigner::NodeMetaInfo metaInfo = m_modelNode.view()->model()->metaInfo("QtQuick3D.Texture"); + texture = m_modelNode.view()->createModelNode("QtQuick3D.Texture", metaInfo.majorVersion(), + metaInfo.minorVersion()); + texture.validId(); + m_modelNode.view()->materialLibraryNode().defaultNodeListProperty().reparentHere(texture); - // set texture source - Utils::FilePath imagePath = Utils::FilePath::fromString(path); - Utils::FilePath currFilePath = QmlDesigner::DocumentManager::currentFilePath(); - QmlDesigner::VariantProperty srcProp = texture.variantProperty("source"); - srcProp.setValue(imagePath.relativePathFrom(currFilePath).toString()); + // set texture source + Utils::FilePath imagePath = Utils::FilePath::fromString(dropData); + Utils::FilePath currFilePath = QmlDesigner::DocumentManager::currentFilePath(); + QmlDesigner::VariantProperty srcProp = texture.variantProperty("source"); + srcProp.setValue(imagePath.relativePathFrom(currFilePath).toString()); + } - // assign the texture to the property - setExpressionWithEmit(texture.id()); + // assign the texture to the property + setExpressionWithEmit(texture.id()); + }); } m_modelNode.view()->model()->endDrag(); diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.h b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.h index e7199170dab..0680ceb9935 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.h +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.h @@ -125,7 +125,7 @@ public: Q_INVOKABLE bool idListAdd(const QString &value); Q_INVOKABLE bool idListRemove(int idx); Q_INVOKABLE bool idListReplace(int idx, const QString &value); - Q_INVOKABLE void commitDrop(const QString &path); + Q_INVOKABLE void commitDrop(const QString &dropData); public slots: void resetValue(); From 910a8864dc43f8dede7617855282f19de62ee939 Mon Sep 17 00:00:00 2001 From: Samuel Ghinet Date: Wed, 23 Nov 2022 11:49:45 +0200 Subject: [PATCH 024/131] Use QML TreeView in Assets Library Task-number: QDS-7344 Change-Id: Ia1ea584fc7acabb0d35b745e36fef18799f21ab5 Reviewed-by: Thomas Hartmann --- .../itemLibraryQmlSources/AssetDelegate.qml | 327 +++++++++++ .../itemLibraryQmlSources/Assets.qml | 83 +-- .../AssetsContextMenu.qml | 131 +++-- .../itemLibraryQmlSources/AssetsView.qml | 538 ++++++++++-------- .../ConfirmDeleteFolderDialog.qml | 53 +- .../itemLibraryQmlSources/ErrorDialog.qml | 40 ++ .../itemLibraryQmlSources/NewFolderDialog.qml | 77 +-- .../RenameFolderDialog.qml | 67 +-- src/plugins/qmldesigner/CMakeLists.txt | 3 - .../assetslibrary/assetslibrarydir.cpp | 75 --- .../assetslibrary/assetslibrarydir.h | 63 -- .../assetslibrary/assetslibrarydirsmodel.cpp | 71 --- .../assetslibrary/assetslibrarydirsmodel.h | 32 -- .../assetslibrary/assetslibraryfilesmodel.cpp | 53 -- .../assetslibrary/assetslibraryfilesmodel.h | 32 -- .../assetslibraryiconprovider.cpp | 72 ++- .../assetslibrary/assetslibraryiconprovider.h | 4 + .../assetslibrary/assetslibrarymodel.cpp | 385 ++++++------- .../assetslibrary/assetslibrarymodel.h | 104 ++-- .../assetslibrary/assetslibrarywidget.cpp | 88 +-- .../assetslibrary/assetslibrarywidget.h | 22 +- .../componentcore/designeractionmanager.cpp | 2 +- .../componentcore/modelnodeoperations.cpp | 8 +- .../componentcore/modelnodeoperations.h | 43 +- .../contentlibrary/contentlibraryview.cpp | 2 +- .../itemlibrary/itemlibraryview.cpp | 2 +- 26 files changed, 1274 insertions(+), 1103 deletions(-) create mode 100644 share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetDelegate.qml create mode 100644 share/qtcreator/qmldesigner/itemLibraryQmlSources/ErrorDialog.qml delete mode 100644 src/plugins/qmldesigner/components/assetslibrary/assetslibrarydir.cpp delete mode 100644 src/plugins/qmldesigner/components/assetslibrary/assetslibrarydir.h delete mode 100644 src/plugins/qmldesigner/components/assetslibrary/assetslibrarydirsmodel.cpp delete mode 100644 src/plugins/qmldesigner/components/assetslibrary/assetslibrarydirsmodel.h delete mode 100644 src/plugins/qmldesigner/components/assetslibrary/assetslibraryfilesmodel.cpp delete mode 100644 src/plugins/qmldesigner/components/assetslibrary/assetslibraryfilesmodel.h diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetDelegate.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetDelegate.qml new file mode 100644 index 00000000000..28f671cf681 --- /dev/null +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetDelegate.qml @@ -0,0 +1,327 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Controls +import StudioTheme as StudioTheme + +TreeViewDelegate { + id: root + + required property Item assetsView + required property Item assetsRoot + + property bool hasChildWithDropHover: false + property bool isHoveringDrop: false + readonly property string suffix: model.fileName.substr(-4) + readonly property bool isFont: root.suffix === ".ttf" || root.suffix === ".otf" + readonly property bool isEffect: root.suffix === ".qep" + property bool currFileSelected: false + property int initialDepth: -1 + property bool _isDirectory: assetsModel.isDirectory(model.filePath) + property int _currentRow: model.index + property string _itemPath: model.filePath + + readonly property int _fileItemHeight: thumbnailImage.height + readonly property int _dirItemHeight: 21 + + implicitHeight: root._isDirectory ? root._dirItemHeight : root._fileItemHeight + implicitWidth: root.assetsView.width > 0 ? root.assetsView.width : 10 + + leftMargin: root._isDirectory ? 0 : thumbnailImage.width + + Component.onCompleted: { + // the depth of the root path will become available before we get to the actual + // items we display, so it's safe to set assetsView.rootPathDepth here. All other + // tree items (below the root) will have the indentation (basically, depth) adjusted. + if (model.filePath === assetsModel.rootPath()) { + root.assetsView.rootPathDepth = root.depth + root.assetsView.rootPathRow = root._currentRow + } else if (model.filePath.includes(assetsModel.rootPath())) { + root.depth -= root.assetsView.rootPathDepth + root.initialDepth = root.depth + } + } + + // workaround for a bug -- might be fixed by https://codereview.qt-project.org/c/qt/qtdeclarative/+/442721 + onYChanged: { + if (root._currentRow === root.assetsView.firstRow) { + if (root.y > root.assetsView.contentY) { + let item = root.assetsView.itemAtCell(0, root.assetsView.rootPathRow) + if (!item) + root.assetsView.contentY = root.y + } + } + } + + onImplicitWidthChanged: { + // a small hack, to fix a glitch: when resizing the width of the tree view, + // the widths of the delegate items remain the same as before, unless we re-set + // that width explicitly. + var newWidth = root.implicitWidth - (root.assetsView.verticalScrollBar.scrollBarVisible + ? root.assetsView.verticalScrollBar.width + : 0) + bg.width = newWidth + bg.implicitWidth = newWidth + } + + onDepthChanged: { + if (root.depth > root.initialDepth && root.initialDepth >= 0) + root.depth = root.initialDepth + } + + background: Rectangle { + id: bg + + color: { + if (root._isDirectory && (root.isHoveringDrop || root.hasChildWithDropHover)) + return StudioTheme.Values.themeInteraction + + if (!root._isDirectory && root.assetsView.selectedAssets[root._itemPath]) + return StudioTheme.Values.themeInteraction + + if (mouseArea.containsMouse) + return StudioTheme.Values.themeSectionHeadBackground + + return root._isDirectory + ? StudioTheme.Values.themeSectionHeadBackground + : "transparent" + } + + // this rectangle exists so as to have some visual indentation for the directories + // We prepend a default pane-colored rectangle so that the nested directory will + // look moved a bit to the right + Rectangle { + anchors.top: bg.top + anchors.bottom: bg.bottom + anchors.left: bg.left + + width: root.indentation * root.depth + implicitWidth: root.indentation * root.depth + color: StudioTheme.Values.themePanelBackground + } + } + + contentItem: Text { + id: assetLabel + text: assetLabel._computeText() + color: StudioTheme.Values.themeTextColor + font.pixelSize: 14 + anchors.verticalCenter: parent.verticalCenter + verticalAlignment: Qt.AlignVCenter + + function _computeText() + { + return root._isDirectory + ? (root.hasChildren + ? model.display.toUpperCase() + : model.display.toUpperCase() + qsTr(" (empty)")) + : model.display + } + } + + DropArea { + id: treeDropArea + + enabled: true + anchors.fill: parent + + onEntered: (drag) => { + root.assetsRoot.updateDropExtFiles(drag) + root.isHoveringDrop = drag.accepted && root.assetsRoot.dropSimpleExtFiles.length > 0 + if (root.isHoveringDrop) + root.assetsView.startDropHoverOver(root._currentRow) + } + + onDropped: (drag) => { + root.isHoveringDrop = false + root.assetsView.endDropHover(root._currentRow) + + let dirPath = root._isDirectory + ? model.filePath + : assetsModel.parentDirPath(model.filePath); + + rootView.emitExtFilesDrop(root.assetsRoot.dropSimpleExtFiles, + root.assetsRoot.dropComplexExtFiles, + dirPath) + } + + onExited: { + if (root.isHoveringDrop) { + root.isHoveringDrop = false + root.assetsView.endDropHover(root._currentRow) + } + } + } + + MouseArea { + id: mouseArea + + property bool allowTooltip: true + + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.LeftButton | Qt.RightButton + + onExited: tooltipBackend.hideTooltip() + onEntered: mouseArea.allowTooltip = true + + onCanceled: { + tooltipBackend.hideTooltip() + mouseArea.allowTooltip = true + } + + onPositionChanged: tooltipBackend.reposition() + + onPressed: (mouse) => { + forceActiveFocus() + mouseArea.allowTooltip = false + tooltipBackend.hideTooltip() + + if (root._isDirectory) + return + + var ctrlDown = mouse.modifiers & Qt.ControlModifier + if (mouse.button === Qt.LeftButton) { + if (!root.assetsView.isAssetSelected(root._itemPath) && !ctrlDown) + root.assetsView.clearSelectedAssets() + root.currFileSelected = ctrlDown ? !root.assetsView.isAssetSelected(root._itemPath) : true + root.assetsView.setAssetSelected(root._itemPath, root.currFileSelected) + + if (root.currFileSelected) { + let selectedPaths = root.assetsView.selectedPathsAsList() + rootView.startDragAsset(selectedPaths, mapToGlobal(mouse.x, mouse.y)) + } + } else { + if (!root.assetsView.isAssetSelected(root._itemPath) && !ctrlDown) + root.assetsView.clearSelectedAssets() + root.currFileSelected = root.assetsView.isAssetSelected(root._itemPath) || !ctrlDown + root.assetsView.setAssetSelected(root._itemPath, root.currFileSelected) + } + } + + onReleased: (mouse) => { + mouseArea.allowTooltip = true + + if (mouse.button === Qt.LeftButton) { + if (!(mouse.modifiers & Qt.ControlModifier)) + root.assetsView.selectedAssets = {} + root.assetsView.selectedAssets[root._itemPath] = root.currFileSelected + root.assetsView.selectedAssetsChanged() + } + } + + onDoubleClicked: (mouse) => { + forceActiveFocus() + allowTooltip = false + tooltipBackend.hideTooltip() + if (mouse.button === Qt.LeftButton && isEffect) + rootView.openEffectMaker(filePath) + } + + ToolTip { + visible: !root.isFont && mouseArea.containsMouse && !root.assetsView.contextMenu.visible + text: model.filePath + delay: 1000 + } + + Timer { + interval: 1000 + running: mouseArea.containsMouse && mouseArea.allowTooltip + onTriggered: { + if (suffix === ".ttf" || suffix === ".otf") { + tooltipBackend.name = model.fileName + tooltipBackend.path = model.filePath + tooltipBackend.showTooltip() + } + } + } // Timer + + onClicked: (mouse) => { + if (mouse.button === Qt.LeftButton) + root._toggleExpandCurrentRow() + else + root._openContextMenuForCurrentRow() + + + } + } // MouseArea + + function _openContextMenuForCurrentRow() + { + let modelIndex = assetsModel.indexForPath(model.filePath) + + if (root._isDirectory) { + var row = root.assetsView.rowAtIndex(modelIndex) + var expanded = root.assetsView.isExpanded(row) + + var allExpandedState = root.assetsView.computeAllExpandedState() + + function onFolderCreated(path) { + root.assetsView.addCreatedFolder(path) + } + + function onFolderRenamed() { + if (expanded) + root.assetsView.rowToExpand = row + } + + root.assetsView.contextMenu.openContextMenuForDir(modelIndex, model.filePath, + model.fileName, allExpandedState, onFolderCreated, onFolderRenamed) + } else { + let parentDirIndex = assetsModel.parentDirIndex(model.filePath) + let selectedPaths = root.assetsView.selectedPathsAsList() + root.assetsView.contextMenu.openContextMenuForFile(modelIndex, parentDirIndex, + selectedPaths) + } + } + + function _toggleExpandCurrentRow() + { + if (!root._isDirectory) + return + + let index = root.assetsView._modelIndex(root._currentRow, 0) + // if the user manually clicked on a directory, then this is definitely not a + // an automatic request to expand all. + root.assetsView.requestedExpandAll = false + + if (root.assetsView.isExpanded(root._currentRow)) { + root.assetsView.requestedExpandAll = false + root.assetsView.collapse(root._currentRow) + } else { + root.assetsView.expand(root._currentRow) + } + } + + function reloadImage() + { + if (root._isDirectory) + return + + thumbnailImage.source = "" + thumbnailImage.source = thumbnailImage._computeSource() + } + + Image { + id: thumbnailImage + visible: !root._isDirectory + x: root.depth * root.indentation + width: 48 + height: 48 + cache: false + sourceSize.width: 48 + sourceSize.height: 48 + asynchronous: true + fillMode: Image.PreserveAspectFit + source: thumbnailImage._computeSource() + + function _computeSource() + { + return root._isDirectory + ? "" + : "image://qmldesigner_assets/" + model.filePath + } + + } // Image +} // TreeViewDelegate diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml index f8c06057346..d35ac63dad7 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml @@ -1,31 +1,26 @@ -// Copyright (C) 2021 The Qt Company Ltd. +// Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 -import QtQuick 2.15 -import QtQuick.Controls 2.15 -import QtQuick.Layouts 1.15 -import QtQuickDesignerTheme 1.0 -import HelperWidgets 2.0 -import StudioControls 1.0 as StudioControls -import StudioTheme 1.0 as StudioTheme +import QtQuick +import HelperWidgets as HelperWidgets +import StudioControls as StudioControls +import StudioTheme as StudioTheme Item { id: root - property var selectedAssets: ({}) - property int allExpandedState: 0 - property string contextFilePath: "" - property var contextDir: undefined - property bool isDirContextMenu: false - // Array of supported externally dropped files that are imported as-is property var dropSimpleExtFiles: [] // Array of supported externally dropped files that trigger custom import process property var dropComplexExtFiles: [] + readonly property int qtVersionAtLeast6_4: rootView.qtVersionIsAtLeast6_4() + property bool _searchBoxEmpty: true + AssetsContextMenu { id: contextMenu + assetsView: assetsView } function clearSearchFilter() @@ -63,7 +58,7 @@ Item { onDropped: { rootView.handleExtFilesDrop(root.dropSimpleExtFiles, root.dropComplexExtFiles, - assetsModel.rootDir().dirPath) + assetsModel.rootPath()) } Canvas { // marker for the drop area @@ -90,11 +85,15 @@ Item { anchors.fill: parent acceptedButtons: Qt.RightButton onClicked: { - if (!assetsModel.isEmpty) { - root.contextFilePath = "" - root.contextDir = assetsModel.rootDir() - root.isDirContextMenu = false - contextMenu.popup() + if (assetsModel.haveFiles) { + function onFolderCreated(path) { + assetsView.addCreatedFolder(path) + } + + var rootIndex = assetsModel.rootIndex() + var dirPath = assetsModel.filePath(rootIndex) + var dirName = assetsModel.fileName(rootIndex) + contextMenu.openContextMenuForRoot(rootIndex, dirPath, dirName, onFolderCreated) } } } @@ -103,13 +102,8 @@ Item { function handleViewFocusOut() { contextMenu.close() - root.selectedAssets = {} - root.selectedAssetsChanged() - } - - RegExpValidator { - id: folderNameValidator - regExp: /^(\w[^*/> rootView.handleSearchFilterChanged(searchText) + onSearchChanged: (searchText) => { + updateSearchFilterTimer.restart() + } } - IconButton { + Timer { + id: updateSearchFilterTimer + interval: 200 + repeat: false + + onTriggered: { + assetsView.resetVerticalScrollPosition() + rootView.handleSearchFilterChanged(searchBox.text) + assetsView.expandAll() + + if (root._searchBoxEmpty && searchBox.text) + root._searchBoxEmpty = false + else if (!root._searchBoxEmpty && !searchBox.text) + root._searchBoxEmpty = true + } + } + + HelperWidgets.IconButton { id: addAssetButton anchors.verticalCenter: parent.verticalCenter tooltip: qsTr("Add a new asset to the project.") @@ -146,14 +159,13 @@ Item { leftPadding: 10 color: StudioTheme.Values.themeTextColor font.pixelSize: 12 - visible: assetsModel.isEmpty && !searchBox.isEmpty() + visible: !assetsModel.haveFiles && !root._searchBoxEmpty } - Item { // placeholder when the assets library is empty width: parent.width height: parent.height - searchRow.height - visible: assetsModel.isEmpty && searchBox.isEmpty() + visible: !assetsModel.haveFiles && root._searchBoxEmpty clip: true DropArea { // handles external drop (goes into default folder based on suffix) @@ -164,7 +176,7 @@ Item { } onDropped: { - rootView.handleExtFilesDrop(root.dropSimpleExtFiles, root.dropComplexExtFiles) + rootView.emitExtFilesDrop(root.dropSimpleExtFiles, root.dropComplexExtFiles) } Column { @@ -217,8 +229,11 @@ Item { AssetsView { id: assetsView + assetsRoot: root + contextMenu: contextMenu + width: parent.width height: parent.height - y } - } + } // Column } diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsContextMenu.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsContextMenu.qml index 5caa139651f..84a689184c1 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsContextMenu.qml +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsContextMenu.qml @@ -1,90 +1,113 @@ -/**************************************************************************** -** -** Copyright (C) 2022 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt Creator. -** -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -****************************************************************************/ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 import QtQuick import QtQuick.Controls -import QtQuick.Layouts -import QtQuickDesignerTheme -import HelperWidgets as HelperWidgets import StudioControls as StudioControls import StudioTheme as StudioTheme StudioControls.Menu { - id: contextMenu + id: root + + required property Item assetsView + + property bool _isDirectory: false + property var _fileIndex: null + property string _dirPath: "" + property string _dirName: "" + property var _onFolderCreated: null + property var _onFolderRenamed: null + property var _dirIndex: null + property string _allExpandedState: "" + property var _selectedAssetPathsList: null closePolicy: Popup.CloseOnPressOutside | Popup.CloseOnEscape - onOpened: { - var numSelected = Object.values(root.selectedAssets).filter(p => p).length + function openContextMenuForRoot(rootModelIndex, dirPath, dirName, onFolderCreated) + { + root._onFolderCreated = onFolderCreated + root._fileIndex = "" + root._dirPath = dirPath + root._dirName = dirName + root._dirIndex = rootModelIndex + root._isDirectory = false + root.popup() + } + + function openContextMenuForDir(dirModelIndex, dirPath, dirName, allExpandedState, + onFolderCreated, onFolderRenamed) + { + root._onFolderCreated = onFolderCreated + root._onFolderRenamed = onFolderRenamed + root._dirPath = dirPath + root._dirName = dirName + root._fileIndex = "" + root._dirIndex = dirModelIndex + root._isDirectory = true + root._allExpandedState = allExpandedState + root.popup() + } + + function openContextMenuForFile(fileIndex, dirModelIndex, selectedAssetPathsList) + { + var numSelected = selectedAssetPathsList.filter(p => p).length deleteFileItem.text = numSelected > 1 ? qsTr("Delete Files") : qsTr("Delete File") + + root._selectedAssetPathsList = selectedAssetPathsList + root._fileIndex = fileIndex + root._dirIndex = dirModelIndex + root._dirPath = assetsModel.filePath(dirModelIndex) + root._isDirectory = false + root.popup() } StudioControls.MenuItem { text: qsTr("Expand All") - enabled: root.allExpandedState !== 1 - visible: root.isDirContextMenu + enabled: root._allExpandedState !== "all_expanded" + visible: root._isDirectory height: visible ? implicitHeight : 0 - onTriggered: assetsModel.toggleExpandAll(true) + onTriggered: root.assetsView.expandAll() } StudioControls.MenuItem { text: qsTr("Collapse All") - enabled: root.allExpandedState !== 2 - visible: root.isDirContextMenu + enabled: root._allExpandedState !== "all_collapsed" + visible: root._isDirectory height: visible ? implicitHeight : 0 - onTriggered: assetsModel.toggleExpandAll(false) + onTriggered: root.assetsView.collapseAll() } StudioControls.MenuSeparator { - visible: root.isDirContextMenu + visible: root._isDirectory height: visible ? StudioTheme.Values.border : 0 } StudioControls.MenuItem { id: deleteFileItem text: qsTr("Delete File") - visible: root.contextFilePath + visible: root._fileIndex height: deleteFileItem.visible ? deleteFileItem.implicitHeight : 0 - onTriggered: { - assetsModel.deleteFiles(Object.keys(root.selectedAssets).filter(p => root.selectedAssets[p])) - } + onTriggered: assetsModel.deleteFiles(root._selectedAssetPathsList) } StudioControls.MenuSeparator { - visible: root.contextFilePath + visible: root._fileIndex height: visible ? StudioTheme.Values.border : 0 } StudioControls.MenuItem { text: qsTr("Rename Folder") - visible: root.isDirContextMenu + visible: root._isDirectory height: visible ? implicitHeight : 0 onTriggered: renameFolderDialog.open() RenameFolderDialog { id: renameFolderDialog + parent: root.assetsView + dirPath: root._dirPath + dirName: root._dirName + + onAccepted: root._onFolderRenamed() } } @@ -93,6 +116,10 @@ StudioControls.Menu { NewFolderDialog { id: newFolderDialog + parent: root.assetsView + dirPath: root._dirPath + + onAccepted: root._onFolderCreated(newFolderDialog.createdDirPath) } onTriggered: newFolderDialog.open() @@ -100,21 +127,25 @@ StudioControls.Menu { StudioControls.MenuItem { text: qsTr("Delete Folder") - visible: root.isDirContextMenu + visible: root._isDirectory height: visible ? implicitHeight : 0 ConfirmDeleteFolderDialog { id: confirmDeleteFolderDialog + parent: root.assetsView + dirName: root._dirName + dirIndex: root._dirIndex } onTriggered: { - var dirEmpty = !(root.contextDir.dirsModel && root.contextDir.dirsModel.rowCount() > 0) - && !(root.contextDir.filesModel && root.contextDir.filesModel.rowCount() > 0); - - if (dirEmpty) - assetsModel.deleteFolder(root.contextDir.dirPath) - else + if (!assetsModel.hasChildren(root._dirIndex)) { + // NOTE: the folder may still not be empty -- it doesn't have files visible to the + // user, but that doesn't mean that there are no other files (e.g. files of unknown + // types) on disk in this directory. + assetsModel.deleteFolderRecursively(root._dirIndex) + } else { confirmDeleteFolderDialog.open() + } } } } diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsView.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsView.qml index 77a784d92d5..8b76eaf0e1f 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsView.qml +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsView.qml @@ -1,255 +1,309 @@ -/**************************************************************************** -** -** Copyright (C) 2022 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt Creator. -** -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -****************************************************************************/ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 import QtQuick import QtQuick.Controls -import QtQuick.Layouts -import QtQuickDesignerTheme -import HelperWidgets +import HelperWidgets as HelperWidgets import StudioControls as StudioControls -import StudioTheme as StudioTheme -ScrollView { // TODO: experiment using ListView instead of ScrollView + Column - id: assetsView +TreeView { + id: root clip: true - interactive: assetsView.verticalScrollBarVisible && !contextMenu.opened + interactive: verticalScrollBar.visible && !root.contextMenu.opened + reuseItems: false + boundsBehavior: Flickable.StopAtBounds + rowSpacing: 5 - Column { - Repeater { - model: assetsModel // context property - delegate: dirSection + required property Item assetsRoot + required property StudioControls.Menu contextMenu + property alias verticalScrollBar: verticalScrollBar + + property var selectedAssets: ({}) + + // used to see if the op requested is to expand or to collapse. + property int lastRowCount: -1 + // we need this to know if we need to expand further, while we're in onRowsChanged() + property bool requestedExpandAll: true + // used to compute the visual depth of the items we show to the user. + property int rootPathDepth: 0 + property int rootPathRow: 0 + // i.e. first child of the root path + readonly property int firstRow: root.rootPathRow + 1 + property int rowToExpand: -1 + property var _createdDirectories: [] + + rowHeightProvider: (row) => { + if (row <= root.rootPathRow) + return 0 + + return -1 + } + + ScrollBar.vertical: HelperWidgets.VerticalScrollBar { + id: verticalScrollBar + scrollBarVisible: root.contentHeight > root.height + } + + model: assetsModel + + onRowsChanged: { + if (root.rows > root.rootPathRow + 1 && !assetsModel.haveFiles || + root.rows <= root.rootPathRow + 1 && assetsModel.haveFiles) { + assetsModel.syncHaveFiles() } - Component { - id: dirSection + updateRows() + } - Section { - id: section + Timer { + id: updateRowsTimer + interval: 200 + repeat: false - width: assetsView.width - - (assetsView.verticalScrollBarVisible ? assetsView.verticalThickness : 0) - 5 - caption: dirName - sectionHeight: 30 - sectionFontSize: 15 - leftPadding: 0 - topPadding: dirDepth > 0 ? 5 : 0 - bottomPadding: 0 - hideHeader: dirDepth === 0 - showLeftBorder: dirDepth > 0 - expanded: dirExpanded - visible: dirVisible - expandOnClick: false - useDefaulContextMenu: false - dropEnabled: true - - onToggleExpand: { - dirExpanded = !dirExpanded - } - - onDropEnter: (drag)=> { - root.updateDropExtFiles(drag) - section.highlight = drag.accepted && root.dropSimpleExtFiles.length > 0 - } - - onDropExit: { - section.highlight = false - } - - onDrop: { - section.highlight = false - rootView.handleExtFilesDrop(root.dropSimpleExtFiles, - root.dropComplexExtFiles, - dirPath) - } - - onShowContextMenu: { - root.contextFilePath = "" - root.contextDir = model - root.isDirContextMenu = true - root.allExpandedState = assetsModel.getAllExpandedState() - contextMenu.popup() - } - - Column { - spacing: 5 - leftPadding: 5 - - Repeater { - model: dirsModel - delegate: dirSection - } - - Repeater { - model: filesModel - delegate: fileSection - } - - Text { - text: qsTr("Empty folder") - color: StudioTheme.Values.themeTextColorDisabled - font.pixelSize: 12 - visible: !(dirsModel && dirsModel.rowCount() > 0) - && !(filesModel && filesModel.rowCount() > 0) - - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.RightButton - onClicked: { - root.contextFilePath = "" - root.contextDir = model - root.isDirContextMenu = true - contextMenu.popup() - } - } - } - } - } - } - - Component { - id: fileSection - - Rectangle { - width: assetsView.width - - (assetsView.verticalScrollBarVisible ? assetsView.verticalThickness : 0) - height: img.height - color: root.selectedAssets[filePath] - ? StudioTheme.Values.themeInteraction - : (mouseArea.containsMouse ? StudioTheme.Values.themeSectionHeadBackground - : "transparent") - - Row { - spacing: 5 - - Image { - id: img - asynchronous: true - fillMode: Image.PreserveAspectFit - width: 48 - height: 48 - source: "image://qmldesigner_assets/" + filePath - } - - Text { - text: fileName - color: StudioTheme.Values.themeTextColor - font.pixelSize: 14 - anchors.verticalCenter: parent.verticalCenter - } - } - - readonly property string suffix: fileName.substr(-4) - readonly property bool isFont: suffix === ".ttf" || suffix === ".otf" - readonly property bool isEffect: suffix === ".qep" - property bool currFileSelected: false - - MouseArea { - id: mouseArea - - property bool allowTooltip: true - - anchors.fill: parent - hoverEnabled: true - acceptedButtons: Qt.LeftButton | Qt.RightButton - - onExited: tooltipBackend.hideTooltip() - onEntered: allowTooltip = true - onCanceled: { - tooltipBackend.hideTooltip() - allowTooltip = true - } - onPositionChanged: tooltipBackend.reposition() - onPressed: (mouse) => { - forceActiveFocus() - allowTooltip = false - tooltipBackend.hideTooltip() - var ctrlDown = mouse.modifiers & Qt.ControlModifier - if (mouse.button === Qt.LeftButton) { - if (!root.selectedAssets[filePath] && !ctrlDown) - root.selectedAssets = {} - currFileSelected = ctrlDown ? !root.selectedAssets[filePath] : true - root.selectedAssets[filePath] = currFileSelected - root.selectedAssetsChanged() - - if (currFileSelected) { - rootView.startDragAsset( - Object.keys(root.selectedAssets).filter(p => root.selectedAssets[p]), - mapToGlobal(mouse.x, mouse.y)) - } - } else { - if (!root.selectedAssets[filePath] && !ctrlDown) - root.selectedAssets = {} - currFileSelected = root.selectedAssets[filePath] || !ctrlDown - root.selectedAssets[filePath] = currFileSelected - root.selectedAssetsChanged() - - root.contextFilePath = filePath - root.contextDir = model.fileDir - root.isDirContextMenu = false - - contextMenu.popup() - } - } - - onReleased: (mouse) => { - allowTooltip = true - if (mouse.button === Qt.LeftButton) { - if (!(mouse.modifiers & Qt.ControlModifier)) - root.selectedAssets = {} - root.selectedAssets[filePath] = currFileSelected - root.selectedAssetsChanged() - } - } - - onDoubleClicked: (mouse) => { - forceActiveFocus() - allowTooltip = false - tooltipBackend.hideTooltip() - if (mouse.button === Qt.LeftButton && isEffect) - rootView.openEffectMaker(filePath) - } - - ToolTip { - visible: !isFont && mouseArea.containsMouse && !contextMenu.visible - text: filePath - delay: 1000 - } - - Timer { - interval: 1000 - running: mouseArea.containsMouse && mouseArea.allowTooltip - onTriggered: { - if (suffix === ".ttf" || suffix === ".otf") { - tooltipBackend.name = fileName - tooltipBackend.path = filePath - tooltipBackend.showTooltip() - } - } - } - } - } + onTriggered: { + root.updateRows() } } -} + + Connections { + target: rootView + + function onDirectoryCreated(path) + { + root._createdDirectories.push(path) + + updateRowsTimer.restart() + } + } + + Connections { + target: assetsModel + function onDirectoryLoaded(path) + { + // updating rows for safety: the rows might have been created before the + // directory (esp. the root path) has been loaded, so we must make sure all rows are + // expanded -- otherwise, the tree may not become visible. + + updateRowsTimer.restart() + + let idx = assetsModel.indexForPath(path) + let row = root.rowAtIndex(idx) + let column = root.columnAtIndex(idx) + + if (row >= root.rootPathRow && !root.isExpanded(row)) + root.expand(row) + } + + function onRootPathChanged() + { + // when we switch from one project to another, we need to reset the state of the + // view: make sure we will do an "expand all" (otherwise, the whole tree might + // be collapsed, and with our visible root not being the actual root of the tree, + // the entire tree would be invisible) + root.lastRowCount = -1 + root.requestedExpandAll = true + } + + function onFileChanged(filePath) + { + rootView.invalidateThumbnail(filePath) + + let index = assetsModel.indexForPath(filePath) + let cell = root.cellAtIndex(index) + let fileItem = root.itemAtCell(cell) + + if (fileItem) + fileItem.reloadImage() + } + + } // Connections + + function addCreatedFolder(path) + { + root._createdDirectories.push(path) + } + + function selectedPathsAsList() + { + return Object.keys(root.selectedAssets) + .filter(itemPath => root.selectedAssets[itemPath]) + } + + // workaround for a bug -- might be fixed by https://codereview.qt-project.org/c/qt/qtdeclarative/+/442721 + function resetVerticalScrollPosition() + { + root.contentY = 0 + } + + function updateRows() + { + if (root.rows <= 0) + return + + while (root._createdDirectories.length > 0) { + let dirPath = root._createdDirectories.pop() + let index = assetsModel.indexForPath(dirPath) + let row = root.rowAtIndex(index) + + if (row > 0) + root.expand(row) + else if (row === -1 && assetsModel.indexIsValid(index)) { + // It is possible that this directory, dirPath, was created inside of a parent + // directory that was not yet expanded in the TreeView. This can happen with the + // bridge plugin. In such a situation, we don't have a "row" for it yet, so we have + // to expand its parents, from root to our `index` + let parents = assetsModel.parentIndices(index); + parents.reverse().forEach(idx => { + let row = root.rowAtIndex(idx) + if (row > 0) + root.expand(row) + }) + } + } + + // we have no way to know beyond doubt here if updateRows() was called due + // to a request to expand or to collapse rows - but it should be safe to + // assume that, if we have more rows now than the last time, then it's an expand + var expanding = (root.rows >= root.lastRowCount) + + if (expanding) { + if (root.requestedExpandAll) + root._doExpandAll() + } else { + if (root.rowToExpand > 0) { + root.expand(root.rowToExpand) + root.rowToExpand = -1 + } + + // on collapsing, set expandAll flag to false. + root.requestedExpandAll = false; + } + + root.lastRowCount = root.rows + } + + function _doExpandAll() + { + let expandedAny = false + for (let nRow = 0; nRow < root.rows; ++nRow) { + let index = root._modelIndex(nRow, 0) + if (assetsModel.isDirectory(index) && !root.isExpanded(nRow)) { + root.expand(nRow); + expandedAny = true + } + } + + if (!expandedAny) + Qt.callLater(root.forceLayout) + } + + function expandAll() + { + // In order for _doExpandAll() to be called repeatedly (every time a new node is + // loaded, and then, expanded), we need to set requestedExpandAll to true. + root.requestedExpandAll = true + root._doExpandAll() + } + + function collapseAll() + { + root.resetVerticalScrollPosition() + + // collapse all, except for the root path - from the last item (leaves) up to the root + for (let nRow = root.rows - 1; nRow >= 0; --nRow) { + let index = root._modelIndex(nRow, 0) + // we don't want to collapse the root path, because doing so will hide the contents + // of the tree. + if (assetsModel.filePath(index) === assetsModel.rootPath()) + break + + root.collapse(nRow) + } + } + + // workaround for a bug -- might be fixed by https://codereview.qt-project.org/c/qt/qtdeclarative/+/442721 + onContentHeightChanged: { + if (root.contentHeight <= root.height) { + let first = root.itemAtCell(0, root.firstRow) + if (!first) + root.contentY = 0 + } + } + + function computeAllExpandedState() + { + var dirsWithChildren = [...Array(root.rows).keys()].filter(row => { + let index = root._modelIndex(row, 0) + return assetsModel.isDirectory(index) && assetsModel.hasChildren(index) + }) + + var countExpanded = dirsWithChildren.filter(row => root.isExpanded(row)).length + + if (countExpanded === dirsWithChildren.length) + return "all_expanded" + + if (countExpanded === 0) + return "all_collapsed" + return "" + } + + function startDropHoverOver(row) + { + let index = root._modelIndex(row, 0) + if (assetsModel.isDirectory(index)) + return + + let parentItem = root._getDelegateParentForIndex(index) + parentItem.hasChildWithDropHover = true + } + + function endDropHover(row) + { + let index = root._modelIndex(row, 0) + if (assetsModel.isDirectory(index)) + return + + let parentItem = root._getDelegateParentForIndex(index) + parentItem.hasChildWithDropHover = false + } + + function isAssetSelected(itemPath) + { + return root.selectedAssets[itemPath] ? true : false + } + + function clearSelectedAssets() + { + root.selectedAssets = {} + } + + function setAssetSelected(itemPath, selected) + { + root.selectedAssets[itemPath] = selected + root.selectedAssetsChanged() + } + + function _getDelegateParentForIndex(index) + { + let parentIndex = assetsModel.parentDirIndex(index) + let parentCell = root.cellAtIndex(parentIndex) + return root.itemAtCell(parentCell) + } + + function _modelIndex(row) + { + // The modelIndex() function exists since 6.3. In Qt 6.3, this modelIndex() function was a + // member of the TreeView, while in Qt6.4 it was moved to TableView. In Qt6.4, the order of + // the arguments was changed. + if (assetsRoot.qtVersionAtLeast6_4) + return root.modelIndex(0, row) + else + return root.modelIndex(row, 0) + } + + delegate: AssetDelegate { + assetsView: root + assetsRoot: root.assetsRoot + indentation: 5 + } +} // TreeView diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/ConfirmDeleteFolderDialog.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/ConfirmDeleteFolderDialog.qml index a4fd300975e..c623af862f5 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/ConfirmDeleteFolderDialog.qml +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/ConfirmDeleteFolderDialog.qml @@ -1,38 +1,13 @@ -/**************************************************************************** -** -** Copyright (C) 2022 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt Creator. -** -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -****************************************************************************/ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 import QtQuick import QtQuick.Controls -import QtQuick.Layouts -import QtQuickDesignerTheme -import HelperWidgets -import StudioControls as StudioControls +import HelperWidgets as HelperWidgets import StudioTheme as StudioTheme Dialog { - id: confirmDeleteFolderDialog + id: root title: qsTr("Folder Not Empty") anchors.centerIn: parent @@ -40,6 +15,9 @@ Dialog { implicitWidth: 300 modal: true + required property string dirName + required property var dirIndex + contentItem: Column { spacing: 20 width: parent.width @@ -47,11 +25,10 @@ Dialog { Text { id: folderNotEmpty - text: qsTr("Folder \"%1\" is not empty. Delete it anyway?") - .arg(root.contextDir ? root.contextDir.dirName : "") + text: qsTr("Folder \"%1\" is not empty. Delete it anyway?").arg(root.dirName) color: StudioTheme.Values.themeTextColor wrapMode: Text.WordWrap - width: confirmDeleteFolderDialog.width + width: root.width leftPadding: 10 rightPadding: 10 @@ -63,27 +40,27 @@ Dialog { text: qsTr("If the folder has assets in use, deleting it might cause the project to not work correctly.") color: StudioTheme.Values.themeTextColor wrapMode: Text.WordWrap - width: confirmDeleteFolderDialog.width + width: root.width leftPadding: 10 rightPadding: 10 } Row { anchors.right: parent.right - Button { + HelperWidgets.Button { id: btnDelete text: qsTr("Delete") onClicked: { - assetsModel.deleteFolder(root.contextDir.dirPath) - confirmDeleteFolderDialog.accept() + assetsModel.deleteFolderRecursively(root.dirIndex) + root.accept() } } - Button { + HelperWidgets.Button { text: qsTr("Cancel") - onClicked: confirmDeleteFolderDialog.reject() + onClicked: root.reject() } } } diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/ErrorDialog.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/ErrorDialog.qml new file mode 100644 index 00000000000..ce5989e2e6c --- /dev/null +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/ErrorDialog.qml @@ -0,0 +1,40 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Controls +import HelperWidgets as HelperWidgets +import StudioTheme as StudioTheme + +Dialog { + id: root + + required property string message + + anchors.centerIn: parent + closePolicy: Popup.CloseOnEscape + implicitWidth: 300 + modal: true + + contentItem: Column { + spacing: 20 + width: parent.width + + Text { + text: root.message + color: StudioTheme.Values.themeTextColor + wrapMode: Text.WordWrap + width: root.width + leftPadding: 10 + rightPadding: 10 + } + + HelperWidgets.Button { + text: qsTr("Close") + anchors.right: parent.right + onClicked: root.reject() + } + } + + onOpened: root.forceActiveFocus() +} diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/NewFolderDialog.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/NewFolderDialog.qml index 130026ddce1..c48bb93a5bf 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/NewFolderDialog.qml +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/NewFolderDialog.qml @@ -1,44 +1,35 @@ -/**************************************************************************** -** -** Copyright (C) 2022 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt Creator. -** -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -****************************************************************************/ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 import QtQuick import QtQuick.Controls -import QtQuick.Layouts -import QtQuickDesignerTheme -import HelperWidgets +import HelperWidgets as HelperWidgets import StudioControls as StudioControls import StudioTheme as StudioTheme Dialog { - id: newFolderDialog + id: root title: qsTr("Create New Folder") anchors.centerIn: parent closePolicy: Popup.CloseOnEscape modal: true + required property string dirPath + property string createdDirPath: "" + readonly property int _maxPath: 260 + + HelperWidgets.RegExpValidator { + id: folderNameValidator + regExp: /^(\w[^*/> root._maxPath + } + Item { // spacer width: 1 height: 20 @@ -76,20 +78,23 @@ Dialog { Row { anchors.right: parent.right - Button { + HelperWidgets.Button { id: btnCreate text: qsTr("Create") - enabled: folderName.text !== "" + enabled: folderName.text !== "" && root.createdDirPath.length <= root._maxPath onClicked: { - assetsModel.addNewFolder(root.contextDir.dirPath + '/' + folderName.text) - newFolderDialog.accept() + root.createdDirPath = root.dirPath + '/' + folderName.text + if (assetsModel.addNewFolder(root.createdDirPath)) + root.accept() + else + creationFailedDialog.open() } } - Button { + HelperWidgets.Button { text: qsTr("Cancel") - onClicked: newFolderDialog.reject() + onClicked: root.reject() } } } @@ -99,4 +104,8 @@ Dialog { folderName.selectAll() folderName.forceActiveFocus() } + + onRejected: { + root.createdDirPath = "" + } } diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/RenameFolderDialog.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/RenameFolderDialog.qml index 351c0a35fc8..e94ba47f732 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/RenameFolderDialog.qml +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/RenameFolderDialog.qml @@ -1,38 +1,14 @@ -/**************************************************************************** -** -** Copyright (C) 2022 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt Creator. -** -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -****************************************************************************/ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 import QtQuick import QtQuick.Controls -import QtQuick.Layouts -import QtQuickDesignerTheme -import HelperWidgets +import HelperWidgets as HelperWidgets import StudioControls as StudioControls import StudioTheme as StudioTheme Dialog { - id: renameFolderDialog + id: root title: qsTr("Rename Folder") anchors.centerIn: parent @@ -41,6 +17,13 @@ Dialog { modal: true property bool renameError: false + required property string dirPath + required property string dirName + + HelperWidgets.RegExpValidator { + id: folderNameValidator + regExp: /^(\w[^*/> AssetsLibraryDir::childAssetsDirs() const -{ - if (m_dirsModel) - return m_dirsModel->assetsDirs(); - - return {}; -} - -void AssetsLibraryDir::addDir(AssetsLibraryDir *assetsDir) -{ - if (!m_dirsModel) - m_dirsModel = new AssetsLibraryDirsModel(this); - - m_dirsModel->addDir(assetsDir); -} - -void AssetsLibraryDir::addFile(const QString &filePath) -{ - if (!m_filesModel) - m_filesModel = new AssetsLibraryFilesModel(this); - - m_filesModel->addFile(filePath); -} - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarydir.h b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarydir.h deleted file mode 100644 index eb98c700234..00000000000 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarydir.h +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (C) 2021 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 - -namespace QmlDesigner { - -class AssetsLibraryDirsModel; -class AssetsLibraryFilesModel; - -class AssetsLibraryDir : public QObject -{ - Q_OBJECT - - Q_PROPERTY(QString dirName READ dirName NOTIFY dirNameChanged) - Q_PROPERTY(QString dirPath READ dirPath NOTIFY dirPathChanged) - Q_PROPERTY(bool dirExpanded READ dirExpanded WRITE setDirExpanded NOTIFY dirExpandedChanged) - Q_PROPERTY(bool dirVisible READ dirVisible WRITE setDirVisible NOTIFY dirVisibleChanged) - Q_PROPERTY(int dirDepth READ dirDepth NOTIFY dirDepthChanged) - Q_PROPERTY(QObject *filesModel READ filesModel NOTIFY filesModelChanged) - Q_PROPERTY(QObject *dirsModel READ dirsModel NOTIFY dirsModelChanged) - -public: - AssetsLibraryDir(const QString &path, int depth, bool expanded = true, QObject *parent = nullptr); - - QString dirName() const; - QString dirPath() const; - int dirDepth() const; - - bool dirExpanded() const; - bool dirVisible() const; - void setDirExpanded(bool expand); - void setDirVisible(bool visible); - - QObject *filesModel() const; - QObject *dirsModel() const; - - QList childAssetsDirs() const; - - void addDir(AssetsLibraryDir *assetsDir); - void addFile(const QString &filePath); - -signals: - void dirNameChanged(); - void dirPathChanged(); - void dirDepthChanged(); - void dirExpandedChanged(); - void dirVisibleChanged(); - void filesModelChanged(); - void dirsModelChanged(); - -private: - QString m_dirPath; - int m_dirDepth = 0; - bool m_dirExpanded = true; - bool m_dirVisible = true; - AssetsLibraryDirsModel *m_dirsModel = nullptr; - AssetsLibraryFilesModel *m_filesModel = nullptr; -}; - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarydirsmodel.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarydirsmodel.cpp deleted file mode 100644 index 499380accb0..00000000000 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarydirsmodel.cpp +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (C) 2021 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 - -#include "assetslibrarydirsmodel.h" -#include "assetslibrarymodel.h" - -#include -#include - -namespace QmlDesigner { - -AssetsLibraryDirsModel::AssetsLibraryDirsModel(QObject *parent) - : QAbstractListModel(parent) -{ - // add roles - const QMetaObject meta = AssetsLibraryDir::staticMetaObject; - for (int i = meta.propertyOffset(); i < meta.propertyCount(); ++i) - m_roleNames.insert(i, meta.property(i).name()); -} - -QVariant AssetsLibraryDirsModel::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) { - qWarning() << Q_FUNC_INFO << "Invalid index requested: " << QString::number(index.row()); - return {}; - } - - if (m_roleNames.contains(role)) - return m_dirs[index.row()]->property(m_roleNames[role]); - - qWarning() << Q_FUNC_INFO << "Invalid role requested: " << QString::number(role); - return {}; -} - -bool AssetsLibraryDirsModel::setData(const QModelIndex &index, const QVariant &value, int role) -{ - // currently only dirExpanded property is updatable - if (index.isValid() && m_roleNames.contains(role)) { - QVariant currValue = m_dirs.at(index.row())->property(m_roleNames.value(role)); - if (currValue != value) { - m_dirs.at(index.row())->setProperty(m_roleNames.value(role), value); - if (m_roleNames.value(role) == "dirExpanded") - AssetsLibraryModel::saveExpandedState(value.toBool(), m_dirs.at(index.row())->dirPath()); - emit dataChanged(index, index, {role}); - return true; - } - } - return false; -} - -int AssetsLibraryDirsModel::rowCount([[maybe_unused]] const QModelIndex &parent) const -{ - return m_dirs.size(); -} - -QHash AssetsLibraryDirsModel::roleNames() const -{ - return m_roleNames; -} - -void AssetsLibraryDirsModel::addDir(AssetsLibraryDir *assetsDir) -{ - m_dirs.append(assetsDir); -} - -const QList AssetsLibraryDirsModel::assetsDirs() const -{ - return m_dirs; -} - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarydirsmodel.h b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarydirsmodel.h deleted file mode 100644 index 7939d1ea5b9..00000000000 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarydirsmodel.h +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (C) 2021 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 -#include "assetslibrarydir.h" - -namespace QmlDesigner { - -class AssetsLibraryDirsModel : public QAbstractListModel -{ - Q_OBJECT - -public: - AssetsLibraryDirsModel(QObject *parent = nullptr); - - QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override; - bool setData(const QModelIndex &index, const QVariant &value, int role) override; - int rowCount(const QModelIndex & parent = QModelIndex()) const override; - QHash roleNames() const override; - - void addDir(AssetsLibraryDir *assetsDir); - - const QList assetsDirs() const; - -private: - QList m_dirs; - QHash m_roleNames; -}; - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryfilesmodel.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryfilesmodel.cpp deleted file mode 100644 index bf8824ae36e..00000000000 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryfilesmodel.cpp +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (C) 2021 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 -#include "assetslibraryfilesmodel.h" - -#include - -namespace QmlDesigner { - -AssetsLibraryFilesModel::AssetsLibraryFilesModel(QObject *parent) - : QAbstractListModel(parent) -{ - // add roles - m_roleNames.insert(FileNameRole, "fileName"); - m_roleNames.insert(FilePathRole, "filePath"); - m_roleNames.insert(FileDirRole, "fileDir"); -} - -QVariant AssetsLibraryFilesModel::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) { - qWarning() << Q_FUNC_INFO << "Invalid index requested: " << QString::number(index.row()); - return {}; - } - - if (role == FileNameRole) - return m_files[index.row()].split('/').last(); - - if (role == FilePathRole) - return m_files[index.row()]; - - if (role == FileDirRole) - return QVariant::fromValue(parent()); - - qWarning() << Q_FUNC_INFO << "Invalid role requested: " << QString::number(role); - return {}; -} - -int AssetsLibraryFilesModel::rowCount([[maybe_unused]] const QModelIndex &parent) const -{ - return m_files.size(); -} - -QHash AssetsLibraryFilesModel::roleNames() const -{ - return m_roleNames; -} - -void AssetsLibraryFilesModel::addFile(const QString &filePath) -{ - m_files.append(filePath); -} - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryfilesmodel.h b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryfilesmodel.h deleted file mode 100644 index 103e57ecd2f..00000000000 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryfilesmodel.h +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (C) 2021 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 - -namespace QmlDesigner { - -class AssetsLibraryFilesModel : public QAbstractListModel -{ - Q_OBJECT - -public: - AssetsLibraryFilesModel(QObject *parent = nullptr); - - QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override; - int rowCount(const QModelIndex & parent = QModelIndex()) const override; - QHash roleNames() const override; - - void addFile(const QString &filePath); - -private: - enum Roles {FileNameRole = Qt::UserRole + 1, - FilePathRole, - FileDirRole}; - - QStringList m_files; - QHash m_roleNames; -}; - -} // QmlDesigner diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.cpp index 90f3b011ff8..bc56e9b7f3f 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.cpp @@ -20,15 +20,48 @@ AssetsLibraryIconProvider::AssetsLibraryIconProvider(SynchronousImageCache &font QPixmap AssetsLibraryIconProvider::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) { QPixmap pixmap; + + if (m_thumbnails.contains(id)) { + pixmap = m_thumbnails[id]; + } else { + pixmap = fetchPixmap(id, requestedSize); + if (pixmap.isNull()) + pixmap = Utils::StyleHelper::dpiSpecificImageFile(":/AssetsLibrary/images/assets_default.png"); + + if (requestedSize.isValid()) + pixmap = pixmap.scaled(requestedSize, Qt::KeepAspectRatio); + + m_thumbnails[id] = pixmap; + } + + if (size) { + size->setWidth(pixmap.width()); + size->setHeight(pixmap.height()); + } + + return pixmap; +} + +QPixmap AssetsLibraryIconProvider::generateFontIcons(const QString &filePath, const QSize &requestedSize) const +{ + QSize reqSize = requestedSize.isValid() ? requestedSize : QSize{48, 48}; + return m_fontImageCache.icon(filePath, {}, + ImageCache::FontCollectorSizesAuxiliaryData{Utils::span{iconSizes}, + Theme::getColor(Theme::DStextColor).name(), + "Abc"}).pixmap(reqSize); +} + +QPixmap AssetsLibraryIconProvider::fetchPixmap(const QString &id, const QSize &requestedSize) const +{ const QString suffix = "*." + id.split('.').last().toLower(); if (id == "browse") { - pixmap = Utils::StyleHelper::dpiSpecificImageFile(":/AssetsLibrary/images/browse.png"); + return Utils::StyleHelper::dpiSpecificImageFile(":/AssetsLibrary/images/browse.png"); } else if (AssetsLibraryModel::supportedFontSuffixes().contains(suffix)) { - pixmap = generateFontIcons(id, requestedSize); + return generateFontIcons(id, requestedSize); } else if (AssetsLibraryModel::supportedImageSuffixes().contains(suffix)) { - pixmap = Utils::StyleHelper::dpiSpecificImageFile(id); + return Utils::StyleHelper::dpiSpecificImageFile(id); } else if (AssetsLibraryModel::supportedTexture3DSuffixes().contains(suffix)) { - pixmap = HdrImage{id}.toPixmap(); + return HdrImage{id}.toPixmap(); } else { QString type; if (AssetsLibraryModel::supportedShaderSuffixes().contains(suffix)) @@ -43,31 +76,20 @@ QPixmap AssetsLibraryIconProvider::requestPixmap(const QString &id, QSize *size, QString pathTemplate = QString(":/AssetsLibrary/images/asset_%1%2.png").arg(type); QString path = pathTemplate.arg('_' + QString::number(requestedSize.width())); - pixmap = Utils::StyleHelper::dpiSpecificImageFile(QFileInfo::exists(path) ? path - : pathTemplate.arg("")); + return Utils::StyleHelper::dpiSpecificImageFile(QFileInfo::exists(path) + ? path + : pathTemplate.arg("")); } - - if (size) { - size->setWidth(pixmap.width()); - size->setHeight(pixmap.height()); - } - - if (pixmap.isNull()) - pixmap = Utils::StyleHelper::dpiSpecificImageFile(":/AssetsLibrary/images/assets_default.png"); - - if (requestedSize.isValid()) - return pixmap.scaled(requestedSize, Qt::KeepAspectRatio); - - return pixmap; } -QPixmap AssetsLibraryIconProvider::generateFontIcons(const QString &filePath, const QSize &requestedSize) const +void AssetsLibraryIconProvider::clearCache() { - QSize reqSize = requestedSize.isValid() ? requestedSize : QSize{48, 48}; - return m_fontImageCache.icon(filePath, {}, - ImageCache::FontCollectorSizesAuxiliaryData{Utils::span{iconSizes}, - Theme::getColor(Theme::DStextColor).name(), - "Abc"}).pixmap(reqSize); + m_thumbnails.clear(); +} + +void AssetsLibraryIconProvider::invalidateThumbnail(const QString &id) +{ + m_thumbnails.remove(id); } } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.h b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.h index cf473c61e32..b18d8e70111 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.h +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.h @@ -15,9 +15,12 @@ public: AssetsLibraryIconProvider(SynchronousImageCache &fontImageCache); QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override; + void clearCache(); + void invalidateThumbnail(const QString &id); private: QPixmap generateFontIcons(const QString &filePath, const QSize &requestedSize) const; + QPixmap fetchPixmap(const QString &id, const QSize &requestedSize) const; SynchronousImageCache &m_fontImageCache; @@ -26,6 +29,7 @@ private: std::vector iconSizes = {{128, 128}, // Drag {96, 96}, // list @2x {48, 48}}; // list + QHash m_thumbnails; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp index e18ce4352f0..d1b93448b7e 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp @@ -1,68 +1,44 @@ // Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 -#include "assetslibrarymodel.h" -#include "assetslibrarydirsmodel.h" -#include "assetslibraryfilesmodel.h" +#include +#include +#include +#include +#include +#include + +#include "assetslibrarymodel.h" -#include -#include -#include -#include -#include #include #include #include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static Q_LOGGING_CATEGORY(assetsLibraryBenchmark, "qtc.assetsLibrary.setRoot", QtWarningMsg) +#include namespace QmlDesigner { -AssetsLibraryModel::AssetsLibraryModel(Utils::FileSystemWatcher *fileSystemWatcher, QObject *parent) - : QAbstractListModel(parent) - , m_fileSystemWatcher(fileSystemWatcher) +AssetsLibraryModel::AssetsLibraryModel(QObject *parent) + : QSortFilterProxyModel{parent} { - // add role names - int role = 0; - const QMetaObject meta = AssetsLibraryDir::staticMetaObject; - for (int i = meta.propertyOffset(); i < meta.propertyCount(); ++i) - m_roleNames.insert(role++, meta.property(i).name()); + createBackendModel(); + + setRecursiveFilteringEnabled(true); } -void AssetsLibraryModel::setSearchText(const QString &searchText) +void AssetsLibraryModel::createBackendModel() { - if (m_searchText != searchText) { - m_searchText = searchText; - refresh(); - } -} + m_sourceFsModel = new QFileSystemModel(parent()); -void AssetsLibraryModel::saveExpandedState(bool expanded, const QString &assetPath) -{ - m_expandedStateHash.insert(assetPath, expanded); -} + m_sourceFsModel->setReadOnly(false); -bool AssetsLibraryModel::loadExpandedState(const QString &assetPath) -{ - return m_expandedStateHash.value(assetPath, true); + setSourceModel(m_sourceFsModel); + QObject::connect(m_sourceFsModel, &QFileSystemModel::directoryLoaded, this, &AssetsLibraryModel::directoryLoaded); + QObject::connect(m_sourceFsModel, &QFileSystemModel::dataChanged, this, &AssetsLibraryModel::onDataChanged); + + QObject::connect(m_sourceFsModel, &QFileSystemModel::directoryLoaded, this, [this](const QString &dir) { + syncHaveFiles(); + }); } bool AssetsLibraryModel::isEffectQmlExist(const QString &effectName) @@ -72,50 +48,57 @@ bool AssetsLibraryModel::isEffectQmlExist(const QString &effectName) return qmlPath.exists(); } -AssetsLibraryModel::DirExpandState AssetsLibraryModel::getAllExpandedState() const +void AssetsLibraryModel::onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, + const QList &roles) { - const auto keys = m_expandedStateHash.keys(); - bool allExpanded = true; - bool allCollapsed = true; - for (const QString &assetPath : keys) { - bool expanded = m_expandedStateHash.value(assetPath); + for (int i = topLeft.row(); i <= bottomRight.row(); ++i) { + QModelIndex index = m_sourceFsModel->index(i, 0, topLeft.parent()); + QString path = m_sourceFsModel->filePath(index); - if (expanded) - allCollapsed = false; - if (!expanded) - allExpanded = false; - - if (!allCollapsed && !allExpanded) - break; + if (!isDirectory(path)) + emit fileChanged(path); } - - return allExpanded ? DirExpandState::AllExpanded : allCollapsed ? DirExpandState::AllCollapsed - : DirExpandState::SomeExpanded; } -void AssetsLibraryModel::toggleExpandAll(bool expand) +void AssetsLibraryModel::destroyBackendModel() { - std::function expandDirRecursive; - expandDirRecursive = [&](AssetsLibraryDir *currAssetsDir) { - if (currAssetsDir->dirDepth() > 0) { - currAssetsDir->setDirExpanded(expand); - saveExpandedState(expand, currAssetsDir->dirPath()); - } + setSourceModel(nullptr); + m_sourceFsModel->disconnect(this); + m_sourceFsModel->deleteLater(); + m_sourceFsModel = nullptr; +} - const QList childDirs = currAssetsDir->childAssetsDirs(); - for (const auto childDir : childDirs) - expandDirRecursive(childDir); - }; +void AssetsLibraryModel::setSearchText(const QString &searchText) +{ + m_searchText = searchText; + resetModel(); +} - beginResetModel(); - expandDirRecursive(m_assetsDir); - endResetModel(); +bool AssetsLibraryModel::indexIsValid(const QModelIndex &index) const +{ + static QModelIndex invalidIndex; + return index != invalidIndex; +} + +QList AssetsLibraryModel::parentIndices(const QModelIndex &index) const +{ + QModelIndex idx = index; + QModelIndex rootIdx = rootIndex(); + QList result; + + while (idx.isValid() && idx != rootIdx) { + result += idx; + idx = idx.parent(); + } + + return result; } void AssetsLibraryModel::deleteFiles(const QStringList &filePaths) { - bool askBeforeDelete = QmlDesignerPlugin::settings().value( - DesignerSettingsKey::ASK_BEFORE_DELETING_ASSET).toBool(); + bool askBeforeDelete = QmlDesignerPlugin::settings() + .value(DesignerSettingsKey::ASK_BEFORE_DELETING_ASSET) + .toBool(); bool assetDelete = true; if (askBeforeDelete) { @@ -123,7 +106,7 @@ void AssetsLibraryModel::deleteFiles(const QStringList &filePaths) tr("File%1 might be in use. Delete anyway?\n\n%2") .arg(filePaths.size() > 1 ? QChar('s') : QChar()) .arg(filePaths.join('\n').remove(DocumentManager::currentProjectDirPath() - .toString().append('/'))), + .toString().append('/'))), QMessageBox::No | QMessageBox::Yes); QCheckBox cb; cb.setText(tr("Do not ask this again")); @@ -162,15 +145,13 @@ bool AssetsLibraryModel::renameFolder(const QString &folderPath, const QString & dir.cdUp(); - saveExpandedState(loadExpandedState(folderPath), dir.absoluteFilePath(newName)); - return dir.rename(oldName, newName); } -void AssetsLibraryModel::addNewFolder(const QString &folderPath) +bool AssetsLibraryModel::addNewFolder(const QString &folderPath) { QString iterPath = folderPath; - QRegularExpression rgx("\\d+$"); // matches a number at the end of a string + static QRegularExpression rgx("\\d+$"); // matches a number at the end of a string QDir dir{folderPath}; while (dir.exists()) { @@ -191,8 +172,8 @@ void AssetsLibraryModel::addNewFolder(const QString &folderPath) --nPaddingZeros; iterPath = folderPath.mid(0, match.capturedStart()) - + QString('0').repeated(nPaddingZeros) - + QString::number(num); + + QString('0').repeated(nPaddingZeros) + + QString::number(num); } else { iterPath = folderPath + '1'; } @@ -200,136 +181,155 @@ void AssetsLibraryModel::addNewFolder(const QString &folderPath) dir.setPath(iterPath); } - dir.mkpath(iterPath); + return dir.mkpath(iterPath); } -void AssetsLibraryModel::deleteFolder(const QString &folderPath) +bool AssetsLibraryModel::deleteFolderRecursively(const QModelIndex &folderIndex) { - QDir{folderPath}.removeRecursively(); + auto idx = mapToSource(folderIndex); + bool ok = m_sourceFsModel->remove(idx); + if (!ok) + qWarning() << __FUNCTION__ << " could not remove folder recursively: " << m_sourceFsModel->filePath(idx); + + return ok; } -QObject *AssetsLibraryModel::rootDir() const +bool AssetsLibraryModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { - return m_assetsDir; -} + QString path = m_sourceFsModel->filePath(sourceParent); -bool AssetsLibraryModel::isEmpty() const -{ - return m_isEmpty; -} + QModelIndex sourceIdx = m_sourceFsModel->index(sourceRow, 0, sourceParent); + QString sourcePath = m_sourceFsModel->filePath(sourceIdx); -void AssetsLibraryModel::setIsEmpty(bool empty) -{ - if (m_isEmpty != empty) { - m_isEmpty = empty; - emit isEmptyChanged(); + if (!m_searchText.isEmpty() && path.startsWith(m_rootPath) && QFileInfo{path}.isDir()) { + QString sourceName = m_sourceFsModel->fileName(sourceIdx); + + return QFileInfo{sourcePath}.isFile() && sourceName.contains(m_searchText, Qt::CaseInsensitive); + } else { + return sourcePath.startsWith(m_rootPath) || m_rootPath.startsWith(sourcePath); } } -QVariant AssetsLibraryModel::data(const QModelIndex &index, int role) const +bool AssetsLibraryModel::checkHaveFiles(const QModelIndex &parentIdx) const { - if (!index.isValid()) { - qWarning() << Q_FUNC_INFO << "Invalid index requested: " << QString::number(index.row()); - return {}; + if (!parentIdx.isValid()) + return false; + + const int rowCount = this->rowCount(parentIdx); + for (int i = 0; i < rowCount; ++i) { + auto newIdx = this->index(i, 0, parentIdx); + if (!isDirectory(newIdx)) + return true; + + if (checkHaveFiles(newIdx)) + return true; } - if (m_roleNames.contains(role)) - return m_assetsDir ? m_assetsDir->property(m_roleNames.value(role)) : QVariant(""); - - qWarning() << Q_FUNC_INFO << "Invalid role requested: " << QString::number(role); - return {}; + return false; } -int AssetsLibraryModel::rowCount([[maybe_unused]] const QModelIndex &parent) const +void AssetsLibraryModel::setHaveFiles(bool value) { - return 1; + if (m_haveFiles != value) { + m_haveFiles = value; + emit haveFilesChanged(); + } } -QHash AssetsLibraryModel::roleNames() const +bool AssetsLibraryModel::checkHaveFiles() const { - return m_roleNames; + auto rootIdx = indexForPath(m_rootPath); + return checkHaveFiles(rootIdx); } -// called when a directory is changed to refresh the model for this directory -void AssetsLibraryModel::refresh() +void AssetsLibraryModel::syncHaveFiles() { - setRootPath(m_assetsDir->dirPath()); + setHaveFiles(checkHaveFiles()); } -void AssetsLibraryModel::setRootPath(const QString &path) +void AssetsLibraryModel::setRootPath(const QString &newPath) { - QElapsedTimer time; - if (assetsLibraryBenchmark().isInfoEnabled()) - time.start(); - - qCInfo(assetsLibraryBenchmark) << "start:" << time.elapsed(); - - static const QStringList ignoredTopLevelDirs {"imports", "asset_imports"}; - - m_fileSystemWatcher->clear(); - - std::function parseDir; - parseDir = [this, &parseDir](AssetsLibraryDir *currAssetsDir, int currDepth, bool recursive) { - m_fileSystemWatcher->addDirectory(currAssetsDir->dirPath(), Utils::FileSystemWatcher::WatchAllChanges); - - QDir dir(currAssetsDir->dirPath()); - dir.setNameFilters(supportedSuffixes().values()); - dir.setFilter(QDir::Files); - QDirIterator itFiles(dir); - bool isEmpty = true; - while (itFiles.hasNext()) { - QString filePath = itFiles.next(); - QString fileName = filePath.split('/').last(); - if (m_searchText.isEmpty() || fileName.contains(m_searchText, Qt::CaseInsensitive)) { - currAssetsDir->addFile(filePath); - m_fileSystemWatcher->addFile(filePath, Utils::FileSystemWatcher::WatchAllChanges); - isEmpty = false; - } - } - - if (recursive) { - dir.setNameFilters({}); - dir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot); - QDirIterator itDirs(dir); - - while (itDirs.hasNext()) { - QDir subDir = itDirs.next(); - if (currDepth == 1 && ignoredTopLevelDirs.contains(subDir.dirName())) - continue; - - auto assetsDir = new AssetsLibraryDir(subDir.path(), currDepth, - loadExpandedState(subDir.path()), currAssetsDir); - currAssetsDir->addDir(assetsDir); - saveExpandedState(loadExpandedState(assetsDir->dirPath()), assetsDir->dirPath()); - isEmpty &= parseDir(assetsDir, currDepth + 1, true); - } - } - - if (!m_searchText.isEmpty() && isEmpty) - currAssetsDir->setDirVisible(false); - - return isEmpty; - }; - - qCInfo(assetsLibraryBenchmark) << "directories parsed:" << time.elapsed(); - - if (m_assetsDir) - delete m_assetsDir; - beginResetModel(); - m_assetsDir = new AssetsLibraryDir(path, 0, true, this); - bool hasProject = !QmlDesignerPlugin::instance()->documentManager().currentProjectDirPath().isEmpty(); - bool isEmpty = parseDir(m_assetsDir, 1, hasProject); - setIsEmpty(isEmpty); - bool noAssets = m_searchText.isEmpty() && isEmpty; - // noAssets: the model has no asset files (project has no assets added) - // isEmpty: the model has no asset files (assets could exist but are filtered out) + destroyBackendModel(); + createBackendModel(); + + m_rootPath = newPath; + m_sourceFsModel->setRootPath(newPath); + + m_sourceFsModel->setNameFilters(supportedSuffixes().values()); + m_sourceFsModel->setNameFilterDisables(false); - m_assetsDir->setDirVisible(!noAssets); // if there are no assets, hide all empty asset folders endResetModel(); - qCInfo(assetsLibraryBenchmark) << "model reset:" << time.elapsed(); + emit rootPathChanged(); +} + +QString AssetsLibraryModel::rootPath() const +{ + return m_rootPath; +} + +QString AssetsLibraryModel::filePath(const QModelIndex &index) const +{ + QModelIndex fsIdx = mapToSource(index); + return m_sourceFsModel->filePath(fsIdx); +} + +QString AssetsLibraryModel::fileName(const QModelIndex &index) const +{ + QModelIndex fsIdx = mapToSource(index); + return m_sourceFsModel->fileName(fsIdx); +} + +QModelIndex AssetsLibraryModel::indexForPath(const QString &path) const +{ + QModelIndex idx = m_sourceFsModel->index(path, 0); + return mapFromSource(idx); +} + +void AssetsLibraryModel::resetModel() +{ + beginResetModel(); + endResetModel(); +} + +QModelIndex AssetsLibraryModel::rootIndex() const +{ + return indexForPath(m_rootPath); +} + +bool AssetsLibraryModel::isDirectory(const QString &path) const +{ + QFileInfo fi{path}; + return fi.isDir(); +} + +bool AssetsLibraryModel::isDirectory(const QModelIndex &index) const +{ + QString path = filePath(index); + return isDirectory(path); +} + +QModelIndex AssetsLibraryModel::parentDirIndex(const QString &path) const +{ + QModelIndex idx = indexForPath(path); + QModelIndex parentIdx = idx.parent(); + + return parentIdx; +} + +QModelIndex AssetsLibraryModel::parentDirIndex(const QModelIndex &index) const +{ + QModelIndex parentIdx = index.parent(); + return parentIdx; +} + +QString AssetsLibraryModel::parentDirPath(const QString &path) const +{ + QModelIndex idx = indexForPath(path); + QModelIndex parentIdx = idx.parent(); + return filePath(parentIdx); } const QStringList &AssetsLibraryModel::supportedImageSuffixes() @@ -408,17 +408,4 @@ const QSet &AssetsLibraryModel::supportedSuffixes() return allSuffixes; } -const QSet &AssetsLibraryModel::previewableSuffixes() const -{ - static QSet previewableSuffixes; - if (previewableSuffixes.isEmpty()) { - auto insertSuffixes = [](const QStringList &suffixes) { - for (const auto &suffix : suffixes) - previewableSuffixes.insert(suffix); - }; - insertSuffixes(supportedFontSuffixes()); - } - return previewableSuffixes; -} - } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h index c7d3ee493b4..6e555f07e23 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h @@ -3,39 +3,53 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include -namespace Utils { class FileSystemWatcher; } +#include namespace QmlDesigner { -class SynchronousImageCache; -class AssetsLibraryDir; - -class AssetsLibraryModel : public QAbstractListModel +class AssetsLibraryModel : public QSortFilterProxyModel { Q_OBJECT - Q_PROPERTY(bool isEmpty READ isEmpty WRITE setIsEmpty NOTIFY isEmptyChanged) - public: - AssetsLibraryModel(Utils::FileSystemWatcher *fileSystemWatcher, QObject *parent = nullptr); + AssetsLibraryModel(QObject *parent = nullptr); - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - QHash roleNames() const override; - - void refresh(); - void setRootPath(const QString &path); + void setRootPath(const QString &newPath); void setSearchText(const QString &searchText); - bool isEmpty() const; + Q_PROPERTY(bool haveFiles READ haveFiles NOTIFY haveFilesChanged); + + Q_INVOKABLE QString rootPath() const; + Q_INVOKABLE QString filePath(const QModelIndex &index) const; + Q_INVOKABLE QString fileName(const QModelIndex &index) const; + + Q_INVOKABLE QModelIndex indexForPath(const QString &path) const; + Q_INVOKABLE QModelIndex rootIndex() const; + Q_INVOKABLE bool isDirectory(const QString &path) const; + Q_INVOKABLE bool isDirectory(const QModelIndex &index) const; + Q_INVOKABLE QModelIndex parentDirIndex(const QString &path) const; + Q_INVOKABLE QModelIndex parentDirIndex(const QModelIndex &index) const; + Q_INVOKABLE QString parentDirPath(const QString &path) const; + Q_INVOKABLE void syncHaveFiles(); + + Q_INVOKABLE QList parentIndices(const QModelIndex &index) const; + Q_INVOKABLE bool indexIsValid(const QModelIndex &index) const; + Q_INVOKABLE void deleteFiles(const QStringList &filePaths); + Q_INVOKABLE bool renameFolder(const QString &folderPath, const QString &newName); + Q_INVOKABLE bool addNewFolder(const QString &folderPath); + Q_INVOKABLE bool deleteFolderRecursively(const QModelIndex &folderIndex); + + int columnCount(const QModelIndex &parent = QModelIndex()) const override + { + int result = QSortFilterProxyModel::columnCount(parent); + return std::min(result, 1); + } + + bool haveFiles() const { return m_haveFiles; } static const QStringList &supportedImageSuffixes(); static const QStringList &supportedFragmentShaderSuffixes(); @@ -47,44 +61,28 @@ public: static const QStringList &supportedEffectMakerSuffixes(); static const QSet &supportedSuffixes(); - const QSet &previewableSuffixes() const; - - static void saveExpandedState(bool expanded, const QString &assetPath); - static bool loadExpandedState(const QString &assetPath); - static bool isEffectQmlExist(const QString &effectName); - enum class DirExpandState { - SomeExpanded, - AllExpanded, - AllCollapsed - }; - Q_ENUM(DirExpandState) - - Q_INVOKABLE void toggleExpandAll(bool expand); - Q_INVOKABLE DirExpandState getAllExpandedState() const; - Q_INVOKABLE void deleteFiles(const QStringList &filePaths); - Q_INVOKABLE bool renameFolder(const QString &folderPath, const QString &newName); - Q_INVOKABLE void addNewFolder(const QString &folderPath); - Q_INVOKABLE void deleteFolder(const QString &folderPath); - Q_INVOKABLE QObject *rootDir() const; - signals: - void isEmptyChanged(); + void directoryLoaded(const QString &path); + void rootPathChanged(); + void haveFilesChanged(); + void fileChanged(const QString &path); private: - - void setIsEmpty(bool empty); - - QHash> m_iconCache; + void setHaveFiles(bool value); + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + void onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList &roles); + void resetModel(); + void createBackendModel(); + void destroyBackendModel(); + bool checkHaveFiles(const QModelIndex &parentIdx) const; + bool checkHaveFiles() const; QString m_searchText; - Utils::FileSystemWatcher *m_fileSystemWatcher = nullptr; - AssetsLibraryDir *m_assetsDir = nullptr; - bool m_isEmpty = true; - - QHash m_roleNames; - inline static QHash m_expandedStateHash; // + QString m_rootPath; + QFileSystemModel *m_sourceFsModel = nullptr; + bool m_haveFiles = false; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp index a335227e47d..2858febe9f9 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp @@ -17,7 +17,6 @@ #include #include -#include #include #include #include @@ -85,16 +84,12 @@ bool AssetsLibraryWidget::eventFilter(QObject *obj, QEvent *event) AssetsLibraryWidget::AssetsLibraryWidget(AsynchronousImageCache &asynchronousFontImageCache, SynchronousImageCache &synchronousFontImageCache) - : m_itemIconSize(24, 24) - , m_fontImageCache(synchronousFontImageCache) - , m_assetsIconProvider(new AssetsLibraryIconProvider(synchronousFontImageCache)) - , m_fileSystemWatcher(new Utils::FileSystemWatcher(this)) - , m_assetsModel(new AssetsLibraryModel(m_fileSystemWatcher, this)) - , m_assetsWidget(new QQuickWidget(this)) + : m_itemIconSize{24, 24} + , m_fontImageCache{synchronousFontImageCache} + , m_assetsIconProvider{new AssetsLibraryIconProvider(synchronousFontImageCache)} + , m_assetsModel{new AssetsLibraryModel(this)} + , m_assetsWidget{new QQuickWidget(this)} { - m_assetCompressionTimer.setInterval(200); - m_assetCompressionTimer.setSingleShot(true); - setWindowTitle(tr("Assets Library", "Title of assets library widget")); setMinimumWidth(250); @@ -119,21 +114,12 @@ AssetsLibraryWidget::AssetsLibraryWidget(AsynchronousImageCache &asynchronousFon m_assetsWidget->setClearColor(Theme::getColor(Theme::Color::QmlDesigner_BackgroundColorDarkAlternate)); m_assetsWidget->engine()->addImageProvider("qmldesigner_assets", m_assetsIconProvider); m_assetsWidget->rootContext()->setContextProperties(QVector{ - {{"assetsModel"}, QVariant::fromValue(m_assetsModel.data())}, + {{"assetsModel"}, QVariant::fromValue(m_assetsModel)}, {{"rootView"}, QVariant::fromValue(this)}, {{"tooltipBackend"}, QVariant::fromValue(m_fontPreviewTooltipBackend.get())} }); - // If project directory contents change, or one of the asset files is modified, we must - // reconstruct the model to update the icons - connect(m_fileSystemWatcher, - &Utils::FileSystemWatcher::directoryChanged, - [this]([[maybe_unused]] const QString &changedDirPath) { - m_assetCompressionTimer.start(); - }); - - connect(m_fileSystemWatcher, &Utils::FileSystemWatcher::fileChanged, - [](const QString &changeFilePath) { + connect(m_assetsModel, &AssetsLibraryModel::fileChanged, [](const QString &changeFilePath) { QmlDesignerPlugin::instance()->emitAssetChanged(changeFilePath); }); @@ -149,23 +135,7 @@ AssetsLibraryWidget::AssetsLibraryWidget(AsynchronousImageCache &asynchronousFon m_qmlSourceUpdateShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_F6), this); connect(m_qmlSourceUpdateShortcut, &QShortcut::activated, this, &AssetsLibraryWidget::reloadQmlSource); - - connect(&m_assetCompressionTimer, &QTimer::timeout, this, [this]() { - // TODO: find a clever way to only refresh the changed directory part of the model - - // Don't bother with asset updates after model has detached, project is probably closing - if (!m_model.isNull()) { - if (QApplication::activeModalWidget()) { - // Retry later, as updating file system watchers can crash when there is an active - // modal widget - m_assetCompressionTimer.start(); - } else { - m_assetsModel->refresh(); - // reload assets qml so that an overridden file's image shows the new image - QTimer::singleShot(100, this, &AssetsLibraryWidget::reloadQmlSource); - } - } - }); + connect(this, &AssetsLibraryWidget::extFilesDrop, this, &AssetsLibraryWidget::handleExtFilesDrop, Qt::QueuedConnection); QmlDesignerPlugin::trackWidgetFocusTime(this, Constants::EVENT_ASSETSLIBRARY_TIME); @@ -173,7 +143,15 @@ AssetsLibraryWidget::AssetsLibraryWidget(AsynchronousImageCache &asynchronousFon reloadQmlSource(); } -AssetsLibraryWidget::~AssetsLibraryWidget() = default; +bool AssetsLibraryWidget::qtVersionIsAtLeast6_4() const +{ + return (QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)); +} + +void AssetsLibraryWidget::invalidateThumbnail(const QString &id) +{ + m_assetsIconProvider->invalidateThumbnail(id); +} QList AssetsLibraryWidget::createToolBarWidgets() { @@ -182,8 +160,9 @@ QList AssetsLibraryWidget::createToolBarWidgets() void AssetsLibraryWidget::handleSearchFilterChanged(const QString &filterText) { - if (filterText == m_filterText || (m_assetsModel->isEmpty() && filterText.contains(m_filterText))) - return; + if (filterText == m_filterText || (!m_assetsModel->haveFiles() + && filterText.contains(m_filterText, Qt::CaseInsensitive))) + return; m_filterText = filterText; updateSearch(); @@ -194,6 +173,16 @@ void AssetsLibraryWidget::handleAddAsset() addResources({}); } +void AssetsLibraryWidget::emitExtFilesDrop(const QList &simpleFilePaths, + const QList &complexFilePaths, + const QString &targetDirPath) +{ + // workaround for but QDS-8010: we need to postpone the call to handleExtFilesDrop, otherwise + // the TreeViewDelegate might be recreated (therefore, destroyed) while we're still in a handler + // of a QML DropArea which is a child of the delegate being destroyed - this would cause a crash. + emit extFilesDrop(simpleFilePaths, complexFilePaths, targetDirPath); +} + void AssetsLibraryWidget::handleExtFilesDrop(const QList &simpleFilePaths, const QList &complexFilePaths, const QString &targetDirPath) @@ -210,7 +199,7 @@ void AssetsLibraryWidget::handleExtFilesDrop(const QList &simpleFilePaths, } else { AddFilesResult result = ModelNodeOperations::addFilesToProject(simpleFilePathStrings, targetDirPath); - if (result == AddFilesResult::Failed) { + if (result.status() == AddFilesResult::Failed) { Core::AsynchronousMessageBox::warning(tr("Failed to Add Files"), tr("Could not add %1 to project.") .arg(simpleFilePathStrings.join(' '))); @@ -276,6 +265,7 @@ void AssetsLibraryWidget::updateSearch() void AssetsLibraryWidget::setResourcePath(const QString &resourcePath) { m_assetsModel->setRootPath(resourcePath); + m_assetsIconProvider->clearCache(); updateSearch(); } @@ -408,10 +398,22 @@ void AssetsLibraryWidget::addResources(const QStringList &files) if (operation) { AddFilesResult result = operation(fileNames, document->fileName().parentDir().toString(), true); - if (result == AddFilesResult::Failed) { + if (result.status() == AddFilesResult::Failed) { Core::AsynchronousMessageBox::warning(tr("Failed to Add Files"), tr("Could not add %1 to project.") .arg(fileNames.join(' '))); + } else { + if (!result.directory().isEmpty()) { + emit directoryCreated(result.directory()); + } else if (result.haveDelayedResult()) { + QObject *delayedResult = result.delayedResult(); + QObject::connect(delayedResult, &QObject::destroyed, this, [this, delayedResult]() { + QVariant propValue = delayedResult->property(AddFilesResult::directoryPropName); + QString directory = propValue.toString(); + if (!directory.isEmpty()) + emit directoryCreated(directory); + }); + } } } else { Core::AsynchronousMessageBox::warning(tr("Failed to Add Files"), diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h index 8ac41a44a36..1c19c65a3e2 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h @@ -21,7 +21,7 @@ class QShortcut; QT_END_NAMESPACE namespace Utils { - class FileSystemWatcher; + class QtcProcess; } namespace QmlDesigner { @@ -42,7 +42,7 @@ class AssetsLibraryWidget : public QFrame public: AssetsLibraryWidget(AsynchronousImageCache &asynchronousFontImageCache, SynchronousImageCache &synchronousFontImageCache); - ~AssetsLibraryWidget(); + ~AssetsLibraryWidget() = default; QList createToolBarWidgets(); @@ -59,14 +59,26 @@ public: Q_INVOKABLE void startDragAsset(const QStringList &assetPaths, const QPointF &mousePos); Q_INVOKABLE void handleAddAsset(); Q_INVOKABLE void handleSearchFilterChanged(const QString &filterText); + Q_INVOKABLE void handleExtFilesDrop(const QList &simpleFilePaths, const QList &complexFilePaths, - const QString &targetDirPath = {}); + const QString &targetDirPath); + + Q_INVOKABLE void emitExtFilesDrop(const QList &simpleFilePaths, + const QList &complexFilePaths, + const QString &targetDirPath = {}); + Q_INVOKABLE QSet supportedAssetSuffixes(bool complex); Q_INVOKABLE void openEffectMaker(const QString &filePath); + Q_INVOKABLE bool qtVersionIsAtLeast6_4() const; + Q_INVOKABLE void invalidateThumbnail(const QString &id); signals: void itemActivated(const QString &itemName); + void extFilesDrop(const QList &simpleFilePaths, + const QList &complexFilePaths, + const QString &targetDirPath); + void directoryCreated(const QString &path); protected: bool eventFilter(QObject *obj, QEvent *event) override; @@ -77,14 +89,12 @@ private: void addResources(const QStringList &files); void updateSearch(); - QTimer m_assetCompressionTimer; QSize m_itemIconSize; SynchronousImageCache &m_fontImageCache; AssetsLibraryIconProvider *m_assetsIconProvider = nullptr; - Utils::FileSystemWatcher *m_fileSystemWatcher = nullptr; - QPointer m_assetsModel; + AssetsLibraryModel *m_assetsModel = nullptr; QScopedPointer m_assetsWidget; std::unique_ptr m_fontPreviewTooltipBackend; diff --git a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp index bb60e2a1c9d..e99d94014f3 100644 --- a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp @@ -277,7 +277,7 @@ QHash DesignerActionManager::handleExternalAssetsDrop(cons AddResourceOperation operation = categoryOperation.value(category); QStringList files = categoryFiles.value(category); AddFilesResult result = operation(files, {}, true); - if (result == AddFilesResult::Succeeded) + if (result.status() == AddFilesResult::Succeeded) addedCategoryFiles.insert(category, files); } diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp index 9edf0869e74..df7f701c4e5 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp +++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp @@ -1042,10 +1042,10 @@ AddFilesResult addFilesToProject(const QStringList &fileNames, const QString &de { QString directory = showDialog ? AddImagesDialog::getDirectory(fileNames, defaultDir) : defaultDir; if (directory.isEmpty()) - return AddFilesResult::Cancelled; + return AddFilesResult::cancelled(directory); DesignDocument *document = QmlDesignerPlugin::instance()->currentDesignDocument(); - QTC_ASSERT(document, return AddFilesResult::Failed); + QTC_ASSERT(document, return AddFilesResult::failed(directory)); QList> copyList; QStringList removeList; @@ -1073,7 +1073,7 @@ AddFilesResult addFilesToProject(const QStringList &fileNames, const QString &de for (const auto &filePair : std::as_const(copyList)) { const bool success = QFile::copy(filePair.first, filePair.second); if (!success) - return AddFilesResult::Failed; + return AddFilesResult::failed(directory); ProjectExplorer::Node *node = ProjectExplorer::ProjectTree::nodeForFile(document->fileName()); if (node) { @@ -1083,7 +1083,7 @@ AddFilesResult addFilesToProject(const QStringList &fileNames, const QString &de } } - return AddFilesResult::Succeeded; + return AddFilesResult::succeeded(directory); } static QString getAssetDefaultDirectory(const QString &assetDir, const QString &defaultDirectory) diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h index 279e18af974..be238a04397 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h +++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h @@ -9,7 +9,48 @@ namespace QmlDesigner { -enum class AddFilesResult { Succeeded, Failed, Cancelled }; +class AddFilesResult +{ +public: + enum Status { Succeeded, Failed, Cancelled, Delayed }; + static constexpr char directoryPropName[] = "directory"; + + static AddFilesResult cancelled(const QString &directory = {}) + { + return AddFilesResult{Cancelled, directory}; + } + + static AddFilesResult failed(const QString &directory = {}) + { + return AddFilesResult{Failed, directory}; + } + + static AddFilesResult succeeded(const QString &directory = {}) + { + return AddFilesResult{Succeeded, directory}; + } + + static AddFilesResult delayed(QObject *delayedResult) + { + return AddFilesResult{Delayed, {}, delayedResult}; + } + + Status status() const { return m_status; } + QString directory() const { return m_directory; } + bool haveDelayedResult() const { return m_delayedResult != nullptr; } + QObject *delayedResult() const { return m_delayedResult; } + +private: + AddFilesResult(Status status, const QString &directory, QObject *delayedResult = nullptr) + : m_status{status} + , m_directory{directory} + , m_delayedResult{delayedResult} + {} + + Status m_status; + QString m_directory; + QObject *m_delayedResult = nullptr; +}; namespace ModelNodeOperations { diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp index 28aa071a8ec..8071363e2a7 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp @@ -55,7 +55,7 @@ WidgetInfo ContentLibraryView::widgetInfo() // copy image to project AddFilesResult result = ModelNodeOperations::addImageToProject({texPath}, "images", false); - if (result == AddFilesResult::Failed) { + if (result.status() == AddFilesResult::Failed) { Core::AsynchronousMessageBox::warning(tr("Failed to Add Texture"), tr("Could not add %1 to project.").arg(texPath)); return; diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp index 3e56c6a0e6d..33d3b8a717d 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp @@ -156,7 +156,7 @@ void ItemLibraryView::updateImport3DSupport(const QVariantMap &supportMap) Core::ICore::dialogParent()); int result = importDlg->exec(); - return result == QDialog::Accepted ? AddFilesResult::Succeeded : AddFilesResult::Cancelled; + return result == QDialog::Accepted ? AddFilesResult::succeeded() : AddFilesResult::cancelled(); }; auto add3DHandler = [&](const QString &group, const QString &ext) { From d0a07dcacb70eaa58b36cd9fc3b37f8c8ce8106c Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Wed, 23 Nov 2022 09:32:45 +0100 Subject: [PATCH 025/131] QmlJS: std::set instead of QList This reduces the CPU time of Export::visibleInVContext() from 50% to 15% on Windows, when switching files. Change-Id: Iff82924c47d1b696c9d3d7ca40f49d9d02bcb3e6 Reviewed-by: hjk Reviewed-by: Thomas Hartmann --- src/libs/qmljs/qmljslink.cpp | 3 ++- src/libs/qmljs/qmljsmodelmanagerinterface.cpp | 4 ++-- src/libs/qmljs/qmljsviewercontext.h | 4 +++- .../qmldesigner/designercore/metainfo/nodemetainfo.cpp | 2 +- .../qmldesigner/designercore/model/rewriterview.cpp | 6 ++++-- src/plugins/qmljstools/qmljsmodelmanager.cpp | 2 +- tests/auto/qml/codemodel/importscheck/tst_importscheck.cpp | 7 +++++-- 7 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/libs/qmljs/qmljslink.cpp b/src/libs/qmljs/qmljslink.cpp index 6c04966cedd..2ce98efad3e 100644 --- a/src/libs/qmljs/qmljslink.cpp +++ b/src/libs/qmljs/qmljslink.cpp @@ -123,7 +123,8 @@ Link::Link(const Snapshot &snapshot, const ViewerContext &vContext, const Librar { d->m_valueOwner = new ValueOwner; d->m_snapshot = snapshot; - d->m_importPaths = vContext.paths; + const QList list(vContext.paths.begin(), vContext.paths.end()); + d->m_importPaths = list; d->m_applicationDirectories = vContext.applicationDirectories; d->m_builtins = builtins; d->m_vContext = vContext; diff --git a/src/libs/qmljs/qmljsmodelmanagerinterface.cpp b/src/libs/qmljs/qmljsmodelmanagerinterface.cpp index 70eebf6faf6..6d3fda5af38 100644 --- a/src/libs/qmljs/qmljsmodelmanagerinterface.cpp +++ b/src/libs/qmljs/qmljsmodelmanagerinterface.cpp @@ -63,8 +63,8 @@ static const char *qtQuickUISuffix = "ui.qml"; static void maybeAddPath(ViewerContext &context, const Utils::FilePath &path) { - if (!path.isEmpty() && !context.paths.contains(path)) - context.paths.append(path); + if (!path.isEmpty() && !context.paths.count(path) > 0) + context.paths.insert(path); } static QList environmentImportPaths() diff --git a/src/libs/qmljs/qmljsviewercontext.h b/src/libs/qmljs/qmljsviewercontext.h index 7d22136844e..043f56b108f 100644 --- a/src/libs/qmljs/qmljsviewercontext.h +++ b/src/libs/qmljs/qmljsviewercontext.h @@ -8,6 +8,8 @@ #include +#include + namespace QmlJS { struct QMLJS_EXPORT ViewerContext @@ -21,7 +23,7 @@ struct QMLJS_EXPORT ViewerContext }; QStringList selectors; - QList paths; + std::set paths; QList applicationDirectories; Dialect language = Dialect::Qml; Flags flags = AddAllPaths; diff --git a/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp b/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp index 927b762ffec..cdda880f84b 100644 --- a/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp +++ b/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp @@ -149,7 +149,7 @@ static QString qualifiedTypeNameForContext(const ObjectValue *objectValue, if (!cImport.valid()) break; for (const Export &e : std::as_const(cImport.possibleExports)) { - if (e.pathRequired.isEmpty() || vContext.paths.contains(e.pathRequired)) { + if (e.pathRequired.isEmpty() || vContext.paths.count(e.pathRequired) > 0) { switch (e.exportName.type) { case ImportType::Library: { diff --git a/src/plugins/qmldesigner/designercore/model/rewriterview.cpp b/src/plugins/qmldesigner/designercore/model/rewriterview.cpp index 1a09a070fd3..89ad9c0ab92 100644 --- a/src/plugins/qmldesigner/designercore/model/rewriterview.cpp +++ b/src/plugins/qmldesigner/designercore/model/rewriterview.cpp @@ -1005,8 +1005,10 @@ QString RewriterView::pathForImport(const Import &import) QStringList RewriterView::importDirectories() const { - return Utils::transform(m_textToModelMerger->vContext().paths, - [](const Utils::FilePath &p) { return p.toString(); }); + const QList list(m_textToModelMerger->vContext().paths.begin(), + m_textToModelMerger->vContext().paths.end()); + + return Utils::transform(list, [](const Utils::FilePath &p) { return p.toString(); }); } QSet > RewriterView::qrcMapping() const diff --git a/src/plugins/qmljstools/qmljsmodelmanager.cpp b/src/plugins/qmljstools/qmljsmodelmanager.cpp index 66b92ebabe0..149897b7520 100644 --- a/src/plugins/qmljstools/qmljsmodelmanager.cpp +++ b/src/plugins/qmljstools/qmljsmodelmanager.cpp @@ -281,7 +281,7 @@ void ModelManager::delayedInitialization() ViewerContext qbsVContext; qbsVContext.language = Dialect::QmlQbs; - qbsVContext.paths.append(ICore::resourcePath("qbs")); + qbsVContext.paths.insert(ICore::resourcePath("qbs")); setDefaultVContext(qbsVContext); } diff --git a/tests/auto/qml/codemodel/importscheck/tst_importscheck.cpp b/tests/auto/qml/codemodel/importscheck/tst_importscheck.cpp index 45d5f51577e..8f300db02f8 100644 --- a/tests/auto/qml/codemodel/importscheck/tst_importscheck.cpp +++ b/tests/auto/qml/codemodel/importscheck/tst_importscheck.cpp @@ -58,7 +58,7 @@ void scanDirectory(const QString &dir) ModelManagerInterface::instance(), false); ModelManagerInterface::instance()->test_joinAllThreads(); ViewerContext vCtx; - vCtx.paths.append(dirPath); + vCtx.paths.insert(dirPath); Snapshot snap = ModelManagerInterface::instance()->snapshot(); ImportDependencies *iDeps = snap.importDependencies(); @@ -176,7 +176,10 @@ void tst_ImportCheck::test() ModelManagerInterface::instance(), false); ModelManagerInterface::instance()->test_joinAllThreads(); ViewerContext vCtx; - vCtx.paths.append(pathPaths); + + for (const Utils::FilePath &path : pathPaths) + vCtx.paths.insert(path); + Snapshot snap = ModelManagerInterface::instance()->snapshot(); ImportDependencies *iDeps = snap.importDependencies(); From 3f5259dd00a3d1cfd14c6253955e74b44d76c61e Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Wed, 23 Nov 2022 16:34:45 +0100 Subject: [PATCH 026/131] QmlDesigner: Fix crash Change-Id: Ic996823ab316f956367ed801d0b8974481fbfb4e Reviewed-by: Vikas Pachdha --- .../qmldesigner/designercore/model/texttomodelmerger.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp index 9857451a4e8..3990eee76ee 100644 --- a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp +++ b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp @@ -498,8 +498,6 @@ public: qDebug() << Q_FUNC_INFO; qDebug() << astTypeNode->name.toString() << typeName; qDebug() << metaInfo.isValid() << metaInfo.typeName(); - if (metaInfo.isValid()) - qDebug() << metaInfo.superClasses().front().typeName(); } typeName = QString::fromUtf8(metaInfo.typeName()); From a3753deebeb881806686721967437790d9169bb6 Mon Sep 17 00:00:00 2001 From: Vikas Pachdha Date: Wed, 23 Nov 2022 19:19:00 +0100 Subject: [PATCH 027/131] Fix build error QList::paths is changed to std::set Change-Id: Ib996723ab316f956367ed801d0b897a481fbfb4a Reviewed-by: Thomas Hartmann --- src/libs/qmljs/qmljsimportdependencies.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/qmljs/qmljsimportdependencies.cpp b/src/libs/qmljs/qmljsimportdependencies.cpp index db8e7905440..df87a89cd8e 100644 --- a/src/libs/qmljs/qmljsimportdependencies.cpp +++ b/src/libs/qmljs/qmljsimportdependencies.cpp @@ -525,7 +525,7 @@ Export::Export(ImportKey exportName, bool Export::visibleInVContext(const ViewerContext &vContext) const { - return pathRequired.isEmpty() || vContext.paths.contains(pathRequired); + return pathRequired.isEmpty() || vContext.paths.count(pathRequired); } CoreImport::CoreImport() : language(Dialect::Qml) { } From 6943322b4292f89a472bbd920251b0add723e27a Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Wed, 23 Nov 2022 13:59:18 +0200 Subject: [PATCH 028/131] QmlDesigner: Suppress some unused params warnings Change-Id: Ib54014fdc6d1256393e60b2d2df17fa3cd4573e4 Reviewed-by: Miikka Heikkinen --- .../components/materialeditor/materialeditorview.cpp | 6 +++--- .../components/textureeditor/textureeditorview.cpp | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp index 00352c50cf5..8e2c5fe9fc0 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp @@ -1107,9 +1107,9 @@ void MaterialEditorView::customNotification([[maybe_unused]] const AbstractView } void MaterialEditorView::nodeReparented(const ModelNode &node, - const NodeAbstractProperty &newPropertyParent, - const NodeAbstractProperty &oldPropertyParent, - PropertyChangeFlags propertyChange) + [[maybe_unused]] const NodeAbstractProperty &newPropertyParent, + [[maybe_unused]] const NodeAbstractProperty &oldPropertyParent, + [[maybe_unused]] PropertyChangeFlags propertyChange) { if (node.id() == Constants::MATERIAL_LIB_ID && m_qmlBackEnd && m_qmlBackEnd->contextObject()) m_qmlBackEnd->contextObject()->setHasMaterialLibrary(true); diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp b/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp index 4464818de7d..a4cc362ffbc 100644 --- a/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp @@ -624,9 +624,9 @@ void TextureEditorView::propertiesAboutToBeRemoved(const QList } void TextureEditorView::nodeReparented(const ModelNode &node, - const NodeAbstractProperty &newPropertyParent, - const NodeAbstractProperty &oldPropertyParent, - PropertyChangeFlags propertyChange) + [[maybe_unused]] const NodeAbstractProperty &newPropertyParent, + [[maybe_unused]] const NodeAbstractProperty &oldPropertyParent, + [[maybe_unused]] PropertyChangeFlags propertyChange) { if (node.id() == Constants::MATERIAL_LIB_ID && m_qmlBackEnd && m_qmlBackEnd->contextObject()) m_qmlBackEnd->contextObject()->setHasMaterialLibrary(true); @@ -779,7 +779,7 @@ void TextureEditorView::duplicateTexture(const ModelNode &texture) void TextureEditorView::customNotification([[maybe_unused]] const AbstractView *view, const QString &identifier, const QList &nodeList, - const QList &data) + [[maybe_unused]] const QList &data) { if (identifier == "selected_texture_changed") { if (!m_hasTextureRoot) { From 07e96c299a514628c541ccfd8957a8b14a07b27a Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Wed, 23 Nov 2022 12:55:12 +0100 Subject: [PATCH 029/131] QmlDesigner: Allow to disable possible imports Getting all possible imports can be slow and is not required in every context. Change-Id: I75c50e0f6600dfa8fca7dfec26382783b084bdb9 Reviewed-by: Vikas Pachdha --- .../qmldesigner/designercore/include/rewriterview.h | 4 ++++ .../qmldesigner/designercore/model/rewriterview.cpp | 10 ++++++++++ .../designercore/model/texttomodelmerger.cpp | 3 +++ 3 files changed, 17 insertions(+) diff --git a/src/plugins/qmldesigner/designercore/include/rewriterview.h b/src/plugins/qmldesigner/designercore/include/rewriterview.h index d95d811261c..56dc01c215d 100644 --- a/src/plugins/qmldesigner/designercore/include/rewriterview.h +++ b/src/plugins/qmldesigner/designercore/include/rewriterview.h @@ -167,6 +167,9 @@ public: void resetPossibleImports(); + bool possibleImportsEnabled() const; + void setPossibleImportsEnabled(bool b); + signals: void modelInterfaceProjectUpdated(); @@ -212,6 +215,7 @@ private: //variables bool m_restoringAuxData = false; bool m_modelAttachPending = false; bool m_allowComponentRoot = false; + bool m_possibleImportsEnabled = true; mutable QHash m_canonicalIntModelNode; mutable QHash m_canonicalModelNodeInt; diff --git a/src/plugins/qmldesigner/designercore/model/rewriterview.cpp b/src/plugins/qmldesigner/designercore/model/rewriterview.cpp index 89ad9c0ab92..4a9ab62814c 100644 --- a/src/plugins/qmldesigner/designercore/model/rewriterview.cpp +++ b/src/plugins/qmldesigner/designercore/model/rewriterview.cpp @@ -693,6 +693,16 @@ void RewriterView::resetPossibleImports() m_textToModelMerger->clearPossibleImportKeys(); } +bool RewriterView::possibleImportsEnabled() const +{ + return m_possibleImportsEnabled; +} + +void RewriterView::setPossibleImportsEnabled(bool b) +{ + m_possibleImportsEnabled = b; +} + Internal::ModelNodePositionStorage *RewriterView::positionStorage() const { return m_positionStorage.data(); diff --git a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp index 3990eee76ee..5ae83880fe4 100644 --- a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp +++ b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp @@ -984,6 +984,9 @@ static QList generatePossibleLibraryImports(const QHashpossibleImportsEnabled()) + return; + static QUrl lastProjectUrl; auto &externalDependencies = m_rewriterView->externalDependencies(); auto projectUrl = externalDependencies.projectUrl(); From 34b236e7fb3ea4f10221b7693f02df59f774e3f5 Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Wed, 23 Nov 2022 16:18:50 +0200 Subject: [PATCH 030/131] QmlDesigner: Disable assigning a texture to a model with no materials Disable apply texture to selected model option from texture editor's toolbar when the selected model has no material. Also relevant fixes to make sure texture assigning happens in the current state. Fixes: QDS-8395 Change-Id: Iab2e8fce4696c6bd5d50636b4077362ba04cb8a0 Reviewed-by: Miikka Heikkinen --- .../TextureEditorToolBar.qml | 2 +- .../materialbrowser/materialbrowserview.cpp | 9 ++++----- .../materialbrowser/materialbrowserview.h | 2 +- .../textureeditor/textureeditorcontextobject.cpp | 12 ++++++------ .../textureeditor/textureeditorcontextobject.h | 11 ++++++----- .../textureeditor/textureeditorview.cpp | 16 +++++++++++++++- .../designercore/include/qmlobjectnode.h | 1 + .../designercore/model/qmlobjectnode.cpp | 15 ++++++++++----- 8 files changed, 44 insertions(+), 24 deletions(-) diff --git a/share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorToolBar.qml b/share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorToolBar.qml index ed40b038a72..e0f0e1324d3 100644 --- a/share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorToolBar.qml +++ b/share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorToolBar.qml @@ -28,7 +28,7 @@ Rectangle { normalColor: StudioTheme.Values.themeSectionHeadBackground iconSize: StudioTheme.Values.bigIconFontSize buttonSize: root.height - enabled: hasTexture && hasModelSelection && hasQuick3DImport && hasMaterialLibrary + enabled: hasTexture && hasSingleModelSelection && hasQuick3DImport && hasMaterialLibrary onClicked: root.toolBarAction(ToolBarAction.ApplyToSelected) tooltip: qsTr("Apply texture to selected model's material.") } diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp index 5599cd585f9..79fc5cb4b61 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp @@ -481,9 +481,9 @@ void MaterialBrowserView::instancePropertyChanged(const QList &completedNodeList) override; void instancePropertyChanged(const QList > &propertyList) override; - void applyTextureToModel3D(const ModelNode &model3D, const ModelNode &texture); + void applyTextureToModel3D(const QmlObjectNode &model3D, const ModelNode &texture); void applyTextureToMaterial(const QList &materials, const ModelNode &texture); Q_INVOKABLE void updatePropsModel(const QString &matId); diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.cpp b/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.cpp index 017661bfcfc..8ccd7b8fad7 100644 --- a/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.cpp +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.cpp @@ -145,18 +145,18 @@ void TextureEditorContextObject::setHasMaterialLibrary(bool b) emit hasMaterialLibraryChanged(); } -bool TextureEditorContextObject::hasModelSelection() const +bool TextureEditorContextObject::hasSingleModelSelection() const { - return m_hasModelSelection; + return m_hasSingleModelSelection; } -void TextureEditorContextObject::setHasModelSelection(bool b) +void TextureEditorContextObject::setHasSingleModelSelection(bool b) { - if (b == m_hasModelSelection) + if (b == m_hasSingleModelSelection) return; - m_hasModelSelection = b; - emit hasModelSelectionChanged(); + m_hasSingleModelSelection = b; + emit hasSingleModelSelectionChanged(); } void TextureEditorContextObject::setSelectedMaterial(const ModelNode &matNode) diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.h b/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.h index c2537589a95..2ad65821281 100644 --- a/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.h +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.h @@ -36,7 +36,8 @@ class TextureEditorContextObject : public QObject Q_PROPERTY(bool hasAliasExport READ hasAliasExport NOTIFY hasAliasExportChanged) Q_PROPERTY(bool hasActiveTimeline READ hasActiveTimeline NOTIFY hasActiveTimelineChanged) Q_PROPERTY(bool hasQuick3DImport READ hasQuick3DImport WRITE setHasQuick3DImport NOTIFY hasQuick3DImportChanged) - Q_PROPERTY(bool hasModelSelection READ hasModelSelection WRITE setHasModelSelection NOTIFY hasModelSelectionChanged) + Q_PROPERTY(bool hasSingleModelSelection READ hasSingleModelSelection WRITE setHasSingleModelSelection + NOTIFY hasSingleModelSelectionChanged) Q_PROPERTY(bool hasMaterialLibrary READ hasMaterialLibrary WRITE setHasMaterialLibrary NOTIFY hasMaterialLibraryChanged) Q_PROPERTY(QQmlPropertyMap *backendValues READ backendValues WRITE setBackendValues NOTIFY backendValuesChanged) @@ -91,8 +92,8 @@ public: bool hasMaterialLibrary() const; void setHasMaterialLibrary(bool b); - bool hasModelSelection() const; - void setHasModelSelection(bool b); + bool hasSingleModelSelection() const; + void setHasSingleModelSelection(bool b); bool hasAliasExport() const { return m_aliasExport; } @@ -124,7 +125,7 @@ signals: void hasActiveTimelineChanged(); void hasQuick3DImportChanged(); void hasMaterialLibraryChanged(); - void hasModelSelectionChanged(); + void hasSingleModelSelectionChanged(); private: QUrl m_specificsUrl; @@ -148,7 +149,7 @@ private: bool m_hasActiveTimeline = false; bool m_hasQuick3DImport = false; bool m_hasMaterialLibrary = false; - bool m_hasModelSelection = false; + bool m_hasSingleModelSelection = false; ModelNode m_selectedTexture; }; diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp b/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp index a4cc362ffbc..37dd6c3ffa9 100644 --- a/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp @@ -451,6 +451,8 @@ void TextureEditorView::setupQmlBackend() currentQmlBackend->contextObject()->setHasQuick3DImport(m_hasQuick3DImport); currentQmlBackend->contextObject()->setHasMaterialLibrary(materialLibraryNode().isValid()); currentQmlBackend->contextObject()->setSpecificQmlData(specificQmlData); + bool hasValidSelection = QmlObjectNode(m_selectedModel).hasBindingProperty("materials"); + currentQmlBackend->contextObject()->setHasSingleModelSelection(hasValidSelection); m_qmlBackEnd = currentQmlBackend; @@ -554,6 +556,11 @@ void TextureEditorView::propertiesRemoved(const QList &propert setValue(m_selectedTexture, property.name(), QmlObjectNode(m_selectedTexture).instanceValue(property.name())); } + if (property.name() == "materials" && (node == m_selectedModel + || QmlObjectNode(m_selectedModel).propertyChangeForCurrentState() == node)) { + m_qmlBackEnd->contextObject()->setHasSingleModelSelection(false); + } + dynamicPropertiesModel()->dispatchPropertyChanges(property); } } @@ -598,6 +605,12 @@ void TextureEditorView::bindingPropertiesChanged(const QList &p setValue(m_selectedTexture, property.name(), QmlObjectNode(m_selectedTexture).modelValue(property.name())); } + if (property.name() == "materials" && (node == m_selectedModel + || QmlObjectNode(m_selectedModel).propertyChangeForCurrentState() == node)) { + bool hasMaterials = QmlObjectNode(m_selectedModel).hasBindingProperty("materials"); + m_qmlBackEnd->contextObject()->setHasSingleModelSelection(hasMaterials); + } + dynamicPropertiesModel()->dispatchPropertyChanges(property); } } @@ -660,7 +673,8 @@ void TextureEditorView::selectedNodesChanged(const QList &selectedNod if (selectedNodeList.size() == 1 && selectedNodeList.at(0).metaInfo().isQtQuick3DModel()) m_selectedModel = selectedNodeList.at(0); - m_qmlBackEnd->contextObject()->setHasModelSelection(m_selectedModel.isValid()); + bool hasValidSelection = QmlObjectNode(m_selectedModel).hasBindingProperty("materials"); + m_qmlBackEnd->contextObject()->setHasSingleModelSelection(hasValidSelection); } void TextureEditorView::currentStateChanged(const ModelNode &node) diff --git a/src/plugins/qmldesigner/designercore/include/qmlobjectnode.h b/src/plugins/qmldesigner/designercore/include/qmlobjectnode.h index 5dd4d32ecaa..0d47b7e36bc 100644 --- a/src/plugins/qmldesigner/designercore/include/qmlobjectnode.h +++ b/src/plugins/qmldesigner/designercore/include/qmlobjectnode.h @@ -69,6 +69,7 @@ public: QVariant modelValue(const PropertyName &name) const; bool isTranslatableText(const PropertyName &name) const; QString stripedTranslatableText(const PropertyName &name) const; + BindingProperty bindingProperty(const PropertyName &name) const; QString expression(const PropertyName &name) const; bool isInBaseState() const; bool timelineIsActive() const; diff --git a/src/plugins/qmldesigner/designercore/model/qmlobjectnode.cpp b/src/plugins/qmldesigner/designercore/model/qmlobjectnode.cpp index c2e6ce107dc..b64cf2612ee 100644 --- a/src/plugins/qmldesigner/designercore/model/qmlobjectnode.cpp +++ b/src/plugins/qmldesigner/designercore/model/qmlobjectnode.cpp @@ -256,23 +256,28 @@ QString QmlObjectNode::stripedTranslatableText(const PropertyName &name) const return instanceValue(name).toString(); } -QString QmlObjectNode::expression(const PropertyName &name) const +BindingProperty QmlObjectNode::bindingProperty(const PropertyName &name) const { if (!isValid()) return {}; if (currentState().isBaseState()) - return modelNode().bindingProperty(name).expression(); + return modelNode().bindingProperty(name); if (!currentState().hasPropertyChanges(modelNode())) - return modelNode().bindingProperty(name).expression(); + return modelNode().bindingProperty(name); QmlPropertyChanges propertyChanges(currentState().propertyChanges(modelNode())); if (!propertyChanges.modelNode().hasProperty(name)) - return modelNode().bindingProperty(name).expression(); + return modelNode().bindingProperty(name); - return propertyChanges.modelNode().bindingProperty(name).expression(); + return propertyChanges.modelNode().bindingProperty(name); +} + +QString QmlObjectNode::expression(const PropertyName &name) const +{ + return bindingProperty(name).expression(); } /*! From fea463bb4a93f38c01dcf9d81abcdd8a892b72be Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Tue, 22 Nov 2022 17:09:31 +0200 Subject: [PATCH 031/131] QmlDesigner: Drag textures from content library to material browser Fixes: QDS-8337 Change-Id: Iee42341a18e4acaa0d455aef276df36013ebf21e Reviewed-by: Mahmoud Badri --- .../MaterialBrowser.qml | 31 +++++++++++++++++++ .../contentlibrary/contentlibraryview.cpp | 13 ++++++++ .../contentlibrary/contentlibraryview.h | 2 ++ .../contentlibrary/contentlibrarywidget.cpp | 11 +------ .../contentlibrary/contentlibrarywidget.h | 3 +- .../materialbrowser/materialbrowserwidget.cpp | 5 +++ .../materialbrowser/materialbrowserwidget.h | 1 + 7 files changed, 54 insertions(+), 12 deletions(-) diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml index 8b1acabe9e9..dda1907dcbb 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml @@ -234,6 +234,22 @@ Item { width: root.width caption: qsTr("Textures") + dropEnabled: true + + onDropEnter: (drag) => { + drag.accepted = drag.formats[0] === "application/vnd.qtdesignstudio.bundletexture" + highlight = drag.accepted + } + + onDropExit: { + highlight = false + } + + onDrop: { + highlight = false + rootView.acceptBundleTextureDrop() + } + Grid { width: scrollView.width leftPadding: 5 @@ -290,6 +306,21 @@ Item { enabled: root.enableUiElements } } + + DropArea { + id: masterDropArea + + property int emptyHeight: scrollView.height - materialsSection.height - texturesSection.height + + width: root.width + height: emptyHeight > 0 ? emptyHeight : 0 + + enabled: true + + onEntered: (drag) => texturesSection.dropEnter(drag) + onDropped: (drag) => texturesSection.drop(drag) + onExited: texturesSection.dropExit() + } } } } diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp index 8071363e2a7..e9221b160d3 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp @@ -8,6 +8,7 @@ #include "contentlibrarywidget.h" #include "contentlibrarymaterial.h" #include "contentlibrarymaterialsmodel.h" +#include "contentlibrarytexture.h" #include "contentlibrarytexturesmodel.h" #include "modelnodeoperations.h" #include "nodelistproperty.h" @@ -49,6 +50,10 @@ WidgetInfo ContentLibraryView::widgetInfo() [&] (QmlDesigner::ContentLibraryMaterial *mat) { m_draggedBundleMaterial = mat; }); + connect(m_widget, &ContentLibraryWidget::bundleTextureDragStarted, this, + [&] (QmlDesigner::ContentLibraryTexture *tex) { + m_draggedBundleTexture = tex; + }); connect(m_widget, &ContentLibraryWidget::addTextureRequested, this, [&] (const QString texPath, ContentLibraryWidget::AddTextureMode mode) { executeInTransaction("ContentLibraryView::widgetInfo", [&] { @@ -237,6 +242,14 @@ void ContentLibraryView::customNotification(const AbstractView *view, const QStr } m_draggedBundleMaterial = nullptr; + } else if (identifier == "drop_bundle_texture") { + ModelNode matLib = materialLibraryNode(); + if (!matLib.isValid()) + return; + + m_widget->addTexture(m_draggedBundleTexture); + + m_draggedBundleTexture = nullptr; } } diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h index a55d7d772fa..aae27c91406 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h @@ -12,6 +12,7 @@ namespace QmlDesigner { class ContentLibraryMaterial; +class ContentLibraryTexture; class ContentLibraryWidget; class Model; @@ -47,6 +48,7 @@ private: QList m_bundleMaterialTargets; QList m_selectedModels; // selected 3D model nodes ContentLibraryMaterial *m_draggedBundleMaterial = nullptr; + ContentLibraryTexture *m_draggedBundleTexture = nullptr; ModelNode m_activeSceneEnv; bool m_bundleMaterialAddToSelected = false; bool m_hasQuick3DImport = false; diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp index 99a24e70b40..729e41b6932 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp @@ -58,11 +58,6 @@ bool ContentLibraryWidget::eventFilter(QObject *obj, QEvent *event) mimeData->setData(Constants::MIME_TYPE_BUNDLE_MATERIAL, data); mimeData->removeFormat("text/plain"); - if (!m_draggedMaterial) { - m_draggedMaterial = m_materialToDrag; - emit draggedMaterialChanged(); - } - emit bundleMaterialDragStarted(m_materialToDrag); model->startDrag(mimeData, m_materialToDrag->icon().toLocalFile()); m_materialToDrag = nullptr; @@ -77,6 +72,7 @@ bool ContentLibraryWidget::eventFilter(QObject *obj, QEvent *event) mimeData->setData(Constants::MIME_TYPE_BUNDLE_TEXTURE, data); mimeData->removeFormat("text/plain"); + emit bundleTextureDragStarted(m_textureToDrag); model->startDrag(mimeData, m_textureToDrag->icon().toLocalFile()); m_textureToDrag = nullptr; } @@ -84,11 +80,6 @@ bool ContentLibraryWidget::eventFilter(QObject *obj, QEvent *event) } else if (event->type() == QMouseEvent::MouseButtonRelease) { m_materialToDrag = nullptr; m_textureToDrag = nullptr; - - if (m_draggedMaterial) { - m_draggedMaterial = nullptr; - emit draggedMaterialChanged(); - } } return QObject::eventFilter(obj, event); diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h index 1c14794c6c2..ec0b26cbc22 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h @@ -49,7 +49,7 @@ public: signals: void bundleMaterialDragStarted(QmlDesigner::ContentLibraryMaterial *bundleMat); - void draggedMaterialChanged(); + void bundleTextureDragStarted(QmlDesigner::ContentLibraryTexture *bundleTex); void addTextureRequested(const QString texPath, QmlDesigner::ContentLibraryWidget::AddTextureMode mode); protected: @@ -70,7 +70,6 @@ private: QString m_filterText; ContentLibraryMaterial *m_materialToDrag = nullptr; - ContentLibraryMaterial *m_draggedMaterial = nullptr; ContentLibraryTexture *m_textureToDrag = nullptr; QPoint m_dragStartPoint; }; diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp index d411d6b825c..541bb385f67 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp @@ -257,6 +257,11 @@ void MaterialBrowserWidget::acceptBundleMaterialDrop() m_materialBrowserView->emitCustomNotification("drop_bundle_material", {}, {}); // To ContentLibraryView } +void MaterialBrowserWidget::acceptBundleTextureDrop() +{ + m_materialBrowserView->emitCustomNotification("drop_bundle_texture", {}, {}); // To ContentLibraryView +} + QString MaterialBrowserWidget::qmlSourcesPath() { #ifdef SHARE_QML_PATH diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h index 8e868a69864..929900d52bb 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h @@ -52,6 +52,7 @@ public: Q_INVOKABLE void startDragMaterial(int index, const QPointF &mousePos); Q_INVOKABLE void startDragTexture(int index, const QPointF &mousePos); Q_INVOKABLE void acceptBundleMaterialDrop(); + Q_INVOKABLE void acceptBundleTextureDrop(); QQuickWidget *quickWidget() const; From 39d0a554b88e41375b771c042aadb64bd9c67d4f Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Thu, 24 Nov 2022 13:54:40 +0200 Subject: [PATCH 032/131] QmlDesigner: Fix initial positioning when dragging item to 2D view Fixes: QDS-8365 Change-Id: I624afcb15d800e5f5597a9e760843148d02d8975 Reviewed-by: Mahmoud Badri --- src/plugins/qmldesigner/designercore/model/qmlvisualnode.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/qmldesigner/designercore/model/qmlvisualnode.cpp b/src/plugins/qmldesigner/designercore/model/qmlvisualnode.cpp index 9df8e9ecd7c..8d08eaa8069 100644 --- a/src/plugins/qmldesigner/designercore/model/qmlvisualnode.cpp +++ b/src/plugins/qmldesigner/designercore/model/qmlvisualnode.cpp @@ -209,7 +209,7 @@ void QmlVisualNode::setDoubleProperty(const PropertyName &name, double value) void QmlVisualNode::setPosition(const QmlVisualNode::Position &position) { - if (!isValid()) + if (!modelNode().isValid()) return; if (!qFuzzyIsNull(position.x()) || modelNode().hasProperty("x")) From 09c4df833babd900544ece35328c232ec33858e2 Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Thu, 24 Nov 2022 15:30:33 +0200 Subject: [PATCH 033/131] QmlDesigner: Fix small typo in MaterialBrowser Fixes: QDS-8422 Change-Id: I9e1cfe677875ea8a7f58f1d52e7d1b9a3d4f8447 Reviewed-by: Miikka Heikkinen --- .../qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml index dda1907dcbb..920a1869ff9 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml @@ -281,7 +281,7 @@ Item { } Text { - text:qsTr("There are no texture in this project.") + text:qsTr("There are no textures in this project.") visible: materialBrowserTexturesModel.isEmpty && searchBox.isEmpty() textFormat: Text.RichText color: StudioTheme.Values.themeTextColor From 2388caa5440b5c87e39ee86c2921c0340e833dd6 Mon Sep 17 00:00:00 2001 From: Henning Gruendl Date: Thu, 24 Nov 2022 13:08:43 +0100 Subject: [PATCH 034/131] QmlDesigner: Fix wrong state group shown Fix an issue were the wrong state group is shown when switching modes e.g. Design > Edit > Design. It is caused by the model being attached and the indices not forwarded to QML. Task-number: QDS-8418 Change-Id: Ie9a99182b0230757a043397d9b906ba1bb15bed9 Reviewed-by: Reviewed-by: Thomas Hartmann --- .../components/stateseditornew/stateseditorview.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/plugins/qmldesigner/components/stateseditornew/stateseditorview.cpp b/src/plugins/qmldesigner/components/stateseditornew/stateseditorview.cpp index e005d6243db..1d96df2bc15 100644 --- a/src/plugins/qmldesigner/components/stateseditornew/stateseditorview.cpp +++ b/src/plugins/qmldesigner/components/stateseditornew/stateseditorview.cpp @@ -714,6 +714,9 @@ void StatesEditorView::modelAttached(Model *model) resetModel(); resetStateGroups(); + + emit m_statesEditorModel->activeStateGroupChanged(); + emit m_statesEditorModel->activeStateGroupIndexChanged(); } void StatesEditorView::modelAboutToBeDetached(Model *model) From ccbda4655e8ab8b39b9f6beaa53f628c33e06af6 Mon Sep 17 00:00:00 2001 From: Henning Gruendl Date: Thu, 24 Nov 2022 18:12:03 +0100 Subject: [PATCH 035/131] QmlDesigner: Fix wrong initial render type quality Task-number: QDS-8334 Change-Id: I3b79093c67fbde94504277a669448c19911ce97b Reviewed-by: Thomas Hartmann Reviewed-by: --- .../imports/HelperWidgets/TextExtrasSection.qml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/TextExtrasSection.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/TextExtrasSection.qml index 67598bb7605..fe4d7658150 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/TextExtrasSection.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/TextExtrasSection.qml @@ -119,9 +119,9 @@ Section { text: qsTr("Render type quality") tooltip: qsTr("Overrides the default rendering type quality for this component.") blockedByTemplate: !root.isBackendValueAvailable("renderTypeQuality") - enabled: backendValues.renderType !== undefined - ? backendValues.renderType.enumeration === "QtRendering" - : false + enabled: root.isBackendValueAvailable("renderTypeQuality") + && (backendValues.renderType.value === "QtRendering" + || backendValues.renderType.enumeration === "QtRendering") } SecondColumnLayout { @@ -134,7 +134,8 @@ Section { "HighRenderTypeQuality", "VeryHighRenderTypeQuality"] backendValue: backendValues.renderTypeQuality enabled: root.isBackendValueAvailable("renderTypeQuality") - && backendValues.renderType.enumeration === "QtRendering" + && (backendValues.renderType.value === "QtRendering" + || backendValues.renderType.enumeration === "QtRendering") } ExpandingSpacer {} From fd1864d6788c3a9ece3aa549316c7af8600de15e Mon Sep 17 00:00:00 2001 From: Henning Gruendl Date: Thu, 24 Nov 2022 17:45:34 +0100 Subject: [PATCH 036/131] QmlDesigner: Fix disappearing plus states editor * Fix issue the plus (Canvas) in the states editor is not drawn when the view was hidden before and comes back on * Improve paint method Task-number: QDS-8419 Change-Id: I959b4b6a418deba95380d8f5cafc7af09a0362e8 Reviewed-by: Thomas Hartmann Reviewed-by: --- .../qmldesigner/newstateseditor/Main.qml | 20 +++++++++++-------- .../stateseditornew/stateseditorwidget.cpp | 5 +++-- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/share/qtcreator/qmldesigner/newstateseditor/Main.qml b/share/qtcreator/qmldesigner/newstateseditor/Main.qml index 21385de257e..e40f7642388 100644 --- a/share/qtcreator/qmldesigner/newstateseditor/Main.qml +++ b/share/qtcreator/qmldesigner/newstateseditor/Main.qml @@ -45,6 +45,10 @@ Rectangle { onWidthChanged: root.responsiveResize(root.width, root.height) onHeightChanged: root.responsiveResize(root.width, root.height) + function showEvent() { + addCanvas.requestPaint() + } + Component.onCompleted: root.responsiveResize(root.width, root.height) function numFit(overall, size, space) { @@ -860,22 +864,22 @@ Rectangle { width: root.thumbWidth height: root.thumbHeight + property int plusExtend: 20 + property int halfWidth: addCanvas.width / 2 + property int halfHeight: addCanvas.height / 2 + onPaint: { var ctx = getContext("2d") ctx.strokeStyle = StudioTheme.Values.themeStateHighlight ctx.lineWidth = 6 - var plusExtend = 20 - var halfWidth = addCanvas.width / 2 - var halfHeight = addCanvas.height / 2 - ctx.beginPath() - ctx.moveTo(halfWidth, halfHeight - plusExtend) - ctx.lineTo(halfWidth, halfHeight + plusExtend) + ctx.moveTo(addCanvas.halfWidth, addCanvas.halfHeight - addCanvas.plusExtend) + ctx.lineTo(addCanvas.halfWidth, addCanvas.halfHeight + addCanvas.plusExtend) - ctx.moveTo(halfWidth - plusExtend, halfHeight) - ctx.lineTo(halfWidth + plusExtend, halfHeight) + ctx.moveTo(addCanvas.halfWidth - addCanvas.plusExtend, addCanvas.halfHeight) + ctx.lineTo(addCanvas.halfWidth + addCanvas.plusExtend, addCanvas.halfHeight) ctx.stroke() ctx.save() diff --git a/src/plugins/qmldesigner/components/stateseditornew/stateseditorwidget.cpp b/src/plugins/qmldesigner/components/stateseditornew/stateseditorwidget.cpp index 96ad08b33ec..c47f19e4cb2 100644 --- a/src/plugins/qmldesigner/components/stateseditornew/stateseditorwidget.cpp +++ b/src/plugins/qmldesigner/components/stateseditornew/stateseditorwidget.cpp @@ -43,10 +43,10 @@ #include -#include -#include #include +#include #include +#include #include #include @@ -142,6 +142,7 @@ void StatesEditorWidget::showEvent(QShowEvent *event) { QQuickWidget::showEvent(event); update(); + QMetaObject::invokeMethod(rootObject(), "showEvent"); } void StatesEditorWidget::focusOutEvent(QFocusEvent *focusEvent) From dd2c4cc6ea97ab8cac3d56c38b86d8ca315257b5 Mon Sep 17 00:00:00 2001 From: Henning Gruendl Date: Thu, 24 Nov 2022 12:33:45 +0100 Subject: [PATCH 037/131] QmlDesigner: Fix cloning of extended states * When cloning an extended state add the new state after the extended group * When cloning a state that extends another one also copy the extends property * Fix two "call to a temporary is a no-op" clang warnings Task-number: QDS-8412 Change-Id: I1e8595ddc69f210a27c04fc91869df3beb4ba980 Reviewed-by: Thomas Hartmann Reviewed-by: --- .../stateseditornew/stateseditorview.cpp | 10 +++++++ .../designercore/model/qmlstate.cpp | 26 ++++++++++++++----- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/plugins/qmldesigner/components/stateseditornew/stateseditorview.cpp b/src/plugins/qmldesigner/components/stateseditornew/stateseditorview.cpp index 1d96df2bc15..e20f5df7b47 100644 --- a/src/plugins/qmldesigner/components/stateseditornew/stateseditorview.cpp +++ b/src/plugins/qmldesigner/components/stateseditornew/stateseditorview.cpp @@ -229,6 +229,16 @@ void StatesEditorView::cloneState(int nodeId) int from = newNode.parentProperty().indexOf(newNode); int to = stateNode.parentProperty().indexOf(stateNode) + 1; + // When duplicating an extended state the new state needs to be added after the extend group. + if (!modelState.hasExtend()) { + auto modelNodeList = activeStatesGroupNode().nodeListProperty("states").toModelNodeList(); + for (; to != modelNodeList.count(); ++to) { + QmlModelState currentState(modelNodeList.at(to)); + if (!currentState.isValid() || currentState.isBaseState() || !currentState.hasExtend()) + break; + } + } + executeInTransaction("moveState", [this, &newState, from, to]() { activeStatesGroupNode().nodeListProperty("states").slide(from, to); setCurrentState(newState); diff --git a/src/plugins/qmldesigner/designercore/model/qmlstate.cpp b/src/plugins/qmldesigner/designercore/model/qmlstate.cpp index bba6d9c1d3a..e620cd948c2 100644 --- a/src/plugins/qmldesigner/designercore/model/qmlstate.cpp +++ b/src/plugins/qmldesigner/designercore/model/qmlstate.cpp @@ -254,17 +254,31 @@ QmlModelState QmlModelState::duplicate(const QString &name) const if (!isValid()) return {}; -// QmlModelState newState(stateGroup().addState(name)); QmlModelState newState(createQmlState(view(), {{PropertyName("name"), QVariant(name)}})); + + if (hasExtend()) + newState.setExtend(extend()); + const QList nodes = modelNode().nodeListProperty("changes").toModelNodeList(); for (const ModelNode &childNode : nodes) { - ModelNode newModelNode(view()->createModelNode(childNode.type(), childNode.majorVersion(), childNode.minorVersion())); + ModelNode newModelNode(view()->createModelNode(childNode.type(), + childNode.majorVersion(), + childNode.minorVersion())); + + for (const BindingProperty &bindingProperty : childNode.bindingProperties()) { + auto property = newModelNode.bindingProperty(bindingProperty.name()); + property.setExpression(bindingProperty.expression()); + } + const QList bindingProperties = childNode.bindingProperties(); for (const BindingProperty &bindingProperty : bindingProperties) - newModelNode.bindingProperty(bindingProperty.name()).setExpression(bindingProperty.expression()); - const QList variantProperties = childNode.variantProperties(); - for (const VariantProperty &variantProperty : variantProperties) - newModelNode.variantProperty(variantProperty.name()).setValue(variantProperty.value()); + newModelNode.bindingProperty(bindingProperty.name()) + .setExpression(bindingProperty.expression()); + + for (const VariantProperty &variantProperty : childNode.variantProperties()) { + auto property = newModelNode.variantProperty(variantProperty.name()); + property.setValue(variantProperty.value()); + } newState.modelNode().nodeListProperty("changes").reparentHere(newModelNode); } From 3321261cf3f2142017b80d1710027c2940fa19fd Mon Sep 17 00:00:00 2001 From: Henning Gruendl Date: Wed, 23 Nov 2022 16:49:45 +0100 Subject: [PATCH 038/131] QmlDesigner: Fix DragHandler being disabled The blockDragHandler property got broken when a menu item triggered a model reset as the popup close signal wasn't triggered from the delegate anymore. Task-number: QDS-8133 Change-Id: If135b12a127f5fea8f3f392e0fd6475c4e188e67 Reviewed-by: Reviewed-by: Thomas Hartmann --- share/qtcreator/qmldesigner/newstateseditor/Main.qml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/share/qtcreator/qmldesigner/newstateseditor/Main.qml b/share/qtcreator/qmldesigner/newstateseditor/Main.qml index e40f7642388..92bf99b86c8 100644 --- a/share/qtcreator/qmldesigner/newstateseditor/Main.qml +++ b/share/qtcreator/qmldesigner/newstateseditor/Main.qml @@ -262,6 +262,11 @@ Rectangle { // the close of the old popup. Using an int keeps track of number of opened popups. property int menuOpen: 0 + Connections { + target: statesEditorModel + onModelReset: root.menuOpen = 0 + } + // This timer is used to delay the current state animation as it didn't work due to the // repeaters item not being positioned in time resulting in 0 x and y position if the grids // row and column were not changed during the layout algorithm . From 45c7a6b8e60955d53601b19b6aeda51c5310db4d Mon Sep 17 00:00:00 2001 From: Samuel Ghinet Date: Thu, 24 Nov 2022 14:54:22 +0200 Subject: [PATCH 039/131] Implement drag-and-drop from Assets View to Texture Editor The asset (i.e. image) can now be dragged to the "Source" property of the Texture Editor. Task-number: QDS-8341 Change-Id: I2d30bdf245a8328a864c116c727d3faaaad347cc Reviewed-by: Mahmoud Badri Reviewed-by: Thomas Hartmann --- .../textureeditor/textureeditorcontextobject.cpp | 13 +++++++++++++ .../textureeditor/textureeditorcontextobject.h | 8 ++++++++ .../components/textureeditor/textureeditorview.cpp | 10 +++++++++- 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.cpp b/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.cpp index 8ccd7b8fad7..d358425a1a3 100644 --- a/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.cpp +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.cpp @@ -103,6 +103,19 @@ void TextureEditorContextObject::setMajorVersion(int majorVersion) emit majorVersionChanged(); } +QString TextureEditorContextObject::activeDragSuffix() const +{ + return m_activeDragSuffix; +} + +void TextureEditorContextObject::setActiveDragSuffix(const QString &suffix) +{ + if (m_activeDragSuffix != suffix) { + m_activeDragSuffix = suffix; + emit activeDragSuffixChanged(); + } +} + bool TextureEditorContextObject::hasActiveTimeline() const { return m_hasActiveTimeline; diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.h b/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.h index 2ad65821281..cc479e5092e 100644 --- a/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.h +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.h @@ -42,6 +42,8 @@ class TextureEditorContextObject : public QObject Q_PROPERTY(QQmlPropertyMap *backendValues READ backendValues WRITE setBackendValues NOTIFY backendValuesChanged) + Q_PROPERTY(QString activeDragSuffix READ activeDragSuffix NOTIFY activeDragSuffixChanged) + public: TextureEditorContextObject(QQmlContext *context, QObject *parent = nullptr); @@ -95,6 +97,9 @@ public: bool hasSingleModelSelection() const; void setHasSingleModelSelection(bool b); + QString activeDragSuffix() const; + void setActiveDragSuffix(const QString &suffix); + bool hasAliasExport() const { return m_aliasExport; } void setSelectedMaterial(const ModelNode &matNode); @@ -126,6 +131,7 @@ signals: void hasQuick3DImportChanged(); void hasMaterialLibraryChanged(); void hasSingleModelSelectionChanged(); + void activeDragSuffixChanged(); private: QUrl m_specificsUrl; @@ -152,6 +158,8 @@ private: bool m_hasSingleModelSelection = false; ModelNode m_selectedTexture; + + QString m_activeDragSuffix; }; } // QmlDesigner diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp b/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp index 37dd6c3ffa9..9384c9baf75 100644 --- a/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp @@ -821,10 +821,15 @@ void QmlDesigner::TextureEditorView::highlightSupportedProperties(bool highlight QTC_ASSERT(metaInfo.isValid(), return); for (const QString &propName : propNames) { - if (metaInfo.property(propName.toUtf8()).propertyType().isQtQuick3DTexture()) { // TODO: support dropping to texture source + if (metaInfo.property(propName.toUtf8()).propertyType().isQtQuick3DTexture()) { QObject *propEditorValObj = propMap.value(propName).value(); PropertyEditorValue *propEditorVal = qobject_cast(propEditorValObj); propEditorVal->setHasActiveDrag(highlight); + } else if (metaInfo.property(propName.toUtf8()).propertyType().isUrl()) { + QObject *propEditorValObj = propMap.value(propName).value(); + PropertyEditorValue *propEditorVal = qobject_cast(propEditorValObj); + if (propEditorVal) + propEditorVal->setHasActiveDrag(highlight); } } } @@ -841,6 +846,9 @@ void TextureEditorView::dragStarted(QMimeData *mimeData) return; highlightSupportedProperties(); + + const QString suffix = "*." + assetPath.split('.').last().toLower(); + m_qmlBackEnd->contextObject()->setActiveDragSuffix(suffix); } void TextureEditorView::dragEnded() From 6747e666b9cc9c47b26b3c2971e56b83ac29c7c3 Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Thu, 24 Nov 2022 23:42:26 +0200 Subject: [PATCH 040/131] QmlDesigner: Open TextureEditor by double-clicking a texture Change-Id: I58645f31f244cf4e5909b35f1cae224a55f4e9a4 Reviewed-by: Reviewed-by: Miikka Heikkinen Reviewed-by: Samuel Ghinet --- .../materialBrowserQmlSource/TextureItem.qml | 2 ++ .../materialbrowsertexturesmodel.cpp | 18 +++++++++++------- .../materialbrowsertexturesmodel.h | 3 ++- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml index b0861aa882e..a666354b832 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml @@ -34,6 +34,8 @@ Rectangle { else if (mouse.button === Qt.RightButton) root.showContextMenu() } + + onDoubleClicked: materialBrowserTexturesModel.openTextureEditor(); } Image { diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp index b7851157ee4..781b43622fd 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp @@ -3,13 +3,12 @@ #include "materialbrowsertexturesmodel.h" -#include -#include -#include -#include -#include -#include -#include "utils/qtcassert.h" +#include "designmodewidget.h" +#include "qmldesignerplugin.h" +#include "qmlobjectnode.h" +#include "variantproperty.h" + +#include namespace QmlDesigner { @@ -264,4 +263,9 @@ void MaterialBrowserTexturesModel::applyToSelectedModel(qint64 internalId) } } +void MaterialBrowserTexturesModel::openTextureEditor() +{ + QmlDesignerPlugin::instance()->mainWidget()->showDockWidget("TextureEditor", true); +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h index 3f5a59ea54f..34e4fb23647 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h @@ -3,7 +3,7 @@ #pragma once -#include +#include "modelnode.h" #include #include @@ -49,6 +49,7 @@ public: Q_INVOKABLE void deleteTexture(int idx); Q_INVOKABLE void applyToSelectedMaterial(qint64 internalId); Q_INVOKABLE void applyToSelectedModel(qint64 internalId); + Q_INVOKABLE void openTextureEditor(); signals: void isEmptyChanged(); From afc7cd2c98a673d843329dea38da271865c41ed3 Mon Sep 17 00:00:00 2001 From: Samuel Ghinet Date: Mon, 31 Oct 2022 15:24:30 +0200 Subject: [PATCH 041/131] QmlDesigner: Use a vertical scrollbar for "Confirm Delete Files" Also, replaced the widgets code with QML. Task-number: QDS-8033 Change-Id: I29f4e0bc02294e839e352096c3d4394a764cfe76 Reviewed-by: Miikka Heikkinen Reviewed-by: Mahmoud Badri --- .../AssetsContextMenu.qml | 14 ++- .../ConfirmDeleteFilesDialog.qml | 107 ++++++++++++++++++ .../assetslibrary/assetslibrarymodel.cpp | 58 ++++------ .../assetslibrary/assetslibrarymodel.h | 4 +- 4 files changed, 148 insertions(+), 35 deletions(-) create mode 100644 share/qtcreator/qmldesigner/itemLibraryQmlSources/ConfirmDeleteFilesDialog.qml diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsContextMenu.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsContextMenu.qml index 84a689184c1..7d09f49f526 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsContextMenu.qml +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsContextMenu.qml @@ -87,7 +87,19 @@ StudioControls.Menu { text: qsTr("Delete File") visible: root._fileIndex height: deleteFileItem.visible ? deleteFileItem.implicitHeight : 0 - onTriggered: assetsModel.deleteFiles(root._selectedAssetPathsList) + onTriggered: { + let deleted = assetsModel.requestDeleteFiles(root._selectedAssetPathsList) + if (!deleted) + confirmDeleteFiles.open() + } + + ConfirmDeleteFilesDialog { + id: confirmDeleteFiles + parent: root.assetsView + files: root._selectedAssetPathsList + + onAccepted: root.assetsView.selectedAssets = {} + } } StudioControls.MenuSeparator { diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/ConfirmDeleteFilesDialog.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/ConfirmDeleteFilesDialog.qml new file mode 100644 index 00000000000..57253864eba --- /dev/null +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/ConfirmDeleteFilesDialog.qml @@ -0,0 +1,107 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Controls +import HelperWidgets as HelperWidgets +import StudioTheme as StudioTheme +import StudioControls as StudioControls + +Dialog { + id: root + title: qsTr("Confirm Delete Files") + anchors.centerIn: parent + closePolicy: Popup.CloseOnEscape + implicitWidth: 350 + modal: true + + required property var files + + contentItem: Column { + spacing: 20 + width: parent.width + + Text { + id: confirmDeleteText + + text: root.files && root.files.length + ? qsTr("Some files might be in use. Delete anyway?") + : "" + color: StudioTheme.Values.themeTextColor + wrapMode: Text.WordWrap + width: root.width + leftPadding: 10 + rightPadding: 10 + + Keys.onEnterPressed: btnDelete.onClicked() + Keys.onReturnPressed: btnDelete.onClicked() + } + + Loader { + id: fileListLoader + sourceComponent: null + width: parent.width - 20 + x: 10 + height: 50 + } + + StudioControls.CheckBox { + id: dontAskAgain + text: qsTr("Do not ask this again") + actionIndicatorVisible: false + width: parent.width + x: 10 + } + + Row { + anchors.right: parent.right + + HelperWidgets.Button { + id: btnDelete + + text: qsTr("Delete") + onClicked: { + assetsModel.deleteFiles(root.files, dontAskAgain.checked) + root.accept() + } + } + + HelperWidgets.Button { + text: qsTr("Cancel") + onClicked: root.reject() + } + } + } + + onOpened: { + fileListLoader.sourceComponent = null + fileListLoader.sourceComponent = fileListComponent + + confirmDeleteText.forceActiveFocus() + } + + onClosed: fileListLoader.sourceComponent = null + + Component { + id: fileListComponent + + ListView { + id: filesListView + model: root.files + boundsBehavior: Flickable.StopAtBounds + clip: true + + ScrollBar.vertical: HelperWidgets.VerticalScrollBar { + id: verticalScrollBar + scrollBarVisible: filesListView.contentHeight > filesListView.height + } + + delegate: Text { + elide: Text.ElideLeft + text: model.modelData.replace(assetsModel.currentProjectDirPath(), "") + color: StudioTheme.Values.themeTextColor + width: parent.width - (verticalScrollBar.scrollBarVisible ? verticalScrollBar.width : 0) + } + } // ListView + } // Component +} diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp index d1b93448b7e..0ea4aa816f2 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp @@ -36,9 +36,10 @@ void AssetsLibraryModel::createBackendModel() QObject::connect(m_sourceFsModel, &QFileSystemModel::directoryLoaded, this, &AssetsLibraryModel::directoryLoaded); QObject::connect(m_sourceFsModel, &QFileSystemModel::dataChanged, this, &AssetsLibraryModel::onDataChanged); - QObject::connect(m_sourceFsModel, &QFileSystemModel::directoryLoaded, this, [this](const QString &dir) { - syncHaveFiles(); - }); + QObject::connect(m_sourceFsModel, &QFileSystemModel::directoryLoaded, this, + [this]([[maybe_unused]] const QString &dir) { + syncHaveFiles(); + }); } bool AssetsLibraryModel::isEffectQmlExist(const QString &effectName) @@ -49,7 +50,7 @@ bool AssetsLibraryModel::isEffectQmlExist(const QString &effectName) } void AssetsLibraryModel::onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, - const QList &roles) + [[maybe_unused]] const QList &roles) { for (int i = topLeft.row(); i <= bottomRight.row(); ++i) { QModelIndex index = m_sourceFsModel->index(i, 0, topLeft.parent()); @@ -94,43 +95,34 @@ QList AssetsLibraryModel::parentIndices(const QModelIndex &index) c return result; } -void AssetsLibraryModel::deleteFiles(const QStringList &filePaths) +QString AssetsLibraryModel::currentProjectDirPath() const +{ + return DocumentManager::currentProjectDirPath().toString().append('/'); +} + +bool AssetsLibraryModel::requestDeleteFiles(const QStringList &filePaths) { bool askBeforeDelete = QmlDesignerPlugin::settings() .value(DesignerSettingsKey::ASK_BEFORE_DELETING_ASSET) .toBool(); - bool assetDelete = true; - if (askBeforeDelete) { - QMessageBox msg(QMessageBox::Question, tr("Confirm Delete File"), - tr("File%1 might be in use. Delete anyway?\n\n%2") - .arg(filePaths.size() > 1 ? QChar('s') : QChar()) - .arg(filePaths.join('\n').remove(DocumentManager::currentProjectDirPath() - .toString().append('/'))), - QMessageBox::No | QMessageBox::Yes); - QCheckBox cb; - cb.setText(tr("Do not ask this again")); - msg.setCheckBox(&cb); - int ret = msg.exec(); + if (askBeforeDelete) + return false; - if (ret == QMessageBox::No) - assetDelete = false; + deleteFiles(filePaths, false); + return true; +} - if (cb.isChecked()) - QmlDesignerPlugin::settings().insert(DesignerSettingsKey::ASK_BEFORE_DELETING_ASSET, false); - } +void AssetsLibraryModel::deleteFiles(const QStringList &filePaths, bool dontAskAgain) +{ + if (dontAskAgain) + QmlDesignerPlugin::settings().insert(DesignerSettingsKey::ASK_BEFORE_DELETING_ASSET, false); - if (assetDelete) { - for (const QString &filePath : filePaths) { - if (!QFile::exists(filePath)) { - QMessageBox::warning(Core::ICore::dialogParent(), - tr("Failed to Locate File"), - tr("Could not find \"%1\".").arg(filePath)); - } else if (!QFile::remove(filePath)) { - QMessageBox::warning(Core::ICore::dialogParent(), - tr("Failed to Delete File"), - tr("Could not delete \"%1\".").arg(filePath)); - } + for (const QString &filePath : filePaths) { + if (QFile::exists(filePath) && !QFile::remove(filePath)) { + QMessageBox::warning(Core::ICore::dialogParent(), + tr("Failed to Delete File"), + tr("Could not delete \"%1\".").arg(filePath)); } } } diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h index 6e555f07e23..b4010497b51 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h @@ -38,7 +38,9 @@ public: Q_INVOKABLE QList parentIndices(const QModelIndex &index) const; Q_INVOKABLE bool indexIsValid(const QModelIndex &index) const; - Q_INVOKABLE void deleteFiles(const QStringList &filePaths); + Q_INVOKABLE QString currentProjectDirPath() const; + Q_INVOKABLE bool requestDeleteFiles(const QStringList &filePaths); + Q_INVOKABLE void deleteFiles(const QStringList &filePaths, bool dontAskAgain); Q_INVOKABLE bool renameFolder(const QString &folderPath, const QString &newName); Q_INVOKABLE bool addNewFolder(const QString &folderPath); Q_INVOKABLE bool deleteFolderRecursively(const QModelIndex &folderIndex); From 056165d0719c275479358bf1c7db89d74906de6b Mon Sep 17 00:00:00 2001 From: Samuel Ghinet Date: Wed, 23 Nov 2022 15:47:34 +0200 Subject: [PATCH 042/131] Use double underscores instead of single ones in QML Also, fixed some QML errors -- they didn't have any real consequence, so they could be considered warnings. Also, AssetsView._modelIndex() was being called with two arguments instead of one -- this didn't result in any warnings. Change-Id: I08274a49531a4082fa94fb89e4230fdac5b1b658 Reviewed-by: Miikka Heikkinen Reviewed-by: Mahmoud Badri --- .../itemLibraryQmlSources/AssetDelegate.qml | 96 +++++++++--------- .../itemLibraryQmlSources/Assets.qml | 14 +-- .../AssetsContextMenu.qml | 99 ++++++++++--------- .../itemLibraryQmlSources/AssetsView.qml | 42 ++++---- .../itemLibraryQmlSources/NewFolderDialog.qml | 6 +- 5 files changed, 130 insertions(+), 127 deletions(-) diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetDelegate.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetDelegate.qml index 28f671cf681..a28becff06b 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetDelegate.qml +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetDelegate.qml @@ -18,17 +18,17 @@ TreeViewDelegate { readonly property bool isEffect: root.suffix === ".qep" property bool currFileSelected: false property int initialDepth: -1 - property bool _isDirectory: assetsModel.isDirectory(model.filePath) - property int _currentRow: model.index - property string _itemPath: model.filePath + property bool __isDirectory: assetsModel.isDirectory(model.filePath) + property int __currentRow: model.index + property string __itemPath: model.filePath - readonly property int _fileItemHeight: thumbnailImage.height - readonly property int _dirItemHeight: 21 + readonly property int __fileItemHeight: thumbnailImage.height + readonly property int __dirItemHeight: 21 - implicitHeight: root._isDirectory ? root._dirItemHeight : root._fileItemHeight + implicitHeight: root.__isDirectory ? root.__dirItemHeight : root.__fileItemHeight implicitWidth: root.assetsView.width > 0 ? root.assetsView.width : 10 - leftMargin: root._isDirectory ? 0 : thumbnailImage.width + leftMargin: root.__isDirectory ? 0 : thumbnailImage.width Component.onCompleted: { // the depth of the root path will become available before we get to the actual @@ -36,7 +36,7 @@ TreeViewDelegate { // tree items (below the root) will have the indentation (basically, depth) adjusted. if (model.filePath === assetsModel.rootPath()) { root.assetsView.rootPathDepth = root.depth - root.assetsView.rootPathRow = root._currentRow + root.assetsView.rootPathRow = root.__currentRow } else if (model.filePath.includes(assetsModel.rootPath())) { root.depth -= root.assetsView.rootPathDepth root.initialDepth = root.depth @@ -45,7 +45,7 @@ TreeViewDelegate { // workaround for a bug -- might be fixed by https://codereview.qt-project.org/c/qt/qtdeclarative/+/442721 onYChanged: { - if (root._currentRow === root.assetsView.firstRow) { + if (root.__currentRow === root.assetsView.firstRow) { if (root.y > root.assetsView.contentY) { let item = root.assetsView.itemAtCell(0, root.assetsView.rootPathRow) if (!item) @@ -74,16 +74,16 @@ TreeViewDelegate { id: bg color: { - if (root._isDirectory && (root.isHoveringDrop || root.hasChildWithDropHover)) + if (root.__isDirectory && (root.isHoveringDrop || root.hasChildWithDropHover)) return StudioTheme.Values.themeInteraction - if (!root._isDirectory && root.assetsView.selectedAssets[root._itemPath]) + if (!root.__isDirectory && root.assetsView.selectedAssets[root.__itemPath]) return StudioTheme.Values.themeInteraction if (mouseArea.containsMouse) return StudioTheme.Values.themeSectionHeadBackground - return root._isDirectory + return root.__isDirectory ? StudioTheme.Values.themeSectionHeadBackground : "transparent" } @@ -104,15 +104,15 @@ TreeViewDelegate { contentItem: Text { id: assetLabel - text: assetLabel._computeText() + text: assetLabel.__computeText() color: StudioTheme.Values.themeTextColor font.pixelSize: 14 anchors.verticalCenter: parent.verticalCenter verticalAlignment: Qt.AlignVCenter - function _computeText() + function __computeText() { - return root._isDirectory + return root.__isDirectory ? (root.hasChildren ? model.display.toUpperCase() : model.display.toUpperCase() + qsTr(" (empty)")) @@ -130,14 +130,14 @@ TreeViewDelegate { root.assetsRoot.updateDropExtFiles(drag) root.isHoveringDrop = drag.accepted && root.assetsRoot.dropSimpleExtFiles.length > 0 if (root.isHoveringDrop) - root.assetsView.startDropHoverOver(root._currentRow) + root.assetsView.startDropHoverOver(root.__currentRow) } onDropped: (drag) => { root.isHoveringDrop = false - root.assetsView.endDropHover(root._currentRow) + root.assetsView.endDropHover(root.__currentRow) - let dirPath = root._isDirectory + let dirPath = root.__isDirectory ? model.filePath : assetsModel.parentDirPath(model.filePath); @@ -149,7 +149,7 @@ TreeViewDelegate { onExited: { if (root.isHoveringDrop) { root.isHoveringDrop = false - root.assetsView.endDropHover(root._currentRow) + root.assetsView.endDropHover(root.__currentRow) } } } @@ -178,25 +178,25 @@ TreeViewDelegate { mouseArea.allowTooltip = false tooltipBackend.hideTooltip() - if (root._isDirectory) + if (root.__isDirectory) return var ctrlDown = mouse.modifiers & Qt.ControlModifier if (mouse.button === Qt.LeftButton) { - if (!root.assetsView.isAssetSelected(root._itemPath) && !ctrlDown) + if (!root.assetsView.isAssetSelected(root.__itemPath) && !ctrlDown) root.assetsView.clearSelectedAssets() - root.currFileSelected = ctrlDown ? !root.assetsView.isAssetSelected(root._itemPath) : true - root.assetsView.setAssetSelected(root._itemPath, root.currFileSelected) + root.currFileSelected = ctrlDown ? !root.assetsView.isAssetSelected(root.__itemPath) : true + root.assetsView.setAssetSelected(root.__itemPath, root.currFileSelected) if (root.currFileSelected) { let selectedPaths = root.assetsView.selectedPathsAsList() rootView.startDragAsset(selectedPaths, mapToGlobal(mouse.x, mouse.y)) } } else { - if (!root.assetsView.isAssetSelected(root._itemPath) && !ctrlDown) + if (!root.assetsView.isAssetSelected(root.__itemPath) && !ctrlDown) root.assetsView.clearSelectedAssets() - root.currFileSelected = root.assetsView.isAssetSelected(root._itemPath) || !ctrlDown - root.assetsView.setAssetSelected(root._itemPath, root.currFileSelected) + root.currFileSelected = root.assetsView.isAssetSelected(root.__itemPath) || !ctrlDown + root.assetsView.setAssetSelected(root.__itemPath, root.currFileSelected) } } @@ -206,7 +206,7 @@ TreeViewDelegate { if (mouse.button === Qt.LeftButton) { if (!(mouse.modifiers & Qt.ControlModifier)) root.assetsView.selectedAssets = {} - root.assetsView.selectedAssets[root._itemPath] = root.currFileSelected + root.assetsView.selectedAssets[root.__itemPath] = root.currFileSelected root.assetsView.selectedAssetsChanged() } } @@ -239,28 +239,28 @@ TreeViewDelegate { onClicked: (mouse) => { if (mouse.button === Qt.LeftButton) - root._toggleExpandCurrentRow() + root.__toggleExpandCurrentRow() else - root._openContextMenuForCurrentRow() + root.__openContextMenuForCurrentRow() } } // MouseArea - function _openContextMenuForCurrentRow() + function __openContextMenuForCurrentRow() { let modelIndex = assetsModel.indexForPath(model.filePath) - if (root._isDirectory) { + function onFolderCreated(path) { + root.assetsView.addCreatedFolder(path) + } + + if (root.__isDirectory) { var row = root.assetsView.rowAtIndex(modelIndex) var expanded = root.assetsView.isExpanded(row) var allExpandedState = root.assetsView.computeAllExpandedState() - function onFolderCreated(path) { - root.assetsView.addCreatedFolder(path) - } - function onFolderRenamed() { if (expanded) root.assetsView.rowToExpand = row @@ -272,40 +272,40 @@ TreeViewDelegate { let parentDirIndex = assetsModel.parentDirIndex(model.filePath) let selectedPaths = root.assetsView.selectedPathsAsList() root.assetsView.contextMenu.openContextMenuForFile(modelIndex, parentDirIndex, - selectedPaths) + selectedPaths, onFolderCreated) } } - function _toggleExpandCurrentRow() + function __toggleExpandCurrentRow() { - if (!root._isDirectory) + if (!root.__isDirectory) return - let index = root.assetsView._modelIndex(root._currentRow, 0) + let index = root.assetsView.__modelIndex(root.__currentRow) // if the user manually clicked on a directory, then this is definitely not a // an automatic request to expand all. root.assetsView.requestedExpandAll = false - if (root.assetsView.isExpanded(root._currentRow)) { + if (root.assetsView.isExpanded(root.__currentRow)) { root.assetsView.requestedExpandAll = false - root.assetsView.collapse(root._currentRow) + root.assetsView.collapse(root.__currentRow) } else { - root.assetsView.expand(root._currentRow) + root.assetsView.expand(root.__currentRow) } } function reloadImage() { - if (root._isDirectory) + if (root.__isDirectory) return thumbnailImage.source = "" - thumbnailImage.source = thumbnailImage._computeSource() + thumbnailImage.source = thumbnailImage.__computeSource() } Image { id: thumbnailImage - visible: !root._isDirectory + visible: !root.__isDirectory x: root.depth * root.indentation width: 48 height: 48 @@ -314,11 +314,11 @@ TreeViewDelegate { sourceSize.height: 48 asynchronous: true fillMode: Image.PreserveAspectFit - source: thumbnailImage._computeSource() + source: thumbnailImage.__computeSource() - function _computeSource() + function __computeSource() { - return root._isDirectory + return root.__isDirectory ? "" : "image://qmldesigner_assets/" + model.filePath } diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml index d35ac63dad7..211cb07d9e2 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml @@ -16,7 +16,7 @@ Item { property var dropComplexExtFiles: [] readonly property int qtVersionAtLeast6_4: rootView.qtVersionIsAtLeast6_4() - property bool _searchBoxEmpty: true + property bool __searchBoxEmpty: true AssetsContextMenu { id: contextMenu @@ -136,10 +136,10 @@ Item { rootView.handleSearchFilterChanged(searchBox.text) assetsView.expandAll() - if (root._searchBoxEmpty && searchBox.text) - root._searchBoxEmpty = false - else if (!root._searchBoxEmpty && !searchBox.text) - root._searchBoxEmpty = true + if (root.__searchBoxEmpty && searchBox.text) + root.__searchBoxEmpty = false + else if (!root.__searchBoxEmpty && !searchBox.text) + root.__searchBoxEmpty = true } } @@ -159,13 +159,13 @@ Item { leftPadding: 10 color: StudioTheme.Values.themeTextColor font.pixelSize: 12 - visible: !assetsModel.haveFiles && !root._searchBoxEmpty + visible: !assetsModel.haveFiles && !root.__searchBoxEmpty } Item { // placeholder when the assets library is empty width: parent.width height: parent.height - searchRow.height - visible: !assetsModel.haveFiles && root._searchBoxEmpty + visible: !assetsModel.haveFiles && root.__searchBoxEmpty clip: true DropArea { // handles external drop (goes into default folder based on suffix) diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsContextMenu.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsContextMenu.qml index 7d09f49f526..87c0966366e 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsContextMenu.qml +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsContextMenu.qml @@ -11,84 +11,85 @@ StudioControls.Menu { required property Item assetsView - property bool _isDirectory: false - property var _fileIndex: null - property string _dirPath: "" - property string _dirName: "" - property var _onFolderCreated: null - property var _onFolderRenamed: null - property var _dirIndex: null - property string _allExpandedState: "" - property var _selectedAssetPathsList: null + property bool __isDirectory: false + property var __fileIndex: null + property string __dirPath: "" + property string __dirName: "" + property var __onFolderCreated: null + property var __onFolderRenamed: null + property var __dirIndex: null + property string __allExpandedState: "" + property var __selectedAssetPathsList: null closePolicy: Popup.CloseOnPressOutside | Popup.CloseOnEscape function openContextMenuForRoot(rootModelIndex, dirPath, dirName, onFolderCreated) { - root._onFolderCreated = onFolderCreated - root._fileIndex = "" - root._dirPath = dirPath - root._dirName = dirName - root._dirIndex = rootModelIndex - root._isDirectory = false + root.__onFolderCreated = onFolderCreated + root.__fileIndex = "" + root.__dirPath = dirPath + root.__dirName = dirName + root.__dirIndex = rootModelIndex + root.__isDirectory = false root.popup() } function openContextMenuForDir(dirModelIndex, dirPath, dirName, allExpandedState, onFolderCreated, onFolderRenamed) { - root._onFolderCreated = onFolderCreated - root._onFolderRenamed = onFolderRenamed - root._dirPath = dirPath - root._dirName = dirName - root._fileIndex = "" - root._dirIndex = dirModelIndex - root._isDirectory = true - root._allExpandedState = allExpandedState + root.__onFolderCreated = onFolderCreated + root.__onFolderRenamed = onFolderRenamed + root.__dirPath = dirPath + root.__dirName = dirName + root.__fileIndex = "" + root.__dirIndex = dirModelIndex + root.__isDirectory = true + root.__allExpandedState = allExpandedState root.popup() } - function openContextMenuForFile(fileIndex, dirModelIndex, selectedAssetPathsList) + function openContextMenuForFile(fileIndex, dirModelIndex, selectedAssetPathsList, onFolderCreated) { var numSelected = selectedAssetPathsList.filter(p => p).length deleteFileItem.text = numSelected > 1 ? qsTr("Delete Files") : qsTr("Delete File") - root._selectedAssetPathsList = selectedAssetPathsList - root._fileIndex = fileIndex - root._dirIndex = dirModelIndex - root._dirPath = assetsModel.filePath(dirModelIndex) - root._isDirectory = false + root.__onFolderCreated = onFolderCreated + root.__selectedAssetPathsList = selectedAssetPathsList + root.__fileIndex = fileIndex + root.__dirIndex = dirModelIndex + root.__dirPath = assetsModel.filePath(dirModelIndex) + root.__isDirectory = false root.popup() } StudioControls.MenuItem { text: qsTr("Expand All") - enabled: root._allExpandedState !== "all_expanded" - visible: root._isDirectory + enabled: root.__allExpandedState !== "all_expanded" + visible: root.__isDirectory height: visible ? implicitHeight : 0 onTriggered: root.assetsView.expandAll() } StudioControls.MenuItem { text: qsTr("Collapse All") - enabled: root._allExpandedState !== "all_collapsed" - visible: root._isDirectory + enabled: root.__allExpandedState !== "all_collapsed" + visible: root.__isDirectory height: visible ? implicitHeight : 0 onTriggered: root.assetsView.collapseAll() } StudioControls.MenuSeparator { - visible: root._isDirectory + visible: root.__isDirectory height: visible ? StudioTheme.Values.border : 0 } StudioControls.MenuItem { id: deleteFileItem text: qsTr("Delete File") - visible: root._fileIndex + visible: root.__fileIndex height: deleteFileItem.visible ? deleteFileItem.implicitHeight : 0 onTriggered: { - let deleted = assetsModel.requestDeleteFiles(root._selectedAssetPathsList) + let deleted = assetsModel.requestDeleteFiles(root.__selectedAssetPathsList) if (!deleted) confirmDeleteFiles.open() } @@ -96,30 +97,30 @@ StudioControls.Menu { ConfirmDeleteFilesDialog { id: confirmDeleteFiles parent: root.assetsView - files: root._selectedAssetPathsList + files: root.__selectedAssetPathsList onAccepted: root.assetsView.selectedAssets = {} } } StudioControls.MenuSeparator { - visible: root._fileIndex + visible: root.__fileIndex height: visible ? StudioTheme.Values.border : 0 } StudioControls.MenuItem { text: qsTr("Rename Folder") - visible: root._isDirectory + visible: root.__isDirectory height: visible ? implicitHeight : 0 onTriggered: renameFolderDialog.open() RenameFolderDialog { id: renameFolderDialog parent: root.assetsView - dirPath: root._dirPath - dirName: root._dirName + dirPath: root.__dirPath + dirName: root.__dirName - onAccepted: root._onFolderRenamed() + onAccepted: root.__onFolderRenamed() } } @@ -129,9 +130,9 @@ StudioControls.Menu { NewFolderDialog { id: newFolderDialog parent: root.assetsView - dirPath: root._dirPath + dirPath: root.__dirPath - onAccepted: root._onFolderCreated(newFolderDialog.createdDirPath) + onAccepted: root.__onFolderCreated(newFolderDialog.createdDirPath) } onTriggered: newFolderDialog.open() @@ -139,22 +140,22 @@ StudioControls.Menu { StudioControls.MenuItem { text: qsTr("Delete Folder") - visible: root._isDirectory + visible: root.__isDirectory height: visible ? implicitHeight : 0 ConfirmDeleteFolderDialog { id: confirmDeleteFolderDialog parent: root.assetsView - dirName: root._dirName - dirIndex: root._dirIndex + dirName: root.__dirName + dirIndex: root.__dirIndex } onTriggered: { - if (!assetsModel.hasChildren(root._dirIndex)) { + if (!assetsModel.hasChildren(root.__dirIndex)) { // NOTE: the folder may still not be empty -- it doesn't have files visible to the // user, but that doesn't mean that there are no other files (e.g. files of unknown // types) on disk in this directory. - assetsModel.deleteFolderRecursively(root._dirIndex) + assetsModel.deleteFolderRecursively(root.__dirIndex) } else { confirmDeleteFolderDialog.open() } diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsView.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsView.qml index 8b76eaf0e1f..782cca5ebc6 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsView.qml +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsView.qml @@ -30,7 +30,7 @@ TreeView { // i.e. first child of the root path readonly property int firstRow: root.rootPathRow + 1 property int rowToExpand: -1 - property var _createdDirectories: [] + property var __createdDirectories: [] rowHeightProvider: (row) => { if (row <= root.rootPathRow) @@ -70,7 +70,7 @@ TreeView { function onDirectoryCreated(path) { - root._createdDirectories.push(path) + root.__createdDirectories.push(path) updateRowsTimer.restart() } @@ -120,7 +120,7 @@ TreeView { function addCreatedFolder(path) { - root._createdDirectories.push(path) + root.__createdDirectories.push(path) } function selectedPathsAsList() @@ -140,8 +140,8 @@ TreeView { if (root.rows <= 0) return - while (root._createdDirectories.length > 0) { - let dirPath = root._createdDirectories.pop() + while (root.__createdDirectories.length > 0) { + let dirPath = root.__createdDirectories.pop() let index = assetsModel.indexForPath(dirPath) let row = root.rowAtIndex(index) @@ -168,7 +168,7 @@ TreeView { if (expanding) { if (root.requestedExpandAll) - root._doExpandAll() + root.__doExpandAll() } else { if (root.rowToExpand > 0) { root.expand(root.rowToExpand) @@ -182,11 +182,11 @@ TreeView { root.lastRowCount = root.rows } - function _doExpandAll() + function __doExpandAll() { let expandedAny = false for (let nRow = 0; nRow < root.rows; ++nRow) { - let index = root._modelIndex(nRow, 0) + let index = root.__modelIndex(nRow) if (assetsModel.isDirectory(index) && !root.isExpanded(nRow)) { root.expand(nRow); expandedAny = true @@ -199,10 +199,10 @@ TreeView { function expandAll() { - // In order for _doExpandAll() to be called repeatedly (every time a new node is + // In order for __doExpandAll() to be called repeatedly (every time a new node is // loaded, and then, expanded), we need to set requestedExpandAll to true. root.requestedExpandAll = true - root._doExpandAll() + root.__doExpandAll() } function collapseAll() @@ -211,7 +211,7 @@ TreeView { // collapse all, except for the root path - from the last item (leaves) up to the root for (let nRow = root.rows - 1; nRow >= 0; --nRow) { - let index = root._modelIndex(nRow, 0) + let index = root.__modelIndex(nRow) // we don't want to collapse the root path, because doing so will hide the contents // of the tree. if (assetsModel.filePath(index) === assetsModel.rootPath()) @@ -233,7 +233,7 @@ TreeView { function computeAllExpandedState() { var dirsWithChildren = [...Array(root.rows).keys()].filter(row => { - let index = root._modelIndex(row, 0) + let index = root.__modelIndex(row) return assetsModel.isDirectory(index) && assetsModel.hasChildren(index) }) @@ -249,22 +249,24 @@ TreeView { function startDropHoverOver(row) { - let index = root._modelIndex(row, 0) + let index = root.__modelIndex(row) if (assetsModel.isDirectory(index)) return - let parentItem = root._getDelegateParentForIndex(index) - parentItem.hasChildWithDropHover = true + let parentItem = root.__getDelegateParentForIndex(index) + if (parentItem) + parentItem.hasChildWithDropHover = true } function endDropHover(row) { - let index = root._modelIndex(row, 0) + let index = root.__modelIndex(row) if (assetsModel.isDirectory(index)) return - let parentItem = root._getDelegateParentForIndex(index) - parentItem.hasChildWithDropHover = false + let parentItem = root.__getDelegateParentForIndex(index) + if (parentItem) + parentItem.hasChildWithDropHover = false } function isAssetSelected(itemPath) @@ -283,14 +285,14 @@ TreeView { root.selectedAssetsChanged() } - function _getDelegateParentForIndex(index) + function __getDelegateParentForIndex(index) { let parentIndex = assetsModel.parentDirIndex(index) let parentCell = root.cellAtIndex(parentIndex) return root.itemAtCell(parentCell) } - function _modelIndex(row) + function __modelIndex(row) { // The modelIndex() function exists since 6.3. In Qt 6.3, this modelIndex() function was a // member of the TreeView, while in Qt6.4 it was moved to TableView. In Qt6.4, the order of diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/NewFolderDialog.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/NewFolderDialog.qml index c48bb93a5bf..7b38c922156 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/NewFolderDialog.qml +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/NewFolderDialog.qml @@ -17,7 +17,7 @@ Dialog { required property string dirPath property string createdDirPath: "" - readonly property int _maxPath: 260 + readonly property int __maxPath: 260 HelperWidgets.RegExpValidator { id: folderNameValidator @@ -67,7 +67,7 @@ Dialog { text: qsTr("Folder path is too long.") color: "#ff0000" anchors.right: parent.right - visible: root.createdDirPath.length > root._maxPath + visible: root.createdDirPath.length > root.__maxPath } Item { // spacer @@ -82,7 +82,7 @@ Dialog { id: btnCreate text: qsTr("Create") - enabled: folderName.text !== "" && root.createdDirPath.length <= root._maxPath + enabled: folderName.text !== "" && root.createdDirPath.length <= root.__maxPath onClicked: { root.createdDirPath = root.dirPath + '/' + folderName.text if (assetsModel.addNewFolder(root.createdDirPath)) From caccbe8377400ba2b6e3a272a59a4d7174fd594c Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Fri, 25 Nov 2022 12:53:21 +0100 Subject: [PATCH 043/131] QmlDesigner: Rename setting to avoid any interference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rename setting to avoid any interference with old installations. Change-Id: Id8cb3ad4a3be6e416c4ad935856c57fc29c07503 Reviewed-by: Henning Gründl --- src/plugins/qmldesigner/utils/designersettings.cpp | 2 +- src/plugins/qmldesigner/utils/designersettings.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/qmldesigner/utils/designersettings.cpp b/src/plugins/qmldesigner/utils/designersettings.cpp index 23df15e0470..338e54072d2 100644 --- a/src/plugins/qmldesigner/utils/designersettings.cpp +++ b/src/plugins/qmldesigner/utils/designersettings.cpp @@ -85,7 +85,7 @@ void DesignerSettings::fromSettings(QSettings *settings) restoreValue(settings, DesignerSettingsKey::EDIT3DVIEW_GRID_COLOR, "#aaaaaa"); restoreValue(settings, DesignerSettingsKey::SMOOTH_RENDERING, false); restoreValue(settings, DesignerSettingsKey::SHOW_DEBUG_SETTINGS, false); - restoreValue(settings, DesignerSettingsKey::OLD_STATES_EDITOR, true); + restoreValue(settings, DesignerSettingsKey::OLD_STATES_EDITOR, false); settings->endGroup(); settings->endGroup(); diff --git a/src/plugins/qmldesigner/utils/designersettings.h b/src/plugins/qmldesigner/utils/designersettings.h index 32fd7dd2987..a24031b579d 100644 --- a/src/plugins/qmldesigner/utils/designersettings.h +++ b/src/plugins/qmldesigner/utils/designersettings.h @@ -53,7 +53,7 @@ const char ALWAYS_DESIGN_MODE[] = "AlwaysDesignMode"; const char DISABLE_ITEM_LIBRARY_UPDATE_TIMER[] = "DisableItemLibraryUpdateTimer"; const char ASK_BEFORE_DELETING_ASSET[] = "AskBeforeDeletingAsset"; const char SMOOTH_RENDERING[] = "SmoothRendering"; -const char OLD_STATES_EDITOR[] = "OldStatesEditor"; +const char OLD_STATES_EDITOR[] = "ForceOldStatesEditor"; } class QMLDESIGNERUTILS_EXPORT DesignerSettings From 21c659400a6ecbf6b49986e4b46d02b914a0ea26 Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Fri, 25 Nov 2022 10:00:19 +0100 Subject: [PATCH 044/131] StudioPlugin: Use custom openOpenProjectDialog() to only allow qmlproject MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task-number: QDS-2549 Change-Id: I7393877b5f0306eafc6dbd5b5919e6592e664c16 Reviewed-by: Henning Gründl --- .../studiowelcome/studiowelcomeplugin.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/plugins/studiowelcome/studiowelcomeplugin.cpp b/src/plugins/studiowelcome/studiowelcomeplugin.cpp index faf17d5f627..5a48bc0148e 100644 --- a/src/plugins/studiowelcome/studiowelcomeplugin.cpp +++ b/src/plugins/studiowelcome/studiowelcomeplugin.cpp @@ -72,6 +72,16 @@ static bool useNewWelcomePage() return settings->value(newWelcomePageEntry, false).toBool(); } +static void openOpenProjectDialog() +{ + const FilePath path = Core::DocumentManager::useProjectsDirectory() + ? Core::DocumentManager::projectsDirectory() + : FilePath(); + const FilePaths files = Core::DocumentManager::getOpenFileNames("*.qmlproject", path); + if (!files.isEmpty()) + Core::ICore::openFiles(files, Core::ICore::None); +} + const char DO_NOT_SHOW_SPLASHSCREEN_AGAIN_KEY[] = "StudioSplashScreen"; const char DETAILED_USAGE_STATISTICS[] = "DetailedUsageStatistics"; @@ -194,16 +204,14 @@ public: Q_INVOKABLE void createProject() { - QTimer::singleShot(0, []() { + QTimer::singleShot(0, this, []() { ProjectExplorer::ProjectExplorerPlugin::openNewProjectDialog(); }); } Q_INVOKABLE void openProject() { - QTimer::singleShot(0, []() { - ProjectExplorer::ProjectExplorerPlugin::openOpenProjectDialog(); - }); + QTimer::singleShot(0, this, []() { openOpenProjectDialog(); }); } Q_INVOKABLE void openProjectAt(int row) From 907a64ac324f5038ffe809891edbdf3b3b5fe32f Mon Sep 17 00:00:00 2001 From: Samuel Ghinet Date: Fri, 25 Nov 2022 15:32:58 +0200 Subject: [PATCH 045/131] Fix bug: drag state remain on after dragging asset to texture Task-number: QDS-8341 Change-Id: If9bc7c0e3f2e017df4fde2cf94e7ffc168644eef Reviewed-by: Mahmoud Badri --- .../qmldesigner/components/textureeditor/textureeditorview.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp b/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp index 9384c9baf75..67779743da6 100644 --- a/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp @@ -854,6 +854,7 @@ void TextureEditorView::dragStarted(QMimeData *mimeData) void TextureEditorView::dragEnded() { highlightSupportedProperties(false); + m_qmlBackEnd->contextObject()->setActiveDragSuffix(""); } // from model to texture editor From c071547aa54b519567b6360af327748787e36141 Mon Sep 17 00:00:00 2001 From: Tim Jenssen Date: Thu, 24 Nov 2022 19:23:55 +0100 Subject: [PATCH 046/131] qmljs: fix warning Change-Id: I69714d6626d9135c5489258346f79b1c4bfbcd19 Reviewed-by: Reviewed-by: Thomas Hartmann --- src/libs/qmljs/qmljsmodelmanagerinterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/qmljs/qmljsmodelmanagerinterface.cpp b/src/libs/qmljs/qmljsmodelmanagerinterface.cpp index 6d3fda5af38..5786171b6a6 100644 --- a/src/libs/qmljs/qmljsmodelmanagerinterface.cpp +++ b/src/libs/qmljs/qmljsmodelmanagerinterface.cpp @@ -63,7 +63,7 @@ static const char *qtQuickUISuffix = "ui.qml"; static void maybeAddPath(ViewerContext &context, const Utils::FilePath &path) { - if (!path.isEmpty() && !context.paths.count(path) > 0) + if (!path.isEmpty() && !(context.paths.count(path) > 0)) context.paths.insert(path); } From 674ad1ead16dafbef53555dd4ff8e32b57265523 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Fri, 25 Nov 2022 13:19:56 +0200 Subject: [PATCH 047/131] QmlDesigner: Fix compiler warnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removed unused code. Change-Id: I40f94b6be1f9a1700c4024e78bae5df67513c839 Reviewed-by: Henning Gründl Reviewed-by: Mahmoud Badri Reviewed-by: --- .../qmldesigner/components/edit3d/edit3dview.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp index d8c2f7678e1..b7efef60d6a 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp @@ -4,14 +4,12 @@ #include "edit3dview.h" #include "backgroundcolorselection.h" -#include "bindingproperty.h" #include "designersettings.h" #include "designmodecontext.h" #include "edit3dactions.h" #include "edit3dcanvas.h" #include "edit3dviewconfig.h" #include "edit3dwidget.h" -#include "materialbrowserwidget.h" #include "metainfo.h" #include "nodehints.h" #include "nodeinstanceview.h" @@ -19,7 +17,6 @@ #include "qmldesignericons.h" #include "qmldesignerplugin.h" #include "seekerslider.h" -#include "variantproperty.h" #include #include @@ -32,15 +29,6 @@ namespace QmlDesigner { -static QString propertyEditorResourcesPath() -{ -#ifdef SHARE_QML_PATH - if (qEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE")) - return QLatin1String(SHARE_QML_PATH) + "/propertyEditorQmlSources"; -#endif - return Core::ICore::resourcePath("qmldesigner/propertyEditorQmlSources").toString(); -} - Edit3DView::Edit3DView(ExternalDependenciesInterface &externalDependencies) : AbstractView{externalDependencies} { From 3a899b34c7fa6ae22708fbae2ebfe39c6c3e33fc Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Fri, 25 Nov 2022 14:16:28 +0200 Subject: [PATCH 048/131] QmlDesigner: Skip imported files that already exist and are the same When importing files to project, if the target file already exists and has the same modified timestamp, silently skip copying the file instead of asking if it should be overwritten. Fixes: QDS-8402 Change-Id: If963e997c6bcfb3e46db6e74c46081784bdddc7e Reviewed-by: Mahmoud Badri --- .../components/componentcore/modelnodeoperations.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp index df7f701c4e5..c5e32b84c5a 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp +++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp @@ -1051,7 +1051,11 @@ AddFilesResult addFilesToProject(const QStringList &fileNames, const QString &de QStringList removeList; for (const QString &fileName : fileNames) { const QString targetFile = directory + "/" + QFileInfo(fileName).fileName(); - if (QFileInfo::exists(targetFile)) { + Utils::FilePath srcFilePath = Utils::FilePath::fromString(fileName); + Utils::FilePath targetFilePath = Utils::FilePath::fromString(targetFile); + if (targetFilePath.exists()) { + if (srcFilePath.lastModified() == targetFilePath.lastModified()) + continue; const QString title = QCoreApplication::translate( "ModelNodeOperations", "Overwrite Existing File?"); const QString question = QCoreApplication::translate( From ddecd338a8d0c7a446b23fc4076570734131a8cd Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Thu, 24 Nov 2022 17:30:12 +0200 Subject: [PATCH 049/131] QmlDesigner: Unify texture image providers Texture editor, material browser, and UrlChooser all had separate texture providers that served nearly identical purpose. Unified all use cases to use same PropertyEditorImageProvider. This provider is asynchronous, which combined with enabling caching on Image elements, significantly improves responsiveness of the material browser UI when there are many textures shown in the browser. Fixes: QDS-8387 Change-Id: I2888aee2f4320dba9456fa046c9ede319673a3d9 Reviewed-by: Mahmoud Badri --- .../materialBrowserQmlSource/TextureItem.qml | 2 +- .../imports/HelperWidgets/UrlChooser.qml | 18 ++++--- .../TextureEditorTopSection.qml | 7 ++- .../components/componentcore/viewmanager.cpp | 4 +- .../materialbrowser/materialbrowserview.cpp | 7 +-- .../materialbrowser/materialbrowserview.h | 4 +- .../materialbrowser/materialbrowserwidget.cpp | 49 +++++++------------ .../materialbrowser/materialbrowserwidget.h | 6 +-- .../propertyeditorimageprovider.cpp | 25 ++++++---- .../textureeditorcontextobject.cpp | 8 +++ .../textureeditorcontextobject.h | 1 + .../textureeditor/textureeditorqmlbackend.cpp | 40 +++------------ .../textureeditor/textureeditorqmlbackend.h | 6 ++- .../textureeditor/textureeditorview.cpp | 6 ++- .../textureeditor/textureeditorview.h | 4 +- .../imagecache/imagecachecollector.cpp | 7 ++- .../imagecache/smallimagecacheprovider.h | 2 + 17 files changed, 95 insertions(+), 101 deletions(-) diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml index a666354b832..00bb835826e 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml @@ -40,10 +40,10 @@ Rectangle { Image { source: "image://materialBrowserTex/" + textureSource + asynchronous: true sourceSize.width: root.width - 10 sourceSize.height: root.height - 10 anchors.centerIn: parent - cache: false smooth: true } } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/UrlChooser.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/UrlChooser.qml index b87005fe4bb..d6373f790ab 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/UrlChooser.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/UrlChooser.qml @@ -102,8 +102,8 @@ Row { Item { visible: thumbnail.status === Image.Ready - Layout.preferredWidth: 100 - Layout.preferredHeight: 100 + Layout.preferredWidth: 96 + Layout.preferredHeight: 96 Image { id: checker @@ -116,7 +116,10 @@ Row { Image { id: thumbnail asynchronous: true - anchors.fill: parent + sourceSize.height: 96 + sourceSize.width: 96 + height: 96 + width: 96 fillMode: Image.PreserveAspectFit source: { if (root.isBuiltInPrimitive(root.absoluteFilePath)) @@ -231,8 +234,8 @@ Row { Item { visible: delegateThumbnail.status === Image.Ready - Layout.preferredWidth: 100 - Layout.preferredHeight: 100 + Layout.preferredWidth: 96 + Layout.preferredHeight: 96 Image { id: delegateChecker @@ -245,7 +248,10 @@ Row { Image { id: delegateThumbnail asynchronous: true - anchors.fill: parent + sourceSize.height: 96 + sourceSize.width: 96 + height: 96 + width: 96 fillMode: Image.PreserveAspectFit source: { if (root.isBuiltInPrimitive(delegateRoot.name)) diff --git a/share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorTopSection.qml b/share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorTopSection.qml index 287278d9548..016049df102 100644 --- a/share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorTopSection.qml +++ b/share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorTopSection.qml @@ -11,7 +11,7 @@ Column { function refreshPreview() { texturePreview.source = "" - texturePreview.source = "image://textureEditor/" + backendValues.source.valueToString + texturePreview.source = "image://qmldesigner_thumbnails/" + resolveResourcePath(backendValues.source.valueToString) } anchors.left: parent.left @@ -34,12 +34,11 @@ Column { Image { id: texturePreview - + asynchronous: true sourceSize.width: 150 sourceSize.height: 150 anchors.centerIn: parent - source: "image://textureEditor/" + backendValues.source.valueToString - cache: false + source: "image://qmldesigner_thumbnails/" + resolveResourcePath(backendValues.source.valueToString) } } } diff --git a/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp b/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp index 08ee673ff16..7ac6d44877e 100644 --- a/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp @@ -69,8 +69,8 @@ public: , navigatorView{externalDependencies} , propertyEditorView(imageCache, externalDependencies) , materialEditorView{externalDependencies} - , materialBrowserView{externalDependencies} - , textureEditorView{externalDependencies} + , materialBrowserView{imageCache, externalDependencies} + , textureEditorView{imageCache, externalDependencies} , statesEditorView{externalDependencies} , newStatesEditorView{externalDependencies} {} diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp index 79fc5cb4b61..0445db3ba6d 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp @@ -41,8 +41,10 @@ static QString propertyEditorResourcesPath() return Core::ICore::resourcePath("qmldesigner/propertyEditorQmlSources").toString(); } -MaterialBrowserView::MaterialBrowserView(ExternalDependenciesInterface &externalDependencies) +MaterialBrowserView::MaterialBrowserView(AsynchronousImageCache &imageCache, + ExternalDependenciesInterface &externalDependencies) : AbstractView{externalDependencies} + , m_imageCache(imageCache) { m_previewTimer.setSingleShot(true); connect(&m_previewTimer, &QTimer::timeout, this, &MaterialBrowserView::requestPreviews); @@ -59,12 +61,11 @@ bool MaterialBrowserView::hasWidget() const WidgetInfo MaterialBrowserView::widgetInfo() { if (m_widget.isNull()) { - m_widget = new MaterialBrowserWidget(this); + m_widget = new MaterialBrowserWidget(m_imageCache, this); auto matEditorContext = new Internal::MaterialBrowserContext(m_widget.data()); Core::ICore::addContextObject(matEditorContext); - // custom notifications below are sent to the MaterialEditor MaterialBrowserModel *matBrowserModel = m_widget->materialBrowserModel().data(); diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h index 53ee5821b99..e8f1160f2f3 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h @@ -22,7 +22,8 @@ class MaterialBrowserView : public AbstractView Q_OBJECT public: - MaterialBrowserView(ExternalDependenciesInterface &externalDependencies); + MaterialBrowserView(class AsynchronousImageCache &imageCache, + ExternalDependenciesInterface &externalDependencies); ~MaterialBrowserView() override; bool hasWidget() const override; @@ -64,6 +65,7 @@ private: void loadPropertyGroups(); void requestPreviews(); + AsynchronousImageCache &m_imageCache; QPointer m_widget; QList m_selectedModels; // selected 3D model nodes diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp index 541bb385f67..d6e990761b8 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -89,33 +90,6 @@ public: } }; -class TextureImageProvider : public QQuickImageProvider -{ -public: - TextureImageProvider() : QQuickImageProvider(Pixmap) {} - - QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override - { - QPixmap pixmap; - const QString suffix = id.split('.').last().toLower(); - if (suffix == "hdr") - pixmap = HdrImage{id}.toPixmap(); - else - pixmap = Utils::StyleHelper::dpiSpecificImageFile(id); - - if (pixmap.isNull()) - pixmap = Utils::StyleHelper::dpiSpecificImageFile(":/textureeditor/images/texture_default.png"); - - if (size) - *size = pixmap.size(); - - if (requestedSize.isValid()) - return pixmap.scaled(requestedSize, Qt::KeepAspectRatio); - - return pixmap; - } -}; - bool MaterialBrowserWidget::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::FocusOut) { @@ -145,9 +119,16 @@ bool MaterialBrowserWidget::eventFilter(QObject *obj, QEvent *event) QString iconPath = QLatin1String("%1/%2") .arg(DocumentManager::currentResourcePath().path(), m_textureToDrag.variantProperty("source").value().toString()); - model->startDrag(mimeData, - m_textureImageProvider->requestPixmap(iconPath, nullptr, - {128, 128})); + + QPixmap pixmap; + const QString suffix = iconPath.split('.').last().toLower(); + if (suffix == "hdr") + pixmap = HdrImage{iconPath}.toPixmap(); + else + pixmap = Utils::StyleHelper::dpiSpecificImageFile(iconPath); + if (pixmap.isNull()) + pixmap = Utils::StyleHelper::dpiSpecificImageFile(":/textureeditor/images/texture_default.png"); + model->startDrag(mimeData, pixmap.scaled({128, 128})); } m_materialToDrag = {}; m_textureToDrag = {}; @@ -161,14 +142,18 @@ bool MaterialBrowserWidget::eventFilter(QObject *obj, QEvent *event) return QObject::eventFilter(obj, event); } -MaterialBrowserWidget::MaterialBrowserWidget(MaterialBrowserView *view) +MaterialBrowserWidget::MaterialBrowserWidget(AsynchronousImageCache &imageCache, + MaterialBrowserView *view) : m_materialBrowserView(view) , m_materialBrowserModel(new MaterialBrowserModel(this)) , m_materialBrowserTexturesModel(new MaterialBrowserTexturesModel(this)) , m_quickWidget(new QQuickWidget(this)) , m_previewImageProvider(new PreviewImageProvider()) - , m_textureImageProvider(new TextureImageProvider()) { + QImage defaultImage; + defaultImage.load(Utils::StyleHelper::dpiSpecificImageFile(":/textureeditor/images/texture_default.png")); + m_textureImageProvider = new PropertyEditorImageProvider(imageCache, defaultImage); + setWindowTitle(tr("Material Browser", "Title of material browser widget")); setMinimumWidth(120); diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h index 929900d52bb..a236ce14714 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h @@ -28,14 +28,14 @@ class MaterialBrowserView; class MaterialBrowserModel; class MaterialBrowserTexturesModel; class PreviewImageProvider; -class TextureImageProvider; +class PropertyEditorImageProvider; class MaterialBrowserWidget : public QFrame { Q_OBJECT public: - MaterialBrowserWidget(MaterialBrowserView *view); + MaterialBrowserWidget(class AsynchronousImageCache &imageCache, MaterialBrowserView *view); ~MaterialBrowserWidget() = default; QList createToolBarWidgets(); @@ -72,7 +72,7 @@ private: QShortcut *m_qmlSourceUpdateShortcut = nullptr; PreviewImageProvider *m_previewImageProvider = nullptr; - TextureImageProvider *m_textureImageProvider = nullptr; + PropertyEditorImageProvider *m_textureImageProvider = nullptr; Core::IContext *m_context = nullptr; QString m_filterText; diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorimageprovider.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorimageprovider.cpp index 90dd2caaebe..e7ff48450d1 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorimageprovider.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorimageprovider.cpp @@ -25,18 +25,25 @@ QQuickImageResponse *PropertyEditorImageProvider::requestImageResponse(const QSt return m_smallImageCacheProvider.requestImageResponse("#" + id.split('.').first(), requestedSize); - QImage image; - auto response = std::make_unique(image); + auto response = std::make_unique(m_smallImageCacheProvider.defaultImage()); QMetaObject::invokeMethod( response.get(), - [response = QPointer(response.get()), image, suffix, id] { - if (AssetsLibraryModel::supportedImageSuffixes().contains(suffix)) - response->setImage(QImage(Utils::StyleHelper::dpiSpecificImageFile(id))); - else if (AssetsLibraryModel::supportedTexture3DSuffixes().contains(suffix)) - response->setImage(HdrImage{id}.image()); - else - response->abort(); + [response = QPointer(response.get()), suffix, id, requestedSize] { + if (AssetsLibraryModel::supportedImageSuffixes().contains(suffix)) { + QImage image = QImage(Utils::StyleHelper::dpiSpecificImageFile(id)); + if (!image.isNull()) { + response->setImage(image.scaled(requestedSize, Qt::KeepAspectRatio)); + return; + } + } else if (AssetsLibraryModel::supportedTexture3DSuffixes().contains(suffix)) { + HdrImage hdr{id}; + if (!hdr.image().isNull()) { + response->setImage(hdr.image().scaled(requestedSize, Qt::KeepAspectRatio)); + return; + } + } + response->setImage(response->image().scaled(requestedSize, Qt::KeepAspectRatio)); }, Qt::QueuedConnection); diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.cpp b/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.cpp index d358425a1a3..690e78db0a5 100644 --- a/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.cpp +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -334,4 +335,11 @@ void TextureEditorContextObject::goIntoComponent() DocumentManager::goIntoComponent(m_selectedTexture); } +QString TextureEditorContextObject::resolveResourcePath(const QString &path) +{ + if (Utils::FilePath::fromString(path).isAbsolutePath()) + return path; + return DocumentManager::currentResourcePath().path() + '/' + path; +} + } // QmlDesigner diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.h b/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.h index cc479e5092e..56249382724 100644 --- a/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.h +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.h @@ -73,6 +73,7 @@ public: Q_INVOKABLE bool isBlocked(const QString &propName) const; Q_INVOKABLE void goIntoComponent(); + Q_INVOKABLE QString resolveResourcePath(const QString &path); enum ToolBarAction { ApplyToSelected, diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.cpp b/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.cpp index b385afe4f37..c3bcdd7d5e7 100644 --- a/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.cpp +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.cpp @@ -6,6 +6,7 @@ #include "bindingproperty.h" #include "documentmanager.h" #include "nodemetainfo.h" +#include "propertyeditorimageprovider.h" #include "propertyeditorvalue.h" #include "qmldesignerconstants.h" #include "qmlobjectnode.h" @@ -41,46 +42,17 @@ static QObject *variantToQObject(const QVariant &value) namespace QmlDesigner { -class TextureEditorImageProvider : public QQuickImageProvider -{ - QPixmap m_previewPixmap; - -public: - TextureEditorImageProvider() - : QQuickImageProvider(Pixmap) {} - - QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override - { - QPixmap pixmap; - const QString suffix = id.split('.').last().toLower(); - const QString path = DocumentManager::currentResourcePath().path() + '/' + id; - if (suffix == "hdr") - pixmap = HdrImage{path}.toPixmap(); - else - pixmap = Utils::StyleHelper::dpiSpecificImageFile(path); - - if (pixmap.isNull()) - pixmap = Utils::StyleHelper::dpiSpecificImageFile(":/textureeditor/images/texture_default.png"); - - if (size) - *size = pixmap.size(); - - if (requestedSize.isValid()) - return pixmap.scaled(requestedSize, Qt::KeepAspectRatio); - - return pixmap; - } -}; - -TextureEditorQmlBackend::TextureEditorQmlBackend(TextureEditorView *textureEditor) +TextureEditorQmlBackend::TextureEditorQmlBackend(TextureEditorView *textureEditor, AsynchronousImageCache &imageCache) : m_view(new QQuickWidget) , m_textureEditorTransaction(new TextureEditorTransaction(textureEditor)) , m_contextObject(new TextureEditorContextObject(m_view->rootContext())) - , m_textureEditorImageProvider(new TextureEditorImageProvider()) { + QImage defaultImage; + defaultImage.load(Utils::StyleHelper::dpiSpecificImageFile(":/textureeditor/images/texture_default.png")); + m_textureEditorImageProvider = new PropertyEditorImageProvider(imageCache, defaultImage); m_view->setResizeMode(QQuickWidget::SizeRootObjectToView); m_view->engine()->addImportPath(propertyEditorResourcesPath() + "/imports"); - m_view->engine()->addImageProvider("textureEditor", m_textureEditorImageProvider); + m_view->engine()->addImageProvider("qmldesigner_thumbnails", m_textureEditorImageProvider); m_contextObject->setBackendValues(&m_backendValuesPropertyMap); m_contextObject->setModel(textureEditor->model()); context()->setContextObject(m_contextObject.data()); diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.h b/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.h index fab25c6d586..2d0131cc9fc 100644 --- a/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.h +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.h @@ -17,6 +17,7 @@ QT_END_NAMESPACE namespace QmlDesigner { +class PropertyEditorImageProvider; class TextureEditorContextObject; class TextureEditorImageProvider; class TextureEditorTransaction; @@ -27,7 +28,8 @@ class TextureEditorQmlBackend Q_DISABLE_COPY(TextureEditorQmlBackend) public: - TextureEditorQmlBackend(TextureEditorView *materialEditor); + TextureEditorQmlBackend(TextureEditorView *materialEditor, + class AsynchronousImageCache &imageCache); ~TextureEditorQmlBackend(); void setup(const QmlObjectNode &selectedTextureNode, const QString &stateName, const QUrl &qmlSpecificsFile, @@ -63,7 +65,7 @@ private: DesignerPropertyMap m_backendValuesPropertyMap; QScopedPointer m_textureEditorTransaction; QScopedPointer m_contextObject; - TextureEditorImageProvider *m_textureEditorImageProvider = nullptr; + PropertyEditorImageProvider *m_textureEditorImageProvider = nullptr; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp b/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp index 67779743da6..b1d3c210ecb 100644 --- a/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp @@ -49,8 +49,10 @@ namespace QmlDesigner { -TextureEditorView::TextureEditorView(ExternalDependenciesInterface &externalDependencies) +TextureEditorView::TextureEditorView(AsynchronousImageCache &imageCache, + ExternalDependenciesInterface &externalDependencies) : AbstractView{externalDependencies} + , m_imageCache(imageCache) , m_stackedWidget(new QStackedWidget) , m_dynamicPropertiesModel(new Internal::DynamicPropertiesModel(true, this)) { @@ -432,7 +434,7 @@ void TextureEditorView::setupQmlBackend() QString currentStateName = currentState().isBaseState() ? currentState().name() : "invalid state"; if (!currentQmlBackend) { - currentQmlBackend = new TextureEditorQmlBackend(this); + currentQmlBackend = new TextureEditorQmlBackend(this, m_imageCache); m_stackedWidget->addWidget(currentQmlBackend->widget()); m_qmlBackendHash.insert(qmlPaneUrl.toString(), currentQmlBackend); diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorview.h b/src/plugins/qmldesigner/components/textureeditor/textureeditorview.h index 8e5c175e853..39586cd8425 100644 --- a/src/plugins/qmldesigner/components/textureeditor/textureeditorview.h +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorview.h @@ -31,7 +31,8 @@ class TextureEditorView : public AbstractView Q_OBJECT public: - TextureEditorView(ExternalDependenciesInterface &externalDependencies); + TextureEditorView(class AsynchronousImageCache &imageCache, + ExternalDependenciesInterface &externalDependencies); ~TextureEditorView() override; bool hasWidget() const override; @@ -105,6 +106,7 @@ private: bool noValidSelection() const; + AsynchronousImageCache &m_imageCache; ModelNode m_selectedTexture; QTimer m_ensureMatLibTimer; QShortcut *m_updateShortcut = nullptr; diff --git a/src/plugins/qmldesigner/designercore/imagecache/imagecachecollector.cpp b/src/plugins/qmldesigner/designercore/imagecache/imagecachecollector.cpp index be176760491..d762299f1cb 100644 --- a/src/plugins/qmldesigner/designercore/imagecache/imagecachecollector.cpp +++ b/src/plugins/qmldesigner/designercore/imagecache/imagecachecollector.cpp @@ -14,6 +14,7 @@ #include #include +#include #include namespace QmlDesigner { @@ -103,7 +104,11 @@ void ImageCacheCollector::start(Utils::SmallStringView name, auto callback = [=, captureCallback = std::move(captureCallback)](const QImage &image) { if (nullImageHandling == ImageCacheCollectorNullImageHandling::CaptureNullImage || !image.isNull()) { - QSize smallImageSize = image.size().scaled(QSize{96, 96}.boundedTo(image.size()), + QSize targetSize {96, 96}; + const qreal ratio = qGuiApp->devicePixelRatio(); + if (ratio > 1.0) + targetSize *= qRound(ratio); + QSize smallImageSize = image.size().scaled(targetSize.boundedTo(image.size()), Qt::KeepAspectRatio); QImage smallImage = image.isNull() ? QImage{} : image.scaled(smallImageSize, diff --git a/src/plugins/qmldesigner/designercore/imagecache/smallimagecacheprovider.h b/src/plugins/qmldesigner/designercore/imagecache/smallimagecacheprovider.h index 1a608c626c9..54b3b63f1d5 100644 --- a/src/plugins/qmldesigner/designercore/imagecache/smallimagecacheprovider.h +++ b/src/plugins/qmldesigner/designercore/imagecache/smallimagecacheprovider.h @@ -20,6 +20,7 @@ public: QQuickTextureFactory *textureFactory() const override; void setImage(const QImage &image); + QImage image() const { return m_image; } void abort(); @@ -37,6 +38,7 @@ public: QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize) override; + QImage defaultImage() const { return m_defaultImage; } private: AsynchronousImageCache &m_cache; From ea55e010511c1032ac8dfad411403e4b0f4211ff Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Fri, 25 Nov 2022 17:30:05 +0200 Subject: [PATCH 050/131] QmlDesigner: Focus material browser's sections separately Focus is shown by a thicker selection border for material and texture items. Delete shortcut works on the selected focused item. Fixes: QDS-8401 Change-Id: I1ddbbd595a0da3f0e2b7cf2bb9e611532770bc3c Reviewed-by: Miikka Heikkinen --- .../materialBrowserQmlSource/MaterialItem.qml | 8 ++++-- .../materialBrowserQmlSource/TextureItem.qml | 4 ++- .../materialbrowser/materialbrowsermodel.h | 2 ++ .../materialbrowsertexturesmodel.h | 2 ++ .../materialbrowser/materialbrowserview.cpp | 2 +- .../materialbrowser/materialbrowserwidget.cpp | 26 +++++++++++++++++++ .../materialbrowser/materialbrowserwidget.h | 9 +++++++ 7 files changed, 49 insertions(+), 4 deletions(-) diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialItem.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialItem.qml index 6eaf0351d2f..bb38fe435ea 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialItem.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialItem.qml @@ -36,7 +36,7 @@ Rectangle { mouseArea.forceActiveFocus() } - border.width: materialBrowserModel.selectedIndex === index ? 1 : 0 + border.width: materialBrowserModel.selectedIndex === index ? rootView.materialSectionFocused ? 3 : 1 : 0 border.color: materialBrowserModel.selectedIndex === index ? StudioTheme.Values.themeControlOutlineInteraction : "transparent" @@ -50,6 +50,7 @@ Rectangle { acceptedButtons: Qt.LeftButton | Qt.RightButton onPressed: (mouse) => { + rootView.focusMaterialSection(true) materialBrowserModel.selectMaterial(index) if (mouse.button === Qt.LeftButton) @@ -116,7 +117,10 @@ Rectangle { anchors.fill: parent - onClicked: materialBrowserModel.selectMaterial(index) + onClicked: { + rootView.focusMaterialSection(true) + materialBrowserModel.selectMaterial(index) + } onDoubleClicked: root.startRename() } } diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml index 00bb835826e..19762299c6d 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml @@ -13,7 +13,8 @@ Rectangle { visible: textureVisible color: "transparent" - border.width: materialBrowserTexturesModel.selectedIndex === index ? 1 : 0 + border.width: materialBrowserTexturesModel.selectedIndex === index + ? !rootView.materialSectionFocused ? 3 : 1 : 0 border.color: materialBrowserTexturesModel.selectedIndex === index ? StudioTheme.Values.themeControlOutlineInteraction : "transparent" @@ -27,6 +28,7 @@ Rectangle { acceptedButtons: Qt.LeftButton | Qt.RightButton onPressed: (mouse) => { + rootView.focusMaterialSection(false) materialBrowserTexturesModel.selectTexture(index) if (mouse.button === Qt.LeftButton) diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h index d836bc0070a..c1286e62e25 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h @@ -47,6 +47,8 @@ public: bool hasMaterialLibrary() const; void setHasMaterialLibrary(bool b); + bool isEmpty() const { return m_isEmpty; } + QString copiedMaterialType() const; void setCopiedMaterialType(const QString &matType); diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h index 34e4fb23647..a1db791beaf 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h @@ -41,6 +41,8 @@ public: bool hasSingleModelSelection() const; void setHasSingleModelSelection(bool b); + bool isEmpty() const { return m_isEmpty; } + void resetModel(); Q_INVOKABLE void selectTexture(int idx, bool force = false); diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp index 0445db3ba6d..33bf07c2409 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp @@ -440,7 +440,7 @@ void MaterialBrowserView::customNotification(const AbstractView *view, refreshModel(true); }); } else if (identifier == "delete_selected_material") { - m_widget->materialBrowserModel()->deleteSelectedMaterial(); + m_widget->deleteSelectedItem(); } else if (identifier == "apply_texture_to_model3D") { applyTextureToModel3D(nodeList.at(0), nodeList.at(1)); } else if (identifier == "apply_texture_to_material") { diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp index d6e990761b8..923a2a42e30 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp @@ -191,6 +191,16 @@ MaterialBrowserWidget::MaterialBrowserWidget(AsynchronousImageCache &imageCache, m_qmlSourceUpdateShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_F8), this); connect(m_qmlSourceUpdateShortcut, &QShortcut::activated, this, &MaterialBrowserWidget::reloadQmlSource); + connect(m_materialBrowserModel, &MaterialBrowserModel::isEmptyChanged, this, [&] { + if (m_materialBrowserModel->isEmpty()) + focusMaterialSection(false); + }); + + connect(m_materialBrowserTexturesModel, &MaterialBrowserTexturesModel::isEmptyChanged, this, [&] { + if (m_materialBrowserTexturesModel->isEmpty()) + focusMaterialSection(true); + }); + QmlDesignerPlugin::trackWidgetFocusTime(this, Constants::EVENT_MATERIALBROWSER_TIME); reloadQmlSource(); @@ -204,6 +214,14 @@ void MaterialBrowserWidget::updateMaterialPreview(const ModelNode &node, const Q QMetaObject::invokeMethod(m_quickWidget->rootObject(), "refreshPreview", Q_ARG(QVariant, idx)); } +void MaterialBrowserWidget::deleteSelectedItem() +{ + if (m_materialSectionFocused) + m_materialBrowserModel->deleteSelectedMaterial(); + else + m_materialBrowserTexturesModel->deleteSelectedTexture(); +} + QList MaterialBrowserWidget::createToolBarWidgets() { return {}; @@ -247,6 +265,14 @@ void MaterialBrowserWidget::acceptBundleTextureDrop() m_materialBrowserView->emitCustomNotification("drop_bundle_texture", {}, {}); // To ContentLibraryView } +void MaterialBrowserWidget::focusMaterialSection(bool focusMatSec) +{ + if (focusMatSec != m_materialSectionFocused) { + m_materialSectionFocused = focusMatSec; + emit materialSectionFocusedChanged(); + } +} + QString MaterialBrowserWidget::qmlSourcesPath() { #ifdef SHARE_QML_PATH diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h index a236ce14714..4b0541ee560 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h @@ -34,6 +34,8 @@ class MaterialBrowserWidget : public QFrame { Q_OBJECT + Q_PROPERTY(bool materialSectionFocused MEMBER m_materialSectionFocused NOTIFY materialSectionFocusedChanged) + public: MaterialBrowserWidget(class AsynchronousImageCache &imageCache, MaterialBrowserView *view); ~MaterialBrowserWidget() = default; @@ -47,17 +49,22 @@ public: QPointer materialBrowserModel() const; QPointer materialBrowserTexturesModel() const; void updateMaterialPreview(const ModelNode &node, const QPixmap &pixmap); + void deleteSelectedItem(); Q_INVOKABLE void handleSearchFilterChanged(const QString &filterText); Q_INVOKABLE void startDragMaterial(int index, const QPointF &mousePos); Q_INVOKABLE void startDragTexture(int index, const QPointF &mousePos); Q_INVOKABLE void acceptBundleMaterialDrop(); Q_INVOKABLE void acceptBundleTextureDrop(); + Q_INVOKABLE void focusMaterialSection(bool focusMatSec); QQuickWidget *quickWidget() const; void clearPreviewCache(); +signals: + void materialSectionFocusedChanged(); + protected: bool eventFilter(QObject *obj, QEvent *event) override; @@ -80,6 +87,8 @@ private: ModelNode m_materialToDrag; ModelNode m_textureToDrag; QPoint m_dragStartPoint; + + bool m_materialSectionFocused = true; }; } // namespace QmlDesigner From 62d86837e9a20db22b425fb1efc3b3e1c76cefe8 Mon Sep 17 00:00:00 2001 From: Henning Gruendl Date: Fri, 25 Nov 2022 15:44:32 +0100 Subject: [PATCH 051/131] QmlDesigner: Fix states editor none visual items When using any none visual item as root (e.g. Window) which don't have a state property the support for adding a state on the root item needs to be deactivated otherwise QtDS is crashing. The new states editor was missing that. On the other hand the use of StatesGroup with none visual items is supported. Change-Id: I194913719a13099ce8ac7cde5f78083ef84dc37f Reviewed-by: Reviewed-by: Thomas Hartmann --- share/qtcreator/qmldesigner/newstateseditor/Main.qml | 1 + .../components/stateseditornew/stateseditorview.cpp | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/share/qtcreator/qmldesigner/newstateseditor/Main.qml b/share/qtcreator/qmldesigner/newstateseditor/Main.qml index 92bf99b86c8..7f20190322a 100644 --- a/share/qtcreator/qmldesigner/newstateseditor/Main.qml +++ b/share/qtcreator/qmldesigner/newstateseditor/Main.qml @@ -863,6 +863,7 @@ Rectangle { Item { id: addWrapper + visible: canAddNewStates Canvas { id: addCanvas diff --git a/src/plugins/qmldesigner/components/stateseditornew/stateseditorview.cpp b/src/plugins/qmldesigner/components/stateseditornew/stateseditorview.cpp index e20f5df7b47..c263a510966 100644 --- a/src/plugins/qmldesigner/components/stateseditornew/stateseditorview.cpp +++ b/src/plugins/qmldesigner/components/stateseditornew/stateseditorview.cpp @@ -105,6 +105,8 @@ void StatesEditorView::setActiveStatesGroupNode(const ModelNode &modelNode) m_activeStatesGroupNode = modelNode; resetModel(); + checkForStatesAvailability(); + emit m_statesEditorModel->activeStateGroupChanged(); emit m_statesEditorModel->activeStateGroupIndexChanged(); } @@ -430,8 +432,10 @@ void StatesEditorView::resetStateGroups() void StatesEditorView::checkForStatesAvailability() { if (m_statesEditorWidget) { - const bool isVisual = QmlVisualNode::isValidQmlVisualNode(activeStatesGroupNode()); - m_statesEditorWidget->showAddNewStatesButton(isVisual); + const bool isVisual = activeStatesGroupNode().metaInfo().isBasedOn( + model()->qtQuickItemMetaInfo(), model()->qtQuick3DNodeMetaInfo()); + const bool isRoot = activeStatesGroupNode().isRootNode(); + m_statesEditorWidget->showAddNewStatesButton(isVisual || !isRoot); } } From 7d13c827503426bbe3519a0c8d7ac302be373834 Mon Sep 17 00:00:00 2001 From: Henning Gruendl Date: Fri, 25 Nov 2022 17:49:36 +0100 Subject: [PATCH 052/131] QmlDesigner: Expose realValue from SpinBox Task-number: QDS-8191 Change-Id: I265c4a95e84f6ef10a4c69f9a0089970f16f4bbf Reviewed-by: Thomas Hartmann Reviewed-by: --- .../propertyEditorQmlSources/imports/HelperWidgets/SpinBox.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SpinBox.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SpinBox.qml index ba059e9e4f1..151356148a0 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SpinBox.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SpinBox.qml @@ -13,6 +13,7 @@ Item { property alias minimumValue: spinBox.realFrom property alias maximumValue: spinBox.realTo + property alias value: spinBox.realValue property alias stepSize: spinBox.realStepSize property alias backendValue: spinBox.backendValue From 5544cdc276612de1c1672fc89a808e4f471f713f Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Fri, 25 Nov 2022 17:01:53 +0200 Subject: [PATCH 053/131] QmlDesigner: Don't create duplicate default texture instance Whenever an operation involving an asset image would result in creation of a new texture node, it now checks first if a matching texture node already exists and uses that instead. Fixes: QDS-8435 Change-Id: I3d091aafcd09afdec897bc4da79789c1d84056e8 Reviewed-by: Mahmoud Badri --- .../contentlibrary/contentlibraryview.cpp | 23 ++++++++++----- .../materialbrowser/materialbrowserview.cpp | 4 +++ .../propertyeditor/propertyeditorvalue.cpp | 28 +++++++++++++------ .../designercore/include/abstractview.h | 1 + .../designercore/model/abstractview.cpp | 24 ++++++++++++++++ 5 files changed, 64 insertions(+), 16 deletions(-) diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp index e9221b160d3..ae1d87276bd 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp @@ -75,20 +75,29 @@ WidgetInfo ContentLibraryView::widgetInfo() return; NodeMetaInfo metaInfo = model()->metaInfo("QtQuick3D.Texture"); - ModelNode newTexNode = createModelNode("QtQuick3D.Texture", metaInfo.majorVersion(), - metaInfo.minorVersion()); - newTexNode.validId(); - VariantProperty sourceProp = newTexNode.variantProperty("source"); - sourceProp.setValue(QLatin1String("images/%1").arg(texPath.split('/').last())); - matLib.defaultNodeListProperty().reparentHere(newTexNode); + + QString sourceVal = QLatin1String("images/%1").arg(texPath.split('/').last()); + ModelNode texNode = getTextureDefaultInstance(sourceVal); + if (!texNode.isValid()) { + texNode = createModelNode("QtQuick3D.Texture", metaInfo.majorVersion(), + metaInfo.minorVersion()); + texNode.validId(); + VariantProperty sourceProp = texNode.variantProperty("source"); + sourceProp.setValue(sourceVal); + matLib.defaultNodeListProperty().reparentHere(texNode); + } // assign the texture as scene environment's light probe if (mode == ContentLibraryWidget::AddTextureMode::LightProbe && m_activeSceneEnv.isValid()) { BindingProperty lightProbeProp = m_activeSceneEnv.bindingProperty("lightProbe"); - lightProbeProp.setExpression(newTexNode.id()); + lightProbeProp.setExpression(texNode.id()); VariantProperty bgModeProp = m_activeSceneEnv.variantProperty("backgroundMode"); bgModeProp.setValue(QVariant::fromValue(Enumeration("SceneEnvironment", "SkyBox"))); } + QTimer::singleShot(0, this, [this, texNode]() { + if (model() && texNode.isValid()) + emitCustomNotification("selected_texture_changed", {texNode}); + }); }); }); diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp index 33bf07c2409..cba8f32199f 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp @@ -435,6 +435,10 @@ void MaterialBrowserView::customNotification(const AbstractView *view, int idx = m_widget->materialBrowserModel()->materialIndex(nodeList.first()); if (idx != -1) m_widget->materialBrowserModel()->selectMaterial(idx); + } else if (identifier == "selected_texture_changed") { + int idx = m_widget->materialBrowserTexturesModel()->textureIndex(nodeList.first()); + if (idx != -1) + m_widget->materialBrowserTexturesModel()->selectTexture(idx); } else if (identifier == "refresh_material_browser") { QTimer::singleShot(0, model(), [this]() { refreshModel(true); diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp index 28ee93cbb2d..6bca126ae69 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp @@ -500,18 +500,28 @@ void PropertyEditorValue::commitDrop(const QString &dropData) m_modelNode.view()->executeInTransaction(__FUNCTION__, [&] { QmlDesigner::ModelNode texture = m_modelNode.view()->modelNodeForInternalId(dropData.toInt()); if (!texture || !texture.metaInfo().isQtQuick3DTexture()) { - // create a texture node - QmlDesigner::NodeMetaInfo metaInfo = m_modelNode.view()->model()->metaInfo("QtQuick3D.Texture"); - texture = m_modelNode.view()->createModelNode("QtQuick3D.Texture", metaInfo.majorVersion(), - metaInfo.minorVersion()); - texture.validId(); - m_modelNode.view()->materialLibraryNode().defaultNodeListProperty().reparentHere(texture); - - // set texture source Utils::FilePath imagePath = Utils::FilePath::fromString(dropData); Utils::FilePath currFilePath = QmlDesigner::DocumentManager::currentFilePath(); + QString sourceVal = imagePath.relativePathFrom(currFilePath).toString(); + texture = m_modelNode.view()->getTextureDefaultInstance(sourceVal); + + if (!texture.isValid()) { + // create a texture node + QmlDesigner::NodeMetaInfo metaInfo = m_modelNode.view()->model()->metaInfo("QtQuick3D.Texture"); + texture = m_modelNode.view()->createModelNode("QtQuick3D.Texture", metaInfo.majorVersion(), + metaInfo.minorVersion()); + texture.validId(); + m_modelNode.view()->materialLibraryNode().defaultNodeListProperty().reparentHere(texture); + } + + // set texture source QmlDesigner::VariantProperty srcProp = texture.variantProperty("source"); - srcProp.setValue(imagePath.relativePathFrom(currFilePath).toString()); + srcProp.setValue(sourceVal); + + QTimer::singleShot(0, this, [this, texture]() { + if (m_modelNode.isValid() && texture.isValid() && m_modelNode.view()) + m_modelNode.view()->emitCustomNotification("selected_texture_changed", {texture}); + }); } // assign the texture to the property diff --git a/src/plugins/qmldesigner/designercore/include/abstractview.h b/src/plugins/qmldesigner/designercore/include/abstractview.h index 2afc56e6e0a..55f7ddcf9d0 100644 --- a/src/plugins/qmldesigner/designercore/include/abstractview.h +++ b/src/plugins/qmldesigner/designercore/include/abstractview.h @@ -230,6 +230,7 @@ public: ModelNode materialLibraryNode(); ModelNode active3DSceneNode(); void assignMaterialTo3dModel(const ModelNode &modelNode, const ModelNode &materialNode = {}); + ModelNode getTextureDefaultInstance(const QString &source); const NodeInstanceView *nodeInstanceView() const; RewriterView *rewriterView() const; diff --git a/src/plugins/qmldesigner/designercore/model/abstractview.cpp b/src/plugins/qmldesigner/designercore/model/abstractview.cpp index fe5d5a18ca8..20d39362a0e 100644 --- a/src/plugins/qmldesigner/designercore/model/abstractview.cpp +++ b/src/plugins/qmldesigner/designercore/model/abstractview.cpp @@ -915,6 +915,30 @@ void AbstractView::assignMaterialTo3dModel(const ModelNode &modelNode, const Mod modelMatsProp.setExpression(newMaterialNode.id()); } +ModelNode AbstractView::getTextureDefaultInstance(const QString &source) +{ + ModelNode matLib = materialLibraryNode(); + if (!matLib.isValid()) + return {}; + + const QList matLibNodes = matLib.directSubModelNodes(); + for (const ModelNode &tex : matLibNodes) { + if (tex.isValid() && tex.metaInfo().isQtQuick3DTexture()) { + const QList props = tex.properties(); + if (props.size() != 1) + continue; + const AbstractProperty &prop = props[0]; + if (prop.name() == "source" && prop.isVariantProperty() + && prop.toVariantProperty().value().toString() == source) { + return tex; + } + } + } + + return {}; +} + + ModelNode AbstractView::currentStateNode() const { if (model()) From ee10ba1909bec243e2a65d5fd191e38628b1d130 Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Fri, 25 Nov 2022 16:55:26 +0100 Subject: [PATCH 054/131] QmlDesigner: Add support for tooltip in item library MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows to define tool tips in the .metainfo files. Change-Id: I3236c6e9f374a052e99b18d8c3983bfd0f072162 Reviewed-by: Henning Gründl Reviewed-by: Miikka Heikkinen --- .../itemLibraryQmlSources/ItemDelegate.qml | 2 ++ .../HelperWidgets/ImagePreviewTooltipArea.qml | 2 +- .../designercore/include/itemlibraryinfo.h | 2 ++ .../designercore/metainfo/itemlibraryinfo.cpp | 17 +++++++++++++++++ .../designercore/metainfo/metainforeader.cpp | 2 ++ 5 files changed, 24 insertions(+), 1 deletion(-) diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/ItemDelegate.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/ItemDelegate.qml index b454ca7d846..72e7b8ba39d 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/ItemDelegate.qml +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/ItemDelegate.qml @@ -68,6 +68,8 @@ Item { id: mouseRegion anchors.fill: parent + tooltip: toolTip + onShowContextMenu: delegateRoot.showContextMenu() onPressed: (mouse)=> { allowTooltip = false diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ImagePreviewTooltipArea.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ImagePreviewTooltipArea.qml index e390906b9f6..ae46115889b 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ImagePreviewTooltipArea.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ImagePreviewTooltipArea.qml @@ -5,7 +5,7 @@ import QtQuick 2.15 import QtQuick.Layouts 1.15 import HelperWidgets 2.0 -MouseArea { +ToolTipArea { id: mouseArea property bool allowTooltip: true diff --git a/src/plugins/qmldesigner/designercore/include/itemlibraryinfo.h b/src/plugins/qmldesigner/designercore/include/itemlibraryinfo.h index cd9d37ec679..3126dc00537 100644 --- a/src/plugins/qmldesigner/designercore/include/itemlibraryinfo.h +++ b/src/plugins/qmldesigner/designercore/include/itemlibraryinfo.h @@ -47,6 +47,7 @@ public: QString requiredImport() const; QString customComponentSource() const; QStringList extraFilePaths() const; + QString toolTip() const; using Property = QmlDesigner::PropertyContainer; @@ -62,6 +63,7 @@ public: void setCategory(const QString &category); void setQmlPath(const QString &qml); void setRequiredImport(const QString &requiredImport); + void setToolTip(const QString &tooltip); void addHints(const QHash &hints); void setCustomComponentSource(const QString &source); void addExtraFilePath(const QString &extraFile); diff --git a/src/plugins/qmldesigner/designercore/metainfo/itemlibraryinfo.cpp b/src/plugins/qmldesigner/designercore/metainfo/itemlibraryinfo.cpp index a96fac3ad09..ffdf282ce1e 100644 --- a/src/plugins/qmldesigner/designercore/metainfo/itemlibraryinfo.cpp +++ b/src/plugins/qmldesigner/designercore/metainfo/itemlibraryinfo.cpp @@ -3,6 +3,7 @@ #include "itemlibraryinfo.h" #include "nodemetainfo.h" +#include "qregularexpression.h" #include @@ -32,6 +33,7 @@ public: QHash hints; QString customComponentSource; QStringList extraFilePaths; + QString toolTip; }; } // namespace Internal @@ -96,6 +98,11 @@ QStringList ItemLibraryEntry::extraFilePaths() const return m_data->extraFilePaths; } +QString ItemLibraryEntry::toolTip() const +{ + return m_data->toolTip; +} + int ItemLibraryEntry::majorVersion() const { return m_data->majorVersion; @@ -165,6 +172,16 @@ void ItemLibraryEntry::setRequiredImport(const QString &requiredImport) m_data->requiredImport = requiredImport; } +void ItemLibraryEntry::setToolTip(const QString &tooltip) +{ + static QRegularExpression regularExpressionPattern(QLatin1String("^qsTr\\(\"(.*)\"\\)$")); + const QRegularExpressionMatch match = regularExpressionPattern.match(tooltip); + if (match.hasMatch()) + m_data->toolTip = match.captured(1); + else + m_data->toolTip = tooltip; +} + void ItemLibraryEntry::addHints(const QHash &hints) { Utils::addToHash(&m_data->hints, hints); diff --git a/src/plugins/qmldesigner/designercore/metainfo/metainforeader.cpp b/src/plugins/qmldesigner/designercore/metainfo/metainforeader.cpp index 862db6095b6..91fb7efab91 100644 --- a/src/plugins/qmldesigner/designercore/metainfo/metainforeader.cpp +++ b/src/plugins/qmldesigner/designercore/metainfo/metainforeader.cpp @@ -266,6 +266,8 @@ void MetaInfoReader::readItemLibraryEntryProperty(const QString &name, const QVa setVersion(value.toString()); } else if (name == QStringLiteral("requiredImport")) { m_currentEntry.setRequiredImport(value.toString()); + } else if (name == QStringLiteral("toolTip")) { + m_currentEntry.setToolTip(value.toString()); } else { addError(::QmlDesigner::Internal::MetaInfoReader::tr( "Unknown property for ItemLibraryEntry %1") From 84104499b74f54f3f86f27f8d872aaba0d461112 Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Mon, 28 Nov 2022 12:29:34 +0100 Subject: [PATCH 055/131] QmlDesigner: Add missing ItemLibraryItem property MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change-Id: Iab3a50ac67002e4a2f3807ee920bce892cd087cc Reviewed-by: Henning Gründl Reviewed-by: Miikka Heikkinen --- .../qmldesigner/components/itemlibrary/itemlibraryitem.cpp | 5 +++++ .../qmldesigner/components/itemlibrary/itemlibraryitem.h | 2 ++ 2 files changed, 7 insertions(+) diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryitem.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryitem.cpp index b1ec214993f..c5f267627dc 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryitem.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryitem.cpp @@ -51,6 +51,11 @@ QString ItemLibraryItem::componentSource() const return m_itemLibraryEntry.customComponentSource(); } +QString ItemLibraryItem::toolTip() const +{ + return m_itemLibraryEntry.toolTip(); +} + bool ItemLibraryItem::setVisible(bool isVisible) { if (isVisible != m_isVisible) { diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryitem.h b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryitem.h index 58daac2e6e8..0f5d4878f51 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryitem.h +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryitem.h @@ -24,6 +24,7 @@ class ItemLibraryItem: public QObject Q_PROPERTY(bool itemUsable READ isUsable FINAL) Q_PROPERTY(QString itemRequiredImport READ requiredImport FINAL) Q_PROPERTY(QString itemComponentSource READ componentSource FINAL) + Q_PROPERTY(QString toolTip READ toolTip FINAL) public: ItemLibraryItem(const ItemLibraryEntry &itemLibraryEntry, bool isImported, QObject *parent); @@ -35,6 +36,7 @@ public: QString componentPath() const; QString requiredImport() const; QString componentSource() const; + QString toolTip() const; bool setVisible(bool isVisible); bool isVisible() const; From 4b72ac3022ecf636c3feeaa596b9bbeaf4cc97d0 Mon Sep 17 00:00:00 2001 From: Henning Gruendl Date: Mon, 28 Nov 2022 12:18:42 +0100 Subject: [PATCH 056/131] QmlDesigner: Fix states editor dialog not closing Task-number: QDS-8439 Change-Id: Idf633b345de6e4f124b2f621699615ac6d764ceb Reviewed-by: Thomas Hartmann --- share/qtcreator/qmldesigner/newstateseditor/Main.qml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/share/qtcreator/qmldesigner/newstateseditor/Main.qml b/share/qtcreator/qmldesigner/newstateseditor/Main.qml index 7f20190322a..6be44417a3b 100644 --- a/share/qtcreator/qmldesigner/newstateseditor/Main.qml +++ b/share/qtcreator/qmldesigner/newstateseditor/Main.qml @@ -264,7 +264,10 @@ Rectangle { Connections { target: statesEditorModel - onModelReset: root.menuOpen = 0 + function onModelReset() { + root.menuOpen = 0 + editDialog.close() + } } // This timer is used to delay the current state animation as it didn't work due to the From 5b19c034ed28053e52988115464dd1897262955b Mon Sep 17 00:00:00 2001 From: Henning Gruendl Date: Mon, 28 Nov 2022 12:42:28 +0100 Subject: [PATCH 057/131] QmlDesigner: Remove error states editor Remove the error "Unable to assign [undefined] to QUrl" thrown by the states editor. Change-Id: Ic48deddbe698f161f486003ccaf9eb22d0ee65a9 Reviewed-by: Thomas Hartmann --- share/qtcreator/qmldesigner/newstateseditor/Main.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/qtcreator/qmldesigner/newstateseditor/Main.qml b/share/qtcreator/qmldesigner/newstateseditor/Main.qml index 6be44417a3b..74b50d5f94a 100644 --- a/share/qtcreator/qmldesigner/newstateseditor/Main.qml +++ b/share/qtcreator/qmldesigner/newstateseditor/Main.qml @@ -553,7 +553,7 @@ Rectangle { baseState: true defaultChecked: !statesEditorModel.baseState.modelHasDefaultState // TODO Make this one a model property isChecked: root.currentStateInternalId === 0 - thumbnailImageSource: statesEditorModel.baseState.stateImageSource // TODO Get rid of the QVariantMap + thumbnailImageSource: statesEditorModel.baseState.stateImageSource ?? "" // TODO Get rid of the QVariantMap isTiny: root.tinyMode onFocusSignal: root.currentStateInternalId = 0 From 397e5f302247508806f0c8d23f63b05b6ac0aa18 Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Mon, 28 Nov 2022 16:37:48 +0200 Subject: [PATCH 058/131] QmlDesigner: Prevent content lib scroll when context-menu is open Fixes: QDS-8441 Change-Id: Idfc3400fac43668d14cd5ff85cb42d745ef97dfe Reviewed-by: Miikka Heikkinen --- .../contentLibraryQmlSource/ContentLibraryMaterialsView.qml | 1 + .../contentLibraryQmlSource/ContentLibraryTexturesView.qml | 1 + 2 files changed, 2 insertions(+) diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialsView.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialsView.qml index 589e0250b5e..6a17a98a9d3 100644 --- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialsView.qml +++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialsView.qml @@ -10,6 +10,7 @@ HelperWidgets.ScrollView { id: root clip: true + interactive: !ctxMenu.opened readonly property int cellWidth: 100 readonly property int cellHeight: 120 diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexturesView.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexturesView.qml index 4e2854a48bf..a7e6d08b3f1 100644 --- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexturesView.qml +++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexturesView.qml @@ -10,6 +10,7 @@ HelperWidgets.ScrollView { id: root clip: true + interactive: !ctxMenu.opened readonly property int cellWidth: 100 readonly property int cellHeight: 100 From 5705194bc78709fbdfc125debcfd228d20179e74 Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Mon, 28 Nov 2022 16:48:13 +0100 Subject: [PATCH 059/131] QmlDesigner: Fix build for tests Updating the NodeMetaInfo mock. Change-Id: Ia3a87095413bb58430d978bb831ed8b808596093 Reviewed-by: Miikka Heikkinen --- .../unit/mockup/qmldesigner/designercore/include/nodemetainfo.h | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/mockup/qmldesigner/designercore/include/nodemetainfo.h b/tests/unit/mockup/qmldesigner/designercore/include/nodemetainfo.h index f898f752152..fe8abea17b6 100644 --- a/tests/unit/mockup/qmldesigner/designercore/include/nodemetainfo.h +++ b/tests/unit/mockup/qmldesigner/designercore/include/nodemetainfo.h @@ -77,6 +77,7 @@ public: bool isQtQuick3DMaterial() const { return {}; } bool isQtQuickLoader() const { return {}; } bool isQtQuickItem() const { return {}; } + bool isQtQuick3DTexture() const { return {}; } QString importDirectoryPath() const { return {}; } From 86ed12e7299c1e6be61f6c42c486acb6fcd4c0de Mon Sep 17 00:00:00 2001 From: Aleksei German Date: Fri, 25 Nov 2022 19:52:05 +0100 Subject: [PATCH 060/131] QmlDesigner: Add StateGroups to Connections Task-number: QDS-8027 Change-Id: I72ce5bfd2505269ad750ee5fbd5b13669891290f Reviewed-by: Qt CI Bot Reviewed-by: Reviewed-by: Thomas Hartmann --- .../componentcore/designeractionmanager.cpp | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp index e99d94014f3..3439d02e44d 100644 --- a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp @@ -492,6 +492,22 @@ struct SlotEntry std::function action; }; +QList stateGroups(const ModelNode &node) +{ + if (!node.view()->isAttached()) + return {}; + + const auto groupMetaInfo = node.view()->model()->qtQuickStateGroupMetaInfo(); + + return node.view()->allModelNodesOfType(groupMetaInfo); +} + +QStringList stateGroupsNames(const ModelNode &node) +{ + return Utils::transform(stateGroups(node), + [](const ModelNode &node) { return node.displayName(); }); +} + QList getSlotsLists(const ModelNode &node) { if (!node.isValid()) @@ -528,6 +544,32 @@ QList getSlotsLists(const ModelNode &node) resultList.push_back(entry); } + const auto sg = stateGroups(node); + + for (const auto &stateGroup : sg) { + QmlObjectNode stateGroupObjectNode(stateGroup); + const QString stateGroupCategory = QString("Change State Group") + " " + + stateGroup.displayName(); + + const SlotEntry defaultGroupState = {stateGroupCategory, + (stateGroupCategory + " to " + "Default State"), + [stateGroup](SignalHandlerProperty signalHandler) { + signalHandler.setSource( + QString("%1.state = \"\"").arg(stateGroup.id())); + }}; + resultList.push_back(defaultGroupState); + + for (const auto &stateName : stateGroupObjectNode.states().names()) { + SlotEntry entry = {stateGroupCategory, + (stateGroupCategory + " to " + stateName), + [stateGroup, stateName](SignalHandlerProperty signalHandler) { + signalHandler.setSource( + QString("%1.state = \"%2\"").arg(stateGroup.id(), stateName)); + }}; + resultList.push_back(entry); + } + } + return resultList; } From 26c1747ae658a251bc9c6dd6784a7c8c0037a1ea Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Mon, 28 Nov 2022 16:51:42 +0200 Subject: [PATCH 061/131] QmlDesigner: Fix issues with adding texture as light probe Changed resolving the currently active scene environment to be done on demand instead of trying to track it realtime, since resolving it is relatively cheap operation, so it doesn't cause noticeable delay at context menu opening. The alternative would be to implement multiple different notification handlers in ContentLibraryView, which would slow down all operations and would be much more complex to ensure all edge cases are covered. Fixes: QDS-8437 Change-Id: Ib33cd1ad549d836b9d780f9b0f92e70d223e2a25 Reviewed-by: Mahmoud Badri Reviewed-by: Qt CI Bot --- .../ContentLibraryTextureContextMenu.qml | 1 + .../contentlibrary/contentlibraryview.cpp | 69 +++++++++++-------- .../contentlibrary/contentlibraryview.h | 3 +- .../contentlibrary/contentlibrarywidget.cpp | 5 ++ .../contentlibrary/contentlibrarywidget.h | 2 + 5 files changed, 49 insertions(+), 31 deletions(-) diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTextureContextMenu.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTextureContextMenu.qml index 61bd0ebfd7e..f9d3d88a789 100644 --- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTextureContextMenu.qml +++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTextureContextMenu.qml @@ -15,6 +15,7 @@ StudioControls.Menu { function popupMenu(targetTexture = null) { this.targetTexture = targetTexture + rootView.updateSceneEnvState(); popup() } diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp index ae1d87276bd..347512a837c 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp @@ -3,7 +3,6 @@ #include "contentlibraryview.h" -#include "bindingproperty.h" #include "contentlibrarybundleimporter.h" #include "contentlibrarywidget.h" #include "contentlibrarymaterial.h" @@ -88,11 +87,14 @@ WidgetInfo ContentLibraryView::widgetInfo() } // assign the texture as scene environment's light probe - if (mode == ContentLibraryWidget::AddTextureMode::LightProbe && m_activeSceneEnv.isValid()) { - BindingProperty lightProbeProp = m_activeSceneEnv.bindingProperty("lightProbe"); - lightProbeProp.setExpression(texNode.id()); - VariantProperty bgModeProp = m_activeSceneEnv.variantProperty("backgroundMode"); - bgModeProp.setValue(QVariant::fromValue(Enumeration("SceneEnvironment", "SkyBox"))); + if (mode == ContentLibraryWidget::AddTextureMode::LightProbe && m_sceneId != -1) { + QmlObjectNode sceneEnv = resolveSceneEnv(); + if (sceneEnv.isValid()) { + sceneEnv.setBindingProperty("lightProbe", texNode.id()); + sceneEnv.setVariantProperty("backgroundMode", + QVariant::fromValue(Enumeration("SceneEnvironment", + "SkyBox"))); + } } QTimer::singleShot(0, this, [this, texNode]() { if (model() && texNode.isValid()) @@ -101,6 +103,9 @@ WidgetInfo ContentLibraryView::widgetInfo() }); }); + connect(m_widget, &ContentLibraryWidget::updateSceneEnvStateRequested, + this, &ContentLibraryView::resolveSceneEnv); + ContentLibraryMaterialsModel *materialsModel = m_widget->materialsModel().data(); connect(materialsModel, &ContentLibraryMaterialsModel::applyToSelectedTriggered, this, @@ -187,30 +192,7 @@ void ContentLibraryView::importsChanged(const QList &addedImports, const void ContentLibraryView::active3DSceneChanged(qint32 sceneId) { - m_activeSceneEnv = {}; - bool sceneEnvExists = false; - if (sceneId != -1) { - ModelNode activeScene = active3DSceneNode(); - if (activeScene.isValid()) { - ModelNode view3D; - if (activeScene.metaInfo().isQtQuick3DView3D()) { - view3D = activeScene; - } else { - ModelNode sceneParent = activeScene.parentProperty().parentModelNode(); - if (sceneParent.metaInfo().isQtQuick3DView3D()) - view3D = sceneParent; - } - - if (view3D.isValid()) { - m_activeSceneEnv = modelNodeForId(view3D.bindingProperty("environment").expression()); - if (m_activeSceneEnv.isValid()) - sceneEnvExists = true; - } - } - } - - m_widget->texturesModel()->setHasSceneEnv(sceneEnvExists); - m_widget->environmentsModel()->setHasSceneEnv(sceneEnvExists); + m_sceneId = sceneId; } void ContentLibraryView::selectedNodesChanged(const QList &selectedNodeList, @@ -361,6 +343,33 @@ ModelNode ContentLibraryView::createMaterial(const NodeMetaInfo &metaInfo) return newMatNode; } +ModelNode ContentLibraryView::resolveSceneEnv() +{ + ModelNode activeSceneEnv; + + if (m_sceneId != -1) { + ModelNode activeScene = active3DSceneNode(); + if (activeScene.isValid()) { + QmlObjectNode view3D; + if (activeScene.metaInfo().isQtQuick3DView3D()) { + view3D = activeScene; + } else { + ModelNode sceneParent = activeScene.parentProperty().parentModelNode(); + if (sceneParent.metaInfo().isQtQuick3DView3D()) + view3D = sceneParent; + } + if (view3D.isValid()) + activeSceneEnv = modelNodeForId(view3D.expression("environment")); + } + } + + const bool sceneEnvExists = activeSceneEnv.isValid(); + m_widget->texturesModel()->setHasSceneEnv(sceneEnvExists); + m_widget->environmentsModel()->setHasSceneEnv(sceneEnvExists); + + return activeSceneEnv; +} + void ContentLibraryView::updateBundleMaterialsImportedState() { using namespace Utils; diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h index aae27c91406..5b4914a50c9 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h @@ -43,15 +43,16 @@ private: void applyBundleMaterialToDropTarget(const ModelNode &bundleMat, const NodeMetaInfo &metaInfo = {}); ModelNode getBundleMaterialDefaultInstance(const TypeName &type); ModelNode createMaterial(const NodeMetaInfo &metaInfo); + ModelNode resolveSceneEnv(); QPointer m_widget; QList m_bundleMaterialTargets; QList m_selectedModels; // selected 3D model nodes ContentLibraryMaterial *m_draggedBundleMaterial = nullptr; ContentLibraryTexture *m_draggedBundleTexture = nullptr; - ModelNode m_activeSceneEnv; bool m_bundleMaterialAddToSelected = false; bool m_hasQuick3DImport = false; + qint32 m_sceneId = -1; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp index 729e41b6932..75eff3af98d 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp @@ -221,6 +221,11 @@ void ContentLibraryWidget::addLightProbe(ContentLibraryTexture *tex) emit addTextureRequested(tex->path(), AddTextureMode::LightProbe); } +void ContentLibraryWidget::updateSceneEnvState() +{ + emit updateSceneEnvStateRequested(); +} + QPointer ContentLibraryWidget::materialsModel() const { return m_materialsModel; diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h index ec0b26cbc22..4d770686322 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h @@ -44,6 +44,7 @@ public: Q_INVOKABLE void addImage(QmlDesigner::ContentLibraryTexture *tex); Q_INVOKABLE void addTexture(QmlDesigner::ContentLibraryTexture *tex); Q_INVOKABLE void addLightProbe(QmlDesigner::ContentLibraryTexture *tex); + Q_INVOKABLE void updateSceneEnvState(); enum class AddTextureMode { Image, Texture, LightProbe }; @@ -51,6 +52,7 @@ signals: void bundleMaterialDragStarted(QmlDesigner::ContentLibraryMaterial *bundleMat); void bundleTextureDragStarted(QmlDesigner::ContentLibraryTexture *bundleTex); void addTextureRequested(const QString texPath, QmlDesigner::ContentLibraryWidget::AddTextureMode mode); + void updateSceneEnvStateRequested(); protected: bool eventFilter(QObject *obj, QEvent *event) override; From 34b966c21d89f262f9cb1eae6c3265e19bd0feec Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Tue, 29 Nov 2022 13:16:02 +0200 Subject: [PATCH 062/131] QmlDesigner: Show informative message when texture library is not found Fixes: QDS-8444 Change-Id: Ief36595fda27b075ef3362d6f393655c56fd9940 Reviewed-by: Mahmoud Badri --- .../ContentLibraryTexturesView.qml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexturesView.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexturesView.qml index a7e6d08b3f1..6027d4b6904 100644 --- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexturesView.qml +++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexturesView.qml @@ -81,13 +81,18 @@ HelperWidgets.ScrollView { } Text { - id: noMatchText - text: qsTr("No match found."); + id: infoText + text: { + if (!searchBox.isEmpty()) + qsTr("No match found.") + else + qsTr("Texture library is not installed.") + } color: StudioTheme.Values.themeTextColor font.pixelSize: StudioTheme.Values.baseFontSize topPadding: 10 leftPadding: 10 - visible: root.model.isEmpty && !searchBox.isEmpty() && !root.model.hasMaterialRoot + visible: root.model.isEmpty } } } From cdbdde7d37d0b34b5ac34c22a7acbf5c1e8fac9c Mon Sep 17 00:00:00 2001 From: Tim Jenssen Date: Mon, 28 Nov 2022 11:25:34 +0100 Subject: [PATCH 063/131] QmlDesigner: crash fix on shutdown Change-Id: I6e20a99189c81fcb8ec0b0474a219ac1056731bc Reviewed-by: Thomas Hartmann Reviewed-by: --- src/plugins/qmldesigner/components/formeditor/movetool.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plugins/qmldesigner/components/formeditor/movetool.cpp b/src/plugins/qmldesigner/components/formeditor/movetool.cpp index 506bb0d85f3..8e10a9ccc5e 100644 --- a/src/plugins/qmldesigner/components/formeditor/movetool.cpp +++ b/src/plugins/qmldesigner/components/formeditor/movetool.cpp @@ -47,7 +47,8 @@ void MoveTool::clear() m_contentNotEditableIndicator.clear(); AbstractFormEditorTool::clear(); - view()->formEditorWidget()->graphicsView()->viewport()->unsetCursor(); + if (view()->formEditorWidget()->graphicsView()) + view()->formEditorWidget()->graphicsView()->viewport()->unsetCursor(); } void MoveTool::start() From 19f1c6c32fb5af0969da0a9f94adc277edec6af5 Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Fri, 25 Nov 2022 17:04:59 +0100 Subject: [PATCH 064/131] QmlDesigner: Add tool tips for positioners Task-number: QDS-8311 Change-Id: Ie981d3a7806381237844707f6018278d5bb05354 Reviewed-by: Miikka Heikkinen Reviewed-by: Mats Honkamaa Reviewed-by: Thomas Hartmann --- src/plugins/qmldesigner/qtquickplugin/quick.metainfo | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/plugins/qmldesigner/qtquickplugin/quick.metainfo b/src/plugins/qmldesigner/qtquickplugin/quick.metainfo index 6420b398651..2e3867bbf5e 100644 --- a/src/plugins/qmldesigner/qtquickplugin/quick.metainfo +++ b/src/plugins/qmldesigner/qtquickplugin/quick.metainfo @@ -232,6 +232,8 @@ MetaInfo { Property { name: "width"; type: "int"; value: 200; } Property { name: "height"; type: "int"; value: 400; } + + toolTip: qsTr("Organizes items in a column.") } } @@ -244,6 +246,7 @@ MetaInfo { category: "c.Qt Quick - Positioner" libraryIcon: ":/qtquickplugin/images/row-positioner-icon.png" version: "2.0" + toolTip: qsTr("Organizes items in a row.") Property { name: "width"; type: "int"; value: 200; } Property { name: "height"; type: "int"; value: 400; } @@ -262,6 +265,7 @@ MetaInfo { Property { name: "width"; type: "int"; value: 400; } Property { name: "height"; type: "int"; value: 400; } + toolTip: qsTr("Organizes items in a fixed grid.") } } @@ -277,6 +281,7 @@ MetaInfo { Property { name: "width"; type: "int"; value: 400; } Property { name: "height"; type: "int"; value: 400; } + toolTip: qsTr("Organizes items in free-flowing rows.") } } From be97b3595915ebb03f1187daae0b011b1bece718 Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Fri, 25 Nov 2022 16:56:09 +0100 Subject: [PATCH 065/131] QmlDesigner: Add tool tips for animations Task-number: QDS-8312 Change-Id: Ic580dd43c4fdb0d7f33c937c1bbe1ecfb963250a Reviewed-by: Miikka Heikkinen Reviewed-by: Mats Honkamaa Reviewed-by: Thomas Hartmann --- src/plugins/qmldesigner/qtquickplugin/quick.metainfo | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/plugins/qmldesigner/qtquickplugin/quick.metainfo b/src/plugins/qmldesigner/qtquickplugin/quick.metainfo index 2e3867bbf5e..d4c02abef42 100644 --- a/src/plugins/qmldesigner/qtquickplugin/quick.metainfo +++ b/src/plugins/qmldesigner/qtquickplugin/quick.metainfo @@ -356,6 +356,7 @@ MetaInfo { category: "d.Qt Quick - Animation" libraryIcon: ":/qtquickplugin/images/item-icon.png" version: "2.0" + toolTip: qsTr("Animates changes in property values.") } } @@ -375,6 +376,7 @@ MetaInfo { category: "d.Qt Quick - Animation" libraryIcon: ":/qtquickplugin/images/item-icon.png" version: "2.0" + toolTip: qsTr("Provides a pause between animations.") } } @@ -393,6 +395,7 @@ MetaInfo { category: "d.Qt Quick - Animation" libraryIcon: ":/qtquickplugin/images/item-icon.png" version: "2.0" + toolTip: qsTr("Runs animations one after the other.") } } @@ -411,6 +414,7 @@ MetaInfo { category: "d.Qt Quick - Animation" libraryIcon: ":/qtquickplugin/images/item-icon.png" version: "2.0" + toolTip: qsTr("Runs animations together at the same time.") } } @@ -430,6 +434,7 @@ MetaInfo { category: "d.Qt Quick - Animation" libraryIcon: ":/qtquickplugin/images/item-icon.png" version: "2.0" + toolTip: qsTr("Provides an immediate property change during animations.") } } @@ -449,6 +454,7 @@ MetaInfo { category: "d.Qt Quick - Animation" libraryIcon: ":/qtquickplugin/images/item-icon.png" version: "2.0" + toolTip: qsTr("Runs a script during animation.") } } @@ -468,6 +474,7 @@ MetaInfo { category: "d.Qt Quick - Animation" libraryIcon: ":/qtquickplugin/images/item-icon.png" version: "2.0" + toolTip: qsTr("Animates the color of an item.") } } @@ -489,6 +496,7 @@ MetaInfo { version: "2.0" Property { name: "to"; type: "int"; value: 0; } Property { name: "from"; type: "int"; value: 0; } + toolTip: qsTr("Animates a numerical property of an item.") } } @@ -508,6 +516,7 @@ MetaInfo { category: "d.Qt Quick - Animation" libraryIcon: ":/qtquickplugin/images/timer-24px.png" version: "2.0" + toolTip: qsTr(" Triggers an action at a given time.") } } From b5befc694514d1b223b56a5c65fe4ac859cdbb2f Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Tue, 29 Nov 2022 16:29:31 +0200 Subject: [PATCH 066/131] QmlDesigner: Fix content library sections not collapsing after search Also enabled the auto expand for textures and environments tabs Fixes: QDS-8443 Change-Id: I190f919ccb2cff90c7cd151e65b77ebd52f0a87c Reviewed-by: Miikka Heikkinen --- .../qmldesigner/contentLibraryQmlSource/ContentLibrary.qml | 2 ++ .../contentLibraryQmlSource/ContentLibraryMaterialsView.qml | 6 +++++- .../contentLibraryQmlSource/ContentLibraryTexturesView.qml | 6 +++++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibrary.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibrary.qml index 8d699308478..53fae29a533 100644 --- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibrary.qml +++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibrary.qml @@ -42,6 +42,8 @@ Item { // make sure categories with matches are expanded materialsView.expandVisibleSections() + texturesView.expandVisibleSections() + environmentsView.expandVisibleSections() } } diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialsView.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialsView.qml index 6a17a98a9d3..6483ebdd6bd 100644 --- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialsView.qml +++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialsView.qml @@ -32,7 +32,7 @@ HelperWidgets.ScrollView { for (let i = 0; i < categoryRepeater.count; ++i) { let cat = categoryRepeater.itemAt(i) if (cat.visible && !cat.expanded) - cat.expanded = true + cat.expandSection() } } @@ -58,6 +58,10 @@ HelperWidgets.ScrollView { expandOnClick: false onToggleExpand: bundleCategoryExpanded = !bundleCategoryExpanded + function expandSection() { + bundleCategoryExpanded = true + } + Grid { width: root.width leftPadding: 5 diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexturesView.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexturesView.qml index 6027d4b6904..dd6509b67ec 100644 --- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexturesView.qml +++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexturesView.qml @@ -33,7 +33,7 @@ HelperWidgets.ScrollView { for (let i = 0; i < categoryRepeater.count; ++i) { let cat = categoryRepeater.itemAt(i) if (cat.visible && !cat.expanded) - cat.expanded = true + cat.expandSection() } } Column { @@ -58,6 +58,10 @@ HelperWidgets.ScrollView { expandOnClick: false onToggleExpand: bundleCategoryExpanded = !bundleCategoryExpanded + function expandSection() { + bundleCategoryExpanded = true + } + Grid { width: root.width leftPadding: 5 From 036948c6286cc01d8e3bbf0e594dc0bc5358e43b Mon Sep 17 00:00:00 2001 From: Tim Jenssen Date: Tue, 29 Nov 2022 17:29:06 +0100 Subject: [PATCH 067/131] qml2puppet: remove unnecessary flexibility standalone builds are doing: cmake --install . --prefix Change-Id: I61c0743c00f96c0505e2fa383e4bee41c2b6ceb9 Reviewed-by: Burak Hancerli Reviewed-by: Tim Jenssen --- src/tools/qml2puppet/CMakeLists.txt | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/tools/qml2puppet/CMakeLists.txt b/src/tools/qml2puppet/CMakeLists.txt index 7ef09420ee2..074099be894 100644 --- a/src/tools/qml2puppet/CMakeLists.txt +++ b/src/tools/qml2puppet/CMakeLists.txt @@ -14,7 +14,7 @@ set(CMAKE_CXX_EXTENSIONS OFF) if (NOT QT_CREATOR_API_DEFINED) # standalone build - set(DESTINATION DESTINATION $ENV{QDS_PUPPET_DESTINATION}) + set(DESTINATION DESTINATION .) include(QtCreatorIDEBranding) include(QtCreatorAPI) endif() @@ -39,11 +39,7 @@ add_qtc_executable(qml2puppet qmlpuppet.qrc ) -if (NOT "$ENV{QDS_PUPPET_VERSION}" STREQUAL "") - set_target_properties(qml2puppet PROPERTIES OUTPUT_NAME qml2puppet-$ENV{QDS_PUPPET_VERSION}) -else() - set_target_properties(qml2puppet PROPERTIES OUTPUT_NAME qml2puppet-${IDE_VERSION}) -endif() +set_target_properties(qml2puppet PROPERTIES OUTPUT_NAME qml2puppet-${IDE_VERSION}) extend_qtc_executable(qml2puppet CONDITION Qt5_VERSION VERSION_GREATER_EQUAL 6.0.0 From 5d9cb68a36d0e82f33e5426dd2ec9d9f39035516 Mon Sep 17 00:00:00 2001 From: Tim Jenssen Date: Tue, 29 Nov 2022 21:16:29 +0100 Subject: [PATCH 068/131] qml2puppet: fix include when building against linux Qt5 Change-Id: I17401443a1e099b9c63d0de2e6f8bf5c3e9cfcb3 Reviewed-by: Tim Jenssen --- src/tools/qml2puppet/qml2puppet/iconrenderer/iconrenderer.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tools/qml2puppet/qml2puppet/iconrenderer/iconrenderer.h b/src/tools/qml2puppet/qml2puppet/iconrenderer/iconrenderer.h index 856811b767f..a585cc8ce56 100644 --- a/src/tools/qml2puppet/qml2puppet/iconrenderer/iconrenderer.h +++ b/src/tools/qml2puppet/qml2puppet/iconrenderer/iconrenderer.h @@ -6,6 +6,8 @@ #include #include +#include + QT_BEGIN_NAMESPACE class QQuickWindow; class QQuickItem; From aa69465ecffac94c9c5781cfe3a0d50e8bb49cf9 Mon Sep 17 00:00:00 2001 From: Amr Essam Date: Tue, 29 Nov 2022 16:14:22 +0200 Subject: [PATCH 069/131] QmlDesigner: drag effects from assets view to navigator Effects can be dragged from the assets panel to the navigator, also effects can be dragged between components in the navigator. There were lots of transformations because the way navigator works in not suitable with effects Task-number: QDS-8235 Change-Id: I378a70b5586fa95d0890bda4fbe53191c7ab0bc4 Reviewed-by: Mahmoud Badri --- .../assetslibrary/assetslibrarymodel.cpp | 7 -- .../assetslibrary/assetslibrarymodel.h | 2 - .../componentcore/modelnodeoperations.cpp | 29 ++++++++- .../componentcore/modelnodeoperations.h | 2 + .../components/formeditor/dragtool.cpp | 25 ++----- .../navigator/navigatortreemodel.cpp | 38 +++++++++-- .../components/navigator/navigatortreemodel.h | 1 + .../designercore/include/qmlitemnode.h | 16 ++++- .../designercore/model/qmlitemnode.cpp | 65 +++++++++++-------- 9 files changed, 119 insertions(+), 66 deletions(-) diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp index 0ea4aa816f2..83683096e95 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp @@ -42,13 +42,6 @@ void AssetsLibraryModel::createBackendModel() }); } -bool AssetsLibraryModel::isEffectQmlExist(const QString &effectName) -{ - Utils::FilePath effectsResDir = ModelNodeOperations::getEffectsDirectory(); - Utils::FilePath qmlPath = effectsResDir.resolvePath(effectName + "/" + effectName + ".qml"); - return qmlPath.exists(); -} - void AssetsLibraryModel::onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, [[maybe_unused]] const QList &roles) { diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h index b4010497b51..82cf2b3ca25 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h @@ -63,8 +63,6 @@ public: static const QStringList &supportedEffectMakerSuffixes(); static const QSet &supportedSuffixes(); - static bool isEffectQmlExist(const QString &effectName); - signals: void directoryLoaded(const QString &path); void rootPathChanged(); diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp index c5e32b84c5a..97e2716b035 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp +++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp @@ -2,13 +2,13 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 #include "modelnodeoperations.h" +#include "coreplugin/coreplugintr.h" #include "designmodewidget.h" #include "modelnodecontextmenu_helper.h" #include "addimagesdialog.h" #include "layoutingridlayout.h" #include "findimplementation.h" - #include "addsignalhandlerdialog.h" #include @@ -1704,6 +1704,33 @@ QString getEffectIcon(const QString &effectPath) return effectResPath.exists() ? QString("effectExported") : QString("effectClass"); } +bool useLayerEffect() +{ + QSettings *settings = Core::ICore::settings(); + const QString layerEffectEntry = "QML/Designer/UseLayerEffect"; + + return settings->value(layerEffectEntry, true).toBool(); +} + +bool validateEffect(const QString &effectPath) +{ + const QString effectName = QFileInfo(effectPath).baseName(); + Utils::FilePath effectsResDir = ModelNodeOperations::getEffectsDirectory(); + Utils::FilePath qmlPath = effectsResDir.resolvePath(effectName + "/" + effectName + ".qml"); + if (!qmlPath.exists()) { + QMessageBox msgBox; + msgBox.setText(QObject::tr("Effect %1 not complete").arg(effectName)); + msgBox.setInformativeText(QObject::tr("Do you want to edit %1?").arg(effectName)); + msgBox.setStandardButtons(QMessageBox::No | QMessageBox::Yes); + msgBox.setDefaultButton(QMessageBox::Yes); + msgBox.setIcon(QMessageBox::Question); + if (msgBox.exec() == QMessageBox::Yes) + ModelNodeOperations::openEffectMaker(effectName); + return false; + } + return true; +} + } // namespace ModelNodeOperations } //QmlDesigner diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h index be238a04397..5dcc20ff911 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h +++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h @@ -122,6 +122,8 @@ void updateImported3DAsset(const SelectionContext &selectionContext); QMLDESIGNERCORE_EXPORT Utils::FilePath getEffectsDirectory(); void openEffectMaker(const QString &filePath); QString getEffectIcon(const QString &effectPath); +bool useLayerEffect(); +bool validateEffect(const QString &effectPath); // ModelNodePreviewImageOperations QVariant previewImageDataForGenericNode(const ModelNode &modelNode); diff --git a/src/plugins/qmldesigner/components/formeditor/dragtool.cpp b/src/plugins/qmldesigner/components/formeditor/dragtool.cpp index ce27128cb35..b64ce3d1007 100644 --- a/src/plugins/qmldesigner/components/formeditor/dragtool.cpp +++ b/src/plugins/qmldesigner/components/formeditor/dragtool.cpp @@ -243,31 +243,14 @@ void DragTool::dropEvent(const QList &itemList, QGraphicsSceneD if (!effectPath.isEmpty()) { FormEditorItem *targetContainerFormEditorItem = targetContainerOrRootItem(itemList); if (targetContainerFormEditorItem) { - QmlItemNode parentQmlItemNode = targetContainerFormEditorItem->qmlItemNode(); - QString effectName = QFileInfo(effectPath).baseName(); - - if (!AssetsLibraryModel::isEffectQmlExist(effectName)) { - QMessageBox msgBox; - msgBox.setText("Effect " + effectName + " is empty"); - msgBox.setInformativeText("Do you want to edit " + effectName + "?"); - msgBox.setStandardButtons(QMessageBox::No |QMessageBox::Yes); - msgBox.setDefaultButton(QMessageBox::Yes); - msgBox.setIcon(QMessageBox::Question); - int ret = msgBox.exec(); - switch (ret) { - case QMessageBox::Yes: - ModelNodeOperations::openEffectMaker(effectPath); - break; - default: - break; - } - + if (!ModelNodeOperations::validateEffect(effectPath)) { event->ignore(); return; } - QmlItemNode::createQmlItemNodeForEffect(view(), parentQmlItemNode, effectName); - + bool layerEffect = ModelNodeOperations::useLayerEffect(); + QmlItemNode parentQmlItemNode = targetContainerFormEditorItem->qmlItemNode(); + QmlItemNode::createQmlItemNodeForEffect(view(), parentQmlItemNode, effectPath, layerEffect); view()->setSelectedModelNodes({parentQmlItemNode}); commitTransaction(); diff --git a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp index a407cf129b3..66b6f640faf 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp @@ -146,16 +146,25 @@ static bool isInLayoutable(NodeAbstractProperty &parentProperty) static void reparentModelNodeToNodeProperty(NodeAbstractProperty &parentProperty, const ModelNode &modelNode) { try { + if (parentProperty.parentModelNode().type().startsWith("Effects.")) + return; + if (!modelNode.hasParentProperty() || parentProperty != modelNode.parentProperty()) { if (isInLayoutable(parentProperty)) { removePosition(modelNode); parentProperty.reparentHere(modelNode); } else { if (QmlItemNode::isValidQmlItemNode(modelNode)) { - QPointF scenePosition = QmlItemNode(modelNode).instanceScenePosition(); - parentProperty.reparentHere(modelNode); - if (!scenePosition.isNull()) - setScenePosition(modelNode, scenePosition); + if (modelNode.hasParentProperty() && modelNode.parentProperty().name() == "layer.effect") { + parentProperty = parentProperty.parentModelNode().nodeAbstractProperty("layer.effect"); + QmlItemNode::placeEffectNode(parentProperty, modelNode, true); + } else { + QPointF scenePosition = QmlItemNode(modelNode).instanceScenePosition(); + parentProperty.reparentHere(modelNode); + if (!scenePosition.isNull()) + setScenePosition(modelNode, scenePosition); + } + } else { parentProperty.reparentHere(modelNode); } @@ -597,6 +606,9 @@ bool NavigatorTreeModel::dropMimeData(const QMimeData *mimeData, } else if (assetType == Constants::MIME_TYPE_ASSET_TEXTURE3D) { currNode = handleItemLibraryTexture3dDrop(assetPath, targetProperty, rowModelIndex, moveNodesAfter); + } else if (assetType == Constants::MIME_TYPE_ASSET_EFFECT) { + currNode = handleItemLibraryEffectDrop(assetPath, rowModelIndex); + moveNodesAfter = false; } if (currNode.isValid()) @@ -1003,6 +1015,24 @@ ModelNode NavigatorTreeModel::handleItemLibraryTexture3dDrop(const QString &tex3 return newModelNode; } +ModelNode NavigatorTreeModel::handleItemLibraryEffectDrop(const QString &effectPath, const QModelIndex &rowModelIndex) +{ + QTC_ASSERT(m_view, return {}); + + ModelNode targetNode(modelNodeForIndex(rowModelIndex)); + ModelNode newModelNode; + + if (targetNode.hasParentProperty() && targetNode.parentProperty().name() == "layer.effect") + return newModelNode; + + if (ModelNodeOperations::validateEffect(effectPath)) { + bool layerEffect = ModelNodeOperations::useLayerEffect(); + newModelNode = QmlItemNode::createQmlItemNodeForEffect(m_view, targetNode, effectPath, layerEffect); + } + + return newModelNode; +} + bool NavigatorTreeModel::dropAsImage3dTexture(const ModelNode &targetNode, const NodeAbstractProperty &targetProp, const QString &imagePath, diff --git a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.h b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.h index 85741c0ff00..e67bcc8849c 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.h +++ b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.h @@ -106,6 +106,7 @@ private: const QModelIndex &rowModelIndex); ModelNode handleItemLibraryTexture3dDrop(const QString &tex3DPath, NodeAbstractProperty targetProperty, const QModelIndex &rowModelIndex, bool &outMoveNodesAfter); + ModelNode handleItemLibraryEffectDrop(const QString &effectPath, const QModelIndex &rowModelIndex); bool dropAsImage3dTexture(const ModelNode &targetNode, const NodeAbstractProperty &targetProp, const QString &imagePath, ModelNode &newNode, bool &outMoveNodesAfter); ModelNode createTextureNode(const NodeAbstractProperty &targetProp, const QString &imagePath); diff --git a/src/plugins/qmldesigner/designercore/include/qmlitemnode.h b/src/plugins/qmldesigner/designercore/include/qmlitemnode.h index 61c38dd5042..a0d12104446 100644 --- a/src/plugins/qmldesigner/designercore/include/qmlitemnode.h +++ b/src/plugins/qmldesigner/designercore/include/qmlitemnode.h @@ -60,9 +60,19 @@ public: const QPointF &position, NodeAbstractProperty parentproperty, bool executeInTransaction = true); - static void createQmlItemNodeForEffect(AbstractView *view, - const QmlItemNode &parentNode, - const QString &effectName); + + static QmlItemNode createQmlItemNodeForEffect(AbstractView *view, + QmlItemNode parentQmlItemNode, + const QString &effectPath, + bool isLayerEffect); + static QmlItemNode createQmlItemNodeForEffect(AbstractView *view, + NodeAbstractProperty parentProperty, + const QString &effectPath, + bool isLayerEffect); + static void placeEffectNode(NodeAbstractProperty &parentProperty, + const QmlItemNode &effectNode, + bool isLayerEffect); + QList children() const; QList resources() const; QList allDirectSubNodes() const; diff --git a/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp b/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp index 46618eed503..4570e5fc432 100644 --- a/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp +++ b/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp @@ -158,24 +158,31 @@ QmlItemNode QmlItemNode::createQmlItemNodeFromFont(AbstractView *view, return newQmlItemNode; } -static bool useLayerEffect() +QmlItemNode QmlItemNode::createQmlItemNodeForEffect(AbstractView *view, + QmlItemNode parentQmlItemNode, + const QString &effectPath, + bool isLayerEffect) { - QSettings *settings = Core::ICore::settings(); - const QString layerEffectEntry = "QML/Designer/UseLayerEffect"; + if (!parentQmlItemNode.isValid()) + parentQmlItemNode = QmlItemNode(view->rootModelNode()); - return settings->value(layerEffectEntry, true).toBool(); + NodeAbstractProperty parentProperty = isLayerEffect + ? parentQmlItemNode.nodeAbstractProperty("layer.effect") + : parentQmlItemNode.defaultNodeAbstractProperty(); + + return createQmlItemNodeForEffect(view, parentProperty, effectPath, isLayerEffect); } -void QmlItemNode::createQmlItemNodeForEffect(AbstractView *view, - const QmlItemNode &parentNode, - const QString &effectName) +QmlItemNode QmlItemNode::createQmlItemNodeForEffect(AbstractView *view, + NodeAbstractProperty parentProperty, + const QString &effectPath, + bool isLayerEffect) { QmlItemNode newQmlItemNode; - view->executeInTransaction("QmlItemNode::createQmlItemNodeForEffect", [=, &newQmlItemNode, &parentNode]() { - const bool layerEffect = useLayerEffect(); - - QmlDesigner::Import import = Import::createLibraryImport("Effects." + effectName, "1.0"); + auto createEffectNode = [=, &newQmlItemNode, &parentProperty]() { + const QString effectName = QFileInfo(effectPath).baseName(); + Import import = Import::createLibraryImport("Effects." + effectName, "1.0"); try { if (!view->model()->hasImport(import, true, true)) view->model()->changeImports({import}, {}); @@ -185,26 +192,28 @@ void QmlItemNode::createQmlItemNodeForEffect(AbstractView *view, TypeName type(effectName.toUtf8()); newQmlItemNode = QmlItemNode(view->createModelNode(type, -1, -1)); - NodeAbstractProperty parentProperty = layerEffect - ? parentNode.nodeAbstractProperty("layer.effect") - : parentNode.defaultNodeAbstractProperty(); - if (layerEffect) { - if (!parentProperty .isEmpty()) { //already contains a node - ModelNode oldEffect = parentProperty.toNodeProperty().modelNode(); - QmlObjectNode(oldEffect).destroy(); - } - } + placeEffectNode(parentProperty, newQmlItemNode, isLayerEffect); + }; - parentProperty.reparentHere(newQmlItemNode); + view->executeInTransaction("QmlItemNode::createQmlItemNodeFromEffect", createEffectNode); + return newQmlItemNode; +} - if (!layerEffect) { - newQmlItemNode.modelNode().bindingProperty("source").setExpression("parent"); - newQmlItemNode.modelNode().bindingProperty("anchors.fill").setExpression("parent"); - } else { - parentNode.modelNode().variantProperty("layer.enabled").setValue(true); - } - }); +void QmlItemNode::placeEffectNode(NodeAbstractProperty &parentProperty, const QmlItemNode &effectNode, bool isLayerEffect) { + if (isLayerEffect && !parentProperty.isEmpty()) { // already contains a node + ModelNode oldEffect = parentProperty.toNodeProperty().modelNode(); + QmlObjectNode(oldEffect).destroy(); + } + + parentProperty.reparentHere(effectNode); + + if (!isLayerEffect) { + effectNode.modelNode().bindingProperty("source").setExpression("parent"); + effectNode.modelNode().bindingProperty("anchors.fill").setExpression("parent"); + } else { + parentProperty.parentModelNode().variantProperty("layer.enabled").setValue(true); + } } bool QmlItemNode::isValid() const From 0c31cc475bb81fd8719e27ce71954ece04b6cb63 Mon Sep 17 00:00:00 2001 From: Aleksei German Date: Tue, 29 Nov 2022 16:55:37 +0100 Subject: [PATCH 070/131] QmlDesigner: Categorize slot actions Task-number: QDS-8447 Change-Id: Ibc96bfcb7c87f0888e15f2533cab2567098a3760 Reviewed-by: Qt CI Bot Reviewed-by: Thomas Hartmann --- .../componentcore/designeractionmanager.cpp | 233 ++++++++++-------- 1 file changed, 134 insertions(+), 99 deletions(-) diff --git a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp index 3439d02e44d..be6a2b14c15 100644 --- a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp @@ -487,11 +487,16 @@ QStringList getSignalsList(const ModelNode &node) struct SlotEntry { - QString category; QString name; std::function action; }; +struct SlotList +{ + QString categoryName; + QList slotEntries; +}; + QList stateGroups(const ModelNode &node) { if (!node.view()->isAttached()) @@ -502,13 +507,7 @@ QList stateGroups(const ModelNode &node) return node.view()->allModelNodesOfType(groupMetaInfo); } -QStringList stateGroupsNames(const ModelNode &node) -{ - return Utils::transform(stateGroups(node), - [](const ModelNode &node) { return node.displayName(); }); -} - -QList getSlotsLists(const ModelNode &node) +QList getSlotsLists(const ModelNode &node) { if (!node.isValid()) return {}; @@ -516,58 +515,52 @@ QList getSlotsLists(const ModelNode &node) if (!node.view()->rootModelNode().isValid()) return {}; - QList resultList; + QList resultList; ModelNode rootNode = node.view()->rootModelNode(); QmlObjectNode rootObjectNode(rootNode); - const QString stateCategory = "Change State"; + const QString changeStateStr = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Change State"); + const QString changeStateGroupStr = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", + "Change State Group"); + const QString defaultStateStr = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Default State"); + auto createStateChangeSlot = + [](const ModelNode &node, const QString &stateName, const QString &displayName) { + return SlotEntry({displayName, [node, stateName](SignalHandlerProperty signalHandler) { + signalHandler.setSource( + QString("%1.state = \"%2\"").arg(node.id(), stateName)); + }}); + }; - //For now we are using category as part of the state name - //We should change it, once we extend number of categories - const SlotEntry defaultState = {stateCategory, - (stateCategory + " to " + "Default State"), - [rootNode](SignalHandlerProperty signalHandler) { - signalHandler.setSource( - QString("%1.state = \"\"").arg(rootNode.id())); - }}; - resultList.push_back(defaultState); + { + SlotList states = {changeStateStr, {}}; - for (const auto &stateName : rootObjectNode.states().names()) { - SlotEntry entry = {stateCategory, - (stateCategory + " to " + stateName), - [rootNode, stateName](SignalHandlerProperty signalHandler) { - signalHandler.setSource( - QString("%1.state = \"%2\"").arg(rootNode.id(), stateName)); - }}; + const SlotEntry defaultState = createStateChangeSlot(rootNode, "", defaultStateStr); + states.slotEntries.push_back(defaultState); - resultList.push_back(entry); + for (const auto &stateName : rootObjectNode.states().names()) { + const SlotEntry entry = createStateChangeSlot(rootNode, stateName, stateName); + + states.slotEntries.push_back(entry); + } + + resultList.push_back(states); } - const auto sg = stateGroups(node); - - for (const auto &stateGroup : sg) { + const QList groups = stateGroups(node); + for (const auto &stateGroup : groups) { QmlObjectNode stateGroupObjectNode(stateGroup); - const QString stateGroupCategory = QString("Change State Group") + " " - + stateGroup.displayName(); + SlotList stateGroupCategory = {changeStateGroupStr + " " + stateGroup.displayName(), {}}; - const SlotEntry defaultGroupState = {stateGroupCategory, - (stateGroupCategory + " to " + "Default State"), - [stateGroup](SignalHandlerProperty signalHandler) { - signalHandler.setSource( - QString("%1.state = \"\"").arg(stateGroup.id())); - }}; - resultList.push_back(defaultGroupState); + const SlotEntry defaultGroupState = createStateChangeSlot(stateGroup, "", defaultStateStr); + stateGroupCategory.slotEntries.push_back(defaultGroupState); for (const auto &stateName : stateGroupObjectNode.states().names()) { - SlotEntry entry = {stateGroupCategory, - (stateGroupCategory + " to " + stateName), - [stateGroup, stateName](SignalHandlerProperty signalHandler) { - signalHandler.setSource( - QString("%1.state = \"%2\"").arg(stateGroup.id(), stateName)); - }}; - resultList.push_back(entry); + const SlotEntry entry = createStateChangeSlot(stateGroup, stateName, stateName); + stateGroupCategory.slotEntries.push_back(entry); } + + resultList.push_back(stateGroupCategory); } return resultList; @@ -577,10 +570,8 @@ QList getSlotsLists(const ModelNode &node) ModelNode createNewConnection(ModelNode targetNode) { NodeMetaInfo connectionsMetaInfo = targetNode.view()->model()->metaInfo("QtQuick.Connections"); - ModelNode newConnectionNode = targetNode.view() - ->createModelNode("QtQuick.Connections", - connectionsMetaInfo.majorVersion(), - connectionsMetaInfo.minorVersion()); + ModelNode newConnectionNode = targetNode.view()->createModelNode( + "QtQuick.Connections", connectionsMetaInfo.majorVersion(), connectionsMetaInfo.minorVersion()); if (QmlItemNode::isValidQmlItemNode(targetNode)) targetNode.nodeAbstractProperty("data").reparentHere(newConnectionNode); @@ -606,9 +597,7 @@ void removeSignal(SignalHandlerProperty signalHandler) class ConnectionsModelNodeActionGroup : public ActionGroup { public: - ConnectionsModelNodeActionGroup(const QString &displayName, - const QByteArray &menuId, - int priority) + ConnectionsModelNodeActionGroup(const QString &displayName, const QByteArray &menuId, int priority) : ActionGroup(displayName, menuId, priority, @@ -638,7 +627,7 @@ public: QmlObjectNode currentObjectNode(currentNode); QStringList signalsList = getSignalsList(currentNode); - QList slotsList = getSlotsLists(currentNode); + QList slotsLists = getSlotsLists(currentNode); if (!currentNode.hasId()) { menu()->setEnabled(false); @@ -654,7 +643,9 @@ public: QMenu *activeSignalHandlerGroup = new QMenu(propertyName, menu()); - QMenu *editSignalGroup = new QMenu("Change Signal", menu()); + QMenu *editSignalGroup = new QMenu(QT_TRANSLATE_NOOP("QmlDesignerContextMenu", + "Change Signal"), + menu()); for (const auto &signalStr : signalsList) { if (prependSignal(signalStr).toUtf8() == signalHandler.name()) @@ -680,40 +671,60 @@ public: activeSignalHandlerGroup->addMenu(editSignalGroup); - if (!slotsList.isEmpty()) { - QMenu *editSlotGroup = new QMenu("Change Slot", menu()); + if (!slotsLists.isEmpty()) { + QMenu *editSlotGroup = new QMenu(QT_TRANSLATE_NOOP("QmlDesignerContextMenu", + "Change Slot"), + menu()); - for (const auto &slot : slotsList) { - ActionTemplate *newSlotAction = new ActionTemplate( - (slot.name + "Id").toLatin1(), - slot.name, - [slot, signalHandler](const SelectionContext &) { - signalHandler.parentModelNode() - .view() - ->executeInTransaction("ConnectionsModelNodeActionGroup::" - "changeSlot", - [slot, signalHandler]() { - slot.action(signalHandler); - }); - }); - editSlotGroup->addAction(newSlotAction); + if (slotsLists.size() == 1) { + for (const auto &slot : slotsLists.at(0).slotEntries) { + ActionTemplate *newSlotAction = new ActionTemplate( + (slot.name + "Id").toLatin1(), + (slotsLists.at(0).categoryName + + (QT_TRANSLATE_NOOP("QmlDesignerContextMenu", + " to ")) //context: Change State _to_ state1 + + slot.name), + [slot, signalHandler](const SelectionContext &) { + signalHandler.parentModelNode().view()->executeInTransaction( + "ConnectionsModelNodeActionGroup::" + "changeSlot", + [slot, signalHandler]() { slot.action(signalHandler); }); + }); + editSlotGroup->addAction(newSlotAction); + } + } else { + for (const auto &slotCategory : slotsLists) { + QMenu *slotCategoryMenu = new QMenu(slotCategory.categoryName, menu()); + for (const auto &slot : slotCategory.slotEntries) { + ActionTemplate *newSlotAction = new ActionTemplate( + (slot.name + "Id").toLatin1(), + slot.name, + [slot, signalHandler](const SelectionContext &) { + signalHandler.parentModelNode().view()->executeInTransaction( + "ConnectionsModelNodeActionGroup::" + "changeSlot", + [slot, signalHandler]() { + slot.action(signalHandler); + }); + }); + slotCategoryMenu->addAction(newSlotAction); + } + editSlotGroup->addMenu(slotCategoryMenu); + } } activeSignalHandlerGroup->addMenu(editSlotGroup); } ActionTemplate *openEditorAction = new ActionTemplate( (propertyName + "OpenEditorId").toLatin1(), - QString( - QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Open Connections Editor")), + QString(QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Open Connections Editor")), [=](const SelectionContext &) { - signalHandler.parentModelNode() - .view() - ->executeInTransaction("ConnectionsModelNodeActionGroup::" - "openConnectionsEditor", - [signalHandler]() { - ActionEditor::invokeEditor(signalHandler, - removeSignal); - }); + signalHandler.parentModelNode().view()->executeInTransaction( + "ConnectionsModelNodeActionGroup::" + "openConnectionsEditor", + [signalHandler]() { + ActionEditor::invokeEditor(signalHandler, removeSignal); + }); }); activeSignalHandlerGroup->addAction(openEditorAction); @@ -725,9 +736,7 @@ public: signalHandler.parentModelNode().view()->executeInTransaction( "ConnectionsModelNodeActionGroup::" "removeSignalHandler", - [signalHandler]() { - removeSignal(signalHandler); - }); + [signalHandler]() { removeSignal(signalHandler); }); }); activeSignalHandlerGroup->addAction(removeSignalHandlerAction); @@ -745,19 +754,46 @@ public: for (const auto &signalStr : signalsList) { QMenu *newSignal = new QMenu(signalStr, addConnection); - for (const auto &slot : slotsList) { - ActionTemplate *newSlot = new ActionTemplate( - QString(signalStr + slot.name + "Id").toLatin1(), - slot.name, - [=](const SelectionContext &) { - currentNode.view()->executeInTransaction( - "ConnectionsModelNodeActionGroup::addConnection", [=]() { - ModelNode newConnectionNode = createNewConnection(currentNode); - slot.action(newConnectionNode.signalHandlerProperty( - prependSignal(signalStr).toLatin1())); + if (!slotsLists.isEmpty()) { + if (slotsLists.size() == 1) { + for (const auto &slot : slotsLists.at(0).slotEntries) { + ActionTemplate *newSlot = new ActionTemplate( + QString(signalStr + slot.name + "Id").toLatin1(), + (slotsLists.at(0).categoryName + + (QT_TRANSLATE_NOOP("QmlDesignerContextMenu", + " to ")) //context: Change State _to_ state1 + + slot.name), + [=](const SelectionContext &) { + currentNode.view()->executeInTransaction( + "ConnectionsModelNodeActionGroup::addConnection", [=]() { + ModelNode newConnectionNode = createNewConnection(currentNode); + slot.action(newConnectionNode.signalHandlerProperty( + prependSignal(signalStr).toLatin1())); + }); }); - }); - newSignal->addAction(newSlot); + newSignal->addAction(newSlot); + } + } else { + for (const auto &slotCategory : slotsLists) { + QMenu *slotCategoryMenu = new QMenu(slotCategory.categoryName, menu()); + for (const auto &slot : slotCategory.slotEntries) { + ActionTemplate *newSlot = new ActionTemplate( + QString(signalStr + slot.name + "Id").toLatin1(), + slot.name, + [=](const SelectionContext &) { + currentNode.view()->executeInTransaction( + "ConnectionsModelNodeActionGroup::addConnection", [=]() { + ModelNode newConnectionNode = createNewConnection( + currentNode); + slot.action(newConnectionNode.signalHandlerProperty( + prependSignal(signalStr).toLatin1())); + }); + }); + slotCategoryMenu->addAction(newSlot); + } + newSignal->addMenu(slotCategoryMenu); + } + } } ActionTemplate *openEditorAction = new ActionTemplate( @@ -770,9 +806,8 @@ public: [=]() { ModelNode newConnectionNode = createNewConnection(currentNode); - SignalHandlerProperty newHandler - = newConnectionNode.signalHandlerProperty( - prependSignal(signalStr).toLatin1()); + SignalHandlerProperty newHandler = newConnectionNode.signalHandlerProperty( + prependSignal(signalStr).toLatin1()); newHandler.setSource( QString("console.log(\"%1.%2\")").arg(currentNode.id(), signalStr)); From c7f9d9af24af1b226e49b8798741c815bc33e6db Mon Sep 17 00:00:00 2001 From: Amr Essam Date: Wed, 30 Nov 2022 12:54:24 +0200 Subject: [PATCH 071/131] QmlDesigner: adjust caller code to new binary name QQEffectMaker -> qqem Task-number: QDS-8450 Change-Id: I8a82a31158e3c44903126b12d430b8563dde7f26 Reviewed-by: Tim Jenssen --- .../components/componentcore/modelnodeoperations.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp index 97e2716b035..e6693f8e5d0 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp +++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp @@ -1646,7 +1646,7 @@ void openEffectMaker(const QString &filePath) const QtSupport::QtVersion *baseQtVersion = QtSupport::QtKitAspect::qtVersion(target->kit()); if (baseQtVersion) { - auto effectMakerPath = baseQtVersion->binPath().pathAppended("QQEffectMaker").withExecutableSuffix(); + auto effectMakerPath = baseQtVersion->binPath().pathAppended("qqem").withExecutableSuffix(); if (!effectMakerPath.exists()) { qWarning() << __FUNCTION__ << "Cannot find EffectMaker app"; return; From 82ecd3910b10d5722f7c2792e70eb4f96d881955 Mon Sep 17 00:00:00 2001 From: hjk Date: Mon, 28 Nov 2022 14:16:21 +0100 Subject: [PATCH 072/131] QmlProjectManager: Use a PathChooser for the fallback qmlscene Allows to browse for binaries on the target instead of having to specify a proper fully qualified remote path manually. Change-Id: Iaf9dc5c4fe921a7188ef2178f10403fa01e69d03 Reviewed-by: Reviewed-by: Thomas Hartmann (cherry picked from commit 50f6afe4d0a8d152bcab7b5fa3c7b7583a9cfa3c) Reviewed-by: hjk --- .../qmlprojectmanager/qmlprojectrunconfiguration.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp b/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp index 3e5d648d520..ee5327cac0c 100644 --- a/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp +++ b/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp @@ -74,7 +74,7 @@ QmlProjectRunConfiguration::QmlProjectRunConfiguration(Target *target, Id id) m_qmlViewerAspect = addAspect(); m_qmlViewerAspect->setLabelText(tr("QML Viewer:")); m_qmlViewerAspect->setPlaceHolderText(commandLine().executable().toString()); - m_qmlViewerAspect->setDisplayStyle(StringAspect::LineEditDisplay); + m_qmlViewerAspect->setDisplayStyle(StringAspect::PathChooserDisplay); m_qmlViewerAspect->setHistoryCompleter("QmlProjectManager.viewer.history"); auto argumentAspect = addAspect(macroExpander()); @@ -157,9 +157,10 @@ QString QmlProjectRunConfiguration::disabledReason() const FilePath QmlProjectRunConfiguration::qmlRuntimeFilePath() const { - const QString qmlViewer = m_qmlViewerAspect->value(); + // Give precedence to the manual override. + const FilePath qmlViewer = m_qmlViewerAspect->filePath(); if (!qmlViewer.isEmpty()) - return FilePath::fromString(qmlViewer); + return qmlViewer; Kit *kit = target()->kit(); QtVersion *version = QtKitAspect::qtVersion(kit); From af8075cbb20a2c8250b3d037fbc9bc344dc47b9c Mon Sep 17 00:00:00 2001 From: Mats Honkamaa Date: Wed, 30 Nov 2022 12:32:10 +0200 Subject: [PATCH 073/131] Doc: Fix missing lights images Add missing 3D lights icons. Task-number: QDS-8433 Change-Id: Iaebe003efcaecbaf394dc4383f4de722d30142fe Reviewed-by: Leena Miettinen --- doc/qtdesignstudio/images/icons/area.png | Bin 0 -> 360 bytes doc/qtdesignstudio/images/icons/directional.png | Bin 0 -> 265 bytes doc/qtdesignstudio/images/icons/point.png | Bin 0 -> 458 bytes doc/qtdesignstudio/images/icons/spot.png | Bin 0 -> 391 bytes .../qtdesignstudio-3d-lights.qdoc | 8 ++++---- 5 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 doc/qtdesignstudio/images/icons/area.png create mode 100644 doc/qtdesignstudio/images/icons/directional.png create mode 100644 doc/qtdesignstudio/images/icons/point.png create mode 100644 doc/qtdesignstudio/images/icons/spot.png diff --git a/doc/qtdesignstudio/images/icons/area.png b/doc/qtdesignstudio/images/icons/area.png new file mode 100644 index 0000000000000000000000000000000000000000..0e0f773b075fcb59a8fa5e1715aeb43c476fcd5c GIT binary patch literal 360 zcmeAS@N?(olHy`uVBq!ia0y~yV2}Y}4i*Lmh8ry6@(c`&`kpS1Ar`0KPFm>g94K+L zKJS%rPHKcpfosY^m5jiL*G}>*dSI_$#`{RVpeuB;f9e_+FiDp3U^*uU34|2u!h8i{B)E^|I{6x;|Gmh96s+(&sRL zFfDMaG)XpDT((~QB^TSmnaR_{bfcz(q*-XJo^mvJmfWMTt%sDqvENv_?kA(8?!)4R z*511pxrN@0Z#otDFlz7oJqLvOXT7|?@c?go-^$xhD$}x0cuPGz|AUQ}$E)SD!mAAo P3=9mOu6{1-oD!M<81I=v literal 0 HcmV?d00001 diff --git a/doc/qtdesignstudio/images/icons/directional.png b/doc/qtdesignstudio/images/icons/directional.png new file mode 100644 index 0000000000000000000000000000000000000000..6ff7d1bbd3485651268e6ef3f9c86c7b3b05286f GIT binary patch literal 265 zcmeAS@N?(olHy`uVBq!ia0y~yV2}Y}7G?$ph6d$B%NZCL3<7*YTtVCh62K`|{jCfP z3<4!Ve!&c^QhMg~?F*K!+;i~hv*%w`5^hB>Ffdejx;TbNTux3sx1BFFn)0O#BqQy7%bGUw7RXuqbtG3WW|M{=1DxE|`W~r^+z@+$TO}*vztxFy!PLK3D zbJ4knq2MrQzl_mALrudN2Lb(Rmz4&K;_mHCnW1#@+N)fJj&|-pW*?_*Fr8H=>51uNzV#=Tdf8xJ|~v>SLX=${I{#i5U$ zid80VGud}VPL;hLqUt%#YVoB9p`Gt9B}}o3^xV}Tq}=Wpk=6GlyT$N?r;og0-@zGo zGTR#O7`7$m&K5oMVDhTW{Tf%dEa~!p%QuVF;I7RZV@{EXg1$%j=h&NrvR~F`nZIUW PU|{fc^>bP0l+XkKXAH>i literal 0 HcmV?d00001 diff --git a/doc/qtdesignstudio/images/icons/spot.png b/doc/qtdesignstudio/images/icons/spot.png new file mode 100644 index 0000000000000000000000000000000000000000..fa49858b67edacfa2511930a51e38a432623f432 GIT binary patch literal 391 zcmeAS@N?(olHy`uVBq!ia0y~yV2}Y}4i*Lmh8ry6@(c`&ex5FlAr`0iPO|l8b`)rv zZ+$%>F?qSbrKlSmfgUQ^!|JLH?H-83~BV0Je45#mYGs(Ky>h3=4^LsXLX8tcbEmG{Lkenu8#N@1`01?$^`}s{b1zjx8 zS)6EiK;h^_4cn#1A51^iUvlaE>6ev4$4lQb&U?4AaZ$n6pxP&oWsmp7o;lLI_*h}@ z@n%Wo8GY>YdlF3L?mP{^aT5bHaVGa>t4M&sJ=lssN4Vm literal 0 HcmV?d00001 diff --git a/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-lights.qdoc b/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-lights.qdoc index 4bd44874ab0..f2063e6a05c 100644 --- a/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-lights.qdoc +++ b/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-lights.qdoc @@ -41,25 +41,25 @@ \li More Information \row - \li \inlineimage directional.png + \li \inlineimage icons/directional.png \li Directional Light \li \li \l{DirectionalLight}{Light Directional} \row - \li \inlineimage point.png + \li \inlineimage icons/point.png \li Point Light \li \li \l{PointLight}{Light Point} \row - \li \inlineimage spot.png + \li \inlineimage icons/spot.png \li Spot Light \li \li \l{SpotLight}{Light Spot} \row - \li \inlineimage area.png + \li \inlineimage icons/area.png \li Area Light \li \inlineimage ok.png \li \l{AreaLight}{Light Area} From bb5c6a846dba84ce85b00d76e7b6f305500bb9fd Mon Sep 17 00:00:00 2001 From: Aleksei German Date: Tue, 29 Nov 2022 17:57:30 +0100 Subject: [PATCH 074/131] QmlDesigner: Add validId verification Task-number: QDS-8449 Change-Id: Iaa965fa1a54d93ff0f76680d1673d883fa3dfa0f Reviewed-by: Thomas Hartmann --- .../componentcore/designeractionmanager.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp index be6a2b14c15..9a416a85eaa 100644 --- a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp @@ -524,13 +524,14 @@ QList getSlotsLists(const ModelNode &node) const QString changeStateGroupStr = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Change State Group"); const QString defaultStateStr = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Default State"); - auto createStateChangeSlot = - [](const ModelNode &node, const QString &stateName, const QString &displayName) { - return SlotEntry({displayName, [node, stateName](SignalHandlerProperty signalHandler) { - signalHandler.setSource( - QString("%1.state = \"%2\"").arg(node.id(), stateName)); - }}); - }; + auto createStateChangeSlot = [](ModelNode node, + const QString &stateName, + const QString &displayName) { + return SlotEntry( + {displayName, [node, stateName](SignalHandlerProperty signalHandler) mutable { + signalHandler.setSource(QString("%1.state = \"%2\"").arg(node.validId(), stateName)); + }}); + }; { SlotList states = {changeStateStr, {}}; From 7e03b29bc7d6f3eba4988e7e2caf9af62f68321b Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Wed, 30 Nov 2022 15:43:04 +0100 Subject: [PATCH 075/131] QmlDesigner: Add new menu for export actions Task-number: QDS-8092 Change-Id: If83ea84fded27485c50865e49d8d184fecc6d454 Reviewed-by: Tim Jenssen --- src/plugins/qmldesigner/generateresource.cpp | 15 ++++++---- src/plugins/qmldesigner/shortcutmanager.cpp | 28 +++++++++++-------- .../cmakegen/cmakeprojectconverter.cpp | 10 ++++--- .../cmakegen/generatecmakelists.cpp | 24 ++++++++++++---- .../qmlprojectmanagerconstants.h | 4 +++ 5 files changed, 55 insertions(+), 26 deletions(-) diff --git a/src/plugins/qmldesigner/generateresource.cpp b/src/plugins/qmldesigner/generateresource.cpp index 4e319f2c1c1..5dfd49bd499 100644 --- a/src/plugins/qmldesigner/generateresource.cpp +++ b/src/plugins/qmldesigner/generateresource.cpp @@ -182,7 +182,8 @@ void GenerateResource::generateMenuEntry(QObject *parent) const Core::Context projectContext(QmlProjectManager::Constants::QML_PROJECT_ID); // ToDo: move this to QtCreator and add tr to the string then auto action = new QAction(QCoreApplication::translate("QmlDesigner::GenerateResource", - "Generate QRC Resource File"), parent); + "Generate QRC Resource File..."), + parent); action->setEnabled(ProjectExplorer::SessionManager::startupProject() != nullptr); // todo make it more intelligent when it gets enabled QObject::connect(ProjectExplorer::SessionManager::instance(), @@ -331,7 +332,8 @@ void GenerateResource::generateMenuEntry(QObject *parent) // ToDo: move this to QtCreator and add tr to the string then auto rccAction = new QAction(QCoreApplication::translate("QmlDesigner::GenerateResource", - "Generate Deployable Package"), parent); + "Generate Deployable Package..."), + parent); rccAction->setEnabled(ProjectExplorer::SessionManager::startupProject() != nullptr); QObject::connect(ProjectExplorer::SessionManager::instance(), &ProjectExplorer::SessionManager::startupProjectChanged, [rccAction]() { @@ -340,7 +342,7 @@ void GenerateResource::generateMenuEntry(QObject *parent) Core::Command *cmd2 = Core::ActionManager::registerAction(rccAction, "QmlProject.CreateRCCResource"); - QObject::connect(rccAction, &QAction::triggered, [] () { + QObject::connect(rccAction, &QAction::triggered, []() { auto currentProject = ProjectExplorer::SessionManager::startupProject(); QTC_ASSERT(currentProject, return); const FilePath projectPath = currentProject->projectFilePath().parentDir(); @@ -558,8 +560,11 @@ void GenerateResource::generateMenuEntry(QObject *parent) "Successfully generated deployable package\n %1") .arg(resourceFileName.toString())); }); - menu->addAction(cmd, Core::Constants::G_FILE_EXPORT); - menu->addAction(cmd2, Core::Constants::G_FILE_EXPORT); + + Core::ActionContainer *exportMenu = Core::ActionManager::actionContainer( + QmlProjectManager::Constants::EXPORT_MENU); + exportMenu->addAction(cmd, QmlProjectManager::Constants::G_EXPORT_GENERATE); + exportMenu->addAction(cmd2, QmlProjectManager::Constants::G_EXPORT_GENERATE); } } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/shortcutmanager.cpp b/src/plugins/qmldesigner/shortcutmanager.cpp index 22f91f95438..6b3e6fa9f31 100644 --- a/src/plugins/qmldesigner/shortcutmanager.cpp +++ b/src/plugins/qmldesigner/shortcutmanager.cpp @@ -19,6 +19,8 @@ #include #include +#include + #include #include @@ -38,16 +40,16 @@ namespace QmlDesigner { ShortCutManager::ShortCutManager() - : QObject(), - m_exportAsImageAction(tr("Export as &Image...")), - m_undoAction(tr("&Undo")), - m_redoAction(tr("&Redo")), - m_deleteAction(tr("Delete")), - m_cutAction(tr("Cu&t")), - m_copyAction(tr("&Copy")), - m_pasteAction(tr("&Paste")), - m_selectAllAction(tr("Select &All")), - m_escapeAction(this) + : QObject() + , m_exportAsImageAction(tr("Export as Image...")) + , m_undoAction(tr("&Undo")) + , m_redoAction(tr("&Redo")) + , m_deleteAction(tr("Delete")) + , m_cutAction(tr("Cu&t")) + , m_copyAction(tr("&Copy")) + , m_pasteAction(tr("&Paste")) + , m_selectAllAction(tr("Select &All")) + , m_escapeAction(this) { } @@ -101,7 +103,11 @@ void ShortCutManager::registerActions(const Core::Context &qmlDesignerMainContex connect(&m_exportAsImageAction, &QAction::triggered, [] { QmlDesignerPlugin::instance()->viewManager().exportAsImage(); }); - fileMenu->addAction(command, Core::Constants::G_FILE_SAVE); + + Core::ActionContainer *exportMenu = Core::ActionManager::actionContainer( + QmlProjectManager::Constants::EXPORT_MENU); + + exportMenu->addAction(command, QmlProjectManager::Constants::G_EXPORT_CONVERT); //Close Editor Core::ActionManager::registerAction(&m_closeCurrentEditorAction, Core::Constants::CLOSE, qmlDesignerMainContext); diff --git a/src/plugins/qmlprojectmanager/cmakegen/cmakeprojectconverter.cpp b/src/plugins/qmlprojectmanager/cmakegen/cmakeprojectconverter.cpp index 159e7f5fad5..4cd3a586de6 100644 --- a/src/plugins/qmlprojectmanager/cmakegen/cmakeprojectconverter.cpp +++ b/src/plugins/qmlprojectmanager/cmakegen/cmakeprojectconverter.cpp @@ -13,6 +13,8 @@ #include #include +#include + #include #include #include @@ -24,7 +26,7 @@ namespace QmlProjectManager { namespace GenerateCmake { const QString MENU_ITEM_CONVERT = QCoreApplication::translate("QmlDesigner::CmakeProjectConverter", - "Export as Latest Project Format"); + "Export as Latest Project Format..."); const QString ERROR_TITLE = QCoreApplication::translate("QmlDesigner::CmakeProjectConverter", "Creating Project"); const QString SUCCESS_TITLE = QCoreApplication::translate("QmlDesigner::CmakeProjectConverter", @@ -36,12 +38,12 @@ const QString SUCCESS_TEXT = QCoreApplication::translate("QmlDesigner::CmakeProj void CmakeProjectConverter::generateMenuEntry(QObject *parent) { - Core::ActionContainer *menu = - Core::ActionManager::actionContainer(Core::Constants::M_FILE); + Core::ActionContainer *exportMenu = Core::ActionManager::actionContainer( + QmlProjectManager::Constants::EXPORT_MENU); auto action = new QAction(MENU_ITEM_CONVERT, parent); QObject::connect(action, &QAction::triggered, CmakeProjectConverter::onConvertProject); Core::Command *cmd = Core::ActionManager::registerAction(action, "QmlProject.ConvertToCmakeProject"); - menu->addAction(cmd, Core::Constants::G_FILE_EXPORT); + exportMenu->addAction(cmd, QmlProjectManager::Constants::G_EXPORT_CONVERT); action->setEnabled(isProjectConvertable(ProjectExplorer::SessionManager::startupProject())); QObject::connect(ProjectExplorer::SessionManager::instance(), diff --git a/src/plugins/qmlprojectmanager/cmakegen/generatecmakelists.cpp b/src/plugins/qmlprojectmanager/cmakegen/generatecmakelists.cpp index 74b37db8c95..b239ca75ccc 100644 --- a/src/plugins/qmlprojectmanager/cmakegen/generatecmakelists.cpp +++ b/src/plugins/qmlprojectmanager/cmakegen/generatecmakelists.cpp @@ -14,18 +14,19 @@ #include #include +#include #include #include -#include #include #include +#include #include -#include #include #include #include +#include using namespace Utils; using namespace QmlProjectManager::GenerateCmake::Constants; @@ -56,16 +57,27 @@ enum ProjectDirectoryError { }; const QString MENU_ITEM_GENERATE = QCoreApplication::translate("QmlDesigner::GenerateCmake", - "Generate CMake Build Files"); + "Generate CMake Build Files..."); void generateMenuEntry(QObject *parent) { - Core::ActionContainer *menu = - Core::ActionManager::actionContainer(Core::Constants::M_FILE); + Core::ActionContainer *menu = Core::ActionManager::actionContainer(Core::Constants::M_FILE); + + Core::ActionContainer *exportMenu = Core::ActionManager::createMenu( + QmlProjectManager::Constants::EXPORT_MENU); + + exportMenu->menu()->setTitle( + QCoreApplication::translate("QmlDesigner::GenerateCmake", "Export Project")); + menu->addMenu(exportMenu, Core::Constants::G_FILE_EXPORT); + + exportMenu->appendGroup(QmlProjectManager::Constants::G_EXPORT_GENERATE); + exportMenu->appendGroup(QmlProjectManager::Constants::G_EXPORT_CONVERT); + exportMenu->addSeparator(QmlProjectManager::Constants::G_EXPORT_CONVERT); + auto action = new QAction(MENU_ITEM_GENERATE, parent); QObject::connect(action, &QAction::triggered, GenerateCmake::onGenerateCmakeLists); Core::Command *cmd = Core::ActionManager::registerAction(action, "QmlProject.CreateCMakeLists"); - menu->addAction(cmd, Core::Constants::G_FILE_EXPORT); + exportMenu->addAction(cmd, QmlProjectManager::Constants::G_EXPORT_GENERATE); action->setEnabled(false); QObject::connect(ProjectExplorer::SessionManager::instance(), diff --git a/src/plugins/qmlprojectmanager/qmlprojectmanagerconstants.h b/src/plugins/qmlprojectmanager/qmlprojectmanagerconstants.h index 43b266d9995..dd4d7a97899 100644 --- a/src/plugins/qmlprojectmanager/qmlprojectmanagerconstants.h +++ b/src/plugins/qmlprojectmanager/qmlprojectmanagerconstants.h @@ -16,5 +16,9 @@ const char USE_MULTILANGUAGE_KEY[] = "QmlProjectManager.QmlRunConfiguration.UseM const char LAST_USED_LANGUAGE[] = "QmlProjectManager.QmlRunConfiguration.LastUsedLanguage"; const char USER_ENVIRONMENT_CHANGES_KEY[] = "QmlProjectManager.QmlRunConfiguration.UserEnvironmentChanges"; +const char EXPORT_MENU[] = "QmlDesigner.ExportMenu"; +const char G_EXPORT_GENERATE[] = "QmlDesigner.Group.GenerateProject"; +const char G_EXPORT_CONVERT[] = "QmlDesigner.Group.ConvertProject"; + } // namespace Constants } // namespace QmlProjectManager From 714c3c381e16e6763e3984f6e66b274b75ecb42b Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Wed, 30 Nov 2022 13:25:06 +0200 Subject: [PATCH 076/131] QmlDesigner: Show content library under enterprise license only Fixes: QDS-8453 Change-Id: I84668ee370e494ff303734faefb0b5e0895ca061 Reviewed-by: Reviewed-by: Thomas Hartmann --- .../components/componentcore/viewmanager.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp b/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp index 7ac6d44877e..438ceb9c3a8 100644 --- a/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp @@ -219,7 +219,6 @@ QList ViewManager::standardViews() const &d->itemLibraryView, &d->navigatorView, &d->propertyEditorView, - &d->contentLibraryView, &d->materialEditorView, &d->materialBrowserView, &d->textureEditorView, @@ -238,6 +237,13 @@ QList ViewManager::standardViews() const .toBool()) list.append(&d->debugView); +#ifdef CHECK_LICENSE + if (checkLicense() == FoundLicense::enterprise) + list.append(&d->contentLibraryView); +#else + list.append(&d->contentLibraryView); +#endif + return list; } @@ -401,7 +407,6 @@ QList ViewManager::widgetInfos() const widgetInfoList.append(d->itemLibraryView.widgetInfo()); widgetInfoList.append(d->navigatorView.widgetInfo()); widgetInfoList.append(d->propertyEditorView.widgetInfo()); - widgetInfoList.append(d->contentLibraryView.widgetInfo()); widgetInfoList.append(d->materialEditorView.widgetInfo()); widgetInfoList.append(d->materialBrowserView.widgetInfo()); widgetInfoList.append(d->textureEditorView.widgetInfo()); @@ -410,6 +415,13 @@ QList ViewManager::widgetInfos() const else widgetInfoList.append(d->newStatesEditorView.widgetInfo()); +#ifdef CHECK_LICENSE + if (checkLicense() == FoundLicense::enterprise) + widgetInfoList.append(d->contentLibraryView.widgetInfo()); +#else + widgetInfoList.append(d->contentLibraryView.widgetInfo()); +#endif + if (d->debugView.hasWidget()) widgetInfoList.append(d->debugView.widgetInfo()); From 072bdb22fc5b3e9c0f61d84175930a361f1cf8fd Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Wed, 30 Nov 2022 17:14:22 +0200 Subject: [PATCH 077/131] QmlDesigner: Update isEmpty state upon content library load Change-Id: Ibb26294efac90e177f5e4f691609a95f6a181475 Reviewed-by: Thomas Hartmann Reviewed-by: --- .../contentlibrary/contentlibrarymaterialsmodel.cpp | 5 +++++ .../contentlibrary/contentlibrarytexturesmodel.cpp | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterialsmodel.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterialsmodel.cpp index 92be78f016b..55acaf15746 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterialsmodel.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterialsmodel.cpp @@ -162,6 +162,11 @@ void ContentLibraryMaterialsModel::loadMaterialBundle() emit importerRunningChanged(); emit bundleMaterialUnimported(metaInfo); }); + + if (m_bundleCategories.isEmpty() != m_isEmpty) { + m_isEmpty = m_bundleCategories.isEmpty(); + emit isEmptyChanged(); + } } bool ContentLibraryMaterialsModel::hasQuick3DImport() const diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturesmodel.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturesmodel.cpp index 4d793e4e2bd..8a0ea67c3e5 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturesmodel.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturesmodel.cpp @@ -85,6 +85,11 @@ void ContentLibraryTexturesModel::loadTextureBundle(const QString &bundlePath) category->addTexture(tex); m_bundleCategories.append(category); } + + if (m_bundleCategories.isEmpty() != m_isEmpty) { + m_isEmpty = m_bundleCategories.isEmpty(); + emit isEmptyChanged(); + } } bool ContentLibraryTexturesModel::hasQuick3DImport() const From b9e7fd2415897b9a17039e747a053e088d22c75f Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Wed, 30 Nov 2022 20:14:57 +0100 Subject: [PATCH 078/131] QmlDesigner: Take puppet version into account Change-Id: I8bd6f588dcf00e20dbcddde9caee8eef3eafc3b0 Reviewed-by: Qt CI Bot Reviewed-by: Reviewed-by: Thomas Hartmann --- src/plugins/qmldesigner/puppetenvironmentbuilder.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/plugins/qmldesigner/puppetenvironmentbuilder.cpp b/src/plugins/qmldesigner/puppetenvironmentbuilder.cpp index 38d2c403409..5c3fd5547df 100644 --- a/src/plugins/qmldesigner/puppetenvironmentbuilder.cpp +++ b/src/plugins/qmldesigner/puppetenvironmentbuilder.cpp @@ -7,6 +7,8 @@ #include +#include + #include #include #include @@ -40,7 +42,9 @@ Utils::FilePath pathForBinPuppet(ProjectExplorer::Target *target) QtSupport::QtVersion *currentQtVersion = QtSupport::QtKitAspect::qtVersion(target->kit()); if (currentQtVersion) - return currentQtVersion->binPath().pathAppended("qml2puppet").withExecutableSuffix(); + return currentQtVersion->binPath() + .pathAppended(QString{"qml2puppet-"} + Core::Constants::IDE_VERSION_LONG) + .withExecutableSuffix(); return {}; } From 916368ede424c90e9ecce96fe1e5d92690a71fe2 Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Wed, 30 Nov 2022 21:44:51 +0100 Subject: [PATCH 079/131] QmlDesigner: initialize unique pointer This crashes with Qt 5. With Qt 6 this "worked", which was most likely pure luck. Change-Id: I652b190ae0d4b00adb4c48e601e7be9512f86d16 Reviewed-by: Reviewed-by: Thomas Hartmann --- .../qml2puppet/qml2puppet/instances/qt5nodeinstanceserver.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tools/qml2puppet/qml2puppet/instances/qt5nodeinstanceserver.cpp b/src/tools/qml2puppet/qml2puppet/instances/qt5nodeinstanceserver.cpp index c7bf859daa2..14672a21953 100644 --- a/src/tools/qml2puppet/qml2puppet/instances/qt5nodeinstanceserver.cpp +++ b/src/tools/qml2puppet/qml2puppet/instances/qt5nodeinstanceserver.cpp @@ -45,6 +45,7 @@ namespace QmlDesigner { Qt5NodeInstanceServer::Qt5NodeInstanceServer(NodeInstanceClientInterface *nodeInstanceClient) : NodeInstanceServer(nodeInstanceClient) + , m_designerSupport(new QQuickDesignerSupport) { if (!ViewConfig::isParticleViewMode()) QQuickDesignerSupport::activateDesignerMode(); From 5f7d36d1cbd80444cf00907e2f64b535747ea943 Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Tue, 29 Nov 2022 19:35:10 +0200 Subject: [PATCH 080/131] QmlDesigner: Fix material browser's textures search Change-Id: Ia0750f0f61d821deeb1d0d3630fb770803e604ee Reviewed-by: Reviewed-by: Miikka Heikkinen --- .../qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml | 4 ++-- .../materialbrowser/materialbrowsertexturesmodel.cpp | 2 +- .../components/materialbrowser/materialbrowserwidget.cpp | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml index 920a1869ff9..4cf2efa54e3 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml @@ -194,7 +194,7 @@ Item { color: StudioTheme.Values.themeTextColor font.pixelSize: StudioTheme.Values.baseFontSize leftPadding: 10 - visible: materialBrowserModel.isEmpty && !searchBox.isEmpty() && materialBrowserModel.hasMaterialLibrary + visible: materialBrowserModel.isEmpty && !searchBox.isEmpty() } Text { @@ -277,7 +277,7 @@ Item { color: StudioTheme.Values.themeTextColor font.pixelSize: StudioTheme.Values.baseFontSize leftPadding: 10 - visible: materialBrowserModel.isEmpty && !searchBox.isEmpty() && materialBrowserModel.hasMaterialLibrary + visible: materialBrowserTexturesModel.isEmpty && !searchBox.isEmpty() } Text { diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp index 781b43622fd..3f5fde5a7c1 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp @@ -54,7 +54,7 @@ bool MaterialBrowserTexturesModel::isTextureVisible(int idx) const if (!isValidIndex(idx)) return false; - return m_searchText.isEmpty() || m_textureList.at(idx).variantProperty("objectName") + return m_searchText.isEmpty() || m_textureList.at(idx).variantProperty("source") .value().toString().contains(m_searchText, Qt::CaseInsensitive); } diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp index 923a2a42e30..901626b5a24 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp @@ -300,6 +300,7 @@ void MaterialBrowserWidget::reloadQmlSource() void MaterialBrowserWidget::updateSearch() { m_materialBrowserModel->setSearchText(m_filterText); + m_materialBrowserTexturesModel->setSearchText(m_filterText); m_quickWidget->update(); } From fd4d1f08fa18327c94fd8046981a865610aed399 Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Thu, 1 Dec 2022 14:13:33 +0200 Subject: [PATCH 081/131] QmlDesigner: Add a tooltip to material browser textures Change-Id: I5a2dd41602bb03357fdd1b7280242f6ea43ff56f Reviewed-by: Miikka Heikkinen --- .../materialBrowserQmlSource/TextureItem.qml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml index 19762299c6d..3492a1dfef3 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 import QtQuick +import QtQuick.Controls import QtQuick.Layouts import QtQuickDesignerTheme import HelperWidgets @@ -26,6 +27,7 @@ Rectangle { anchors.fill: parent acceptedButtons: Qt.LeftButton | Qt.RightButton + hoverEnabled: true onPressed: (mouse) => { rootView.focusMaterialSection(false) @@ -40,6 +42,14 @@ Rectangle { onDoubleClicked: materialBrowserTexturesModel.openTextureEditor(); } + ToolTip { + property bool hasSource: textureSource.slice(-1) !== "/" + + visible: mouseArea.containsMouse + text: hasSource ? textureSource : qsTr("Texture has no source image.") + delay: 1000 + } + Image { source: "image://materialBrowserTex/" + textureSource asynchronous: true From 17c09c1e92e51e9831902878d1a94807da88fb4f Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Thu, 1 Dec 2022 11:15:07 +0100 Subject: [PATCH 082/131] QmlDesigner: Fix warnings Change-Id: Ia88bbe9bc05fcd1725833d1627499dac3a32c11d Reviewed-by: Tim Jenssen Reviewed-by: --- src/plugins/qmldesigner/generateresource.cpp | 3 --- src/plugins/qmldesigner/qmldesignerplugin.cpp | 2 +- src/plugins/qmldesigner/shortcutmanager.cpp | 1 - 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/plugins/qmldesigner/generateresource.cpp b/src/plugins/qmldesigner/generateresource.cpp index 5dfd49bd499..7164ea0df0b 100644 --- a/src/plugins/qmldesigner/generateresource.cpp +++ b/src/plugins/qmldesigner/generateresource.cpp @@ -176,9 +176,6 @@ QList getFilesFromQrc(QFile *file, bool inProjec void GenerateResource::generateMenuEntry(QObject *parent) { - Core::ActionContainer *menu = - Core::ActionManager::actionContainer(Core::Constants::M_FILE); - const Core::Context projectContext(QmlProjectManager::Constants::QML_PROJECT_ID); // ToDo: move this to QtCreator and add tr to the string then auto action = new QAction(QCoreApplication::translate("QmlDesigner::GenerateResource", diff --git a/src/plugins/qmldesigner/qmldesignerplugin.cpp b/src/plugins/qmldesigner/qmldesignerplugin.cpp index f5a6cfa2dec..592c89eeeb7 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.cpp +++ b/src/plugins/qmldesigner/qmldesignerplugin.cpp @@ -85,7 +85,7 @@ namespace Internal { class EnterpriseFeatureProvider : public Core::IFeatureProvider { public: - QSet availableFeatures(Utils::Id id) const override + QSet availableFeatures(Utils::Id) const override { return {"QmlDesigner.Wizards.Enterprise"}; } diff --git a/src/plugins/qmldesigner/shortcutmanager.cpp b/src/plugins/qmldesigner/shortcutmanager.cpp index 6b3e6fa9f31..04edfd72094 100644 --- a/src/plugins/qmldesigner/shortcutmanager.cpp +++ b/src/plugins/qmldesigner/shortcutmanager.cpp @@ -63,7 +63,6 @@ void ShortCutManager::registerActions(const Core::Context &qmlDesignerMainContex Q_UNUSED(qmlDesignerMaterialBrowserContext) Core::ActionContainer *editMenu = Core::ActionManager::actionContainer(Core::Constants::M_EDIT); - Core::ActionContainer *fileMenu = Core::ActionManager::actionContainer(Core::Constants::M_FILE); connect(&m_undoAction, &QAction::triggered, this, &ShortCutManager::undo); From 9e9cfeeeac3c55d8141ce33cee448723c5c1fca1 Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Thu, 1 Dec 2022 16:23:01 +0100 Subject: [PATCH 083/131] StudioWelcome: Fix warning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change-Id: Id6a218998f2b0a767d4bd23c8936f5863c00d7b1 Reviewed-by: Henning Gründl --- src/plugins/studiowelcome/studiowelcomeplugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/studiowelcome/studiowelcomeplugin.cpp b/src/plugins/studiowelcome/studiowelcomeplugin.cpp index 5a48bc0148e..24b3254d136 100644 --- a/src/plugins/studiowelcome/studiowelcomeplugin.cpp +++ b/src/plugins/studiowelcome/studiowelcomeplugin.cpp @@ -254,7 +254,7 @@ public: const QString qmlFile = QFileInfo(projectFile).dir().absolutePath() + "/" + formFile; // This timer should be replaced with a signal send from project loading - QTimer::singleShot(1000, [qmlFile](){ + QTimer::singleShot(1000, this, [qmlFile]() { Core::EditorManager::openEditor(Utils::FilePath::fromString(qmlFile)); }); } From 740a65571fd877fb5722443c8172cfdc047aa159 Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Thu, 1 Dec 2022 15:18:50 +0100 Subject: [PATCH 084/131] StudioWelcome: Use QQuickWindow on macOS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes flickering on macOS introduced by Qt 6.4. Unfortuntly keyboard focus does not work on Windows in this case if the splashscreen flag is set. Therefore we only use QQuickWindow on macOS. Simplfiying code and removing unused code. The variable doNotShowAgain is always true now. The check in delayedInitialize can be removed, since we already show a warning during creation. Task-number: QDS-8113 Change-Id: I8185a58175f4d2eafe92561fbded535d7ae393a2 Reviewed-by: Henning Gründl Reviewed-by: Qt CI Bot --- .../studiowelcome/studiowelcomeplugin.cpp | 114 +++++++++++------- .../studiowelcome/studiowelcomeplugin.h | 1 - 2 files changed, 70 insertions(+), 45 deletions(-) diff --git a/src/plugins/studiowelcome/studiowelcomeplugin.cpp b/src/plugins/studiowelcome/studiowelcomeplugin.cpp index 24b3254d136..818aeffb9d7 100644 --- a/src/plugins/studiowelcome/studiowelcomeplugin.cpp +++ b/src/plugins/studiowelcome/studiowelcomeplugin.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -91,7 +92,8 @@ const char CRASH_REPORTER_SETTING[] = "CrashReportingEnabled"; const char EXAMPLES_DOWNLOAD_PATH[] = "StudioWelcome/ExamplesDownloadPath"; -QPointer s_view = nullptr; +QPointer s_viewWindow = nullptr; +QPointer s_viewWidget = nullptr; static StudioWelcomePlugin *s_pluginInstance = nullptr; std::unique_ptr makeUserFeedbackSettings() @@ -449,22 +451,13 @@ private: void StudioWelcomePlugin::closeSplashScreen() { - if (!s_view.isNull()) { - const bool doNotShowAgain = s_view->rootObject()->property("doNotShowAgain").toBool(); - if (doNotShowAgain) - Utils::CheckableMessageBox::doNotAskAgain(Core::ICore::settings(), - DO_NOT_SHOW_SPLASHSCREEN_AGAIN_KEY); + Utils::CheckableMessageBox::doNotAskAgain(Core::ICore::settings(), + DO_NOT_SHOW_SPLASHSCREEN_AGAIN_KEY); + if (!s_viewWindow.isNull()) + s_viewWindow->deleteLater(); - s_view->deleteLater(); - } -} - -void StudioWelcomePlugin::showSystemSettings() -{ - Core::ICore::infoBar()->removeInfo("WarnCrashReporting"); - Core::ICore::infoBar()->globallySuppressInfo("WarnCrashReporting"); - - Core::ICore::showOptionsDialog(Core::Constants::SETTINGS_ID_SYSTEM); + if (!s_viewWidget.isNull()) + s_viewWidget->deleteLater(); } StudioWelcomePlugin::StudioWelcomePlugin() @@ -537,47 +530,80 @@ void StudioWelcomePlugin::extensionsInitialized() if (showSplashScreen()) { connect(Core::ICore::instance(), &Core::ICore::coreOpened, this, [this] { - s_view = new QQuickWidget(Core::ICore::dialogParent()); - s_view->setResizeMode(QQuickWidget::SizeRootObjectToView); - s_view->setWindowFlag(Qt::SplashScreen, true); - s_view->setWindowModality(Qt::ApplicationModal); - s_view->engine()->addImportPath("qrc:/studiofonts"); + if (Utils::HostOsInfo::isMacHost()) { + s_viewWindow = new QQuickView(Core::ICore::mainWindow()->windowHandle()); + + s_viewWindow->setFlag(Qt::FramelessWindowHint); + + s_viewWindow->setModality(Qt::ApplicationModal); + s_viewWindow->engine()->addImportPath("qrc:/studiofonts"); #ifdef QT_DEBUG - s_view->engine()->addImportPath(QLatin1String(STUDIO_QML_PATH) + "splashscreen/imports"); - s_view->setSource( - QUrl::fromLocalFile(QLatin1String(STUDIO_QML_PATH) + "splashscreen/main.qml")); + s_viewWindow->engine()->addImportPath(QLatin1String(STUDIO_QML_PATH) + + "splashscreen/imports"); + s_viewWindow->setSource( + QUrl::fromLocalFile(QLatin1String(STUDIO_QML_PATH) + "splashscreen/main.qml")); #else - s_view->engine()->addImportPath("qrc:/qml/splashscreen/imports"); - s_view->setSource(QUrl("qrc:/qml/splashscreen/main.qml")); + s_viewWindow->engine()->addImportPath("qrc:/qml/splashscreen/imports"); + s_viewWindow->setSource(QUrl("qrc:/qml/splashscreen/main.qml")); #endif + QTC_ASSERT(s_viewWindow->rootObject(), + qWarning() << "The StudioWelcomePlugin has a runtime depdendency on " + "qt/qtquicktimeline."; + return ); - QTC_ASSERT(s_view->rootObject(), - qWarning() << "The StudioWelcomePlugin has a runtime depdendency on " - "qt/qtquicktimeline."; - return ); + connect(s_viewWindow->rootObject(), + SIGNAL(closeClicked()), + this, + SLOT(closeSplashScreen())); - connect(s_view->rootObject(), SIGNAL(closeClicked()), this, SLOT(closeSplashScreen())); - connect(s_view->rootObject(), - SIGNAL(configureClicked()), - this, - SLOT(showSystemSettings())); + auto mainWindow = Core::ICore::mainWindow()->windowHandle(); + s_viewWindow->setPosition((mainWindow->width() - s_viewWindow->width()) / 2, + (mainWindow->height() - s_viewWindow->height()) / 2); - s_view->show(); - s_view->raise(); - s_view->setFocus(); + s_viewWindow->show(); + s_viewWindow->raise(); + + s_viewWindow->requestActivate(); + } else { + s_viewWidget = new QQuickWidget(Core::ICore::dialogParent()); + + s_viewWidget->setWindowFlag(Qt::SplashScreen, true); + + s_viewWidget->setWindowModality(Qt::ApplicationModal); + s_viewWidget->setResizeMode(QQuickWidget::SizeRootObjectToView); + s_viewWidget->engine()->addImportPath("qrc:/studiofonts"); +#ifdef QT_DEBUG + s_viewWidget->engine()->addImportPath(QLatin1String(STUDIO_QML_PATH) + + "splashscreen/imports"); + s_viewWidget->setSource( + QUrl::fromLocalFile(QLatin1String(STUDIO_QML_PATH) + "splashscreen/main.qml")); +#else + s_viewWidget->engine()->addImportPath("qrc:/qml/splashscreen/imports"); + s_viewWidget->setSource(QUrl("qrc:/qml/splashscreen/main.qml")); +#endif + + QTC_ASSERT(s_viewWidget->rootObject(), + qWarning() << "The StudioWelcomePlugin has a runtime depdendency on " + "qt/qtquicktimeline."; + return ); + + connect(s_viewWidget->rootObject(), + SIGNAL(closeClicked()), + this, + SLOT(closeSplashScreen())); + + s_viewWidget->show(); + s_viewWidget->raise(); + s_viewWidget->setFocus(); + } }); } } bool StudioWelcomePlugin::delayedInitialize() { - if (s_view.isNull()) - return false; - - QTC_ASSERT(s_view->rootObject(), return true); - - return false; + return true; } Utils::FilePath StudioWelcomePlugin::defaultExamplesPath() diff --git a/src/plugins/studiowelcome/studiowelcomeplugin.h b/src/plugins/studiowelcome/studiowelcomeplugin.h index 6368e5dd8d7..88a8b0abbb2 100644 --- a/src/plugins/studiowelcome/studiowelcomeplugin.h +++ b/src/plugins/studiowelcome/studiowelcomeplugin.h @@ -45,7 +45,6 @@ class StudioWelcomePlugin final : public ExtensionSystem::IPlugin public slots: void closeSplashScreen(); - void showSystemSettings(); public: StudioWelcomePlugin(); From d9054d1f10cffb3ed957204287add76df58a78d3 Mon Sep 17 00:00:00 2001 From: Henning Gruendl Date: Fri, 11 Nov 2022 15:49:16 +0100 Subject: [PATCH 085/131] QmlDesigner: Add qt insight infrastructure * Add InsightSection for property editor * Add functions in property editor qml backend, context object and view * Add InsightTracker in text to model merger * Add auxiliary data properties Task-number: QDS-7489 Task-number: QDS-7833 Task-number: QDS-8073 Change-Id: I3fbec3d387f815d71640b512e67829076b600d11 Reviewed-by: Reviewed-by: Thomas Hartmann --- .../QtQuick/ItemPane.qml | 4 + .../imports/HelperWidgets/InsightSection.qml | 88 +++++++++++++++++++ .../imports/HelperWidgets/qmldir | 1 + .../imports/StudioControls/ComboBoxInput.qml | 2 +- .../propertyeditorcontextobject.cpp | 22 +++++ .../propertyeditorcontextobject.h | 14 +++ .../propertyeditorqmlbackend.cpp | 36 +++++++- .../propertyeditor/propertyeditorqmlbackend.h | 10 ++- .../propertyeditor/propertyeditorview.cpp | 45 ++++++++-- .../include/auxiliarydataproperties.h | 6 ++ .../designercore/model/texttomodelmerger.cpp | 3 +- 11 files changed, 219 insertions(+), 12 deletions(-) create mode 100644 share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/InsightSection.qml diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ItemPane.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ItemPane.qml index 3ae08a1249a..fe29c2ef322 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ItemPane.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ItemPane.qml @@ -15,6 +15,10 @@ PropertyEditorPane { showState: true } + InsightSection { + visible: insightEnabled + } + DynamicPropertiesSection { propertiesModel: SelectionDynamicPropertiesModel {} visible: !hasMultiSelection diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/InsightSection.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/InsightSection.qml new file mode 100644 index 00000000000..988e5ebbf9e --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/InsightSection.qml @@ -0,0 +1,88 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +import QtQuick +import HelperWidgets 2.0 +import StudioControls 1.0 as StudioControls +import StudioTheme 1.0 as StudioTheme + +Section { + id: root + caption: qsTr("Analytics") + + anchors.left: parent.left + anchors.right: parent.right + + property string defaultItem: qsTr("[None]") + + function addDefaultItem(arr) + { + var copy = arr.slice() + copy.unshift(root.defaultItem) + return copy + } + + SectionLayout { + PropertyLabel { text: qsTr("Category") } + + SecondColumnLayout { + Spacer { implicitWidth: StudioTheme.Values.actionIndicatorWidth } + + StudioControls.ComboBox { + id: comboBox + property var backendValue: backendValues.InsightCategory_category + property var valueFromBackend: comboBox.backendValue === undefined ? 0 : comboBox.backendValue.value + + onValueFromBackendChanged: comboBox.invalidate() + onModelChanged: comboBox.invalidate() + + actionIndicatorVisible: false + implicitWidth: StudioTheme.Values.singleControlColumnWidth + width: implicitWidth + model: root.addDefaultItem(insightCategories) + editable: false + + onCompressedActivated: function(index, reason) { + if (comboBox.backendValue === undefined) + return + + verifyInsightImport() + + if (index === 0) + comboBox.backendValue.resetValue() + else + comboBox.backendValue.value = comboBox.currentText + } + + Connections { + target: modelNodeBackend + function onSelectionToBeChanged() { + comboBox.popup.close() + } + } + + function invalidate() { + var index = comboBox.find(comboBox.valueFromBackend) + if (index < 0) { + if (comboBox.valueFromBackend === "") { + comboBox.currentIndex = 0 + comboBox.labelColor = StudioTheme.Values.themeTextColor + } else { + comboBox.currentIndex = index + comboBox.editText = comboBox.valueFromBackend + comboBox.labelColor = StudioTheme.Values.themeError + } + } else { + if (index !== comboBox.currentIndex) + comboBox.currentIndex = index + + comboBox.labelColor = StudioTheme.Values.themeTextColor + } + } + Component.onCompleted: comboBox.invalidate() + } + + ExpandingSpacer {} + } + } +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir index f145b098776..ca1411a4d6e 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir @@ -42,6 +42,7 @@ IconButton 2.0 IconButton.qml IconLabel 2.0 IconLabel.qml ImagePreviewTooltipArea 2.0 ImagePreviewTooltipArea.qml ImageSection 2.0 ImageSection.qml +InsightSection 2.0 InsightSection.qml ItemFilterComboBox 2.0 ItemFilterComboBox.qml Label 2.0 Label.qml LineEdit 2.0 LineEdit.qml diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ComboBoxInput.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ComboBoxInput.qml index 6ac2df56f2c..f29fc0311aa 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ComboBoxInput.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ComboBoxInput.qml @@ -57,7 +57,7 @@ TextInput { myControl.focus = false } else { myControl.popup.open() - myControl.forceActiveFocus() + //myControl.forceActiveFocus() } } else { textInput.forceActiveFocus() diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.cpp index 10f08b542ae..396dac9039f 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.cpp @@ -424,6 +424,20 @@ void PropertyEditorContextObject::setHasMultiSelection(bool b) emit hasMultiSelectionChanged(); } +void PropertyEditorContextObject::setInsightEnabled(bool value) +{ + if (value != m_insightEnabled) { + m_insightEnabled = value; + emit insightEnabledChanged(); + } +} + +void PropertyEditorContextObject::setInsightCategories(const QStringList &categories) +{ + m_insightCategories = categories; + emit insightCategoriesChanged(); +} + void PropertyEditorContextObject::setSpecificsUrl(const QUrl &newSpecificsUrl) { if (newSpecificsUrl == m_specificsUrl) @@ -581,6 +595,14 @@ bool PropertyEditorContextObject::isBlocked(const QString &propName) const return false; } +void PropertyEditorContextObject::verifyInsightImport() +{ + Import import = Import::createLibraryImport("QtInsightTracker", "1.0"); + + if (!m_model->hasImport(import)) + m_model->changeImports({import}, {}); +} + void EasingCurveEditor::registerDeclarativeType() { qmlRegisterType("HelperWidgets", 2, 0, "EasingCurveEditor"); diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.h b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.h index 23a2bba47fb..402b2d9bfa2 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.h +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.h @@ -49,6 +49,9 @@ class PropertyEditorContextObject : public QObject Q_PROPERTY(bool hasMultiSelection READ hasMultiSelection WRITE setHasMultiSelection NOTIFY hasMultiSelectionChanged) + Q_PROPERTY(bool insightEnabled MEMBER m_insightEnabled NOTIFY insightEnabledChanged) + Q_PROPERTY(QStringList insightCategories MEMBER m_insightCategories NOTIFY insightCategoriesChanged) + public: PropertyEditorContextObject(QObject *parent = nullptr); @@ -87,6 +90,8 @@ public: Q_INVOKABLE bool isBlocked(const QString &propName) const; + Q_INVOKABLE void verifyInsightImport(); + QString activeDragSuffix() const; void setActiveDragSuffix(const QString &suffix); @@ -111,6 +116,9 @@ public: void setHasMultiSelection(bool); + void setInsightEnabled(bool value); + void setInsightCategories(const QStringList &categories); + signals: void specificsUrlChanged(); void specificQmlDataChanged(); @@ -129,6 +137,9 @@ signals: void activeDragSuffixChanged(); void hasMultiSelectionChanged(); + void insightEnabledChanged(); + void insightCategoriesChanged(); + public slots: void setSpecificsUrl(const QUrl &newSpecificsUrl); @@ -180,6 +191,9 @@ private: QString m_activeDragSuffix; bool m_hasMultiSelection = false; + + bool m_insightEnabled = false; + QStringList m_insightCategories; }; class EasingCurveEditor : public QObject diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp index 3606f626a54..09af5cb0a0f 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp @@ -171,6 +171,17 @@ QVariant properDefaultLayoutAttachedProperties(const QmlObjectNode &qmlObjectNod return QVariant(); } + +QVariant properDefaultInsightAttachedProperties(const QmlObjectNode &qmlObjectNode, + const PropertyName &propertyName) +{ + const QVariant value = qmlObjectNode.modelValue("InsightCategory." + propertyName); + + if (value.isValid()) + return value; + + return QString(); +} } // namespace void PropertyEditorQmlBackend::setupLayoutAttachedProperties(const QmlObjectNode &qmlObjectNode, PropertyEditorView *propertyEditor) @@ -188,6 +199,16 @@ void PropertyEditorQmlBackend::setupLayoutAttachedProperties(const QmlObjectNode } } +void PropertyEditorQmlBackend::setupInsightAttachedProperties(const QmlObjectNode &qmlObjectNode, + PropertyEditorView *propertyEditor) +{ + const PropertyName propertyName = "category"; + createPropertyEditorValue(qmlObjectNode, + "InsightCategory." + propertyName, + properDefaultInsightAttachedProperties(qmlObjectNode, propertyName), + propertyEditor); +} + void PropertyEditorQmlBackend::setupAuxiliaryProperties(const QmlObjectNode &qmlObjectNode, PropertyEditorView *propertyEditor) { @@ -262,9 +283,9 @@ void PropertyEditorQmlBackend::setupAuxiliaryProperties(const QmlObjectNode &qml } void PropertyEditorQmlBackend::createPropertyEditorValue(const QmlObjectNode &qmlObjectNode, - const PropertyName &name, - const QVariant &value, - PropertyEditorView *propertyEditor) + const PropertyName &name, + const QVariant &value, + PropertyEditorView *propertyEditor) { PropertyName propertyName(name); propertyName.replace('.', '_'); @@ -397,6 +418,7 @@ void PropertyEditorQmlBackend::setup(const QmlObjectNode &qmlObjectNode, const Q propertyEditor); } setupLayoutAttachedProperties(qmlObjectNode, propertyEditor); + setupInsightAttachedProperties(qmlObjectNode, propertyEditor); setupAuxiliaryProperties(qmlObjectNode, propertyEditor); // model node @@ -888,6 +910,14 @@ void PropertyEditorQmlBackend::setValueforLayoutAttachedProperties(const QmlObje } } +void PropertyEditorQmlBackend::setValueforInsightAttachedProperties(const QmlObjectNode &qmlObjectNode, + const PropertyName &name) +{ + PropertyName propertyName = name; + propertyName.replace("InsightCategory.", ""); + setValue(qmlObjectNode, name, properDefaultInsightAttachedProperties(qmlObjectNode, propertyName)); +} + void PropertyEditorQmlBackend::setValueforAuxiliaryProperties(const QmlObjectNode &qmlObjectNode, AuxiliaryDataKeyView key) { diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h index dd176bb9e84..1334ff55f2f 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h @@ -57,10 +57,16 @@ public: void emitSelectionToBeChanged(); void emitSelectionChanged(); - void setValueforLayoutAttachedProperties(const QmlObjectNode &qmlObjectNode, const PropertyName &name); + void setValueforLayoutAttachedProperties(const QmlObjectNode &qmlObjectNode, + const PropertyName &name); + void setValueforInsightAttachedProperties(const QmlObjectNode &qmlObjectNode, + const PropertyName &name); void setValueforAuxiliaryProperties(const QmlObjectNode &qmlObjectNode, AuxiliaryDataKeyView key); - void setupLayoutAttachedProperties(const QmlObjectNode &qmlObjectNode, PropertyEditorView *propertyEditor); + void setupLayoutAttachedProperties(const QmlObjectNode &qmlObjectNode, + PropertyEditorView *propertyEditor); + void setupInsightAttachedProperties(const QmlObjectNode &qmlObjectNode, + PropertyEditorView *propertyEditor); void setupAuxiliaryProperties(const QmlObjectNode &qmlObjectNode, PropertyEditorView *propertyEditor); static NodeMetaInfo findCommonAncestor(const ModelNode &node); diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp index 5788f3b3a52..1d93f26f01a 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp @@ -7,9 +7,10 @@ #include "propertyeditortransaction.h" #include "propertyeditorvalue.h" +#include +#include #include #include -#include #include #include @@ -48,6 +49,11 @@ static bool propertyIsAttachedLayoutProperty(const PropertyName &propertyName) return propertyName.contains("Layout."); } +static bool propertyIsAttachedInsightProperty(const PropertyName &propertyName) +{ + return propertyName.contains("InsightCategory."); +} + PropertyEditorView::PropertyEditorView(AsynchronousImageCache &imageCache, ExternalDependenciesInterface &externalDependencies) : AbstractView(externalDependencies) @@ -164,7 +170,8 @@ void PropertyEditorView::changeValue(const QString &name) if (auto property = metaInfo.property(propertyName)) { castedValue = property.castedValue(value->value()); - } else if (propertyIsAttachedLayoutProperty(propertyName)) { + } else if (propertyIsAttachedLayoutProperty(propertyName) + || propertyIsAttachedInsightProperty(propertyName)) { castedValue = value->value(); } else { qWarning() << "PropertyEditor:" << propertyName << "cannot be casted (metainfo)"; @@ -500,6 +507,13 @@ void PropertyEditorView::setupQmlBackend() m_qmlBackEndForCurrentType = currentQmlBackend; + if (rootModelNode().hasAuxiliaryData(insightEnabledProperty)) + m_qmlBackEndForCurrentType->contextObject()->setInsightEnabled( + rootModelNode().auxiliaryData(insightEnabledProperty)->toBool()); + + if (rootModelNode().hasAuxiliaryData(insightCategoriesProperty)) + m_qmlBackEndForCurrentType->contextObject()->setInsightCategories( + rootModelNode().auxiliaryData(insightCategoriesProperty)->toStringList()); } void PropertyEditorView::commitVariantValueToModel(const PropertyName &propertyName, const QVariant &value) @@ -641,6 +655,11 @@ void PropertyEditorView::propertiesRemoved(const QList& proper } } + if (propertyIsAttachedInsightProperty(property.name())) { + m_qmlBackEndForCurrentType->setValueforInsightAttachedProperties(m_selectedNode, + property.name()); + } + if ("width" == property.name() || "height" == property.name()) { const QmlItemNode qmlItemNode = m_selectedNode; if (qmlItemNode.isInLayout()) @@ -662,7 +681,12 @@ void PropertyEditorView::variantPropertiesChanged(const QList& ModelNode node(property.parentModelNode()); if (propertyIsAttachedLayoutProperty(property.name())) - m_qmlBackEndForCurrentType->setValueforLayoutAttachedProperties(m_selectedNode, property.name()); + m_qmlBackEndForCurrentType->setValueforLayoutAttachedProperties(m_selectedNode, + property.name()); + + if (propertyIsAttachedInsightProperty(property.name())) + m_qmlBackEndForCurrentType->setValueforInsightAttachedProperties(m_selectedNode, + property.name()); if (node == m_selectedNode || QmlObjectNode(m_selectedNode).propertyChangeForCurrentState() == node) { if ( QmlObjectNode(m_selectedNode).modelNode().property(property.name()).isBindingProperty()) @@ -701,9 +725,8 @@ void PropertyEditorView::bindingPropertiesChanged(const QList& void PropertyEditorView::auxiliaryDataChanged(const ModelNode &node, [[maybe_unused]] AuxiliaryDataKeyView key, - const QVariant &) + const QVariant &data) { - if (noValidSelection()) return; @@ -711,6 +734,12 @@ void PropertyEditorView::auxiliaryDataChanged(const ModelNode &node, return; m_qmlBackEndForCurrentType->setValueforAuxiliaryProperties(m_selectedNode, key); + + if (key == insightEnabledProperty) + m_qmlBackEndForCurrentType->contextObject()->setInsightEnabled(data.toBool()); + + if (key == insightCategoriesProperty) + m_qmlBackEndForCurrentType->contextObject()->setInsightCategories(data.toStringList()); } void PropertyEditorView::instanceInformationsChanged(const QMultiHash &informationChangedHash) @@ -747,6 +776,12 @@ void PropertyEditorView::select() m_qmlBackEndForCurrentType->emitSelectionToBeChanged(); delayedResetView(); + + auto nodes = selectedModelNodes(); + + for (const auto &n : nodes) { + n.metaInfo().isFileComponent(); + } } void PropertyEditorView::setSelelectedModelNode() diff --git a/src/plugins/qmldesigner/designercore/include/auxiliarydataproperties.h b/src/plugins/qmldesigner/designercore/include/auxiliarydataproperties.h index 41889045f2e..665ec0bcb42 100644 --- a/src/plugins/qmldesigner/designercore/include/auxiliarydataproperties.h +++ b/src/plugins/qmldesigner/designercore/include/auxiliarydataproperties.h @@ -81,6 +81,12 @@ inline constexpr AuxiliaryDataKeyDefaultValue areaFillColorProperty{AuxiliaryDat inline constexpr AuxiliaryDataKeyDefaultValue fillColorProperty{AuxiliaryDataType::Document, "fillColor", QColor{0, 0, 0, 0}}; +inline constexpr AuxiliaryDataKeyDefaultValue insightEnabledProperty{AuxiliaryDataType::Temporary, + "insightEnabled", + false}; +inline constexpr AuxiliaryDataKeyDefaultValue insightCategoriesProperty{AuxiliaryDataType::Temporary, + "insightCategories", + {}}; inline constexpr AuxiliaryDataKeyView uuidProperty{AuxiliaryDataType::Document, "uuid"}; inline constexpr AuxiliaryDataKeyView active3dSceneProperty{AuxiliaryDataType::Temporary, "active3dScene"}; diff --git a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp index 5ae83880fe4..8070319a2d4 100644 --- a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp +++ b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp @@ -54,7 +54,8 @@ namespace { bool isSupportedAttachedProperties(const QString &propertyName) { - return propertyName.startsWith(QLatin1String("Layout.")); + return propertyName.startsWith(QLatin1String("Layout.")) + || propertyName.startsWith(QLatin1String("InsightCategory.")); } QStringList supportedVersionsList() From 44cfc4a8cda8a39a83ebb4e0a7ea0b86b74ef8b0 Mon Sep 17 00:00:00 2001 From: Aleksei German Date: Thu, 1 Dec 2022 19:05:21 +0100 Subject: [PATCH 086/131] QmlDesigner: Add nullptr checks in ActionEditor Encountered an odd nullptr in QmlJS::Value *value. Change-Id: I9140ee15f01a430477b808aa7b664f5c40889c30 Reviewed-by: Thomas Hartmann Reviewed-by: Qt CI Bot --- .../components/bindingeditor/actioneditor.cpp | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/plugins/qmldesigner/components/bindingeditor/actioneditor.cpp b/src/plugins/qmldesigner/components/bindingeditor/actioneditor.cpp index ba2e439fca5..cfb163fd776 100644 --- a/src/plugins/qmldesigner/components/bindingeditor/actioneditor.cpp +++ b/src/plugins/qmldesigner/components/bindingeditor/actioneditor.cpp @@ -132,8 +132,7 @@ bool isLiteral(QmlJS::AST::Node *ast) || QmlJS::AST::cast(ast) || QmlJS::AST::cast(ast)) return true; - else - return false; + return false; } TypeName skipCpp(TypeName typeName) @@ -229,16 +228,18 @@ void ActionEditor::prepareConnections() QmlJS::AST::ExpressionNode *expression = newDoc->expression(); if (expression && !isLiteral(expression)) { - QmlJS::ValueOwner *interp = context->valueOwner(); - const QmlJS::Value *value = interp->convertToObject(scopeChain.evaluate(expression)); + if (QmlJS::ValueOwner *interp = context->valueOwner()) { + if (const QmlJS::Value *value = interp->convertToObject( + scopeChain.evaluate(expression))) { + if (value->asNullValue() && !methodBlackList.contains(slotName)) + connection.methods.append(QString::fromUtf8(slotName)); - if (value->asNullValue() && !methodBlackList.contains(slotName)) - connection.methods.append(QString::fromUtf8(slotName)); - - if (const QmlJS::FunctionValue *f = value->asFunctionValue()) { - // Only add slots with zero arguments - if (f->namedArgumentCount() == 0 && !methodBlackList.contains(slotName)) - connection.methods.append(QString::fromUtf8(slotName)); + if (const QmlJS::FunctionValue *f = value->asFunctionValue()) { + // Only add slots with zero arguments + if (f->namedArgumentCount() == 0 && !methodBlackList.contains(slotName)) + connection.methods.append(QString::fromUtf8(slotName)); + } + } } } } From 57448021b8475f622c655c0e4dde1d0a3c58329c Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Tue, 29 Nov 2022 16:56:08 +0200 Subject: [PATCH 087/131] QmlDesigner: Fix content library visibility logic Also fix enable logic of context menus in content library. Fixes: QDS-8446 Change-Id: I82f80779f507aa5336ebafac5cffc36365238fc5 Reviewed-by: Mahmoud Badri Reviewed-by: Reviewed-by: Qt CI Bot --- .../ContentLibrary.qml | 34 ++----- .../ContentLibraryMaterialContextMenu.qml | 22 +++-- .../ContentLibraryMaterialsView.qml | 25 ++++- .../ContentLibraryTextureContextMenu.qml | 6 +- .../ContentLibraryTexturesView.qml | 8 +- .../contentlibrarymaterialsmodel.cpp | 83 ++++++++-------- .../contentlibrarymaterialsmodel.h | 23 ++--- .../contentlibrarytexturesmodel.cpp | 96 +++++-------------- .../contentlibrarytexturesmodel.h | 35 +------ .../contentlibrary/contentlibraryview.cpp | 27 +++++- .../contentlibrary/contentlibraryview.h | 4 + .../contentlibrary/contentlibrarywidget.cpp | 38 ++++++++ .../contentlibrary/contentlibrarywidget.h | 14 +++ 13 files changed, 207 insertions(+), 208 deletions(-) diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibrary.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibrary.qml index 53fae29a533..19c1a039c16 100644 --- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibrary.qml +++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibrary.qml @@ -35,7 +35,15 @@ Item { id: searchBox width: root.width - enabled: !materialsModel.hasMaterialRoot && materialsModel.hasQuick3DImport + enabled: { + if (tabBar.currIndex == 0) { // Materials tab + materialsModel.matBundleExists + && rootView.hasMaterialLibrary + && materialsModel.hasRequiredQuick3DImport + } else { // Textures / Environments tabs + texturesModel.texBundleExists + } + } onSearchChanged: (searchText) => { rootView.handleSearchFilterChanged(searchText) @@ -47,35 +55,12 @@ Item { } } - Text { - // TODO: only disable the materials section, textures should be available - text: { - if (materialsModel.hasMaterialRoot) - qsTr("Content Library is disabled inside a material component.") - else if (!materialsModel.hasQuick3DImport) - qsTr("To use Content Library, first add the QtQuick3D module in the Components view.") - else - "" - } - - textFormat: Text.RichText - color: StudioTheme.Values.themeTextColor - font.pixelSize: StudioTheme.Values.mediumFontSize - topPadding: 30 - horizontalAlignment: Text.AlignHCenter - wrapMode: Text.WordWrap - width: root.width - visible: text !== "" - } - UnimportBundleMaterialDialog { id: confirmUnimportDialog } ContentLibraryTabBar { id: tabBar - - visible: materialsModel.hasQuick3DImport // TODO: update icons tabsModel: [{name: qsTr("Materials"), icon: StudioTheme.Constants.gradient}, {name: qsTr("Textures"), icon: StudioTheme.Constants.materialPreviewEnvironment}, @@ -86,7 +71,6 @@ Item { width: root.width height: root.height - y currentIndex: tabBar.currIndex - visible: materialsModel.hasQuick3DImport ContentLibraryMaterialsView { id: materialsView diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialContextMenu.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialContextMenu.qml index a85699e6dc3..4d5d75b6e58 100644 --- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialContextMenu.qml +++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialContextMenu.qml @@ -10,7 +10,13 @@ StudioControls.Menu { id: root property var targetMaterial: null + property bool hasModelSelection: false + property bool importerRunning: false + + readonly property bool targetAvailable: targetMaterial && !importerRunning + signal unimport(var bundleMat); + signal addToProject(var bundleMat) function popupMenu(targetMaterial = null) { @@ -22,31 +28,31 @@ StudioControls.Menu { StudioControls.MenuItem { text: qsTr("Apply to selected (replace)") - enabled: root.targetMaterial && materialsModel.hasModelSelection - onTriggered: materialsModel.applyToSelected(root.targetMaterial, false) + enabled: root.targetAvailable && root.hasModelSelection + onTriggered: root.applyToSelected(root.targetMaterial, false) } StudioControls.MenuItem { text: qsTr("Apply to selected (add)") - enabled: root.targetMaterial && materialsModel.hasModelSelection - onTriggered: materialsModel.applyToSelected(root.targetMaterial, true) + enabled: root.targetAvailable && root.hasModelSelection + onTriggered: root.applyToSelected(root.targetMaterial, true) } StudioControls.MenuSeparator {} StudioControls.MenuItem { - enabled: !materialsModel.importerRunning + enabled: root.targetAvailable text: qsTr("Add an instance to project") onTriggered: { - materialsModel.addToProject(root.targetMaterial) + root.addToProject(root.targetMaterial) } } StudioControls.MenuItem { - enabled: !materialsModel.importerRunning && root.targetMaterial && root.targetMaterial.bundleMaterialImported + enabled: root.targetAvailable && root.targetMaterial.bundleMaterialImported text: qsTr("Remove from project") - onTriggered: root.unimport(root.targetMaterial); + onTriggered: root.unimport(root.targetMaterial) } } diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialsView.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialsView.qml index 6483ebdd6bd..57c507c3ff1 100644 --- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialsView.qml +++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialsView.qml @@ -40,7 +40,11 @@ HelperWidgets.ScrollView { ContentLibraryMaterialContextMenu { id: ctxMenu + hasModelSelection: materialsModel.hasModelSelection + importerRunning: materialsModel.importerRunning + onUnimport: (bundleMat) => root.unimport(bundleMat) + onAddToProject: (bundleMat) => materialsModel.addToProject(bundleMat) } Repeater { @@ -53,7 +57,7 @@ HelperWidgets.ScrollView { caption: bundleCategoryName addTopPadding: false sectionBackgroundColor: "transparent" - visible: bundleCategoryVisible + visible: bundleCategoryVisible && !materialsModel.isEmpty expanded: bundleCategoryExpanded expandOnClick: false onToggleExpand: bundleCategoryExpanded = !bundleCategoryExpanded @@ -84,13 +88,26 @@ HelperWidgets.ScrollView { } Text { - id: noMatchText - text: qsTr("No match found."); + id: infoText + text: { + if (!materialsModel.matBundleExists) + qsTr("Content Library materials are not installed.") + else if (!rootView.hasQuick3DImport) + qsTr("To use Content Library, first add the QtQuick3D module in the Components view.") + else if (!materialsModel.hasRequiredQuick3DImport) + qsTr("To use Content Library, version 6.3 or later of the QtQuick3D module is required.") + else if (!rootView.hasMaterialLibrary) + qsTr("Content Library is disabled inside a non-visual component.") + else if (!searchBox.isEmpty()) + qsTr("No match found.") + else + "" + } color: StudioTheme.Values.themeTextColor font.pixelSize: StudioTheme.Values.baseFontSize topPadding: 10 leftPadding: 10 - visible: materialsModel.isEmpty && !searchBox.isEmpty() && !materialsModel.hasMaterialRoot + visible: materialsModel.isEmpty } } } diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTextureContextMenu.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTextureContextMenu.qml index f9d3d88a789..b1617578ce1 100644 --- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTextureContextMenu.qml +++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTextureContextMenu.qml @@ -12,6 +12,8 @@ StudioControls.Menu { property var targetTexture: null property bool hasSceneEnv: false + property bool canUse3D: targetTexture && rootView.hasQuick3DImport && rootView.hasMaterialLibrary + function popupMenu(targetTexture = null) { this.targetTexture = targetTexture @@ -29,13 +31,13 @@ StudioControls.Menu { StudioControls.MenuItem { text: qsTr("Add texture") - enabled: root.targetTexture + enabled: canUse3D onTriggered: rootView.addTexture(root.targetTexture) } StudioControls.MenuItem { text: qsTr("Add light probe") - enabled: root.hasSceneEnv && root.targetTexture + enabled: root.hasSceneEnv && canUse3D onTriggered: rootView.addLightProbe(root.targetTexture) } } diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexturesView.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexturesView.qml index dd6509b67ec..b80ef7471ae 100644 --- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexturesView.qml +++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexturesView.qml @@ -53,7 +53,7 @@ HelperWidgets.ScrollView { caption: bundleCategoryName addTopPadding: false sectionBackgroundColor: "transparent" - visible: bundleCategoryVisible + visible: bundleCategoryVisible && !root.model.isEmpty expanded: bundleCategoryExpanded expandOnClick: false onToggleExpand: bundleCategoryExpanded = !bundleCategoryExpanded @@ -87,10 +87,12 @@ HelperWidgets.ScrollView { Text { id: infoText text: { - if (!searchBox.isEmpty()) + if (!root.model.texBundleExists) + qsTr("Content Library textures are not installed.") + else if (!searchBox.isEmpty()) qsTr("No match found.") else - qsTr("Texture library is not installed.") + "" } color: StudioTheme.Values.themeTextColor font.pixelSize: StudioTheme.Values.baseFontSize diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterialsmodel.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterialsmodel.cpp index 55acaf15746..2acc4499786 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterialsmodel.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterialsmodel.cpp @@ -6,7 +6,10 @@ #include "contentlibrarybundleimporter.h" #include "contentlibrarymaterial.h" #include "contentlibrarymaterialscategory.h" +#include "contentlibrarywidget.h" #include "qmldesignerconstants.h" + +#include "utils/algorithm.h" #include "utils/qtcassert.h" #include @@ -16,8 +19,9 @@ namespace QmlDesigner { -ContentLibraryMaterialsModel::ContentLibraryMaterialsModel(QObject *parent) +ContentLibraryMaterialsModel::ContentLibraryMaterialsModel(ContentLibraryWidget *parent) : QAbstractListModel(parent) + , m_widget(parent) { loadMaterialBundle(); } @@ -59,6 +63,22 @@ bool ContentLibraryMaterialsModel::isValidIndex(int idx) const return idx > -1 && idx < rowCount(); } +void ContentLibraryMaterialsModel::updateIsEmpty() +{ + const bool anyCatVisible = Utils::anyOf(m_bundleCategories, + [&](ContentLibraryMaterialsCategory *cat) { + return cat->visible(); + }); + + const bool newEmpty = !anyCatVisible || m_bundleCategories.isEmpty() + || !m_widget->hasMaterialLibrary() || !hasRequiredQuick3DImport(); + + if (newEmpty != m_isEmpty) { + m_isEmpty = newEmpty; + emit isEmptyChanged(); + } +} + QHash ContentLibraryMaterialsModel::roleNames() const { static const QHash roles { @@ -72,7 +92,7 @@ QHash ContentLibraryMaterialsModel::roleNames() const void ContentLibraryMaterialsModel::loadMaterialBundle() { - if (m_matBundleLoaded || m_probeMatBundleDir) + if (m_matBundleExists || m_probeMatBundleDir) return; QDir matBundleDir(qEnvironmentVariable("MATERIAL_BUNDLE_PATH")); @@ -108,7 +128,7 @@ void ContentLibraryMaterialsModel::loadMaterialBundle() } } - m_matBundleLoaded = true; + m_matBundleExists = true; QString bundleId = m_matBundleObj.value("id").toString(); @@ -163,43 +183,17 @@ void ContentLibraryMaterialsModel::loadMaterialBundle() emit bundleMaterialUnimported(metaInfo); }); - if (m_bundleCategories.isEmpty() != m_isEmpty) { - m_isEmpty = m_bundleCategories.isEmpty(); - emit isEmptyChanged(); - } + updateIsEmpty(); } -bool ContentLibraryMaterialsModel::hasQuick3DImport() const +bool ContentLibraryMaterialsModel::hasRequiredQuick3DImport() const { - return m_hasQuick3DImport; -} - -void ContentLibraryMaterialsModel::setHasQuick3DImport(bool b) -{ - if (b == m_hasQuick3DImport) - return; - - m_hasQuick3DImport = b; - emit hasQuick3DImportChanged(); -} - -bool ContentLibraryMaterialsModel::hasMaterialRoot() const -{ - return m_hasMaterialRoot; -} - -void ContentLibraryMaterialsModel::setHasMaterialRoot(bool b) -{ - if (m_hasMaterialRoot == b) - return; - - m_hasMaterialRoot = b; - emit hasMaterialRootChanged(); + return m_widget->hasQuick3DImport() && m_quick3dMajorVersion == 6 && m_quick3dMinorVersion >= 3; } bool ContentLibraryMaterialsModel::matBundleExists() const { - return m_matBundleLoaded && m_quick3dMajorVersion == 6 && m_quick3dMinorVersion >= 3; + return m_matBundleExists; } Internal::ContentLibraryBundleImporter *ContentLibraryMaterialsModel::bundleImporter() const @@ -216,18 +210,11 @@ void ContentLibraryMaterialsModel::setSearchText(const QString &searchText) m_searchText = lowerSearchText; - bool anyCatVisible = false; bool catVisibilityChanged = false; - - for (ContentLibraryMaterialsCategory *cat : std::as_const(m_bundleCategories)) { + for (ContentLibraryMaterialsCategory *cat : std::as_const(m_bundleCategories)) catVisibilityChanged |= cat->filter(m_searchText); - anyCatVisible |= cat->visible(); - } - if (anyCatVisible == m_isEmpty) { - m_isEmpty = !anyCatVisible; - emit isEmptyChanged(); - } + updateIsEmpty(); if (catVisibilityChanged) resetModel(); @@ -245,13 +232,19 @@ void ContentLibraryMaterialsModel::updateImportedState(const QStringList &import void ContentLibraryMaterialsModel::setQuick3DImportVersion(int major, int minor) { - bool bundleExisted = matBundleExists(); + bool oldRequiredImport = hasRequiredQuick3DImport(); m_quick3dMajorVersion = major; m_quick3dMinorVersion = minor; - if (bundleExisted != matBundleExists()) - emit matBundleExistsChanged(); + bool newRequiredImport = hasRequiredQuick3DImport(); + + if (oldRequiredImport == newRequiredImport) + return; + + emit hasRequiredQuick3DImportChanged(); + + updateIsEmpty(); } void ContentLibraryMaterialsModel::resetModel() diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterialsmodel.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterialsmodel.h index 9fe76cd25df..91ec476c51f 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterialsmodel.h +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterialsmodel.h @@ -12,6 +12,7 @@ namespace QmlDesigner { class ContentLibraryMaterial; class ContentLibraryMaterialsCategory; +class ContentLibraryWidget; namespace Internal { class ContentLibraryBundleImporter; @@ -23,13 +24,12 @@ class ContentLibraryMaterialsModel : public QAbstractListModel Q_PROPERTY(bool matBundleExists READ matBundleExists NOTIFY matBundleExistsChanged) Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged) - Q_PROPERTY(bool hasQuick3DImport READ hasQuick3DImport WRITE setHasQuick3DImport NOTIFY hasQuick3DImportChanged) - Q_PROPERTY(bool hasModelSelection READ hasModelSelection WRITE setHasModelSelection NOTIFY hasModelSelectionChanged) - Q_PROPERTY(bool hasMaterialRoot READ hasMaterialRoot WRITE setHasMaterialRoot NOTIFY hasMaterialRootChanged) + Q_PROPERTY(bool hasRequiredQuick3DImport READ hasRequiredQuick3DImport NOTIFY hasRequiredQuick3DImportChanged) + Q_PROPERTY(bool hasModelSelection READ hasModelSelection NOTIFY hasModelSelectionChanged) Q_PROPERTY(bool importerRunning MEMBER m_importerRunning NOTIFY importerRunningChanged) public: - ContentLibraryMaterialsModel(QObject *parent = nullptr); + ContentLibraryMaterialsModel(ContentLibraryWidget *parent = nullptr); int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; @@ -41,11 +41,7 @@ public: void setQuick3DImportVersion(int major, int minor); - bool hasQuick3DImport() const; - void setHasQuick3DImport(bool b); - - bool hasMaterialRoot() const; - void setHasMaterialRoot(bool b); + bool hasRequiredQuick3DImport() const; bool matBundleExists() const; @@ -53,6 +49,7 @@ public: void setHasModelSelection(bool b); void resetModel(); + void updateIsEmpty(); Internal::ContentLibraryBundleImporter *bundleImporter() const; @@ -62,9 +59,8 @@ public: signals: void isEmptyChanged(); - void hasQuick3DImportChanged(); + void hasRequiredQuick3DImportChanged(); void hasModelSelectionChanged(); - void hasMaterialRootChanged(); void materialVisibleChanged(); void applyToSelectedTriggered(QmlDesigner::ContentLibraryMaterial *mat, bool add = false); void bundleMaterialImported(const QmlDesigner::NodeMetaInfo &metaInfo); @@ -77,15 +73,14 @@ private: void loadMaterialBundle(); bool isValidIndex(int idx) const; + ContentLibraryWidget *m_widget = nullptr; QString m_searchText; QList m_bundleCategories; QJsonObject m_matBundleObj; Internal::ContentLibraryBundleImporter *m_importer = nullptr; bool m_isEmpty = true; - bool m_hasMaterialRoot = false; - bool m_hasQuick3DImport = false; - bool m_matBundleLoaded = false; + bool m_matBundleExists = false; bool m_hasModelSelection = false; bool m_probeMatBundleDir = false; bool m_importerRunning = false; diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturesmodel.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturesmodel.cpp index 8a0ea67c3e5..b12473da616 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturesmodel.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturesmodel.cpp @@ -4,6 +4,8 @@ #include "contentlibrarytexturesmodel.h" #include "contentlibrarytexturescategory.h" + +#include "utils/algorithm.h" #include "utils/qtcassert.h" #include @@ -55,6 +57,21 @@ bool ContentLibraryTexturesModel::isValidIndex(int idx) const return idx > -1 && idx < rowCount(); } +void ContentLibraryTexturesModel::updateIsEmpty() +{ + const bool anyCatVisible = Utils::anyOf(m_bundleCategories, + [&](ContentLibraryTexturesCategory *cat) { + return cat->visible(); + }); + + const bool newEmpty = !anyCatVisible || m_bundleCategories.isEmpty(); + + if (newEmpty != m_isEmpty) { + m_isEmpty = newEmpty; + emit isEmptyChanged(); + } +} + QHash ContentLibraryTexturesModel::roleNames() const { static const QHash roles { @@ -74,7 +91,7 @@ void ContentLibraryTexturesModel::loadTextureBundle(const QString &bundlePath) return; } - if (m_texBundleLoaded) + if (!m_bundleCategories.isEmpty()) return; const QFileInfoList dirs = bundleDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); @@ -86,38 +103,12 @@ void ContentLibraryTexturesModel::loadTextureBundle(const QString &bundlePath) m_bundleCategories.append(category); } - if (m_bundleCategories.isEmpty() != m_isEmpty) { - m_isEmpty = m_bundleCategories.isEmpty(); - emit isEmptyChanged(); - } + updateIsEmpty(); } -bool ContentLibraryTexturesModel::hasQuick3DImport() const +bool ContentLibraryTexturesModel::texBundleExists() const { - return m_hasQuick3DImport; -} - -void ContentLibraryTexturesModel::setHasQuick3DImport(bool b) -{ - if (b == m_hasQuick3DImport) - return; - - m_hasQuick3DImport = b; - emit hasQuick3DImportChanged(); -} - -bool ContentLibraryTexturesModel::hasMaterialRoot() const -{ - return m_hasMaterialRoot; -} - -void ContentLibraryTexturesModel::setHasMaterialRoot(bool b) -{ - if (m_hasMaterialRoot == b) - return; - - m_hasMaterialRoot = b; - emit hasMaterialRootChanged(); + return !m_bundleCategories.isEmpty(); } bool ContentLibraryTexturesModel::hasSceneEnv() const @@ -134,11 +125,6 @@ void ContentLibraryTexturesModel::setHasSceneEnv(bool b) emit hasSceneEnvChanged(); } -bool ContentLibraryTexturesModel::matBundleExists() const -{ - return m_texBundleLoaded && m_quick3dMajorVersion == 6 && m_quick3dMinorVersion >= 3; -} - void ContentLibraryTexturesModel::setSearchText(const QString &searchText) { QString lowerSearchText = searchText.toLower(); @@ -148,57 +134,21 @@ void ContentLibraryTexturesModel::setSearchText(const QString &searchText) m_searchText = lowerSearchText; - bool anyCatVisible = false; bool catVisibilityChanged = false; - for (ContentLibraryTexturesCategory *cat : std::as_const(m_bundleCategories)) { + for (ContentLibraryTexturesCategory *cat : std::as_const(m_bundleCategories)) catVisibilityChanged |= cat->filter(m_searchText); - anyCatVisible |= cat->visible(); - } - if (anyCatVisible == m_isEmpty) { - m_isEmpty = !anyCatVisible; - emit isEmptyChanged(); - } + updateIsEmpty(); if (catVisibilityChanged) resetModel(); } -void ContentLibraryTexturesModel::setQuick3DImportVersion(int major, int minor) -{ - bool bundleExisted = matBundleExists(); - - m_quick3dMajorVersion = major; - m_quick3dMinorVersion = minor; - - if (bundleExisted != matBundleExists()) - emit matBundleExistsChanged(); -} - void ContentLibraryTexturesModel::resetModel() { beginResetModel(); endResetModel(); } -void ContentLibraryTexturesModel::addToProject(const QString &mat) -{ - // TODO: import asset -} - -bool ContentLibraryTexturesModel::hasModelSelection() const -{ - return m_hasModelSelection; -} - -void ContentLibraryTexturesModel::setHasModelSelection(bool b) -{ - if (b == m_hasModelSelection) - return; - - m_hasModelSelection = b; - emit hasModelSelectionChanged(); -} - } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturesmodel.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturesmodel.h index ca5e7a6963b..cbbd2a364c3 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturesmodel.h +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturesmodel.h @@ -13,12 +13,9 @@ class ContentLibraryTexturesModel : public QAbstractListModel { Q_OBJECT - Q_PROPERTY(bool matBundleExists READ matBundleExists NOTIFY matBundleExistsChanged) + Q_PROPERTY(bool texBundleExists READ texBundleExists CONSTANT) Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged) - Q_PROPERTY(bool hasQuick3DImport READ hasQuick3DImport WRITE setHasQuick3DImport NOTIFY hasQuick3DImportChanged) - Q_PROPERTY(bool hasModelSelection READ hasModelSelection WRITE setHasModelSelection NOTIFY hasModelSelectionChanged) - Q_PROPERTY(bool hasMaterialRoot READ hasMaterialRoot WRITE setHasMaterialRoot NOTIFY hasMaterialRootChanged) - Q_PROPERTY(bool hasSceneEnv READ hasSceneEnv WRITE setHasSceneEnv NOTIFY hasSceneEnvChanged) + Q_PROPERTY(bool hasSceneEnv READ hasSceneEnv NOTIFY hasSceneEnvChanged) public: ContentLibraryTexturesModel(QObject *parent = nullptr); @@ -30,18 +27,7 @@ public: void setSearchText(const QString &searchText); - void setQuick3DImportVersion(int major, int minor); - - bool hasQuick3DImport() const; - void setHasQuick3DImport(bool b); - - bool hasMaterialRoot() const; - void setHasMaterialRoot(bool b); - - bool matBundleExists() const; - - bool hasModelSelection() const; - void setHasModelSelection(bool b); + bool texBundleExists() const; bool hasSceneEnv() const; void setHasSceneEnv(bool b); @@ -49,32 +35,21 @@ public: void resetModel(); void loadTextureBundle(const QString &bundlePath); - Q_INVOKABLE void addToProject(const QString &mat); - signals: void isEmptyChanged(); - void hasQuick3DImportChanged(); - void hasModelSelectionChanged(); - void hasMaterialRootChanged(); void materialVisibleChanged(); - void matBundleExistsChanged(); void hasSceneEnvChanged(); private: bool isValidIndex(int idx) const; + void updateIsEmpty(); QString m_searchText; QList m_bundleCategories; bool m_isEmpty = true; - bool m_hasMaterialRoot = false; - bool m_hasQuick3DImport = false; - bool m_texBundleLoaded = false; - bool m_hasModelSelection = false; bool m_hasSceneEnv = false; - - int m_quick3dMajorVersion = -1; - int m_quick3dMinorVersion = -1; + bool m_hasModelSelection = false; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp index 347512a837c..5e9f572c7ad 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp @@ -11,6 +11,7 @@ #include "contentlibrarytexturesmodel.h" #include "modelnodeoperations.h" #include "nodelistproperty.h" +#include "qmldesignerconstants.h" #include "qmlobjectnode.h" #include "variantproperty.h" @@ -161,15 +162,18 @@ void ContentLibraryView::modelAttached(Model *model) m_hasQuick3DImport = model->hasImport("QtQuick3D"); - m_widget->materialsModel()->setHasMaterialRoot(rootModelNode().metaInfo().isQtQuick3DMaterial()); - m_widget->materialsModel()->setHasQuick3DImport(m_hasQuick3DImport); - updateBundleMaterialsQuick3DVersion(); updateBundleMaterialsImportedState(); + + const bool hasLibrary = materialLibraryNode().isValid(); + m_widget->setHasMaterialLibrary(hasLibrary); + m_widget->setHasQuick3DImport(m_hasQuick3DImport); } void ContentLibraryView::modelAboutToBeDetached(Model *model) { + m_widget->setHasMaterialLibrary(false); + m_widget->setHasQuick3DImport(false); AbstractView::modelAboutToBeDetached(model); } @@ -187,7 +191,7 @@ void ContentLibraryView::importsChanged(const QList &addedImports, const return; m_hasQuick3DImport = hasQuick3DImport; - m_widget->materialsModel()->setHasQuick3DImport(m_hasQuick3DImport); + m_widget->setHasQuick3DImport(m_hasQuick3DImport); } void ContentLibraryView::active3DSceneChanged(qint32 sceneId) @@ -244,6 +248,21 @@ void ContentLibraryView::customNotification(const AbstractView *view, const QStr } } +void ContentLibraryView::nodeReparented(const ModelNode &node, + [[maybe_unused]] const NodeAbstractProperty &newPropertyParent, + [[maybe_unused]] const NodeAbstractProperty &oldPropertyParent, + [[maybe_unused]] PropertyChangeFlags propertyChange) +{ + if (node.id() == Constants::MATERIAL_LIB_ID) + m_widget->setHasMaterialLibrary(true); +} + +void ContentLibraryView::nodeAboutToBeRemoved(const ModelNode &removedNode) +{ + if (removedNode.id() == Constants::MATERIAL_LIB_ID) + m_widget->setHasMaterialLibrary(false); +} + void ContentLibraryView::applyBundleMaterialToDropTarget(const ModelNode &bundleMat, const NodeMetaInfo &metaInfo) { diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h index 5b4914a50c9..c54ec924d00 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h @@ -36,6 +36,10 @@ public: const QList &lastSelectedNodeList) override; void customNotification(const AbstractView *view, const QString &identifier, const QList &nodeList, const QList &data) override; + void nodeReparented(const ModelNode &node, const NodeAbstractProperty &newPropertyParent, + const NodeAbstractProperty &oldPropertyParent, + AbstractView::PropertyChangeFlags propertyChange) override; + void nodeAboutToBeRemoved(const ModelNode &removedNode) override; private: void updateBundleMaterialsImportedState(); diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp index 75eff3af98d..4f6c268b786 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp @@ -157,6 +157,44 @@ void ContentLibraryWidget::clearSearchFilter() QMetaObject::invokeMethod(m_quickWidget->rootObject(), "clearSearchFilter"); } +bool ContentLibraryWidget::hasQuick3DImport() const +{ + return m_hasQuick3DImport; +} + +void ContentLibraryWidget::setHasQuick3DImport(bool b) +{ + if (b == m_hasQuick3DImport) + return; + + const bool oldRequired = m_materialsModel->hasRequiredQuick3DImport(); + m_hasQuick3DImport = b; + const bool newRequired = m_materialsModel->hasRequiredQuick3DImport(); + + if (oldRequired != newRequired) + emit m_materialsModel->hasRequiredQuick3DImportChanged(); + + emit hasQuick3DImportChanged(); + + m_materialsModel->updateIsEmpty(); +} + +bool ContentLibraryWidget::hasMaterialLibrary() const +{ + return m_hasMaterialLibrary; +} + +void ContentLibraryWidget::setHasMaterialLibrary(bool b) +{ + if (m_hasMaterialLibrary == b) + return; + + m_hasMaterialLibrary = b; + emit hasMaterialLibraryChanged(); + + m_materialsModel->updateIsEmpty(); +} + void ContentLibraryWidget::reloadQmlSource() { const QString materialBrowserQmlPath = qmlSourcesPath() + "/ContentLibrary.qml"; diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h index 4d770686322..b6b24c4dec1 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h @@ -23,6 +23,9 @@ class ContentLibraryWidget : public QFrame { Q_OBJECT + Q_PROPERTY(bool hasQuick3DImport READ hasQuick3DImport NOTIFY hasQuick3DImportChanged) + Q_PROPERTY(bool hasMaterialLibrary READ hasMaterialLibrary NOTIFY hasMaterialLibraryChanged) + public: ContentLibraryWidget(); @@ -31,6 +34,12 @@ public: static QString qmlSourcesPath(); void clearSearchFilter(); + bool hasQuick3DImport() const; + void setHasQuick3DImport(bool b); + + bool hasMaterialLibrary() const; + void setHasMaterialLibrary(bool b); + Q_INVOKABLE void handleSearchFilterChanged(const QString &filterText); void setMaterialsModel(QPointer newMaterialsModel); @@ -53,6 +62,8 @@ signals: void bundleTextureDragStarted(QmlDesigner::ContentLibraryTexture *bundleTex); void addTextureRequested(const QString texPath, QmlDesigner::ContentLibraryWidget::AddTextureMode mode); void updateSceneEnvStateRequested(); + void hasQuick3DImportChanged(); + void hasMaterialLibraryChanged(); protected: bool eventFilter(QObject *obj, QEvent *event) override; @@ -74,6 +85,9 @@ private: ContentLibraryMaterial *m_materialToDrag = nullptr; ContentLibraryTexture *m_textureToDrag = nullptr; QPoint m_dragStartPoint; + + bool m_hasMaterialLibrary = false; + bool m_hasQuick3DImport = false; }; } // namespace QmlDesigner From 36c96a0972a4c923e3c6f8045836c336466fee02 Mon Sep 17 00:00:00 2001 From: Aleksei German Date: Thu, 1 Dec 2022 22:08:49 +0100 Subject: [PATCH 088/131] QmlDesigner: Use Model ConnectionMetaInfo method Change-Id: I1ac34ed36df45e8fe1f8baa60311b1c337c4ac71 Reviewed-by: Reviewed-by: Qt CI Bot Reviewed-by: Thomas Hartmann --- src/plugins/qmldesigner/components/bindingeditor/signallist.cpp | 2 +- .../components/componentcore/designeractionmanager.cpp | 2 +- .../qmldesigner/components/connectioneditor/connectionmodel.cpp | 2 +- src/plugins/qmldesigner/designercore/model/qmlconnections.cpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/plugins/qmldesigner/components/bindingeditor/signallist.cpp b/src/plugins/qmldesigner/components/bindingeditor/signallist.cpp index 298ba7f91ea..b30f0763d35 100644 --- a/src/plugins/qmldesigner/components/bindingeditor/signallist.cpp +++ b/src/plugins/qmldesigner/components/bindingeditor/signallist.cpp @@ -223,7 +223,7 @@ void SignalList::addConnection(const QModelIndex &modelIndex) const ModelNode rootModelNode = view->rootModelNode(); if (rootModelNode.isValid() && rootModelNode.metaInfo().isValid()) { - NodeMetaInfo nodeMetaInfo = view->model()->metaInfo("QtQuick.Connections"); + NodeMetaInfo nodeMetaInfo = view->model()->qtQuickConnectionsMetaInfo(); if (nodeMetaInfo.isValid()) { view->executeInTransaction("ConnectionModel::addConnection", [=, &rootModelNode](){ ModelNode newNode = view->createModelNode("QtQuick.Connections", diff --git a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp index 9a416a85eaa..20027265685 100644 --- a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp @@ -570,7 +570,7 @@ QList getSlotsLists(const ModelNode &node) //creates connection without signalHandlerProperty ModelNode createNewConnection(ModelNode targetNode) { - NodeMetaInfo connectionsMetaInfo = targetNode.view()->model()->metaInfo("QtQuick.Connections"); + NodeMetaInfo connectionsMetaInfo = targetNode.view()->model()->qtQuickConnectionsMetaInfo(); ModelNode newConnectionNode = targetNode.view()->createModelNode( "QtQuick.Connections", connectionsMetaInfo.majorVersion(), connectionsMetaInfo.minorVersion()); if (QmlItemNode::isValidQmlItemNode(targetNode)) diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp index 8a4f7bbdb27..5711d696145 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp @@ -297,7 +297,7 @@ void ConnectionModel::addConnection() if (rootModelNode.isValid() && rootModelNode.metaInfo().isValid()) { - NodeMetaInfo nodeMetaInfo = connectionView()->model()->metaInfo("QtQuick.Connections"); + NodeMetaInfo nodeMetaInfo = connectionView()->model()->qtQuickConnectionsMetaInfo(); if (nodeMetaInfo.isValid()) { connectionView()->executeInTransaction("ConnectionModel::addConnection", [=, &rootModelNode](){ diff --git a/src/plugins/qmldesigner/designercore/model/qmlconnections.cpp b/src/plugins/qmldesigner/designercore/model/qmlconnections.cpp index 7ba31a8fcc0..aae9528eb64 100644 --- a/src/plugins/qmldesigner/designercore/model/qmlconnections.cpp +++ b/src/plugins/qmldesigner/designercore/model/qmlconnections.cpp @@ -99,7 +99,7 @@ QList QmlConnections::signalProperties() const ModelNode QmlConnections::createQmlConnections(AbstractView *view) { - NodeMetaInfo nodeMetaInfo = view->model()->metaInfo("QtQuick.Connections"); + NodeMetaInfo nodeMetaInfo = view->model()->qtQuickConnectionsMetaInfo(); return view->createModelNode("QtQuick.Connections", nodeMetaInfo.majorVersion(), nodeMetaInfo.minorVersion()); From e774aa238be546812754d7ab5497f8603fe51f62 Mon Sep 17 00:00:00 2001 From: Samuel Ghinet Date: Thu, 24 Nov 2022 19:23:03 +0200 Subject: [PATCH 089/131] Add "Add Texture" and "Add Light Probe" actions to Assets Library When the user right-clicks on an image asset, "Add Light probe" now appears in the context menu. When the user right-clicks one or more image assets, "Add texture" or "Add textures" becomes available. Task-number: QDS-8344 Change-Id: Ia8d8379be2a0a285b33e4a230e08527c18756b47 Reviewed-by: Qt CI Bot Reviewed-by: Thomas Hartmann --- .../AssetsContextMenu.qml | 26 +++- src/plugins/qmldesigner/CMakeLists.txt | 1 + .../qmldesigner/components/addtexture.h | 10 ++ .../assetslibrary/assetslibrarymodel.cpp | 12 +- .../assetslibrary/assetslibrarymodel.h | 1 + .../assetslibrary/assetslibraryview.cpp | 12 +- .../assetslibrary/assetslibrarywidget.cpp | 11 ++ .../assetslibrary/assetslibrarywidget.h | 4 + .../contentlibrary/contentlibraryview.cpp | 50 +------- .../contentlibrary/contentlibrarywidget.h | 6 +- .../materialbrowser/materialbrowserview.cpp | 117 +++++++++++++++++- .../materialbrowser/materialbrowserview.h | 8 ++ 12 files changed, 200 insertions(+), 58 deletions(-) create mode 100644 src/plugins/qmldesigner/components/addtexture.h diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsContextMenu.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsContextMenu.qml index 87c0966366e..6dedaf79012 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsContextMenu.qml +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsContextMenu.qml @@ -50,8 +50,13 @@ StudioControls.Menu { function openContextMenuForFile(fileIndex, dirModelIndex, selectedAssetPathsList, onFolderCreated) { - var numSelected = selectedAssetPathsList.filter(p => p).length - deleteFileItem.text = numSelected > 1 ? qsTr("Delete Files") : qsTr("Delete File") + if (selectedAssetPathsList.length > 1) { + deleteFileItem.text = qsTr("Delete Files") + addTexturesItem.text = qsTr("Add Textures") + } else { + deleteFileItem.text = qsTr("Delete File") + addTexturesItem.text = qsTr("Add Texture") + } root.__onFolderCreated = onFolderCreated root.__selectedAssetPathsList = selectedAssetPathsList @@ -83,6 +88,23 @@ StudioControls.Menu { height: visible ? StudioTheme.Values.border : 0 } + StudioControls.MenuItem { + id: addTexturesItem + text: qsTr("Add Texture") + visible: root.__fileIndex && assetsModel.allFilePathsAreImages(root.__selectedAssetPathsList) + height: addTexturesItem.visible ? addTexturesItem.implicitHeight : 0 + onTriggered: rootView.addTextures(root.__selectedAssetPathsList) + } + + StudioControls.MenuItem { + id: addLightProbes + text: qsTr("Add Light Probe") + visible: root.__fileIndex && root.__selectedAssetPathsList.length === 1 + && assetsModel.allFilePathsAreImages(root.__selectedAssetPathsList) + height: addLightProbes.visible ? addLightProbes.implicitHeight : 0 + onTriggered: rootView.addLightProbe(root.__selectedAssetPathsList[0]) + } + StudioControls.MenuItem { id: deleteFileItem text: qsTr("Delete File") diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 7abed7212d8..01fd8802637 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -588,6 +588,7 @@ extend_qtc_plugin(QmlDesigner PUBLIC_INCLUDES components DEFINES QMLDESIGNERCOMPONENTS_LIBRARY SOURCES + addtexture.h qmldesignercomponents_global.h ) diff --git a/src/plugins/qmldesigner/components/addtexture.h b/src/plugins/qmldesigner/components/addtexture.h new file mode 100644 index 00000000000..8547b4bf843 --- /dev/null +++ b/src/plugins/qmldesigner/components/addtexture.h @@ -0,0 +1,10 @@ +// Copyright (C) 2022 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 + +namespace QmlDesigner { + +enum class AddTextureMode { Image, Texture, LightProbe }; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp index 83683096e95..864660a1476 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp @@ -10,10 +10,11 @@ #include "assetslibrarymodel.h" -#include #include +#include #include +#include #include namespace QmlDesigner { @@ -179,6 +180,15 @@ bool AssetsLibraryModel::deleteFolderRecursively(const QModelIndex &folderIndex) return ok; } +bool AssetsLibraryModel::allFilePathsAreImages(const QStringList &filePaths) const +{ + return Utils::allOf(filePaths, [](const QString &path) { + const QString suffix = "*." + path.split('.').last().toLower(); + + return AssetsLibraryModel::supportedImageSuffixes().contains(suffix); + }); +} + bool AssetsLibraryModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { QString path = m_sourceFsModel->filePath(sourceParent); diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h index 82cf2b3ca25..f73d7fb7e3f 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h @@ -44,6 +44,7 @@ public: Q_INVOKABLE bool renameFolder(const QString &folderPath, const QString &newName); Q_INVOKABLE bool addNewFolder(const QString &folderPath); Q_INVOKABLE bool deleteFolderRecursively(const QModelIndex &folderIndex); + Q_INVOKABLE bool allFilePathsAreImages(const QStringList &filePaths) const; int columnCount(const QModelIndex &parent = QModelIndex()) const override { diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryview.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryview.cpp index 56a536c82e8..94e8c6412eb 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryview.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryview.cpp @@ -2,8 +2,9 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 #include "assetslibraryview.h" + #include "assetslibrarywidget.h" -#include "metainfo.h" +#include "qmldesignerplugin.h" #include #include #include @@ -22,9 +23,7 @@ #include #include #include -#include #include -#include namespace QmlDesigner { @@ -60,6 +59,13 @@ WidgetInfo AssetsLibraryView::widgetInfo() if (m_widget.isNull()) { m_widget = new AssetsLibraryWidget{imageCacheData()->asynchronousFontImageCache, imageCacheData()->synchronousFontImageCache}; + + connect(m_widget, &AssetsLibraryWidget::addTexturesRequested, this, + [&] (const QStringList &filePaths, AddTextureMode mode) { + // to MaterialBrowserView + emitCustomNotification("add_textures", {}, {"AssetsLibraryView::widgetInfo", + filePaths, QVariant::fromValue(mode), false}); + }); } return createWidgetInfo(m_widget.data(), "Assets", WidgetInfo::LeftPane, 0, tr("Assets")); diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp index 2858febe9f9..05ebb729869 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp @@ -148,6 +148,17 @@ bool AssetsLibraryWidget::qtVersionIsAtLeast6_4() const return (QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)); } + +void AssetsLibraryWidget::addTextures(const QStringList &filePaths) +{ + emit addTexturesRequested(filePaths, AddTextureMode::Texture); +} + +void AssetsLibraryWidget::addLightProbe(const QString &filePath) +{ + emit addTexturesRequested({filePath}, AddTextureMode::LightProbe); +} + void AssetsLibraryWidget::invalidateThumbnail(const QString &id) { m_assetsIconProvider->invalidateThumbnail(id); diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h index 1c19c65a3e2..4d8737518ac 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h @@ -4,6 +4,7 @@ #pragma once #include +#include "addtexture.h" #include "assetslibrarymodel.h" #include @@ -72,6 +73,8 @@ public: Q_INVOKABLE void openEffectMaker(const QString &filePath); Q_INVOKABLE bool qtVersionIsAtLeast6_4() const; Q_INVOKABLE void invalidateThumbnail(const QString &id); + Q_INVOKABLE void addTextures(const QStringList &filePaths); + Q_INVOKABLE void addLightProbe(const QString &filePaths); signals: void itemActivated(const QString &itemName); @@ -79,6 +82,7 @@ signals: const QList &complexFilePaths, const QString &targetDirPath); void directoryCreated(const QString &path); + void addTexturesRequested(const QStringList &filePaths, QmlDesigner::AddTextureMode mode); protected: bool eventFilter(QObject *obj, QEvent *event) override; diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp index 5e9f572c7ad..58c677039fc 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp @@ -55,53 +55,9 @@ WidgetInfo ContentLibraryView::widgetInfo() m_draggedBundleTexture = tex; }); connect(m_widget, &ContentLibraryWidget::addTextureRequested, this, - [&] (const QString texPath, ContentLibraryWidget::AddTextureMode mode) { - executeInTransaction("ContentLibraryView::widgetInfo", [&] { - // copy image to project - AddFilesResult result = ModelNodeOperations::addImageToProject({texPath}, "images", false); - - if (result.status() == AddFilesResult::Failed) { - Core::AsynchronousMessageBox::warning(tr("Failed to Add Texture"), - tr("Could not add %1 to project.").arg(texPath)); - return; - } - - if (mode == ContentLibraryWidget::AddTextureMode::Image) - return; - - // create a texture from the image - ModelNode matLib = materialLibraryNode(); - if (!matLib.isValid()) - return; - - NodeMetaInfo metaInfo = model()->metaInfo("QtQuick3D.Texture"); - - QString sourceVal = QLatin1String("images/%1").arg(texPath.split('/').last()); - ModelNode texNode = getTextureDefaultInstance(sourceVal); - if (!texNode.isValid()) { - texNode = createModelNode("QtQuick3D.Texture", metaInfo.majorVersion(), - metaInfo.minorVersion()); - texNode.validId(); - VariantProperty sourceProp = texNode.variantProperty("source"); - sourceProp.setValue(sourceVal); - matLib.defaultNodeListProperty().reparentHere(texNode); - } - - // assign the texture as scene environment's light probe - if (mode == ContentLibraryWidget::AddTextureMode::LightProbe && m_sceneId != -1) { - QmlObjectNode sceneEnv = resolveSceneEnv(); - if (sceneEnv.isValid()) { - sceneEnv.setBindingProperty("lightProbe", texNode.id()); - sceneEnv.setVariantProperty("backgroundMode", - QVariant::fromValue(Enumeration("SceneEnvironment", - "SkyBox"))); - } - } - QTimer::singleShot(0, this, [this, texNode]() { - if (model() && texNode.isValid()) - emitCustomNotification("selected_texture_changed", {texNode}); - }); - }); + [&] (const QString &texPath, AddTextureMode mode) { + emitCustomNotification("add_texture", {}, {"ContentLibraryView::widgetInfo", + texPath, QVariant::fromValue(mode), true}); }); connect(m_widget, &ContentLibraryWidget::updateSceneEnvStateRequested, diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h index b6b24c4dec1..f6866e03092 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h @@ -3,6 +3,8 @@ #pragma once +#include "addtexture.h" + #include #include @@ -55,12 +57,10 @@ public: Q_INVOKABLE void addLightProbe(QmlDesigner::ContentLibraryTexture *tex); Q_INVOKABLE void updateSceneEnvState(); - enum class AddTextureMode { Image, Texture, LightProbe }; - signals: void bundleMaterialDragStarted(QmlDesigner::ContentLibraryMaterial *bundleMat); void bundleTextureDragStarted(QmlDesigner::ContentLibraryTexture *bundleTex); - void addTextureRequested(const QString texPath, QmlDesigner::ContentLibraryWidget::AddTextureMode mode); + void addTextureRequested(const QString texPath, QmlDesigner::AddTextureMode mode); void updateSceneEnvStateRequested(); void hasQuick3DImportChanged(); void hasMaterialLibraryChanged(); diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp index cba8f32199f..39b9efe13ae 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp @@ -4,9 +4,10 @@ #include "materialbrowserview.h" #include "bindingproperty.h" -#include "materialbrowserwidget.h" #include "materialbrowsermodel.h" #include "materialbrowsertexturesmodel.h" +#include "materialbrowserwidget.h" +#include "modelnodeoperations.h" #include "nodeabstractproperty.h" #include "nodemetainfo.h" #include "qmlobjectnode.h" @@ -14,11 +15,11 @@ #include #include -#include #include #include #include +#include #include #include @@ -449,9 +450,121 @@ void MaterialBrowserView::customNotification(const AbstractView *view, applyTextureToModel3D(nodeList.at(0), nodeList.at(1)); } else if (identifier == "apply_texture_to_material") { applyTextureToMaterial({nodeList.at(0)}, nodeList.at(1)); + } else if (identifier == "add_textures") { + if (data.size() != 4) { + qWarning() << "Wrong number of arguments passed to add_textures: " << data.size(); + return; + } + + QByteArray identifier = data.at(0).toByteArray(); + QStringList filePaths = data.at(1).toStringList(); + AddTextureMode mode = data.at(2).value(); + bool addToProject = data.at(3).toBool(); + + executeInTransaction(identifier, [&] { + addTextures(filePaths, mode, addToProject); + }); + } else if (identifier == "add_texture") { + if (data.size() != 4) { + qWarning() << "Wrong number of arguments passed to add_texture: " << data.size(); + return; + } + + QByteArray identifier = data.at(0).toByteArray(); + QString filePath = data.at(1).toString(); + AddTextureMode mode = data.at(2).value(); + bool addToProject = data.at(3).toBool(); + + executeInTransaction(identifier, [&] { + addOneTexture(filePath, mode, addToProject); + }); } } +void MaterialBrowserView::addOneTexture(const QString &texPath, AddTextureMode mode, bool addToProject) +{ + if (addToProject) { + // copy image to project + AddFilesResult result = ModelNodeOperations::addImageToProject({texPath}, "images", false); + + if (result.status() == AddFilesResult::Failed) { + Core::AsynchronousMessageBox::warning(tr("Failed to Add Texture"), + tr("Could not add %1 to project.").arg(texPath)); + return; + } + } + + if (mode == AddTextureMode::Image) + return; + + // create a texture from the image + ModelNode matLib = materialLibraryNode(); + if (!matLib.isValid()) + return; + + NodeMetaInfo metaInfo = model()->metaInfo("QtQuick3D.Texture"); + + QString sourceVal = QLatin1String("images/%1").arg(texPath.split('/').last()); + ModelNode texNode = getTextureDefaultInstance(sourceVal); + if (!texNode.isValid()) { + texNode = createModelNode("QtQuick3D.Texture", metaInfo.majorVersion(), + metaInfo.minorVersion()); + texNode.validId(); + VariantProperty sourceProp = texNode.variantProperty("source"); + sourceProp.setValue(sourceVal); + matLib.defaultNodeListProperty().reparentHere(texNode); + } + + // assign the texture as scene environment's light probe + if (mode == AddTextureMode::LightProbe && m_sceneId != -1) { + QmlObjectNode sceneEnv = resolveSceneEnv(); + if (sceneEnv.isValid()) { + sceneEnv.setBindingProperty("lightProbe", texNode.id()); + sceneEnv.setVariantProperty("backgroundMode", + QVariant::fromValue(Enumeration("SceneEnvironment", + "SkyBox"))); + } + } + QTimer::singleShot(0, this, [this, texNode]() { + if (model() && texNode.isValid()) + emitCustomNotification("selected_texture_changed", {texNode}); + }); +} + +void MaterialBrowserView::active3DSceneChanged(qint32 sceneId) +{ + m_sceneId = sceneId; +} + +ModelNode MaterialBrowserView::resolveSceneEnv() +{ + ModelNode activeSceneEnv; + + if (m_sceneId != -1) { + ModelNode activeScene = active3DSceneNode(); + if (activeScene.isValid()) { + QmlObjectNode view3D; + if (activeScene.metaInfo().isQtQuick3DView3D()) { + view3D = activeScene; + } else { + ModelNode sceneParent = activeScene.parentProperty().parentModelNode(); + if (sceneParent.metaInfo().isQtQuick3DView3D()) + view3D = sceneParent; + } + if (view3D.isValid()) + activeSceneEnv = modelNodeForId(view3D.expression("environment")); + } + } + + return activeSceneEnv; +} + +void MaterialBrowserView::addTextures(const QStringList &filePaths, AddTextureMode mode, bool addToProject) +{ + for (const QString &texPath : filePaths) + addOneTexture(texPath, mode, addToProject); +} + void MaterialBrowserView::instancesCompleted(const QVector &completedNodeList) { for (const ModelNode &node : completedNodeList) { diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h index e8f1160f2f3..5b16c0e626e 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h @@ -4,6 +4,7 @@ #pragma once #include "abstractview.h" +#include "addtexture.h" #include #include @@ -51,6 +52,8 @@ public: void applyTextureToModel3D(const QmlObjectNode &model3D, const ModelNode &texture); void applyTextureToMaterial(const QList &materials, const ModelNode &texture); + void active3DSceneChanged(qint32 sceneId) override; + Q_INVOKABLE void updatePropsModel(const QString &matId); Q_INVOKABLE void applyTextureToProperty(const QString &matId, const QString &propName); Q_INVOKABLE void closeChooseMatPropsView(); @@ -64,6 +67,10 @@ private: bool isTexture(const ModelNode &node) const; void loadPropertyGroups(); void requestPreviews(); + ModelNode resolveSceneEnv(); + + void addOneTexture(const QString &filePath, AddTextureMode mode, bool addToProject); + void addTextures(const QStringList &texturePaths, AddTextureMode mode, bool addToProject); AsynchronousImageCache &m_imageCache; QPointer m_widget; @@ -79,6 +86,7 @@ private: QPointer m_chooseMatPropsView; QHash> m_textureModels; QString m_appliedTextureId; + int m_sceneId = -1; }; } // namespace QmlDesigner From 06b40d0aaad931b580c70d6288facd3ba9f73cb0 Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Fri, 2 Dec 2022 12:40:13 +0100 Subject: [PATCH 090/131] QmlDesigner: Bump version in application template Change-Id: I5ecbe2da5e403f883344f9e0987ab11585140603 Reviewed-by: Thomas Hartmann --- .../studio_templates/projects/common/app.qmlproject.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/common/app.qmlproject.tpl b/share/qtcreator/qmldesigner/studio_templates/projects/common/app.qmlproject.tpl index 2e933c76136..722b0d72ee6 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/common/app.qmlproject.tpl +++ b/share/qtcreator/qmldesigner/studio_templates/projects/common/app.qmlproject.tpl @@ -100,7 +100,7 @@ Project { /* Required for deployment */ targetDirectory: "/opt/%{ProjectName}" - qdsVersion: "3.8" + qdsVersion: "3.9" quickVersion: "%{QtQuickVersion}" From 16ec1ab67fb5728e56aa0a57ca7fb478a0de7b89 Mon Sep 17 00:00:00 2001 From: Knud Dollereder Date: Wed, 30 Nov 2022 16:36:21 +0100 Subject: [PATCH 091/131] QmlDesigner: Fix zoom speed for windows Fixes: QDS-7835 Change-Id: I4ba76f9b7bf7be2472a073515891d50dbe7213c8 Reviewed-by: Qt CI Bot Reviewed-by: Thomas Hartmann --- .../components/componentcore/navigation2d.cpp | 18 +++++++++++++++--- .../formeditor/formeditorgraphicsview.cpp | 9 ++++++--- .../qmldesigner/utils/designersettings.cpp | 1 + .../qmldesigner/utils/designersettings.h | 1 + 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/plugins/qmldesigner/components/componentcore/navigation2d.cpp b/src/plugins/qmldesigner/components/componentcore/navigation2d.cpp index 6250b6a25fc..487f84229c4 100644 --- a/src/plugins/qmldesigner/components/componentcore/navigation2d.cpp +++ b/src/plugins/qmldesigner/components/componentcore/navigation2d.cpp @@ -1,6 +1,9 @@ // Copyright (C) 2020 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 #include "navigation2d.h" +#include +#include +#include #include #include @@ -73,15 +76,24 @@ bool Navigation2dFilter::wheelEvent(QWheelEvent *event) bool zoomChangedConnected = QObject::isSignalConnected(zoomChangedSignal); if (zoomChangedConnected) { + const double globalMouseSpeed = + QmlDesignerPlugin::settings().value(DesignerSettingsKey::EDITOR_ZOOM_FACTOR).toDouble(); + + double speed = globalMouseSpeed/20.; + if (Utils::HostOsInfo::isMacHost()) + speed = 1.0/200.; + if (QPointF delta = event->pixelDelta(); !delta.isNull()) { double dist = std::abs(delta.x()) > std::abs(delta.y()) ? -delta.x() : delta.y(); - emit zoomChanged(dist/200.0, event->position()); + emit zoomChanged(dist * speed, event->position()); event->accept(); return true; } else if (QPointF delta = event->angleDelta(); !delta.isNull()) { + constexpr double degreePerStep = 15.; + constexpr double stepCount = 8.; double dist = std::abs(delta.x()) > std::abs(delta.y()) ? -delta.x() : delta.y(); - dist = dist / (8*15); - emit zoomChanged(dist/200.0, event->position()); + dist = dist / (stepCount*degreePerStep); + emit zoomChanged(dist * speed, event->position()); event->accept(); return true; } diff --git a/src/plugins/qmldesigner/components/formeditor/formeditorgraphicsview.cpp b/src/plugins/qmldesigner/components/formeditor/formeditorgraphicsview.cpp index f17e280702e..6bfe64dd22a 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditorgraphicsview.cpp +++ b/src/plugins/qmldesigner/components/formeditor/formeditorgraphicsview.cpp @@ -5,6 +5,7 @@ #include "formeditoritem.h" #include "formeditorwidget.h" #include "navigation2d.h" +#include #include #include @@ -44,9 +45,11 @@ FormEditorGraphicsView::FormEditorGraphicsView(QWidget *parent) connect(filter, &Navigation2dFilter::zoomIn, this, &FormEditorGraphicsView::zoomIn); connect(filter, &Navigation2dFilter::zoomOut, this, &FormEditorGraphicsView::zoomOut); - connect(filter, &Navigation2dFilter::panChanged, [this](const QPointF &direction) { - Navigation2dFilter::scroll(direction, horizontalScrollBar(), verticalScrollBar()); - }); + if (Utils::HostOsInfo::isMacHost()) { + connect(filter, &Navigation2dFilter::panChanged, [this](const QPointF &direction) { + Navigation2dFilter::scroll(direction, horizontalScrollBar(), verticalScrollBar()); + }); + } auto zoomChanged = &Navigation2dFilter::zoomChanged; connect(filter, zoomChanged, [this](double s, const QPointF &/*pos*/) { diff --git a/src/plugins/qmldesigner/utils/designersettings.cpp b/src/plugins/qmldesigner/utils/designersettings.cpp index 338e54072d2..797a904b177 100644 --- a/src/plugins/qmldesigner/utils/designersettings.cpp +++ b/src/plugins/qmldesigner/utils/designersettings.cpp @@ -86,6 +86,7 @@ void DesignerSettings::fromSettings(QSettings *settings) restoreValue(settings, DesignerSettingsKey::SMOOTH_RENDERING, false); restoreValue(settings, DesignerSettingsKey::SHOW_DEBUG_SETTINGS, false); restoreValue(settings, DesignerSettingsKey::OLD_STATES_EDITOR, false); + restoreValue(settings, DesignerSettingsKey::EDITOR_ZOOM_FACTOR, 1.0); settings->endGroup(); settings->endGroup(); diff --git a/src/plugins/qmldesigner/utils/designersettings.h b/src/plugins/qmldesigner/utils/designersettings.h index a24031b579d..49942e79509 100644 --- a/src/plugins/qmldesigner/utils/designersettings.h +++ b/src/plugins/qmldesigner/utils/designersettings.h @@ -54,6 +54,7 @@ const char DISABLE_ITEM_LIBRARY_UPDATE_TIMER[] = "DisableItemLibraryUpdateTimer" const char ASK_BEFORE_DELETING_ASSET[] = "AskBeforeDeletingAsset"; const char SMOOTH_RENDERING[] = "SmoothRendering"; const char OLD_STATES_EDITOR[] = "ForceOldStatesEditor"; +const char EDITOR_ZOOM_FACTOR[] = "EditorZoomFactor"; } class QMLDESIGNERUTILS_EXPORT DesignerSettings From 728605b2ae903f786dd26346635d368fddef0c69 Mon Sep 17 00:00:00 2001 From: Samuel Ghinet Date: Fri, 25 Nov 2022 20:02:30 +0200 Subject: [PATCH 092/131] Refactor: Extract the code for adding a texture Task-number: QDS-8344 Change-Id: I433e2beb3ffd6346ea37e093705943b0701efd3d Reviewed-by: Mahmoud Badri Reviewed-by: Qt CI Bot Reviewed-by: Miikka Heikkinen --- src/plugins/qmldesigner/CMakeLists.txt | 2 +- .../qmldesigner/components/addtexture.h | 10 -- .../assetslibrary/assetslibraryview.cpp | 14 ++- .../assetslibrary/assetslibraryview.h | 6 +- .../assetslibrary/assetslibrarywidget.h | 3 +- .../contentlibrary/contentlibraryview.cpp | 45 ++----- .../contentlibrary/contentlibraryview.h | 3 +- .../contentlibrary/contentlibrarywidget.h | 2 +- .../qmldesigner/components/createtexture.cpp | 116 ++++++++++++++++++ .../qmldesigner/components/createtexture.h | 43 +++++++ .../materialbrowser/materialbrowserview.h | 2 +- 11 files changed, 194 insertions(+), 52 deletions(-) delete mode 100644 src/plugins/qmldesigner/components/addtexture.h create mode 100644 src/plugins/qmldesigner/components/createtexture.cpp create mode 100644 src/plugins/qmldesigner/components/createtexture.h diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 01fd8802637..e3e1122abc1 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -588,7 +588,7 @@ extend_qtc_plugin(QmlDesigner PUBLIC_INCLUDES components DEFINES QMLDESIGNERCOMPONENTS_LIBRARY SOURCES - addtexture.h + createtexture.cpp createtexture.h qmldesignercomponents_global.h ) diff --git a/src/plugins/qmldesigner/components/addtexture.h b/src/plugins/qmldesigner/components/addtexture.h deleted file mode 100644 index 8547b4bf843..00000000000 --- a/src/plugins/qmldesigner/components/addtexture.h +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (C) 2022 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 - -namespace QmlDesigner { - -enum class AddTextureMode { Image, Texture, LightProbe }; - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryview.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryview.cpp index 94e8c6412eb..ef6aa9f610e 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryview.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryview.cpp @@ -4,7 +4,9 @@ #include "assetslibraryview.h" #include "assetslibrarywidget.h" +#include "createtexture.h" #include "qmldesignerplugin.h" + #include #include #include @@ -44,6 +46,7 @@ public: AssetsLibraryView::AssetsLibraryView(ExternalDependenciesInterface &externalDependencies) : AbstractView{externalDependencies} + , m_createTextures{this, false} {} AssetsLibraryView::~AssetsLibraryView() @@ -62,9 +65,9 @@ WidgetInfo AssetsLibraryView::widgetInfo() connect(m_widget, &AssetsLibraryWidget::addTexturesRequested, this, [&] (const QStringList &filePaths, AddTextureMode mode) { - // to MaterialBrowserView - emitCustomNotification("add_textures", {}, {"AssetsLibraryView::widgetInfo", - filePaths, QVariant::fromValue(mode), false}); + executeInTransaction("AssetsLibraryView::widgetInfo", [&]() { + m_createTextures.execute(filePaths, mode, m_sceneId); + }); }); } @@ -111,4 +114,9 @@ AssetsLibraryView::ImageCacheData *AssetsLibraryView::imageCacheData() return m_imageCacheData.get(); } +void AssetsLibraryView::active3DSceneChanged(qint32 sceneId) +{ + m_sceneId = sceneId; +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryview.h b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryview.h index 4194195ec56..95a4c868d52 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryview.h +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryview.h @@ -3,7 +3,8 @@ #pragma once -#include +#include "abstractview.h" +#include "createtexture.h" #include @@ -30,6 +31,7 @@ public: void modelAboutToBeDetached(Model *model) override; void setResourcePath(const QString &resourcePath); + void active3DSceneChanged(qint32 sceneId) override; private: class ImageCacheData; @@ -39,6 +41,8 @@ private: std::unique_ptr m_imageCacheData; QPointer m_widget; QString m_lastResourcePath; + CreateTextures m_createTextures; + qint32 m_sceneId = -1; }; } diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h index 4d8737518ac..90a7038b5db 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h @@ -4,8 +4,9 @@ #pragma once #include -#include "addtexture.h" + #include "assetslibrarymodel.h" +#include "createtexture.h" #include #include diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp index 58c677039fc..856d4077e12 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp @@ -4,12 +4,11 @@ #include "contentlibraryview.h" #include "contentlibrarybundleimporter.h" -#include "contentlibrarywidget.h" #include "contentlibrarymaterial.h" #include "contentlibrarymaterialsmodel.h" #include "contentlibrarytexture.h" #include "contentlibrarytexturesmodel.h" -#include "modelnodeoperations.h" +#include "contentlibrarywidget.h" #include "nodelistproperty.h" #include "qmldesignerconstants.h" #include "qmlobjectnode.h" @@ -31,6 +30,7 @@ namespace QmlDesigner { ContentLibraryView::ContentLibraryView(ExternalDependenciesInterface &externalDependencies) : AbstractView(externalDependencies) + , m_createTexture(this, true) {} ContentLibraryView::~ContentLibraryView() @@ -54,14 +54,20 @@ WidgetInfo ContentLibraryView::widgetInfo() [&] (QmlDesigner::ContentLibraryTexture *tex) { m_draggedBundleTexture = tex; }); + connect(m_widget, &ContentLibraryWidget::addTextureRequested, this, [&] (const QString &texPath, AddTextureMode mode) { - emitCustomNotification("add_texture", {}, {"ContentLibraryView::widgetInfo", - texPath, QVariant::fromValue(mode), true}); + executeInTransaction("ContentLibraryView::widgetInfo", [&]() { + m_createTexture.execute(texPath, mode, m_sceneId); + }); }); - connect(m_widget, &ContentLibraryWidget::updateSceneEnvStateRequested, - this, &ContentLibraryView::resolveSceneEnv); + connect(m_widget, &ContentLibraryWidget::updateSceneEnvStateRequested, this, [&]() { + ModelNode activeSceneEnv = m_createTexture.resolveSceneEnv(m_sceneId); + const bool sceneEnvExists = activeSceneEnv.isValid(); + m_widget->texturesModel()->setHasSceneEnv(sceneEnvExists); + m_widget->environmentsModel()->setHasSceneEnv(sceneEnvExists); + }); ContentLibraryMaterialsModel *materialsModel = m_widget->materialsModel().data(); @@ -318,33 +324,6 @@ ModelNode ContentLibraryView::createMaterial(const NodeMetaInfo &metaInfo) return newMatNode; } -ModelNode ContentLibraryView::resolveSceneEnv() -{ - ModelNode activeSceneEnv; - - if (m_sceneId != -1) { - ModelNode activeScene = active3DSceneNode(); - if (activeScene.isValid()) { - QmlObjectNode view3D; - if (activeScene.metaInfo().isQtQuick3DView3D()) { - view3D = activeScene; - } else { - ModelNode sceneParent = activeScene.parentProperty().parentModelNode(); - if (sceneParent.metaInfo().isQtQuick3DView3D()) - view3D = sceneParent; - } - if (view3D.isValid()) - activeSceneEnv = modelNodeForId(view3D.expression("environment")); - } - } - - const bool sceneEnvExists = activeSceneEnv.isValid(); - m_widget->texturesModel()->setHasSceneEnv(sceneEnvExists); - m_widget->environmentsModel()->setHasSceneEnv(sceneEnvExists); - - return activeSceneEnv; -} - void ContentLibraryView::updateBundleMaterialsImportedState() { using namespace Utils; diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h index c54ec924d00..46055f49a9d 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h @@ -3,6 +3,7 @@ #pragma once +#include "createtexture.h" #include "abstractview.h" #include "nodemetainfo.h" @@ -47,7 +48,6 @@ private: void applyBundleMaterialToDropTarget(const ModelNode &bundleMat, const NodeMetaInfo &metaInfo = {}); ModelNode getBundleMaterialDefaultInstance(const TypeName &type); ModelNode createMaterial(const NodeMetaInfo &metaInfo); - ModelNode resolveSceneEnv(); QPointer m_widget; QList m_bundleMaterialTargets; @@ -57,6 +57,7 @@ private: bool m_bundleMaterialAddToSelected = false; bool m_hasQuick3DImport = false; qint32 m_sceneId = -1; + CreateTexture m_createTexture; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h index f6866e03092..ea41027a67c 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h @@ -3,7 +3,7 @@ #pragma once -#include "addtexture.h" +#include "createtexture.h" #include #include diff --git a/src/plugins/qmldesigner/components/createtexture.cpp b/src/plugins/qmldesigner/components/createtexture.cpp new file mode 100644 index 00000000000..5a1c1e36f33 --- /dev/null +++ b/src/plugins/qmldesigner/components/createtexture.cpp @@ -0,0 +1,116 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "createtexture.h" + +#include "abstractview.h" +#include "modelnodeoperations.h" +#include "nodelistproperty.h" +#include "nodemetainfo.h" +#include "qmlobjectnode.h" +#include "variantproperty.h" + +#include + +#include + +namespace QmlDesigner { + +CreateTexture::CreateTexture(AbstractView *view, bool importFile) + : m_view{view} + , m_importFile{importFile} +{} + +void CreateTexture::execute(const QString &filePath, AddTextureMode mode, int sceneId) +{ + if (m_importFile && !addFileToProject(filePath)) + return; + + ModelNode texture = createTextureFromImage(filePath, mode); + if (!texture.isValid()) + return; + + if (mode == AddTextureMode::LightProbe && sceneId != -1) + assignTextureAsLightProbe(texture, sceneId); + + QTimer::singleShot(0, m_view, [this, texture]() { + if (m_view->model()) + m_view->emitCustomNotification("selected_texture_changed", {texture}); + }); +} + +bool CreateTexture::addFileToProject(const QString &filePath) +{ + AddFilesResult result = ModelNodeOperations::addImageToProject({filePath}, "images", false); + + if (result.status() == AddFilesResult::Failed) { + Core::AsynchronousMessageBox::warning(QObject::tr("Failed to Add Texture"), + QObject::tr("Could not add %1 to project.").arg(filePath)); + return false; + } + + return true; +} + +ModelNode CreateTexture::createTextureFromImage(const QString &assetPath, AddTextureMode mode) +{ + if (mode != AddTextureMode::Texture && mode != AddTextureMode::LightProbe) + return {}; + + ModelNode matLib = m_view->materialLibraryNode(); + if (!matLib.isValid()) + return {}; + + NodeMetaInfo metaInfo = m_view->model()->qtQuick3DTextureMetaInfo(); + + QString sourceVal = QLatin1String("images/%1").arg(assetPath.split('/').last()); + ModelNode newTexNode = m_view->getTextureDefaultInstance(sourceVal); + if (!newTexNode.isValid()) { + newTexNode = m_view->createModelNode("QtQuick3D.Texture", + metaInfo.majorVersion(), + metaInfo.minorVersion()); + newTexNode.validId(); + VariantProperty sourceProp = newTexNode.variantProperty("source"); + sourceProp.setValue(sourceVal); + matLib.defaultNodeListProperty().reparentHere(newTexNode); + } + + return newTexNode; +} + +void CreateTexture::assignTextureAsLightProbe(const ModelNode &texture, int sceneId) +{ + ModelNode sceneEnvNode = resolveSceneEnv(sceneId); + QmlObjectNode sceneEnv = sceneEnvNode; + if (sceneEnv.isValid()) { + sceneEnv.setBindingProperty("lightProbe", texture.id()); + sceneEnv.setVariantProperty("backgroundMode", + QVariant::fromValue(Enumeration("SceneEnvironment", + "SkyBox"))); + } +} + +ModelNode CreateTexture::resolveSceneEnv(int sceneId) +{ + ModelNode activeSceneEnv; + + if (sceneId != -1) { + ModelNode activeScene = m_view->active3DSceneNode(); + if (activeScene.isValid()) { + QmlObjectNode view3D; + if (activeScene.metaInfo().isQtQuick3DView3D()) { + view3D = activeScene; + } else { + ModelNode sceneParent = activeScene.parentProperty().parentModelNode(); + if (sceneParent.metaInfo().isQtQuick3DView3D()) + view3D = sceneParent; + } + if (view3D.isValid()) + activeSceneEnv = m_view->modelNodeForId(view3D.expression("environment")); + } + } + + return activeSceneEnv; +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/createtexture.h b/src/plugins/qmldesigner/components/createtexture.h new file mode 100644 index 00000000000..ba29397c205 --- /dev/null +++ b/src/plugins/qmldesigner/components/createtexture.h @@ -0,0 +1,43 @@ +// Copyright (C) 2022 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 + +namespace QmlDesigner { + +class AbstractView; + +enum class AddTextureMode { Image, Texture, LightProbe }; + +class CreateTexture +{ +public: + CreateTexture(AbstractView *view, bool importFiles = false); + void execute(const QString &filePath, AddTextureMode mode, int sceneId); + ModelNode resolveSceneEnv(int sceneId); + +private: + bool addFileToProject(const QString &filePath); + ModelNode createTextureFromImage(const QString &assetPath, AddTextureMode mode); + + void assignTextureAsLightProbe(const ModelNode &texture, int sceneId); + +private: + AbstractView *m_view = nullptr; + bool m_importFile = false; +}; + +class CreateTextures : public CreateTexture +{ +public: + using CreateTexture::CreateTexture; + void execute(const QStringList &filePaths, AddTextureMode mode, int sceneId) + { + for (const QString &path : filePaths) + CreateTexture::execute(path, mode, sceneId); + } +}; + +} diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h index 5b16c0e626e..2f8113a1645 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h @@ -4,7 +4,7 @@ #pragma once #include "abstractview.h" -#include "addtexture.h" +#include "createtexture.h" #include #include From a86cb745261d96ce71da306b0b373db726876961 Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Fri, 2 Dec 2022 15:52:55 +0100 Subject: [PATCH 093/131] QmlDesigner: QtObject is in QML now MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If a type cannot be created we created a QtObject instead. Since we create Item for items this crash was rare. Task-number: QDS-8460 Change-Id: Ifd80f6670bec5328ddd122e0f2f2c133c487b3bd Reviewed-by: Qt CI Bot Reviewed-by: Henning Gründl --- .../qml2puppet/instances/servernodeinstance.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/tools/qml2puppet/qml2puppet/instances/servernodeinstance.cpp b/src/tools/qml2puppet/qml2puppet/instances/servernodeinstance.cpp index 8ea0eaf8061..e7229f06a6e 100644 --- a/src/tools/qml2puppet/qml2puppet/instances/servernodeinstance.cpp +++ b/src/tools/qml2puppet/qml2puppet/instances/servernodeinstance.cpp @@ -285,7 +285,15 @@ ServerNodeInstance ServerNodeInstance::create(NodeInstanceServer *nodeInstanceSe if (object == nullptr) object = new QQuickItem; } else { - object = Internal::ObjectNodeInstance::createPrimitive("QtQml/QtObject", 2, 0, nodeInstanceServer->context()); + object = Internal::ObjectNodeInstance::createPrimitive("QML/QtObject", + 1, + 0, + nodeInstanceServer->context()); + if (object == nullptr) //Fallback for Qt 5 + object = Internal::ObjectNodeInstance::createPrimitive("QtQml/QtObject", + 2, + 0, + nodeInstanceServer->context()); } } From e1fce66f38332eb0995b2a4847429e8a71808c9c Mon Sep 17 00:00:00 2001 From: Henning Gruendl Date: Mon, 5 Dec 2022 10:46:30 +0100 Subject: [PATCH 094/131] QmlDesigner: Remove unnecessary function Change-Id: I3a8c29ba7e24b10df957b4cb8fdb9f6e3acd6855 Reviewed-by: Thomas Hartmann --- share/qtcreator/qmldesigner/newstateseditor/Main.qml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/share/qtcreator/qmldesigner/newstateseditor/Main.qml b/share/qtcreator/qmldesigner/newstateseditor/Main.qml index 74b50d5f94a..0176cc79f60 100644 --- a/share/qtcreator/qmldesigner/newstateseditor/Main.qml +++ b/share/qtcreator/qmldesigner/newstateseditor/Main.qml @@ -315,15 +315,8 @@ Rectangle { standardButtons: Dialog.Apply | Dialog.Cancel x: editButton.x - Math.max(0, editButton.x + editDialog.width - root.width) y: toolBar.height - closePolicy: Popup.NoAutoClose - width: Math.min(300, root.width) - - function apply() { - let renamed = statesEditorModel.renameActiveStateGroup(editTextField.text) - if (renamed) - editDialog.close() - } + closePolicy: Popup.NoAutoClose onApplied: editDialog.accept() From 2987f5a96c880cc708192fc76c77e4101665df12 Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Mon, 5 Dec 2022 13:51:50 +0200 Subject: [PATCH 095/131] QmlDesigner: Show content library material + button only on hover Fixes: QDS-8486 Change-Id: I2207e6558d2124bd9f602e6571ac3958da3c9cdf Reviewed-by: Miikka Heikkinen --- .../contentLibraryQmlSource/ContentLibraryMaterial.qml | 2 ++ .../imports/HelperWidgets/IconButton.qml | 1 + 2 files changed, 3 insertions(+) diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterial.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterial.qml index 4d6377359ce..23a8c5891e7 100644 --- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterial.qml +++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterial.qml @@ -19,6 +19,7 @@ Item { MouseArea { id: mouseArea + hoverEnabled: true anchors.fill: parent acceptedButtons: Qt.LeftButton | Qt.RightButton @@ -81,6 +82,7 @@ Item { anchors.right: img.right anchors.bottom: img.bottom enabled: !materialsModel.importerRunning + visible: containsMouse || mouseArea.containsMouse onClicked: { materialsModel.addToProject(modelData) diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/IconButton.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/IconButton.qml index 98437edb578..e7e8134c44c 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/IconButton.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/IconButton.qml @@ -14,6 +14,7 @@ Rectangle { property alias icon: icon.text property alias tooltip: toolTip.text property alias iconSize: icon.font.pixelSize + property alias containsMouse: mouseArea.containsMouse property bool enabled: true property int buttonSize: StudioTheme.Values.height From 69c042b6fdbb74cbb8b99e5aa7965f7213922ca3 Mon Sep 17 00:00:00 2001 From: Mats Honkamaa Date: Mon, 5 Dec 2022 09:02:44 +0200 Subject: [PATCH 096/131] Doc: Add content library, texture view docs - Document content library - Document texture library - Update Material browser docs Task-number: QDS-8369 Change-Id: I162a3f105dc227a93b9cea3566996d0ab7c51eaf Reviewed-by: Miikka Heikkinen Reviewed-by: Mahmoud Badri --- .../creator-open-documents-view.qdoc | 4 ++ .../images/content-library-add-texture.png | Bin 0 -> 15426 bytes .../images/content-library.webp | Bin 0 -> 31938 bytes .../images/material-editor-browser.webp | Bin 48502 -> 42272 bytes .../images/select-material-property.png | Bin 0 -> 25355 bytes doc/qtdesignstudio/images/texture-editor.png | Bin 0 -> 56206 bytes .../src/qtdesignstudio-toc.qdoc | 2 + .../src/views/studio-content-library.qdoc | 66 ++++++++++++++++++ .../src/views/studio-material-editor.qdoc | 60 ++++++++++++---- .../src/views/studio-texture-editor.qdoc | 47 +++++++++++++ .../src/views/studio-workspaces.qdoc | 4 ++ 11 files changed, 168 insertions(+), 15 deletions(-) create mode 100644 doc/qtdesignstudio/images/content-library-add-texture.png create mode 100644 doc/qtdesignstudio/images/content-library.webp create mode 100644 doc/qtdesignstudio/images/select-material-property.png create mode 100644 doc/qtdesignstudio/images/texture-editor.png create mode 100644 doc/qtdesignstudio/src/views/studio-content-library.qdoc create mode 100644 doc/qtdesignstudio/src/views/studio-texture-editor.qdoc diff --git a/doc/qtcreator/src/user-interface/creator-open-documents-view.qdoc b/doc/qtcreator/src/user-interface/creator-open-documents-view.qdoc index 5250f79af3c..47b6b3dd00e 100644 --- a/doc/qtcreator/src/user-interface/creator-open-documents-view.qdoc +++ b/doc/qtcreator/src/user-interface/creator-open-documents-view.qdoc @@ -4,7 +4,11 @@ /*! \page creator-open-documents-view.html \previouspage creator-file-system-view.html + \if defined(qtdesignstudio) + \nextpage studio-content-library.html + \else \nextpage creator-output-panes.html + \endif \title Open Documents diff --git a/doc/qtdesignstudio/images/content-library-add-texture.png b/doc/qtdesignstudio/images/content-library-add-texture.png new file mode 100644 index 0000000000000000000000000000000000000000..ae820a39c8692d732607ef7ce91201d18553e094 GIT binary patch literal 15426 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

9MznewN1>c<@_~gRvil1|D&5HK!t;#fr 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⪚|K%nBp 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 literal 0 HcmV?d00001 diff --git a/doc/qtdesignstudio/images/content-library.webp b/doc/qtdesignstudio/images/content-library.webp new file mode 100644 index 0000000000000000000000000000000000000000..ac5ca5f69715ed15b634fc61e1f1fa1e661799bf GIT binary patch literal 31938 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Ɉ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 literal 48502 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 \uicontrol Image is set to the selected texture. + \endlist + \endlist + +*/ diff --git a/doc/qtdesignstudio/src/views/studio-material-editor.qdoc b/doc/qtdesignstudio/src/views/studio-material-editor.qdoc index 4120bb633e4..94209c7bcb9 100644 --- a/doc/qtdesignstudio/src/views/studio-material-editor.qdoc +++ b/doc/qtdesignstudio/src/views/studio-material-editor.qdoc @@ -10,11 +10,13 @@ \title Material Editor and Browser In the \uicontrol {Material Editor} and \uicontrol {Material Browser} views, - you create and manage materials. + you create and manage materials and textures. \image material-editor-browser.webp "Material Editor and Browser" - \section1 Creating a Material + \section1 Working with Materials + + \section2 Creating a Material To create a new material, do one of the following: @@ -25,7 +27,7 @@ . \endlist - \section1 Editing a Material + \section2 Editing a Material To edit a material, select it in \uicontrol{Material Browser} and edit its properties in \uicontrol{Material Editor}. If \uicontrol {Material Editor} @@ -37,7 +39,7 @@ \li In \uicontrol{Material Browser}, double-click a material. \endlist - \section1 Assigning a Material to an Object + \section2 Assigning a Material to an Object To assign a material to a 3D object in your project, drag the material from \uicontrol {Material Browser} to the object in the \uicontrol Navigator or @@ -57,7 +59,7 @@ . This replaces any material already assigned to the object. \endlist - \section1 Removing a Material from an Object + \section2 Removing a Material from an Object To remove an assigned material from an object: \list 1 @@ -68,7 +70,7 @@ \image materials-remove-material.png \endlist - \section1 Copying and Pasting Material Properties + \section2 Copying and Pasting Material Properties You can copy properties from one material to another. You can choose if you want to copy all properties or certain property groups. @@ -88,7 +90,7 @@ \note You can't copy material properties between materials of different material types. - \section1 Using Texture Maps + \section2 Using Texture Maps In \QDS you can add many different texture maps to your material. @@ -100,7 +102,7 @@ the image to \uicontrol{Diffuse Map} in \uicontrol{Material Editor}. \endlist - \section2 Using a Reflection Map for Environmental Mapping + \section3 Using a Reflection Map for Environmental Mapping To use a texture for environmental mapping, you need to set the mapping mode to \e {environment}. @@ -122,7 +124,7 @@ \uicontrol {Environment}. \endlist - \section1 Blending Colors + \section2 Blending Colors To determine how the colors of a model blend with the colors of the models behind it, set the \uicontrol {Blend mode} property. To make opaque objects @@ -144,7 +146,7 @@ For a result with higher contrast, select \uicontrol Overlay, which is a mix of the multiply and screen modes. - \section1 Lighting Materials + \section2 Lighting Materials To set the lighting method for generating a material, use the \uicontrol Lighting property. Select \uicontrol {Fragment lighting} to @@ -164,7 +166,7 @@ the opacity of the material independently of the model as the value of the \uicontrol Opacity property. - \section1 Self-Illuminating Materials + \section2 Self-Illuminating Materials To set the color and amount of self-illumination for a material, use the \uicontrol {Emissive color} and \uicontrol {Emissive factor} properties. In @@ -177,7 +179,7 @@ image does not affect the color of the result, while using a color image produces glowing regions with the color affected by the emissive map. - \section1 Using Highlights and Reflections + \section2 Using Highlights and Reflections You can control the highlights and reflections on a material by setting the properties in the \uicontrol Specular group. You can use the color picker @@ -221,7 +223,7 @@ highlights and blurring reflections. To control the specular roughness of the material using a Texture, set the \uicontrol {Roughness map property}. - \section1 Simulating Geometry Displacement + \section2 Simulating Geometry Displacement Specify the properties in the \uicontrol {Bump/Normal} group to simulate fine geometry displacement across the surface of the material. Set the @@ -240,7 +242,7 @@ of the material. The \uicontrol {Displacement amount} property specifies the offset amount. - \section1 Specifying Material Translucency + \section2 Specifying Material Translucency Set the properties in the \uicontrol Translucency group to control how much light can pass through the material from behind. To use a grayscale texture, @@ -254,7 +256,7 @@ the angle of the normals of the object to the light source, set the \uicontrol {Translucency falloff} property. - \section1 Culling Faces + \section2 Culling Faces Set the \uicontrol {Culling mode} property to determine whether the front and back faces of a model are rendered. Culling modes check whether the @@ -265,4 +267,32 @@ is not rendered. Culling makes rendering objects quicker and more efficient by reducing the number of polygons to draw. + \section1 Working with Textures + + \target creating texture material browser + + \section2 Creating a Texture + + To create a new texture, do one of the following in + \uicontrol {Material Browser}: + \list + \li Select \inlineimage icons/plus.png + in the \uicontrol Textures section. + \li Right-click anywhere in the \uicontrol Textures section and select + \uicontrol {Create new texture}. + \endlist + + \note You can also create textures from the \l {Assets} or + \l {Texture Editor} views. + + \section2 Applying a Texture to a Material + + To apply a texture to a material, select the material in + \uicontrol {Material Browser} and do one of the following: + \list + \li Right-click the texture and select + \uicontrol {Apply to selected material}. + \li Drag the texture to a supported property in the + \uicontrol {Material Editor} + \endlist */ diff --git a/doc/qtdesignstudio/src/views/studio-texture-editor.qdoc b/doc/qtdesignstudio/src/views/studio-texture-editor.qdoc new file mode 100644 index 00000000000..95a99401a9c --- /dev/null +++ b/doc/qtdesignstudio/src/views/studio-texture-editor.qdoc @@ -0,0 +1,47 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \page studio-texture-editor.html + \previouspage studio-content-library.html + \nextpage creator-project-managing-workspaces.html + + \title Texture Editor + + In the \uicontrol {Texture Editor} view, you create and manage textures. + + \image texture-editor.png + + \section1 Creating a Texture + + To create a texture, select \inlineimage icons/plus.png + in the \uicontrol {Texture Editor} view. + + \note You can also create textures from the + \l{creating texture material browser}{Material Browser view}. + + When you create a texture, it is empty. To add an image to the texture, + do one of the following: + + \list + \li In the \uicontrol{Texture Editor} view, set the image in the + \uicontrol Source property. + \li From the \uicontrol Assets view, drag an image to the + \uicontrol Source property in the \uicontrol {Texture Editor} view. + \endlist + + \section1 Applying a Texture to a Material + + To apply a texture to a material, first select the material in the + \uicontrol {Material Browser} view and then: + \list 1 + \li Select \inlineimage icons/apply-material.png + . + \li Select the material and property that you want to add the texture to. + \image select-material-property.png + \endlist + + \note You can also apply textures to materials in the + \l {Material Editor and Browser}{Material Browser view}. + +*/ diff --git a/doc/qtdesignstudio/src/views/studio-workspaces.qdoc b/doc/qtdesignstudio/src/views/studio-workspaces.qdoc index a6a6d97dfb0..fdc359d96d1 100644 --- a/doc/qtdesignstudio/src/views/studio-workspaces.qdoc +++ b/doc/qtdesignstudio/src/views/studio-workspaces.qdoc @@ -3,7 +3,11 @@ /*! \page creator-project-managing-workspaces.html + \if defined(qtdesignstudio) + \previouspage studio-texture-editor.html + \else \previouspage creator-open-documents-view.html + \endif \nextpage creator-project-managing-sessions.html \title Managing Workspaces From aa8b9572b40f62b814055b7c339f19a888fd5ada Mon Sep 17 00:00:00 2001 From: Mats Honkamaa Date: Thu, 17 Nov 2022 08:26:49 +0200 Subject: [PATCH 097/131] Doc: Fix typo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task-number: QDS-8196 Change-Id: I1316ba5fa54070950b6277c0f234ff8e2d36d9f8 Reviewed-by: Esa Törmänen Reviewed-by: Leena Miettinen (cherry picked from commit 20ee8a5b4b8655062a006b3b4407403639952990) Reviewed-by: Mats Honkamaa Reviewed-by: Thomas Hartmann --- .../qtdesignstudio-logic-helpers.qdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/qtdesignstudio/src/qtquickdesigner-components/qtdesignstudio-logic-helpers.qdoc b/doc/qtdesignstudio/src/qtquickdesigner-components/qtdesignstudio-logic-helpers.qdoc index 906d1a9ba46..e616f82c972 100644 --- a/doc/qtdesignstudio/src/qtquickdesigner-components/qtdesignstudio-logic-helpers.qdoc +++ b/doc/qtdesignstudio/src/qtquickdesigner-components/qtdesignstudio-logic-helpers.qdoc @@ -92,7 +92,7 @@ \image studio-logic-helper-not.png "NOT operator properties" We then select the other check box instance and bind the value of its - \uicontrol Checked field to the value of of \uicontrol Output + \uicontrol Checked field to the value of \uicontrol Output field of the \uicontrol {Not Operator} component. \image studio-logic-helper-not-check-box.png "Check box checked property bound to NOT operator output" From 0e0a196b7f194447e7b28daa2b8ab086f30bc7ec Mon Sep 17 00:00:00 2001 From: Mats Honkamaa Date: Tue, 15 Nov 2022 14:17:43 +0200 Subject: [PATCH 098/131] Doc: Add documentation for new particle components MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add documentation for Dynamic Burst, Line Particle, Repeller, and Scale Affector. Task-number: QDS-8005 Change-Id: I31402641db4ee717bd1f19995d1ebb50c838a007 Reviewed-by: Antti Määttä Reviewed-by: Leena Miettinen (cherry picked from commit 8352418ef6137f8e2d98d7be2348d15e8f072788) Reviewed-by: Mats Honkamaa Reviewed-by: Thomas Hartmann --- .../images/icons/line-particles-16px.png | Bin 0 -> 550 bytes .../images/icons/repeller-16px.png | Bin 0 -> 488 bytes .../images/icons/scale-affector-16px.png | Bin 0 -> 518 bytes .../images/studio-3d-particles.png | Bin 27722 -> 36535 bytes .../studio-3d-properties-line-particle.png | Bin 0 -> 20963 bytes ...o-3d-properties-particle-dynamic-burst.png | Bin 0 -> 9076 bytes ...studio-3d-properties-particle-repeller.png | Bin 0 -> 7096 bytes ...-3d-properties-particle-scale-affector.png | Bin 0 -> 9517 bytes .../qtdesignstudio-3d-particles.qdoc | 157 ++++++++++++++++-- 9 files changed, 143 insertions(+), 14 deletions(-) create mode 100644 doc/qtdesignstudio/images/icons/line-particles-16px.png create mode 100644 doc/qtdesignstudio/images/icons/repeller-16px.png create mode 100644 doc/qtdesignstudio/images/icons/scale-affector-16px.png create mode 100644 doc/qtdesignstudio/images/studio-3d-properties-line-particle.png create mode 100644 doc/qtdesignstudio/images/studio-3d-properties-particle-dynamic-burst.png create mode 100644 doc/qtdesignstudio/images/studio-3d-properties-particle-repeller.png create mode 100644 doc/qtdesignstudio/images/studio-3d-properties-particle-scale-affector.png diff --git a/doc/qtdesignstudio/images/icons/line-particles-16px.png b/doc/qtdesignstudio/images/icons/line-particles-16px.png new file mode 100644 index 0000000000000000000000000000000000000000..318ecbc3d6d092e464d0fc90434a11cc244212f3 GIT binary patch literal 550 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4i*LmhONKMUokK+zV&o*4ABSytQ!6v>raY<@xO^Uc~>X;pjDJa(HT%dM38;U}Lta=8`A} z1&NCu8BC7yJU0vYnmgK-|7Kzp<9Q49(&YS`?z`3lTPjj zpJy>zF`fMQ`GlpTLcq~21!WtZZ5dB}Gv+wvxMY^3$He(({dc|RPyf{=oBw^)akX~k z$D7wY59XidA7pT8z3r|i+S-#@v+qs&q;YcMs}pShm5c?tcP+T1x=wA%HQ%kVSD7W# za_lS=#g^}|yq)v@)x@iZoxi@0Khd{ET;%EcoR7xEKBr={>hsR>h4|bro36uok#W}^ zRWX~_WhuK?vmfT4u48)bP={#Gjuy4k`S*iK^-IzJ1ypGJef9G+Ni1O7l3P%5~sGZY~vRu1Mz9lVZ z_P1-jjnzD-a#U}wJd#&x4zrU^+?l0BcAn~K_Nn`EAHo3H&nwN@aq+_5 zJhpl3e`UY%wc4eWGM_)ls_VnWhLvxQw|4igkb05&Vd9Md)007a_H5#Oc&xMFs^9y1 z*`&Pf1}ipcZz}tI{>;8#Yzz0_Hr~l+XRi40X!|MlNta_&idvs_1|MBEedTJGA4NhB x1;y-m4w>F7t2goK$P+R?JpEa@g3XNjzsoh>MU=E?F)%PNc)I$ztaD0e0ss_< ziP&XZQp{{vwP!9~5XhU9z`D1g-T%C@&YaZu!u56HemW+_n;yGAIU8lm$D^l{cwx@| zxwoeaIwcDq&rDy({qR_`AY1xA#X`;N9h=Vf@5=Wn`zN>e_hu<}y*m<#A0D{Of5jHh za^3l<$HO<*f8;EcdU&W;@KE0--NyG9r~W+rbk?NueY$__z9g;{la}k5wD+Z%$k`)t z$}Iv;3%_=s7hqf5cb1*`=CgghSKW?W%gJ6hRcngXrRb9~4j$b5CVl_c8o+5CmHTSR zZ~sE}Lznj(3a^PcYNa?u#_BsSd)0R9KW4u+#Weq`Iy8TB^YhP*{f82wttx(950+oV zrKn?aRaR$7^e^x9YpvYYQZua*Xm8id&PNe-~TqUo0sJ+ zyKCkPP5$#rGC2p;O5Sd)H!fuLjggP&NpaGi_|!#4F{>cCC24(4%v?7P-+3p@6ic#G zcClOM^=#08>wl!U`oi3Y7u$=E7bIxsy;s`S@gbpwTl!F&(oP4)hX)ekEq3kUSJzwf Zf6{W!ww^yR!VC-y44$rjF6*2Ung9mx;xGUJ literal 0 HcmV?d00001 diff --git a/doc/qtdesignstudio/images/studio-3d-particles.png b/doc/qtdesignstudio/images/studio-3d-particles.png index 14711846f32918351ebd6bd6159f18edcedb28e8..21715f577c5154ce14d074441c2a3f7523e7ff2c 100644 GIT binary patch literal 36535 zcmeAS@N?(olHy`uVBq!ia0y~yVBE^Uz@);##K6EHCZ+U|fuXO?)5S5Q;?|qF?eW5q z-~Z45pHh1Fm7RALhc1g#uoZ{bWF@9155o>FrO<#7t44)I99?%qOH^Ooy4EG?D#Eds zqeJ1yreC#n|K6QZnLJZKV7@!^tDWo5zti@y{eJGw_c_()=6;O7@7T!1$|Vx}H2wlh zzygOxrkWNI|ADfIM!*6F`GpRROsqdRSh++r;&tTr|37=hHYfJhw6&4ai)+`;`23-@ zIAYtrMn_?<1*<>yue$r}&ylU4`+d)yx;IH|{~gZ{uVc;%)^Cpy5_pmK&G@U1kMw

Zi}|YH>~tYho)58a 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 literal 27722 zcmeAS@N?(olHy`uVBq!ia0y~yV7$%1z@);##K6FC(?raOfgwlG)5S5Q;?|qFo$(^! z*S^pHpK@k~kJ?Phm#wwA4y=L`LRlhN0!3URLt_L2l)E-*Zh6$iy~CxqvvAIZPPL0O zI+J6PuSmq)3<(i1G@SXovey6m+jZ+!#Ra~|v)cdrXWf?Fufo2rj1Jvj{ch*-uKz!U zxwyEvx{{~-cNAW*V8H^1iGcwD0Ran6XlQ6?1f+<7fQAtl7uP9ypNaqfnBTu)sj7W_ z?fRIB({196!gl|7-_;^1$)=jy_1s#_?9b)-$9Ax&pYz}@mOcD?>c)8!jV}c|rEYl> zJO9NN$-sN{52X#u<|JjEn{+kwwf1T8^S$4H|Bg7Em0#^XnenpvjurI}WBX5>dGu-P z>u_;Z?R)j!;jVi*HyhiUYUXWn`gd=>ul)9XpL(9OaQrXK|9tlE=GRM}NNOrxGE_4= z>EYcs^TDro(a)q`nf8bLi9U0p<-q>p-xGTL&n%o4|J^auF{V7}%3>JU?<^R9v?&c!L`mh0EoUMlpDeHV9Q z&X0Yqyq?m3!sfi)y+t=BoF;6viBDm;L^2>B}#vXLj5ujW6H(W9$2O zPmSa2zP?>LU;az%eUqoMfm`d3n8&@`U9NIHdT;Rerft>R&;NV~s{bnQiDxnJFFVzfHDTGO$$qPD-?jemy1u^t&-`9a zJ*#Z>khqGE_tWo(vhq~ zO_BYs&!4s*H$Puj`{?xhx;J%2ou*$lY~keM>bfgZ*^_;3;=230`=|R<#6RcS_trA| z``71p|DXAM?f!;OcOLWK+J5Z+)nB~ri%z_E&D&vcqkf&;$*HStpT*96lb`=_^ zJJv4#{rb~{^2px(UQeymKg_*QD$ZUP=9YbpV`=Ug>rE$*{$6MQ|LL*56mjLQHS6|F z50(3z=kRa&-Tn77=Pi2~{7)$IxASxJ`E#p3CFlOIe&k#7yZ*Lud(^{g*KX{em9t`@ z{L0SUudYU~PaJ;!KhCnTc*XxmubzFnRl>EIOef!aU7QF(S0#Um_gQfxdR}F2e|*jUs26X#L?hQ`e7v2{Ul+enA+);wRo`v?zNbfZ zf3EueY}$_RKC7PG5B^?e7x;8%n|-Etw~-i0jJI`562ejkkXz4%_LS1Vxa zZg%k{Ve=|W{tJt(HPFc4D<4((OR@8O^|H`2sm~U^Q&;b{(bf2LKloeOxA{TR7J0|o zV{PSAy^d8pSvjxdo%i2%&Z#x#YhF3s|Jt@N&`Q~kMn)g`mK!(McUAH0~o{au;;p4znQlOncnd=`?E^P)|DUVNl^;hI8O zmA$f`uT}3e-X2@|@k_VU`tAQz=70Y9_qMKPmh7~1Kev3pcu{BNfhFh7bmDdtoILf* z`T4G0=ksQ%84BN%JTy@%@JaLCzu)!dCO`eXxt*)#W|#eLHS;-d|NhsLL}&@(!=`9;e8z0+R)zwl>)O7j1^^Ui;=snD{#Q2F)JROwjeW_$TKiBIPHBAHL1uW&d^8+dhl_oU1uI|La_h0`ZI^wgg}=|(n%CD}+IFCJ2gub&{knXN3%}a)-Mf4Gx=1hgoI6$dAMR-VUDN;Y z%)Fz#$Ctj}f4^Ag_|*6I`T@2(Z`*veUFfz)eDR-`;#`6|W?WurU!5lZvf}2?!oKAn zb+(=9m$S36DV|gKwb1k4)qjtwlCQ+~zOP>&u%ZLBxB8~C&)x@=s+06xml$}&-=@pOE24&yn7t|zRJ!0=YHK4-{OAkDrVjv6MAg*)BH8@HANSyZ!P>4 z@&C}{Sblr`4>E_}RvxqeXOea%I{AKSpwD`XTmK&2+_~z+ci)!_0w&&{d+EpJ2darX zp3d#qB|ks@zQj*)=hENqpVr+zU!T)ad7sO5+ar&wX`2t-4%;8Fxg@9H+$Eo*zi+L3 znS1}wV}7>%(SjeY8GuT?uD>FYe|H?5Gk1Db{GUtzADlaHf4}y||4;S*-fA>Dlzy9m+FG&Z-j5IJ z>;I=tU^=W{|Ns4a{ds?utUCLl_V|yD(psN>cUtG$|E~+(a6wewy72AN_&oE~)xsP1 zTOKQYcX8gq?$_%3vm+;-mDNl))BZdCn!l8G+>b?%9e&;a|0V3gs^aGVe@h=+ygR>s z-{s5SPj~x;W}b+@x>M%xB}fIHqGtL3L-(9{mgVoxWKKSup8N0L%m4c_>|<;iy${P; zpHsIu@%XQy^Suvi_wQ=;nQLiY9rkHTL!r&~&*$sE^YDH9BOU+!*dALw1tUviYjtg1 zyCai#Hn3g2HUu_iOJQ?(b2I`%~kHX5+eh{0}h9Yj0_B2JPZ&bAccW}0Yo$?F*7iL2@y#K z1_ltZ-~p!h>cGl6o%fuIBnJ*iFi)kDL(VU zcbC4tmTP17%=Ls$j;qg%=&hBxX&ctfQZd=RD>HT0e~b51f>LYimdw#D|JZkGWscpa zo6qOV-rb)0vZPgi?cEj8uV)9{+p@GMOyGO;riLGjc3oSXn=7x$y?D`pXKY`( zuqw3Bt!2JTNx&{H-`VEtG<~>Ao`$iehL~M_)+TxF&aEpETUC^g+&!{UeMwaA{(|jN ztBRe@uC3au+BQw+wDwBlqH8Mug1cWu_5JZK{j*qh^`)kk^mws(;rmx=crCMyx8szT zxnrj95Z@U6NOFHg^4UKpi|$k~^vqH6ZWfc-Y5n8Hp@+XVEnPa*+V7rJMM+lb6rWYW zOBaXl(v!{1%DS~_Y2We_YTFit9!y;>U$<4I<95Bv)u-?Li)xQorq*BioSeI4iddD} zwRvyf?*DnT?^LRH_?p{wYu4&r^xs^@wYIc3qIB8!bM3{<6>eHw<+W>KjO>@Ma4UWP z@+SYG)K_~;?*`@OXWiQOd%>$ie-Fm*-oIkTqf@tC!V_=LQ(j*8?)~lv`>Vd2ZI=AG zGM8QUxw(tSQPbIFau$Vq^B-g-FIv7ax&Qpeoc(FK_1ov0=U?T_=-3&$e*2LNLZP2` zRWYBQ%-vV^HP(99$~7r3R~M)tbH)I_K9P9_Acw6&4RD@?JwQ)^TuE4Q!CfG zW&IMBsjpeB>6rKA#@Y?>Yfjj3AD0Y!w!Z4D>Mhv(=~h zU8&*IyY=(ote&1(+_F}#W-&rhb_G5%Io`hNaPLqEH%4{z;l-mISTW@lmTaUS!!vl?43c{-i) z`#vvTSj}+piL^K7Ay0ZQu3IJ&xxwqYVr0yr`=@rOvQ6_&eqATA=X2o26mw_qJ)8Oe zfBEUeTL19LDek#ee@!=~9NIKFJUYGd!8hguvsSRGpSpI!F!RooSK4=vf39Wa-kT=B zqk6HXXIjw2vw5kG6H_N%4X^4G{~_5Gl74(|U$y%)_T3U2gT;bt_kF+l`Q2t~Zy^z( zi4Qg{D2vK=ch6om+hPBd%!d;DG7c6VAxd1rLd=e6q&N8XF! zd3bN;;dZ{-Ur(p5>&ssyWD|2nV^ikuUX4%P%WY%l&YgSLR@}^Q>g;G&?_Kiuch$U9 zntD2Zt$%^(K9>zswKpHRezZxczjz}1^EdnM{Ez;>Rax}%pC4ym3x$^dESkGddb86+ z)sNBZV($p_-R5V|ete?x=g*d@yE)eH`tqU0qoB6fZeRUUv#_LF?q=Jks=6-Qarm&l z7^_6!mTdO&c`x5u%o5iz-4guh-Q(pm1T2g1ipS2CzpOL4QK|ZQ-LDtVbwB=j@u4~X zm)y6K@@@0&qvi_w=xsQ^rq;aPX3we(3k>wvm;CRY_xLB{tyy=??bodfsw;YW@a^H9 zjcO%lH~n?1d2;rppy0${B_-wNJu1(_R_5;5W?X;m&CSTo>F2w6CrQRD-SEn~BNUu| z*48BZ@yd7c)&C+T&gf2A{$}4l-^dN81w<-iGM?^~zAc=x<1d3m<4oJ{Gud}l^*{gl zS*$AU&EEUP2B(kvTsd?n?4N6H|GG8(7cwW?6yAPYz5DVV%d?+m)Na`Q{YH)b;)Pcm zX70|n`@2x{=B=4T>FVOsS0`F&PV`x4^S$NKy`aWRtWV^tFH~CGzJKS;yv*O`$Er>|%_|Bw zUm0>;b%pK~UBRzne?r5wHzjBatLd?dLzHF3*3n z(4g;Y*PEH8FP=^JW-k2AXb|-C#J$d`^Y2&w`{WuQ|G#_s|L^tw`&RpYEWcPCz1(`A z!Iy1ka#i``>)w?8Px$}%-toWZ<>%g?bdZ&O-8F;GkMrV6AHDm0KaTH;KxD ztlE5h$5uN@>%1@be;I@??9OKsk$o*$^KHZ8)#VHRpG$ulnD=V0?7N?DryqQNo}Hb2 z{=MJl|81&>UwMQ7?Y>pvCMTD4<;%}o-SfmXP+jl$uIEXmJuUe&x=m7kUhmJ_o>Cz# zvs?A|)^96xtIA3*hjgAh)Y#bg{5<=+H?!X!-0Zu@OZN1$oCk|;KT>ro%gE@A$=SK+ zN5%Qm_v86&-d6uRec-Qf_9L50+w3R5xUbJzwv+SA_uqZWO0L)R3;plb{hWREp&lsZ zUQGX{^6sDGHbcAJ(&v(k=w-Qf=h6|NSz)mFrG5!;N>ESu1D%KQduLK)wIV z6Zx!fdP|B{zWUQx$#vRP^2;3|pvT>Ec3T<%VY;}nlS=uaG zv0}%%b$Q?4-TiHv>KzFVm0CH1$~|9Fnb4W8MF#yMqXu6*3HV}gUdeIw)dZ(OjT zVV03y-K#4tEo;_TYKO1O%FVsIH9P#;97~~*% zU97Z~W{>Wdub=&APxOnG|NZLaOH+OG?Ms(#UAFAl@$2W`@iKyJYCC&wE@>6+EY)hjoyB1Z(*WwPTScHH#tRTvWZ_=V{$4X zKC{4auT1UT_1dD^;g_7&Y1;1HBP}KM>ecJiqMduA7@>{|m?+xlDDvXu$(hIZ{rbrs z;IU#!O6UCf|L56ON1Q%)=FE}+!CGIhS?5- zty6RAY-uT}%#MR>QpKA$ue`iCL$EZPTU>8ZnBS~+t2%-(cGPR-7mb zH|KhH#=}27{`9F++jDQby*<5lu7uF3{@($QgR-lB|G1QHUe5n(&P>bG$By$uZJqzu z?XbYBQ_EDP3LCrWoT`R zGB7ke8PZ~>n|tip+1aaCtzv3!l2LJj8tgaw@3uW#rc~MI%gZ0u5v!7FpLafyH#TzT zwL+u(1W4R_&KLOjEaeYgu`BSLu~~Yl;^e2(*5VteAJr>X5WE)B&04-!i;HzJ%FcJ7Th9!L=v)C3CjF zItVePSZmX-vzK>%URn4vf1cRGj{MVYP-mT~Q&wwj?cKHi91}E#SDxtEaPr|70js~P z&RNGVeiG+_#|YM*(v2>q(A`GUYWu>rZ5Q2p@?Mhk?f947t7ep)cq;=cAGU?0%+*^x zGb($6UjNuO`P?M&HIXlZ?p&)&*#FNszNxY+W=YWd33X0O zv#!7K-H;CIhINR{6wjzwDjKltqRqxSS?!k&3Q_vf7vy4+^@1xl2sX#Gf?Fj$0m(a;dxQphd63WkP~y4WJDNWHl2JYT zTB0lG#mS}g`*4wAOpWZ&NX6v%-Rqkckz}>x@TUo-@cXCIq z&RAF$b?1f6?js-hTyEU@Hut9dy(O%7?`@fs{>7!kZ`#_6b4qrX9Ckau+k=s%H@Tp; zujyH-gsa)5>`W;S`zZ-)HAC3{*)I6GYLox<+yy6mp0zCdl)HIyzm7!vs@4XkrZw}E z#h1R&=}_+JD^mFNaQVMU8?K-E_d!kf#W%HG;wo9-sO8 zm88E;{Q6iq*=TwE>GuY=-%ebqwZ`|#{7 z3w9Tk_^sF|{Ly`$p|QpLBvykxlXw@4)i*qoJ!r9MVgDNG@;yeEoIf?QmF*5IVOKH< z&6usMvFTU##B=V?;#(h8mbF_86nEvkmd>o0EViY3v7LV{r{p{ijZLodH#aH$XPi|& zqewUD%RScf$3s7xHx4?!aaV`zAv|@1kI!9xo0(M)J1Ug}QWy@%`%jcL&1ZpE(jZOX z5fCr|QjB*P#Gp&0vZ%r(Ft9K-_U^m8yT50jnzn4&r>?FkS4_gq%OSNgV*$KQ-X2-| z_gGUC)2_3R9xwOv^*C{A*5;z8tFB$U7Q4GF_3TXLiKk?iTP7IiupPK5a?@4m>|Fc# z@3wEvzTW1?mCRlH_s`|DC6l9%?5nD(I(zzTZf54%l`B7f{JOc?`9RtYo`zzrB2Ukg z`)Yq18Ko6@@7YsZ_U_I}w`H0uSKj%w;K4_)rA4o=tu5RC5o`gdI-XZ@`Xa}a+NF)^ zl@4D@_pb|C6XVIf^Y)p`r*|)0xNzORIJN1wSBpZGM+!=~I3AcYr}E9}`SlE~+}>W( ze*FCT{^sWPyQRj)#+p4I@slSl+nm+tcu-eY*JQ?4Z(*pbK7D$+w70i6XyzPC3$Lfo z+S=Tt>NYvPxbr5bs6%6thu--^t=%v9d|-9)J7<1>{oCe@Ic;YdEEX!Aj=ih#!CcvP zZmq7i_WfH)~|ip510saf4?zV}YM*US3{WxyVoJlFb)a6#xD8H7E4r zq1Hva`#$}@sa0i?@c!Q3H^oUoLTfiYJqT)O9gugL_|k9nx^>%Xt}k73$!Axy1}oT? z=~tSbCB8^y6?0!^v@_c$XHIj^vP4iauuT8PB*L|8w~+8;pR@OtEIoT;Pi1jvXlO#f zgyu8%z~Os2ZNIL*uIl*{w~rq;INUk!%A)k`H+&;N_3x%l;JAa-zILnvB08(w_>&43 zU*VNrCHvweE7%uOu6zM3mvVT%FVU&AHEDM?e&#)0Z?S~Z-q+e%R%z$%?0h2p^tt#1 zZ5PYsBA#2`Pu{da(v11Rmco=-Ef4qmw$1CFJ8#aOyt`JrzR!5IH4pPQLj(Bh~u*%?ws%XoY*@ha6~-(H?TcvDT~QOpF%E*^#kCI06A zKexKp+-3odqh8ZA`kXj(QkLoWsRn&I6T*EpZYIe@uKx5ZrRU+4w7F-Uj8&c)xk(tF zDsyz1-4|l3upz$d#EC8ZYgiBb{4{Z^-6m!^EqyH`6Yk_Gxo`LQX4t>g%I$FLc^Hu3 z!k1#=Ir~P`Y!g9-`V!Y?HUaH>@0WC~;jKLW;?UJyFI}2N1m`Jfdrp~|?khKcRp83N zlx>}P_PMTZ!Cr1p99VjFw$1$P8F`Is`vlfs-M4S$&Mwi|5O%?(sYmMK%Y@u>`?6ju zeq?i)IG3|-iL1o=3&-P*Xe!N7I$mVuH-B+u*P6dM$?O5eI#JxMX0MYqXUTa~E6ka_ ze&c;T-Syf2vBEP?ta!+iHc{!T$@*pgCb*k1I82m{nRvB)-@Vk!Z2MR*7A)S}?Q^su z>P&b_V#l3j&P9tSulTh@T*%bO?eqF$30rdy{X4wu#i7VsYDOnk2+reM_xyn$%d=`m zk(->Nk>+*Jw|G8Lo*k0%H6*2a_ciIK+D7~Pa+{azvTMuVe8T8amEd*N!ee0RrW-=ENAZGS#Y9$jSK?=LqVRa*i(iJC6(6C?Tgm0 z6*#kMb+C9>%$teK3=9l@a_(l+m=}a*y<1%n7As@U!oZ;M$>RwFhtRw)o_=m=SKrP6 zE1R3Gc*-8*S;Yhn0tMWryiGji3(9aWL%i~_iXE87^ zXl(i`5;=KFSNM4bh65+a=!Z5qYkZmyTDK996Zb%wnSo)E81@1G-I|-SJ&twhviGmP z)wk`+lcPVpG8U$M+H-4d^!B{R8x8fH(h{|k4&7OGHru$l^s36jn^%3cFaJ4SxqOP& z>(9zhb9Q!@hxny_o_OBw|C<;4e7%=z*JQqRyBl?X%BN+zQ#Uv5xIc;Yi1^j5a=F>R zJt8HZE{$7sZ}zc0y1Z9ccNr`FQCZY{DKz8Ooi{Yh2yiIqz7Xt=;iWe`js|bGPo^o=|l?#XW~lTHm@_{`SSPO_P?c zmg1L>G}c}B&Fg*nYA4lMTOUuK^JZs^tgd+Y>N7L*uV`IdUy&-m>-Ub|pSHcs+jC7~ z{~wp{pYL5>`peHR>ybqJF7w52-=toU(qrq*U&{DsTi5gQy^~IC{lD+lq<6ZK&+TeY z_wNmlI$b1|6HsdNqvj$Dd~(5v@y(JGpPxOIGu7b!^hxK9>VgmPdu``GV=eTXP#tu6 z>%Pq;x|`Pvhs}9u6}f0bopyKEo2fg$|9iYXti?OFh3VGkgiT%(K6rKd%>h+@sa{O4x^UcApk(HoZGHW>rV&{rgt;YTo?g zeHs$3Z2Fh0&wk(4DGKIAZ^CZqho#JGt*H@{sjk?oI7?sbU*&PRd-rEQv^{s!xb*kd zZ~U8Ud%}!UMgNEI*~EYU=fmIGvd6^={I=Wd`*(={|IRywRjw0G7rZ~4VJ#h=lH8kr zk|DVFK+M-Q&e#2P=B}9b?1k_3>b^4{!}RBG`DHJA{Bv!r)9Lef?#zG5JCmX8^F;KC ziAU%Y6Yn;%+voO8=hnTr=59<(`MT$ij=AUuzuOaCar310{WTmDU**OAwUyuU=E`OH zEG1PX$15QP=WpD;y0?DWaf_c<+Oog%otrmdR*Jke{h1+&FLQgYXQ` z=dEw0{mbgMYqe}`EC06e_Vexe|90KBa+SMvuV>rlKv%O{Kc?{*T;(`3>9Bq6zJP^4 z-^{#N^!wbyCIkQZKend7o2$pK9G?iC*>KQokX#ilRqIPu%X0m5T7Q%M z-MtL2Ni#!IHb+MWq(r8CojUI^Yn>ij*R`94>mT2n`17Ei+nXszL*~D){kG@#n{$&` zT3u9FbXLs0cI)EBS+CRtTvDSdQgV)MV!r+>|Gnq;NjsLl*;l(=^=j;+X?JQ?zb=?& zE*`g|{Mm{-}SHemn}(}HUC@1-)Hh~ zcYZz_etq5BQ(sJi&vt|!iockVk^1J$wcGZ&o+XQ|6t(^C2kFk+_DB1*NM%R0@wIbq zqBkoZN1GgQQC`9B7Far6|N6^c3EL;I?$)=hH2m?%_4vuJm;YX|*X_Jt^!8C(fd=b_ zd>Ko9`}rG+#2v}Wy_shlew;%bu_%MIn%jz4K@7}EYmr?3w zP&wCb^S8TyK7LqrsOiw2J(vEPSM@z-&&V#j|7XW8ub(HUvTx^q&NfT-;_S`$JNoxn z+A1j(dAX#dOqlNYcz1hRuFciI+2`%Kn+sKL`LmxVs^{#htA6iupY!zEuM^w1nJd4|37P0?vd@*-$An4I|7Pvq*_#s9-Z8)b z>Ss=3$L#Lz@<+!$&yClU_CM{ceg5u^c?!)z4Hu0!Kezkr5dQw7P5nKm^F4dNSM#O) zoY5IIB0&wf#|L;hB^LoclRllr-vp&AqW^gV*RBtlN^qPx5 z-?oLy-`~4no+r1?&r4q5T)~#k^F4XhM_s9eMS>iWkNRHUUpsHbPOV4M>w?|i9<6zP zGE`i?Hc&>Pkn?`Q$5|J@u^dP&)?Fefx?H}jZlBh<=(l%w>~Pnyx%2PC?o%E!kK8#k zZ|7R4@Hv$=n^jN8uaYc(UVG`{--%Tnr|#x#T48$M_T9ftMhC+m`+MGL7Fyo=YvW8? zvyox z&QEuQyQ;lewFD;Azj1v2FsHsaEA`gD-i>laJ7?dy>Z^6tbLuUpyFznS88=VY;rM=$ z$I<3dw*HrSv7(QXTqgR48VglC{oi$Mb?EVlMrD5sw*T(ST6k-HNW|I`&bMbfTEG5& zIGcAer087W`BTHXHkrTG@rru+$CK*c`BQ#glq{Sd^fUJU+P9tSgwC&VHs2p3#j%uO zv5x#r>y4j2Tc3~L_u}>I`j4Of|9gM3e5>ZaJu9BR?9=BFx%npS(}$#jbWcfU@u=3h-g!H1W7|G&}i{%$JJ`fhdA(g{AR zetdhl^OQ%NLeZm5>#Oy`j(hxQ+v(vvt^V(eGViwS2RBAC&x5xahbFA@(Crdb8>o)K0ycM}aJ~sQw8k4I(rtxr?UgVJ4u2A&1HQV>{ z9TUmdle_rt%ls&P;CcVSgT<7FRgS3_Y|g{W7sEmCbO+v9it}uDNL`Z z{Qt*;jdn$ybyqYea>uydGAsDg!jNw+et2t8czF0uh0j;csyAp=pZr=~Tv}dSuKw8P zPSK+Ii`Tz1ddP6CmCsj8^!k(&DS5kQH6?G)*}r;W9J^ve&>5f=W)-LwwcGaXEb!WT@cu_ zw6KP_VI|P8F4z=? z<-t!*J`{9yJz82?y1V@Sz4e;Wvu2&r)(Q%`{6asesJcR#w(4Q$BtSUaqE+l6p3BXHjT&_Um=AyEi>MGtncp@3IVd(EY}noGzdG6x;HD zHCdNY$C!ezAICAqG&^xK_E9Ezv{9z~80Zv&lk2jBtftz%MmNHxVgmHtKj{`&9R+S+#gs+t*-w?3=^GU%=zlH!^mz*<#Q^lan$x_^u!LRy-UkDfo@pMGBM z+9uFIQ-}-7KvTj-$Uqa=X^5ew(j+g)P*c)|#$$KhSzIlDqrcb%Iw*~$#%2sb{`SjTC%8o0u z?%D18a`WunX1nyXbDKVX1P$rTPTc%Ceg8#+mR*}NJ3HTKo0^;d($LpWmu$Uq?e_Nk z^QX_JmuCE&b;`?n8)&F!gQS^tr_%AGM=xJ_zAg8*@UoTB#h~FP&r?Ngn#HL|!%c^U zQHPsAlY5W>r?qaiQl=WcI@i|6pAQq&kI~4AJoPbcwZ`9S^zo+IiJ#@2CayGHy?V9o z_8c#*v(v0jTxEbxWk_ya5MgaQA+PX@;^f`OjvTW*(ZLCtmg1Q`w>VATVc`NXA0Hnh z(S`orvo}_Lezt1WssjrY*gG#keGM6QT6*t1XxOQ{b>6Xs&x<3R!^75<&N(9ujr&XL zE;f6&%)IA#a8cHm4ZP+;H#aQ2)wt<0bj~DG#L_{~L$Lf^rpK$B8h-wp7lB5eEClzS z*My8bU3vfXxp+Xidu6ML?xy#bCqUEfmckUSEuZg8IGwli^Y@F}o+rCi|MeXW9+!tT zV)K@7?fY;{0P28SGi_%HiS7;UH#OwdTVeW>TlV?dDyS)Qy4KWvE_qjz74vWQa#xE- zw>LqZrMdZjhi78q;&)o_0V zMJ=Ol-door`)bwog&tSKT0XNdG$_qpvSaPuy?>{!%8<~}$O{&Ap2*1Xpj33Hhija{ z&qGmbm#o(c{jIGf)te?IwKykO>vl%uZlhfuU%R5E7hkz@MW?(oH|M#X_m-_&oYu!) zUDl+;%&_3ZGDX(Bo{JgU#rd9Irju4(&6ulXtZo(cxBjC=p056mg+Et)o@lC-ukn3L zKFDcZIacRBbZJK|pCKH)Pigx{@mKaQ%%5u)Z8=wZt>VnasEh7bm>3vbw@GXg__5PD z@JYtfF3+_K`*Y4TxApTdG$=*$L~dBGQ(&kfA1SVT?dOSE6^8c9&ip*L-c6;?=FCck zGDm^#?YmX~aK>3>?>T+=RAtmZr;7`DGh0$Ne%cVNy(^>g+$oNEC9l)2^u|0_yX{^t zGV8AR?As4N^hdgy@B4gj+X?q=O5QD}zigQD$#BV^2@|h7Uc24t^to)qOr6ZL$HwDu~4LtS@{&CaNtE1bKqHc2`(tmMPtbtjx}FWj4NtMSP|d4|~& zBU`>V&x7o?R4T7hVzv)?edn|G>)qwCHgj{cwOz0Eh4Z*6E^^M$7J9OUXS=FOanANH z+N<1hL%ePnvCTAY+9Q?RrDJ6hSnHVA|25O{aGvhk2iz?u44muZPfp8_Elw<&c(v;c z+cGxm>wms%&)uUPnXu=f(8u?$L~dTuJs{7>|5{fw%h^%8@Yss_>podxB9$dd=Y$Nu zOf0Qbe$=t;fZ@gSZl69o^K6&>$hby><^0jsEi3hF*b9!-q~uA3eV+Mu#fsD;o0$&n zk$N1+{coY?LE$zqD zUbAI}u4bj&JsnE3Go05=ZTYDEcx7b3lR)WB0;!!6!6|n+#h$cqEPB4{)UVb>6}#5f zq-=ES$am6nv)|OE6Jq{#|DtJvspmRAW%c|z@yFSC&H4G;C7hzpZHQj|{$;%6%tvBN zMPHhS%;)}?*KSj(aBt_oT~9SPJ=kShW#LwvlDz!@ht{SIyg%2bEb;7Jn&vpsz3{}L z3bQSZO5H_HEyCPtwJFt_MZ3$E2c9zaf}w$ah%FaQndKsI4e3S-gzF zA^U>YIbru>b2J$kEUui9d*;K)a3HZr*C3sXf#F+d%9)u=3=NB)DIfmH$^fqS!3_s6 zL1OD6XZeZAhn2g!x?aC}Rrdbg-uU2cCZ7sf~M`KL*U`6ta!XU_O=8D74=WXa1l zQ3(MB-rn4-Rs|4k$v;0`I(E!$rO&xJ@k@(}ot>3i)gl^_^2*FCPm83gl~+7G;60n`{cgwz;NCwd4?TW8jb`BZI@lc1f=I z3EVcih7)U5XFgjb_Vnpf9pRZKRlmQzt^E0O4WDj&aAapN6GMa2e$B$dk5h~`f@hqv zMCX}So2iCxh)F(Gaa&)fTTDMKT2xFdFrvA3o~?DZd$F+}Cj&#*pJgWI=Ehr9<7c$Y zP!f85eSP}sE^$2(O(R?BLtz(_m8-wMOPwz~c~XWbSh>xaE+4I!*x202iNQgdrpCa%HB@zm@YGg7kB^pTf&7;I)|U{Hpv@Ys_E zTH*1=ysE2z-s@zh*vOq?H}e@74DwRIt2^uzg~0PmFAD{Gc%`pAOtVpBVqiGD<^*Gx z!>pB$=Pgp}pYiOw#_Xh*mr_OImwi9r>U#XlOzY`?p8om6R5V#re&(Vx6C0W{s zEEyOY9)6g(c)Owwxu+hviS1TcTDMeJ>Fm`NW!1?&c3FaJ zKQA}cE7j3g;RR);g{_mWDn3zqJ4dcoCyjgNvm!gA_=&u=DUK!}cQz>PFVmRGZImH8 zP1oeDa<^fOtN1H+CO?@&jEKf1_l>h2K3oI za0HYEr(B=VBO1*o7rs?Ccjd|Mk5ex=oqSTcHF|sA-DAngaV=*Kg&k?x89MuV8r$nt zo=%%r&kkGmv;VW7X6Uu&?x$`RiN9Yl=hQRh`L+LUUaXxx%`g1NrCYtbw(iq>>Km<< z&RV!%mAgZKRkrP|Yu_erGC1YE*JZDHZ)LR9s&LVCmmi)k>`PZ)*i!j;)}bX1;d!9} zZmGe!c{38vuJ!E=^)_<4<116`wCd_N?()?Ot<17>$P5aX4j>muU3I-LPT{f59FXp}Uj$1QcWWKraxLD`* z`pDDw-#$MZUD_pMmHnJ~%S{!d*`}MqK$?E5K)@@=fem;F|yvTd==Z9A>5BHt)XdB}JwJw|dxOaig zn-uiVABtn*`hWHI=ghbX-FcnR`GEJEh@TJGNz{D6oKpnn16s~ECG)R0cZt5>vYP+G zQT5jswhDWzXW6lfzx`9->*jX0m2a)m?&WW@_V2VkI(hr^3!jY5zo%C{-o|*q&2E#& z6y4K?l|j(G1`|3jbiJ4RpI$mc(iOalT`jTd!{uicJ1+>Gy><3z>GLYRJfAN{PtWac zoj!NZ=d)b`EB!Wn;gTSJME{mLmJ$6T?P9_REi_1o$pk|Nh`VUq9r7 z`TUs5g_AWm>14Y9_V@Kv3{HRkY?*5YvzPF+xqEI|on|iQV=(KEDp-`$D>IqJXXU<2 zwKn&|H(bd6*0|ez)}$0Q(VK6=#6%*+B5RjEuV|d#-MFZMZTCBr_E+4xeD&tdn|F&7ABTL} z{q&&aZvF57uhqZxIBH@)-Q3z?`ifnrr@#Kfq#KfQ^wH$l>-w>KOaF0S-M)REt@VDz zIKRKXf2L)No3}hZ$9P~`;gdaoY7U)>Z3&O9s&G=-vCmTe{{A3oZNs<9vu~f8q;z@1 zpNHbzOg$R6wl-Uz|57G%mV27iEzQF3AA@~5=Ingez3yCp=BHB*8}=02*M9jtt)i~v z$3gvC{_0)Ux6jM3J;$LYek1+bzS;J2aiI|bGp2{{`tNrB;~$3Hv+rihhlgw0@BH-O z+rv8!fBU6EQ!am#{y5Qmxx0&tUuQ=T&!$vQd++Ub zns^9&_&WD$(dTy;nOZGf&9e6=%2)kjT{P$n%u1)CMzwpTCM;pBNRh8_t3R>EzVs^8)TfYfU)U@F$tyia-7L?AbSsbN?TU;NN-2_uVVCsjqgiY;C;-8}{z8fem|q zQe(H+_vTBltw^KcsuPbzj^^5io2gbzIMS~3d((5Tvmz^x7{9)7R)0F%qjQW07Ji<% zS6JJA|K~qXyZ7z;C$9hh{r`D2;jBgKky#e}=WboMbjW-H-C-c|^LL-}tvBX3k38~!&u90(_QR9N?<=={oKo^| z-mbr&%vT?d1tsGj=ihkV`P+P~|NGYu{;TXhzu2z+e9BC&XMSF>PmNwq*j_Yo_wRmw zzAqDBFQ2zWXy>uYC%MuVbxsZ2XLmo0TDf}l>N)>?T8^NPOMCRZc>ZGbZXb(;@`cWm zy^fly8qJ)&ZrOtyy0_oIdUozY&bqA=7XDPM5;}6&JY&zHdrU7xN43nopPv^IMCyZig+ z^QGV2wd%ng`)sTQjsx;Q%TOIOjQFHfF5dsg|mXt|oolCvJ_R~53dvahd= ze*W&^OAfK1`wpAD-GYKA%S@lPPGPBX*qVrq$;bJkJXW7bYtOoR!87rUSzzSuJlT`S zSM2t9A(E1ECMPFniSvz}#p)~PZSU`YJY)N!H9hxFcu&_WzA^2~mrs5D^JmSE56B95 zc7Fc)iD~j0pQKM3YO9>$ye#CNEZsZbz_94ll&`O^Yfqg$T_-jwE^c4;^>v_yov%Hn zynUp3xqn)C$OgZ@IbK0=Sr1>mdew14bBmUShJ~7%nU$JZ#UGYmR;gYgzP`Gl3i_T} zldYX5|N8Ok?S(J$Zl_;emG9Zr-4bJYG0?=+)Urckvb#^Y{C%^cX|lOGPHt{)ekV_! z)ZDu0$+Kt2jvTA1s&bqdAKW){w%a8$ui2fy-hTPQ<5%NLF>oQ#EIDZyJCT#~a?h`YiVG)aZ16X7&s*lBC!T3id1}kFq*G>Q7Ah)JiYs>%2*|QV zI(ff(^(y0sjG3j4PL9#hh!cg&TCWsOIOsDa`0}MUw;!aues_2GS?y1oo}PBU7#k80 zk?}=Cqb5;*a?RgAFPBW_Eit|nJ}dTtwA*8wC<$@#^Xqt5<>*^qzML*1A~t1T-og*w zb&dx=Jw3hP#JR_^)2HYtDxZvepx@Kixp2mUpp{DujxIXdoq4I#?dFxqpOZyIMMX7N zW}1X1B{yqpU+?-Ot9B|fvN$a*ZPId|`0lQ5Pqi1*l$%%HUSRBGE?0R>Ed~Y>_l!cA9s%=ggTiik`-yaa%G9OTV^7`8Fz9cgh%>H;VdA@if|IVj6g*8nghj z;?mD$ilIqKLY=ohT?+D>GF>;?Z~CO(_f}lI()Yg2(YX9!?ZSnP`@4P#?fUC6<)!gg zjVEs|vm4z$C_2-owtbi1iuv6gJ!j6Hi`-LDn3bi)eL;HHQW3`I@~mH9lo@Vaw{PBA z?Fj;(!!JHnoMpO{D@Vk2_Jy`@K`DKblE!IgPw8lAnW!FdJ?h%(>Y9Ch?OIVWjZK0p zPRqs?`Iw9DJXq6g8MsV=Aye|~3|H6VEg6qurbsyWX>=OZra9+#-3a=|`cr({7EAN; zeSw^pt4|d_>j+yLxw&oH|HrAJnK26EI;%^>z?99y2(9k9Q zCP`bDp4Ex42??BMkf?Ol=TiHE2OqDT4QQLAo4YPz=Oj1jKawi5J{he)q4O&<-rX+q zSIMNKE6Qm6Uj|v$3&B@=6A$-fA)X zvZ3-@lcwu6@8VxQlT=l=P6IjalHUKmTZU3b+CDFvG?txlG?)JA^&}_ANQU?Oi}+c) z1S>t2duJ{&G+yMxsi-60P$Z$@KeNqf+m@L(H>_OPIO+2aMmy8FD;GxuM`nIso^NDi z^z6(`=XEnh8P7U#W$4B12w0srF}O=yZ^?@ZwP)s8PL`UGCiR0`vvE3GaciqK7AvooAr#^2S|x2%VIx5g&3&ZL`0(;oWUx@_Kful3VDTN#D(Cb7XD z(><4*I2gYvrPHIsvq^J7WMpLIVoftSt(i?vLwx-Go~2)wzO|l@Nkrs=3Rifq*+Sl2 z`E#4xCQjFn=W~@aG7)%|rxhrkdsbPZtg^yICvqg)NraAbUU9NSNdZFO2w}jifZCOfs zdaz70o8%PRFl8OSMJL{vEqUI*Q*~Cu)NB8C7H|Ii`Lm|m`J80}GXE#0emkHpa??{} z=KNpA_5Yd_Pa6L8DyuwmYtfcYmzj?iitM(^+mB3|^(v$3G6)tkH zuU&9$*&p@Y+l@639hnf2=XB;`oY17aMHh79dpAB(=sJ_&R%@ZFbC&_fX962_9{m)5rCm(*wc`-yT_J~Su*%W>+?%VmX54WpZ zy6epHsl2wW-s4D5>lE|1Gx8m!mUiXLV6jzk-B!zFDpI*bWai%FR#C5I8`IvLoSqz( z^Jfd|-sOj+G8c#Y7p{}B z_OK#7G{r3bl>dqiK@(rzS`b>2>o9Sy%S2bNM8oYD1Nt=YWImpwUKMiPEAz3-&A5KE z!hojZ{}zh)O+_OZpr{8Q@}gwJ!Dc;CXZXzmM<=#?jKoigJ;?l{riYoaFq z!Ba9u>=Tof>av=?UOLvf>F@lkhrI4>DyK>~rf58xv3cT~)|4stb*t(*L@GTt?2MW9 z;ez#kqn|%M3O{=M*wS^`7vE3wHTWmBc}-FA3s#;I=k_gl-LA(ricGF@Y%cE;OjS?u zDII^aQtaaFOKzOZUba%<#|gisC#JaFN|^A_`yqFo&&;L`)-uk0rTp7`)qe5NF(14WZfhpHqrTwN)yzU&%x%5b9 zTvS@Q<+W#9IOpd|iZ1f;ujrl^S)LMj?|)S5y;?`hE8o2Q<@R>@?9_f^EBZx@Tdj68Pv(2@%taNqHE>fCo?Y~~8XobD0i(sOdgvwT<&qmBuS}s!1occ%l11Omu593r){!c zq_la)riU)_;V+^#c0W$mTs9}CZc#s5GvANpOedGLv2ouNH#zM0eqzBB_5HeHRmodr zrDs+dxh5p|`0&_H@Tqsuwy`<%@vl*psL@{5&i^mOY(JdbYuRvX^3ShVRtDrd*XLYu z{Kuv|(>C$Zksm8RwsrZ$dV6`T+TOqZGv~(+ZKLTz?sbx>*%wnCYfWuU*Z-@YdwH_# zlRMH*+a;GCxp=$aMeO4o2lsG;oBsJ>D*A^L^PPftW*IFlN&aqI$qzOrCoV>Xu$)~%Hw=ruW#!0+`Kz#mhdt$QPGQv z7H(Rm>-N?60w&&`vhj;z)XJN>B9aah86}!POaD#(t?|xLKDlv{ zrcrI$KE2Niw#MHMiMps0^zoWhaLT2!uXSBL*Ym?y-tv7f!=PdG*&qr@b|jX;mMMD+l`ha3;HhZzqEKmeO~jnh?REA)gm^Gsoz|> zADy^Wyf(KWcE$8=BWaE;{}_HMrWO7zJJ{bD_!KQFn^f-cbF>q(w9H z%2tb3&d@bCebAuPJ*&rj$KyZU%)c$&ovrt;(K7mAvi0(XKc^PW%*(r-c-_eC>blJV zFK%`0QJH-C#jl&v4;iIy9_G;86zYC3KW=gG|HGH|i2c#|csA|Ns(|MPbeD_%>5iYWBWVWzx)kp%eLGpR z=ekYYdGl4-M(bt27tD?qyFb54&o4SsE zU4CMHMdJyWV$}r|`xUoB{;*Hnx%k0aC%+T3 zpSN(d>$53`A2e0o5#76N;*A4E;=*%xU32=_xaZO9^BJf0|I9b%irn#GPt@M|D}Eoy z-Vxq(v#f4I=nhf)Ri-^%Yra40*3Fc>f7^70^*qn^r#ANCHJq8M z5l@s?hqPVHdL3Llp`YhKfDJ=*#WLmj6ByjiFiTv0%kRp@*LUpr#z?6-XN80qL?pX- z4!r((;_&~uyO^Y8?-fw2Q)8~ zc+B46;-yQw=7$GezI5ST!^3CqHs;*ibnRN$E)TD1Ium#ATIkz0eZhhUckcWQ4Gpy} zf0tseP`hCLmI+~&3l8cDWtRAyS#-4f_3PI!4<(r8-`!RE_0?5lW8=MhY8RZyKlSG) zw}s`p)vFham49%XDI*sc65_M$-MziDXYO6Pa^;>yN~)%3ub#cjxc-mo`;?ekQ6NrlO{5x#QSc4eiI@uX#_`lXf*ff2;Jh#c6dPo`tHuzP@*a z%K80&&k~mq*;4fM)ZaAj&;?n2#j2M@y{E5}Y<|fVzoTH{^6BOxl^GQQvwvn(8w<7O z-rly6O?BhrU3u(No^9B0($sWoN;jh;5oj9_;wBve!$sd~e?EZMDJ9%$6d* zzr9WNE_-uhqG9o}uRZ+ z%T{{kR`!COl_xJP%{0E0{P+>)FNgR{#c1!VD>mJoJ>}M^r2hv+cdc6P;_5tcZL3Pp zxpFTrt?eqARYqHtD|7EmGMzg0>eZ`t{}}r&+dn@y-`{Or>~6D?Ac<%XbJ5s+G5U|d zXE#hgcHPv})NWthH`55U@}(c`b~~JmoIP!Z$6keaql_2syysN<-B$KJ4)aJYdUf@L z99Lw=JCjY*Kz)4&d6_%b%kIsxyMFch?2CN6XSfS;etUiP>{;VWmwUIV=q?F8YZbe! z`~E)PU47;cHY~fabkY zEtHehwztfw(fcxO(yI5dO`zis>>`yUCs|IM`Z^~0_I6@L6ObJfpRdJ@M&Qi^m>E?wZ*>8xoK zd9-d;OvOKosZP-<2U%(#Bt?cae*W_IwdUp4Q>FaZx;K5UJau@@iI*=W*KGXs>9gx$ ze<8tt;ggKZ(kGd)YcRdN&2^>w!cEod^IjWxaKF$lf2SR`Zb|DLmdV1`FI;dqH}S=S z9JbGW=6QEA&bqKyUKS||%#QKZmGn96{LUkn$5f$IagFjq2Epu;{B}PY?#7yw9==l( zbu@JI?}#HwEH`U5*1nm+s_l_J^UbA~%RB`yzOkxZ+N!g{>FNxP%WdC!HTSn^1lOu} zYw|Bw@}FTa@oaBe`tt}$w>uja&U$tI+O=DTRVz4nlr|TGmPtPOI-%&Ii)7`O>Thon z)#s^IdaAw9HV@$9;aaABLoa!6OG~ED+BN&dGCNM$q;i!^`W6_UG~Iw(`a$p(uM|s9 zW>5?kiHdzO#~&4@YQBE9t5{{^a!s^;6rjxxP;g zGfeN$GS*yj_U-Kr>lIIRi|IBg%`T8Jd7N>|(?FH0jr-)$Wc34m%FCXf_Li2Go>{5F zVzMhgBV)-07VdyEXV0ni2PN;DqgHt`q^lFW5L2!9nR|XNgQSz#B+0hNTTf^PcXf52 zoV(m)Q%h!NjJkXKff&DaB4T1U_s!L0IUjQ1l-S}dAKwh;;=xm#RZSl>bZaaJu*O*Bp1d}rwwOXK2c zzLT|048vaiaS8q}%J%unlh{obI!jxO1)p=xp0r6-Wqr;8K|#Tjx=XgHdUr6iHvQuN zHZxy3beV6?{kOa)gVQA2|2X}X*WF&bNM-hg#Rdw+`Wku%pUhx7-ZnK;Q8;zgF&Q

wG!?Sk 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 G&;$U}B{#YN diff --git a/doc/qtdesignstudio/images/studio-3d-properties-line-particle.png b/doc/qtdesignstudio/images/studio-3d-properties-line-particle.png new file mode 100644 index 0000000000000000000000000000000000000000..e22adc41fb0647567f48fabe393283ce7d28aac6 GIT binary patch literal 20963 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 literal 0 HcmV?d00001 diff --git a/doc/qtdesignstudio/images/studio-3d-properties-particle-repeller.png b/doc/qtdesignstudio/images/studio-3d-properties-particle-repeller.png new file mode 100644 index 0000000000000000000000000000000000000000..755c76376a578ba9fb56444cf03ff3c29015c854 GIT binary patch literal 7096 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 literal 0 HcmV?d00001 diff --git a/doc/qtdesignstudio/images/studio-3d-properties-particle-scale-affector.png b/doc/qtdesignstudio/images/studio-3d-properties-particle-scale-affector.png new file mode 100644 index 0000000000000000000000000000000000000000..98d5ad7f727d5eeef1043dd4e2802ce04448cf30 GIT binary patch literal 9517 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 BmBau5 literal 0 HcmV?d00001 diff --git a/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-particles.qdoc b/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-particles.qdoc index 545b091d53c..b9f5463c83d 100644 --- a/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-particles.qdoc +++ b/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-particles.qdoc @@ -70,10 +70,15 @@ \li \inlineimage icons/attractor-16px.png \li Attractor \li Attracts particles towards a specific point. + \row + \li \inlineimage icons/emit-burst-16px.png + \li Dynamic Burst + \li Emits particles in dynamic bursts. Use dynamic burst for + emitters that are moving. \row \li \inlineimage icons/emit-burst-16px.png \li Emit Burst - \li Generates declarative emitter bursts. + \li Emits particles in bursts. \row \li \inlineimage icons/emitter-16px.png \li Emitter @@ -83,6 +88,10 @@ \li Gravity \li Accelerates particles to a vector of the specified magnitude in the specified direction. + \row + \li \inlineimage icons/line-particles-16px.png + \li Line Particle + \li Creates line-shaped sprite particles. \row \li \inlineimage icons/model-blend-particle-16px.png \li Model Blend Particle @@ -110,6 +119,15 @@ \li \inlineimage icons/point-rotator-16px.png \li Point Rotator \li Rotates particles around a pivot point. + \row + \li \inlineimage icons/repeller-16px.png + \li Repeller + \li Repels particles from its location. + \row + \li \inlineimage icons/scale-affector-16px.png + \li Scale Affector + \li Scales particles based on the particles' lifetime and other + parameters. \row \li \inlineimage icons/sprite-particle-16px.png \li Sprite Particle @@ -399,19 +417,23 @@ visualized, and some logical particles could lead to multiple visual particles being drawn on screen. - Two different logical particle components are supported: - \uicontrol {Sprite Particle} for \l{Textures}{2D texture} particles and - \uicontrol {Model Particle} for \l{3D Models}{3D model} particles. Model - particles use \l{Instanced Rendering}{instanced rendering} to enabled the - rendering of thousands of particles, with full \l{3D Materials}{materials} - and \l{Lights}{lights} support. + \QDS supports the following logical particle components: - The following components are available for adding logical particles and - for modifying their actions and appearance: + \list + \li \uicontrol {Sprite Particle} and \uicontrol{Line Particle} for + \l{Textures}{2D texture} particles. + \li \uicontrol {Model Particle} for \l{3D Models}{3D model} particles. + Model particles use \l{Instanced Rendering}{instanced rendering} to render + thousands of particles, with full \l{3D Materials}{materials} and + \l{Lights}{lights} support. + \endlist + + You can use the following components to add and modify logical particles: \list \li \l{Sprite Particle} \li \l{Sprite Sequence} + \li \l{Line Particle} \li \l{Model Particle} \li \l{Model Blend Particle} \endlist @@ -509,6 +531,48 @@ \uicontrol {Frame index} is rendered. When it is enabled, each particle renders a random frame. + \section1 Line Particle + + Specify properties for line particles in \uicontrol Properties > + \uicontrol {Line Particle}. + + \image studio-3d-properties-line-particle.png + + \uicontrol {Segments} defines the number of segments in each line. + + \uicontrol {Alpha Fade} defines the alpha fade factor of the lines. The + value range is [0, 1]. When the value is greater than 0.0, the line fades + more the further the segment is from the first particle segment. + + \uicontrol {Scale Multiplier} modifies the line size for the line segments. + The value range is [0, 2]. If the value is less than 1.0, + the line gets smaller the further a segment is from the first segment and + if the value is greater than 1.0 the line gets bigger. + + \uicontrol {Texcoord Multipier} defines the texture coordinate multiplier of + the line. This value is factored to the texture coordinate values of the + line. + + \uicontrol {Texcoord Mode} defines the texture coordinate mode of the line. + + \uicontrol {Line Length} defines the length of the line. If the value is + set, the line length is limited to the value. In this case the minimum + delta of the line is the length divided by the segment count. If the value + is not set, the line length varies based on the particle speed, segment + count, and minimum delta. + + \uicontrol {Line Length Variation} defines the length variation of the line. + This parameter is not used if \uicontrol {Line Length} has not been set. + When the length is set, this parameter can be used to vary the length of + each line. + + \uicontrol {Minimum Segment Length} defines he minimum length between + segment points. This parameter is ignored if \uicontrol {Line Length} is set. + + \uicontrol {Eol Fade Out Duration} defines the end-of-life fade-out duration + of the line. If set, each line remains in the place it was when the particle + reached the end of its lifetime, then fades out during this time period. + \section1 Model Particle Specify properties for model particles in \uicontrol Properties > @@ -678,6 +742,7 @@ \li \l Emitter \li \l {Trail Emitter} \li \l {Emit Burst} + \li \l {Dynamic Burst} \li \l {Model Shape} \li \l {Particle Shape} \endlist @@ -775,6 +840,30 @@ instance, set \uicontrol Time to 2000, \uicontrol Amount to 50, and \uicontrol Duration to 200. + \section1 Dynamic Burst + + Specify properties for emit bursts in \uicontrol Properties > + \uicontrol {Dynamic Burst}. + + \image studio-3d-properties-particle-dynamic-burst.png + + \uicontrol {Trigger Mode} defines when the burst is triggered: + + \list + \li Select \uicontrol TriggerTime to emit the burst when the burst time + is due. + \li Select \uicontrol TriggerStart to emit the burst when the followed + particle is emitted. + \note This mode only works with trail emitters. + \note In this mode, \uicontrol Time and \uicontrol Duration don't have an + effect. + \li Select \uicontrol TriggerEnd to emit the burst when the followed + particle life span ends. + \endlist + + \uicontrol {Amount Variation} defines the random variation in the number of + emitted particles. + \section1 Particle Shape The \uicontrol {Particle Shape} component supports shapes, such as cube, @@ -830,6 +919,9 @@ \li \l Gravity accelerates particles to a vector of the specified magnitude in the specified direction. \li \l {Point Rotator} rotates particles around a pivot point. + \li \l Repeller repels particles from its location. + \li \l {Scale Affector} scales particles based on its lifetime and other + parameters. \li \l Wander applies random wave curves to particles. \endlist @@ -909,20 +1001,57 @@ \section1 Point Rotator - Specify settings for \uicontrol {Point Rotator} component instances in - \uicontrol Properties > \uicontrol {Point Rotator}. - - \image studio-3d-properties-particle-point-rotator.png "Particle Point Rotator properties" - The \uicontrol {Point Rotator} component rotates particles around the pivot point specified in \uicontrol {Pivot point} towards the direction specified in \uicontrol Direction. Direction \uicontrol X, \uicontrol Y, and \uicontrol Z values are automatically normalized to a unit vector. + Specify settings for \uicontrol {Point Rotator} component instances in + \uicontrol Properties > \uicontrol {Point Rotator}. + + \image studio-3d-properties-particle-point-rotator.png "Particle Point Rotator properties" + \uicontrol Magnitude defines the magnitude in particle position change in degrees per second. A negative value accelerates in the opposite way from the direction specified in \uicontrol Direction. + \section1 Repeller + + The \uicontrol Repeller component repels particles from its location. + + Specify settings for \uicontrol Repeller component instances in + \uicontrol Properties > \uicontrol {Particle Repeller}. + + \image studio-3d-properties-particle-repeller.png + + \uicontrol {Outer Radius} defines the outer radius of the repeller. The + particle is not affected until it enters this radius and the repel + strength grows smoothly until the particle reaches \uicontrol Radius. + + \uicontrol Radius defines the inner radius of the repeller. Particles + located inside \uicontrol Radius are repelled at full strength. + + \uicontrol Strength defines the strength of the repeller. + + \section1 Scale Affector + + \uicontrol {Scale Affector} scales particles based on their lifetime and + other parameters. + + \image studio-3d-properties-particle-scale-affector.png + + \uicontrol {Minimum Size} defines the minimum size that the affector can + scale particles to. + + \uicontrol {Maximum Size} defines the maximum size that the affector can + scale particles to. + + \uicontrol Duration defines the the duration of the scaling cycle in + milliseconds. + + \uicontrol {Easing Curve} defines the + \l{Editing Easing Curves}{easing curve} for the scaling animation. + \section1 Wander The \uicontrol Wander component applies random wave curves to particles. From fb24f791b85374d41f8cb72cf246aa0243c433c7 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Mon, 5 Dec 2022 14:22:13 +0200 Subject: [PATCH 099/131] QmlDesigner: Fix texture source path for newly created textures Fixes: QDS-8448 Change-Id: Ibaf51444f2dff8d3de95edd4c8afbc7bc2cd2241 Reviewed-by: Mahmoud Badri Reviewed-by: Qt CI Bot --- .../materialBrowserQmlSource/TextureItem.qml | 4 +- .../componentcore/modelnodeoperations.cpp | 7 ++ .../componentcore/modelnodeoperations.h | 2 + .../qmldesigner/components/createtexture.cpp | 14 +++- .../materialbrowsertexturesmodel.cpp | 7 +- .../materialbrowser/materialbrowserview.cpp | 84 ------------------- .../materialbrowser/materialbrowserview.h | 3 - 7 files changed, 26 insertions(+), 95 deletions(-) diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml index 3492a1dfef3..2ad2a317ed4 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml @@ -43,10 +43,8 @@ Rectangle { } ToolTip { - property bool hasSource: textureSource.slice(-1) !== "/" - visible: mouseArea.containsMouse - text: hasSource ? textureSource : qsTr("Texture has no source image.") + text: textureSource ? textureSource : qsTr("Texture has no source image.") delay: 1000 } diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp index e6693f8e5d0..3a3dd7d6838 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp +++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp @@ -1731,6 +1731,13 @@ bool validateEffect(const QString &effectPath) return true; } +Utils::FilePath getImagesDefaultDirectory() +{ + return Utils::FilePath::fromString( + getAssetDefaultDirectory( + "images", QmlDesignerPlugin::instance()->documentManager().currentProjectDirPath().toString())); +} + } // namespace ModelNodeOperations } //QmlDesigner diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h index 5dcc20ff911..2e60b253195 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h +++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h @@ -125,6 +125,8 @@ QString getEffectIcon(const QString &effectPath); bool useLayerEffect(); bool validateEffect(const QString &effectPath); +Utils::FilePath getImagesDefaultDirectory(); + // ModelNodePreviewImageOperations QVariant previewImageDataForGenericNode(const ModelNode &modelNode); QVariant previewImageDataForImageNode(const ModelNode &modelNode); diff --git a/src/plugins/qmldesigner/components/createtexture.cpp b/src/plugins/qmldesigner/components/createtexture.cpp index 5a1c1e36f33..c7ab9701a98 100644 --- a/src/plugins/qmldesigner/components/createtexture.cpp +++ b/src/plugins/qmldesigner/components/createtexture.cpp @@ -4,6 +4,7 @@ #include "createtexture.h" #include "abstractview.h" +#include "documentmanager.h" #include "modelnodeoperations.h" #include "nodelistproperty.h" #include "nodemetainfo.h" @@ -41,7 +42,8 @@ void CreateTexture::execute(const QString &filePath, AddTextureMode mode, int sc bool CreateTexture::addFileToProject(const QString &filePath) { - AddFilesResult result = ModelNodeOperations::addImageToProject({filePath}, "images", false); + AddFilesResult result = ModelNodeOperations::addImageToProject( + {filePath}, ModelNodeOperations::getImagesDefaultDirectory().toString(), false); if (result.status() == AddFilesResult::Failed) { Core::AsynchronousMessageBox::warning(QObject::tr("Failed to Add Texture"), @@ -63,12 +65,16 @@ ModelNode CreateTexture::createTextureFromImage(const QString &assetPath, AddTex NodeMetaInfo metaInfo = m_view->model()->qtQuick3DTextureMetaInfo(); - QString sourceVal = QLatin1String("images/%1").arg(assetPath.split('/').last()); + Utils::FilePath imagePath = ModelNodeOperations::getImagesDefaultDirectory() + .pathAppended(Utils::FilePath::fromString(assetPath).fileName()); + QString sourceVal = imagePath.relativePathFrom( + QmlDesigner::DocumentManager::currentFilePath()).toString(); + ModelNode newTexNode = m_view->getTextureDefaultInstance(sourceVal); if (!newTexNode.isValid()) { newTexNode = m_view->createModelNode("QtQuick3D.Texture", - metaInfo.majorVersion(), - metaInfo.minorVersion()); + metaInfo.majorVersion(), + metaInfo.minorVersion()); newTexNode.validId(); VariantProperty sourceProp = newTexNode.variantProperty("source"); sourceProp.setValue(sourceVal); diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp index 3f5fde5a7c1..0184b1cae15 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp @@ -34,7 +34,12 @@ QVariant MaterialBrowserTexturesModel::data(const QModelIndex &index, int role) QByteArray roleName = roleNames().value(role); if (roleName == "textureSource") { QString source = m_textureList.at(index.row()).variantProperty("source").value().toString(); - return QVariant(DocumentManager::currentResourcePath().path() + '/' + source); + if (source.isEmpty()) + return {}; + if (Utils::FilePath::fromString(source).isAbsolutePath()) + return QVariant(source); + return QVariant(QmlDesignerPlugin::instance()->documentManager().currentDesignDocument() + ->fileName().absolutePath().pathAppended(source).cleanPath().toString()); } if (roleName == "textureVisible") diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp index 39b9efe13ae..37fd15000da 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp @@ -450,87 +450,9 @@ void MaterialBrowserView::customNotification(const AbstractView *view, applyTextureToModel3D(nodeList.at(0), nodeList.at(1)); } else if (identifier == "apply_texture_to_material") { applyTextureToMaterial({nodeList.at(0)}, nodeList.at(1)); - } else if (identifier == "add_textures") { - if (data.size() != 4) { - qWarning() << "Wrong number of arguments passed to add_textures: " << data.size(); - return; - } - - QByteArray identifier = data.at(0).toByteArray(); - QStringList filePaths = data.at(1).toStringList(); - AddTextureMode mode = data.at(2).value(); - bool addToProject = data.at(3).toBool(); - - executeInTransaction(identifier, [&] { - addTextures(filePaths, mode, addToProject); - }); - } else if (identifier == "add_texture") { - if (data.size() != 4) { - qWarning() << "Wrong number of arguments passed to add_texture: " << data.size(); - return; - } - - QByteArray identifier = data.at(0).toByteArray(); - QString filePath = data.at(1).toString(); - AddTextureMode mode = data.at(2).value(); - bool addToProject = data.at(3).toBool(); - - executeInTransaction(identifier, [&] { - addOneTexture(filePath, mode, addToProject); - }); } } -void MaterialBrowserView::addOneTexture(const QString &texPath, AddTextureMode mode, bool addToProject) -{ - if (addToProject) { - // copy image to project - AddFilesResult result = ModelNodeOperations::addImageToProject({texPath}, "images", false); - - if (result.status() == AddFilesResult::Failed) { - Core::AsynchronousMessageBox::warning(tr("Failed to Add Texture"), - tr("Could not add %1 to project.").arg(texPath)); - return; - } - } - - if (mode == AddTextureMode::Image) - return; - - // create a texture from the image - ModelNode matLib = materialLibraryNode(); - if (!matLib.isValid()) - return; - - NodeMetaInfo metaInfo = model()->metaInfo("QtQuick3D.Texture"); - - QString sourceVal = QLatin1String("images/%1").arg(texPath.split('/').last()); - ModelNode texNode = getTextureDefaultInstance(sourceVal); - if (!texNode.isValid()) { - texNode = createModelNode("QtQuick3D.Texture", metaInfo.majorVersion(), - metaInfo.minorVersion()); - texNode.validId(); - VariantProperty sourceProp = texNode.variantProperty("source"); - sourceProp.setValue(sourceVal); - matLib.defaultNodeListProperty().reparentHere(texNode); - } - - // assign the texture as scene environment's light probe - if (mode == AddTextureMode::LightProbe && m_sceneId != -1) { - QmlObjectNode sceneEnv = resolveSceneEnv(); - if (sceneEnv.isValid()) { - sceneEnv.setBindingProperty("lightProbe", texNode.id()); - sceneEnv.setVariantProperty("backgroundMode", - QVariant::fromValue(Enumeration("SceneEnvironment", - "SkyBox"))); - } - } - QTimer::singleShot(0, this, [this, texNode]() { - if (model() && texNode.isValid()) - emitCustomNotification("selected_texture_changed", {texNode}); - }); -} - void MaterialBrowserView::active3DSceneChanged(qint32 sceneId) { m_sceneId = sceneId; @@ -559,12 +481,6 @@ ModelNode MaterialBrowserView::resolveSceneEnv() return activeSceneEnv; } -void MaterialBrowserView::addTextures(const QStringList &filePaths, AddTextureMode mode, bool addToProject) -{ - for (const QString &texPath : filePaths) - addOneTexture(texPath, mode, addToProject); -} - void MaterialBrowserView::instancesCompleted(const QVector &completedNodeList) { for (const ModelNode &node : completedNodeList) { diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h index 2f8113a1645..22e6c6a8d8f 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h @@ -69,9 +69,6 @@ private: void requestPreviews(); ModelNode resolveSceneEnv(); - void addOneTexture(const QString &filePath, AddTextureMode mode, bool addToProject); - void addTextures(const QStringList &texturePaths, AddTextureMode mode, bool addToProject); - AsynchronousImageCache &m_imageCache; QPointer m_widget; QList m_selectedModels; // selected 3D model nodes From c7eb6b8ae6e61132f5bfe0e9c5f136ef5c2dbb15 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Wed, 30 Nov 2022 08:37:14 +0100 Subject: [PATCH 100/131] AdvancedDockingSystem: Fix dropWidget() I believe the original intention was to pass dockWidget into notifyWidgetOrAreaRelocation(), otherwise the whole "if (!dockWidget)" condition above would be no-op. (cherry picked from commit 4080f31d25a69b624a2b7f3fd73294943fa9fa6b) Change-Id: I0b809ec6587489eaf5e4f79c5c16f26bbb8346c2 Reviewed-by: Tim Jenssen --- src/libs/advanceddockingsystem/dockcontainerwidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/advanceddockingsystem/dockcontainerwidget.cpp b/src/libs/advanceddockingsystem/dockcontainerwidget.cpp index eff53f33186..9bd1b3ef272 100644 --- a/src/libs/advanceddockingsystem/dockcontainerwidget.cpp +++ b/src/libs/advanceddockingsystem/dockcontainerwidget.cpp @@ -1215,7 +1215,7 @@ namespace ADS } window()->activateWindow(); - d->m_dockManager->notifyWidgetOrAreaRelocation(widget); + d->m_dockManager->notifyWidgetOrAreaRelocation(dockWidget); } QList DockContainerWidget::openedDockAreas() const From 8a7b1d7a0045087f0361549fbf73873239b4ea6b Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Wed, 30 Nov 2022 08:49:07 +0100 Subject: [PATCH 101/131] AdvancedDockingSystem: Add const reference into range-for loops (cherry picked from commit 0a8c1d6e85de2ac4d73145886cf8a593f90de171) Change-Id: I623e6d315180596c0dcd5bf09982e27428b4674a Reviewed-by: Tim Jenssen --- .../advanceddockingsystem/dockmanager.cpp | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/libs/advanceddockingsystem/dockmanager.cpp b/src/libs/advanceddockingsystem/dockmanager.cpp index 5771241dc33..e5daf2c611f 100644 --- a/src/libs/advanceddockingsystem/dockmanager.cpp +++ b/src/libs/advanceddockingsystem/dockmanager.cpp @@ -106,13 +106,15 @@ namespace ADS void hideFloatingWidgets() { // Hide updates of floating widgets from user - for (auto floatingWidget : std::as_const(m_floatingWidgets)) - floatingWidget->hide(); + for (const auto &floatingWidget : std::as_const(m_floatingWidgets)) { + if (floatingWidget) + floatingWidget->hide(); + } } void markDockWidgetsDirty() { - for (auto dockWidget : std::as_const(m_dockWidgetsMap)) + for (const auto &dockWidget : std::as_const(m_dockWidgetsMap)) dockWidget->setProperty("dirty", true); } @@ -328,18 +330,11 @@ namespace ADS // Using a temporal vector since the destructor of // FloatingDockWidgetContainer alters d->m_floatingWidgets. - std::vector aboutToDeletes; - for (auto floatingWidget : std::as_const(d->m_floatingWidgets)) { + const auto copy = d->m_floatingWidgets; + for (const auto &floatingWidget : copy) { if (floatingWidget) - aboutToDeletes.push_back(floatingWidget); + delete floatingWidget.get(); } - - for (auto del : aboutToDeletes) { - delete del; - } - - d->m_floatingWidgets.clear(); - delete d; } From 216f3dd243cef16fdb2c088d51e067968dd3564a Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Wed, 30 Nov 2022 09:01:35 +0100 Subject: [PATCH 102/131] AdvancedDockingSystem: Add context objects into connections (cherry picked from commit d701cd5dbe938000ec82c58898cceff208d08ed7) Change-Id: I564b9ffe6c7809a6d50a172062c52558f3023fc1 Reviewed-by: Tim Jenssen --- .../dockareatitlebar.cpp | 2 +- .../advanceddockingsystem/workspacedialog.cpp | 2 +- .../advanceddockingsystem/workspaceview.cpp | 22 +++++++------------ 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/libs/advanceddockingsystem/dockareatitlebar.cpp b/src/libs/advanceddockingsystem/dockareatitlebar.cpp index 039b33eaaf4..e050f7446d5 100644 --- a/src/libs/advanceddockingsystem/dockareatitlebar.cpp +++ b/src/libs/advanceddockingsystem/dockareatitlebar.cpp @@ -222,7 +222,7 @@ namespace ADS floatingWidget = floatingDockContainer = new FloatingDockContainer(m_dockArea); } else { auto w = new FloatingDragPreview(m_dockArea); - QObject::connect(w, &FloatingDragPreview::draggingCanceled, [=]() { + QObject::connect(w, &FloatingDragPreview::draggingCanceled, q, [this] { m_dragState = DraggingInactive; }); floatingWidget = w; diff --git a/src/libs/advanceddockingsystem/workspacedialog.cpp b/src/libs/advanceddockingsystem/workspacedialog.cpp index 6594e05d7ca..481f597e8a3 100644 --- a/src/libs/advanceddockingsystem/workspacedialog.cpp +++ b/src/libs/advanceddockingsystem/workspacedialog.cpp @@ -71,7 +71,7 @@ WorkspaceNameInputDialog::WorkspaceNameInputDialog(DockManager *manager, QWidget m_okButton = buttons->button(QDialogButtonBox::Ok); m_switchToButton = new QPushButton; buttons->addButton(m_switchToButton, QDialogButtonBox::AcceptRole); - connect(m_switchToButton, &QPushButton::clicked, [this]() { m_usedSwitchTo = true; }); + connect(m_switchToButton, &QPushButton::clicked, this, [this] { m_usedSwitchTo = true; }); connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); hlayout->addWidget(buttons); diff --git a/src/libs/advanceddockingsystem/workspaceview.cpp b/src/libs/advanceddockingsystem/workspaceview.cpp index 41d0dfb28ab..b1c381fc309 100644 --- a/src/libs/advanceddockingsystem/workspaceview.cpp +++ b/src/libs/advanceddockingsystem/workspaceview.cpp @@ -68,25 +68,19 @@ WorkspaceView::WorkspaceView(QWidget *parent) m_workspaceModel.index(0, m_workspaceModel.columnCount() - 1)); selectionModel()->select(firstRow, QItemSelectionModel::QItemSelectionModel::SelectCurrent); - connect(this, &Utils::TreeView::activated, [this](const QModelIndex &index) { + connect(this, &Utils::TreeView::activated, this, [this](const QModelIndex &index) { emit workspaceActivated(m_workspaceModel.workspaceAt(index.row())); }); - connect(selectionModel(), &QItemSelectionModel::selectionChanged, [this] { + connect(selectionModel(), &QItemSelectionModel::selectionChanged, this, [this] { emit workspacesSelected(selectedWorkspaces()); }); - connect(&m_workspaceModel, - &WorkspaceModel::workspaceSwitched, - this, - &WorkspaceView::workspaceSwitched); - connect(&m_workspaceModel, - &WorkspaceModel::modelReset, - this, - &WorkspaceView::selectActiveWorkspace); - connect(&m_workspaceModel, - &WorkspaceModel::workspaceCreated, - this, - &WorkspaceView::selectWorkspace); + connect(&m_workspaceModel, &WorkspaceModel::workspaceSwitched, + this, &WorkspaceView::workspaceSwitched); + connect(&m_workspaceModel, &WorkspaceModel::modelReset, + this, &WorkspaceView::selectActiveWorkspace); + connect(&m_workspaceModel, &WorkspaceModel::workspaceCreated, + this, &WorkspaceView::selectWorkspace); } void WorkspaceView::createNewWorkspace() From a5fb9fc95f8399365d1002d1114d7521a32bb68e Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Mon, 5 Dec 2022 15:54:45 +0200 Subject: [PATCH 103/131] QmlDesigner: Add "Apply as light probe" option to material browser Textures in material browser can now be applied as light probes to the active SceneEnvironment node. Also changed the logic for resolving the active SceneEnvironment node to prefer a currently single-selected SceneEnvironment node over the SceneEnvironment node associated with currently active 3D scene. Fixes: QDS-8472 Change-Id: I9a3a7b9c2fad3c8120e85ade507a7ea66de19f1d Reviewed-by: Mahmoud Badri --- .../TextureBrowserContextMenu.qml | 7 ++++ .../qmldesigner/components/createtexture.cpp | 5 ++- .../qmldesigner/components/createtexture.h | 3 +- .../materialbrowsertexturesmodel.cpp | 28 ++++++++++++++ .../materialbrowsertexturesmodel.h | 10 +++++ .../materialbrowser/materialbrowserview.cpp | 37 +++++++------------ 6 files changed, 64 insertions(+), 26 deletions(-) diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureBrowserContextMenu.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureBrowserContextMenu.qml index 5081356b29f..4ac0d72635f 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureBrowserContextMenu.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureBrowserContextMenu.qml @@ -15,6 +15,7 @@ StudioControls.Menu { function popupMenu(targetTexture = null) { this.targetTexture = targetTexture + materialBrowserTexturesModel.updateSceneEnvState() popup() } @@ -32,6 +33,12 @@ StudioControls.Menu { onTriggered: materialBrowserTexturesModel.applyToSelectedMaterial(root.targetTexture.textureInternalId) } + StudioControls.MenuItem { + text: qsTr("Apply as light probe") + enabled: root.targetTexture && materialBrowserTexturesModel.hasSceneEnv + onTriggered: materialBrowserTexturesModel.applyAsLightProbe(root.targetTexture.textureInternalId) + } + StudioControls.MenuSeparator {} StudioControls.MenuItem { diff --git a/src/plugins/qmldesigner/components/createtexture.cpp b/src/plugins/qmldesigner/components/createtexture.cpp index c7ab9701a98..91e40959806 100644 --- a/src/plugins/qmldesigner/components/createtexture.cpp +++ b/src/plugins/qmldesigner/components/createtexture.cpp @@ -99,8 +99,11 @@ void CreateTexture::assignTextureAsLightProbe(const ModelNode &texture, int scen ModelNode CreateTexture::resolveSceneEnv(int sceneId) { ModelNode activeSceneEnv; + ModelNode selectedNode = m_view->firstSelectedModelNode(); - if (sceneId != -1) { + if (selectedNode.metaInfo().isQtQuick3DSceneEnvironment()) { + activeSceneEnv = selectedNode; + } else if (sceneId != -1) { ModelNode activeScene = m_view->active3DSceneNode(); if (activeScene.isValid()) { QmlObjectNode view3D; diff --git a/src/plugins/qmldesigner/components/createtexture.h b/src/plugins/qmldesigner/components/createtexture.h index ba29397c205..3898b67862f 100644 --- a/src/plugins/qmldesigner/components/createtexture.h +++ b/src/plugins/qmldesigner/components/createtexture.h @@ -17,13 +17,12 @@ public: CreateTexture(AbstractView *view, bool importFiles = false); void execute(const QString &filePath, AddTextureMode mode, int sceneId); ModelNode resolveSceneEnv(int sceneId); + void assignTextureAsLightProbe(const ModelNode &texture, int sceneId); private: bool addFileToProject(const QString &filePath); ModelNode createTextureFromImage(const QString &assetPath, AddTextureMode mode); - void assignTextureAsLightProbe(const ModelNode &texture, int sceneId); - private: AbstractView *m_view = nullptr; bool m_importFile = false; diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp index 0184b1cae15..b732b9265e0 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp @@ -214,6 +214,20 @@ void MaterialBrowserTexturesModel::setHasSingleModelSelection(bool b) emit hasSingleModelSelectionChanged(); } +bool MaterialBrowserTexturesModel::hasSceneEnv() const +{ + return m_hasSceneEnv; +} + +void MaterialBrowserTexturesModel::setHasSceneEnv(bool b) +{ + if (b == m_hasSceneEnv) + return; + + m_hasSceneEnv = b; + emit hasSceneEnvChanged(); +} + void MaterialBrowserTexturesModel::resetModel() { beginResetModel(); @@ -273,4 +287,18 @@ void MaterialBrowserTexturesModel::openTextureEditor() QmlDesignerPlugin::instance()->mainWidget()->showDockWidget("TextureEditor", true); } +void MaterialBrowserTexturesModel::updateSceneEnvState() +{ + emit updateSceneEnvStateRequested(); +} + +void MaterialBrowserTexturesModel::applyAsLightProbe(qint64 internalId) +{ + int idx = m_textureIndexHash.value(internalId); + if (idx != -1) { + ModelNode tex = m_textureList.at(idx); + emit applyAsLightProbeRequested(tex); + } +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h index a1db791beaf..53b27e68a6c 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h @@ -18,6 +18,7 @@ class MaterialBrowserTexturesModel : public QAbstractListModel Q_PROPERTY(int selectedIndex MEMBER m_selectedIndex NOTIFY selectedIndexChanged) Q_PROPERTY(bool hasSingleModelSelection READ hasSingleModelSelection WRITE setHasSingleModelSelection NOTIFY hasSingleModelSelectionChanged) + Q_PROPERTY(bool hasSceneEnv READ hasSceneEnv NOTIFY hasSceneEnvChanged) public: MaterialBrowserTexturesModel(QObject *parent = nullptr); @@ -41,6 +42,9 @@ public: bool hasSingleModelSelection() const; void setHasSingleModelSelection(bool b); + bool hasSceneEnv() const; + void setHasSceneEnv(bool b); + bool isEmpty() const { return m_isEmpty; } void resetModel(); @@ -52,6 +56,8 @@ public: Q_INVOKABLE void applyToSelectedMaterial(qint64 internalId); Q_INVOKABLE void applyToSelectedModel(qint64 internalId); Q_INVOKABLE void openTextureEditor(); + Q_INVOKABLE void updateSceneEnvState(); + Q_INVOKABLE void applyAsLightProbe(qint64 internalId); signals: void isEmptyChanged(); @@ -61,6 +67,9 @@ signals: void applyToSelectedMaterialTriggered(const QmlDesigner::ModelNode &texture); void applyToSelectedModelTriggered(const QmlDesigner::ModelNode &texture); void addNewTextureTriggered(); + void updateSceneEnvStateRequested(); + void hasSceneEnvChanged(); + void applyAsLightProbeRequested(const QmlDesigner::ModelNode &texture); private: bool isTextureVisible(int idx) const; @@ -74,6 +83,7 @@ private: int m_selectedIndex = 0; bool m_isEmpty = true; bool m_hasSingleModelSelection = false; + bool m_hasSceneEnv = false; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp index 37fd15000da..997e7ab563a 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp @@ -4,6 +4,7 @@ #include "materialbrowserview.h" #include "bindingproperty.h" +#include "createtexture.h" #include "materialbrowsermodel.h" #include "materialbrowsertexturesmodel.h" #include "materialbrowserwidget.h" @@ -186,6 +187,19 @@ WidgetInfo MaterialBrowserView::widgetInfo() connect(texturesModel, &MaterialBrowserTexturesModel::addNewTextureTriggered, this, [&] { emitCustomNotification("add_new_texture"); }); + + connect(texturesModel, &MaterialBrowserTexturesModel::updateSceneEnvStateRequested, this, [&]() { + ModelNode activeSceneEnv = CreateTexture(this).resolveSceneEnv(m_sceneId); + const bool sceneEnvExists = activeSceneEnv.isValid(); + m_widget->materialBrowserTexturesModel()->setHasSceneEnv(sceneEnvExists); + }); + + connect(texturesModel, &MaterialBrowserTexturesModel::applyAsLightProbeRequested, this, + [&] (const ModelNode &texture) { + executeInTransaction(__FUNCTION__, [&] { + CreateTexture(this).assignTextureAsLightProbe(texture, m_sceneId); + }); + }); } return createWidgetInfo(m_widget.data(), @@ -458,29 +472,6 @@ void MaterialBrowserView::active3DSceneChanged(qint32 sceneId) m_sceneId = sceneId; } -ModelNode MaterialBrowserView::resolveSceneEnv() -{ - ModelNode activeSceneEnv; - - if (m_sceneId != -1) { - ModelNode activeScene = active3DSceneNode(); - if (activeScene.isValid()) { - QmlObjectNode view3D; - if (activeScene.metaInfo().isQtQuick3DView3D()) { - view3D = activeScene; - } else { - ModelNode sceneParent = activeScene.parentProperty().parentModelNode(); - if (sceneParent.metaInfo().isQtQuick3DView3D()) - view3D = sceneParent; - } - if (view3D.isValid()) - activeSceneEnv = modelNodeForId(view3D.expression("environment")); - } - } - - return activeSceneEnv; -} - void MaterialBrowserView::instancesCompleted(const QVector &completedNodeList) { for (const ModelNode &node : completedNodeList) { From 4b6c1c9f14a8088e1550a5051adba8f2b92d0d5d Mon Sep 17 00:00:00 2001 From: Mats Honkamaa Date: Wed, 7 Dec 2022 15:33:26 +0200 Subject: [PATCH 104/131] Doc: Add missing views to overview page Task-number: QDS-8404 Change-Id: I071ee72d01f7bdf007fd4eee27e6e17f35f8b5e0 Reviewed-by: Leena Miettinen --- .../src/views/qtquick-designer.qdoc | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/doc/qtdesignstudio/src/views/qtquick-designer.qdoc b/doc/qtdesignstudio/src/views/qtquick-designer.qdoc index 4dfe24718b6..d7af982392d 100644 --- a/doc/qtdesignstudio/src/views/qtquick-designer.qdoc +++ b/doc/qtdesignstudio/src/views/qtquick-designer.qdoc @@ -51,6 +51,12 @@ \li Provides an editor for files you created using 3D graphics applications and stored in one of the supported formats. \li \l {3D} + \row + \li \l {Material Editor and Browser} + \li In the \uicontrol {Material Editor} and + \uicontrol {Material Browser} views, you create and manage materials and + textures. + \li \l {Material Editor and Browser} \row \li \l Components \li Contains preset components and your own components, that you can use @@ -121,6 +127,16 @@ \li \l{Open Documents} \li Shows currently open files. \li \l{Open Documents} + \row + \li \l{Content Library} + \li The \uicontrol {Content Library} view contains material, texture, + and environment bundles with assets that you can use in your project. + \li \l{Content Library} + \row + \li \l{Texture Editor} + \li In the \uicontrol {Texture Editor} view, you create and manage + textures. + \li \l{Texture Editor} \endtable \section1 Summary of Main Toolbar Actions From b530f938baf566c678318533b94211fa2746951d Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Wed, 7 Dec 2022 18:47:19 +0200 Subject: [PATCH 105/131] QmlDesigner: Keep search on when adding a material or texture Fixes: QDS-8487 Change-Id: Id02d693a5a707e97555450ce860ee4480e7742eb Reviewed-by: Miikka Heikkinen --- .../materialbrowser/materialbrowsermodel.cpp | 13 +++++++++++-- .../materialbrowser/materialbrowsermodel.h | 1 + .../materialbrowsertexturesmodel.cpp | 5 +++++ .../materialbrowser/materialbrowsertexturesmodel.h | 1 + .../materialbrowser/materialbrowserview.cpp | 7 +++++-- 5 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp index 2250dacf80f..f7af499a6f0 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp @@ -213,6 +213,11 @@ void MaterialBrowserModel::setSearchText(const QString &searchText) m_searchText = lowerSearchText; + refreshSearch(); +} + +void MaterialBrowserModel::refreshSearch() +{ bool isEmpty = false; // if selected material goes invisible, select nearest material @@ -256,9 +261,13 @@ void MaterialBrowserModel::setMaterials(const QList &materials, bool emit isEmptyChanged(); } - setHasQuick3DImport(hasQuick3DImport); + if (!m_searchText.isEmpty()) + refreshSearch(); + else + resetModel(); + updateSelectedMaterial(); - resetModel(); + setHasQuick3DImport(hasQuick3DImport); } void MaterialBrowserModel::removeMaterial(const ModelNode &material) diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h index c1286e62e25..71a6407632a 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h @@ -37,6 +37,7 @@ public: QHash roleNames() const override; void setSearchText(const QString &searchText); + void refreshSearch(); bool hasQuick3DImport() const; void setHasQuick3DImport(bool b); diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp index b732b9265e0..a24cd06bfa5 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp @@ -94,6 +94,11 @@ void MaterialBrowserTexturesModel::setSearchText(const QString &searchText) m_searchText = lowerSearchText; + refreshSearch(); +} + +void MaterialBrowserTexturesModel::refreshSearch() +{ bool isEmpty = false; // if selected texture goes invisible, select nearest one diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h index 53b27e68a6c..50a07623fa4 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h @@ -29,6 +29,7 @@ public: QHash roleNames() const override; void setSearchText(const QString &searchText); + void refreshSearch(); QList textures() const; void setTextures(const QList &textures); diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp index 997e7ab563a..99f858744d0 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp @@ -244,7 +244,6 @@ void MaterialBrowserView::refreshModel(bool updateImages) } } - m_widget->clearSearchFilter(); m_widget->materialBrowserModel()->setMaterials(materials, m_hasQuick3DImport); m_widget->materialBrowserTexturesModel()->setTextures(textures); m_widget->materialBrowserModel()->setHasMaterialLibrary(matLib.isValid()); @@ -366,9 +365,11 @@ void MaterialBrowserView::nodeReparented(const ModelNode &node, } int idx = m_widget->materialBrowserModel()->materialIndex(node); m_widget->materialBrowserModel()->selectMaterial(idx); + m_widget->materialBrowserModel()->refreshSearch(); } else { // is texture int idx = m_widget->materialBrowserTexturesModel()->textureIndex(node); m_widget->materialBrowserTexturesModel()->selectTexture(idx); + m_widget->materialBrowserTexturesModel()->refreshSearch(); } } @@ -452,8 +453,10 @@ void MaterialBrowserView::customNotification(const AbstractView *view, m_widget->materialBrowserModel()->selectMaterial(idx); } else if (identifier == "selected_texture_changed") { int idx = m_widget->materialBrowserTexturesModel()->textureIndex(nodeList.first()); - if (idx != -1) + if (idx != -1) { m_widget->materialBrowserTexturesModel()->selectTexture(idx); + m_widget->materialBrowserTexturesModel()->refreshSearch(); + } } else if (identifier == "refresh_material_browser") { QTimer::singleShot(0, model(), [this]() { refreshModel(true); From e70f909bcf01aed5b3369c0d307a10319b529adb Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Wed, 7 Dec 2022 23:16:50 +0200 Subject: [PATCH 106/131] QmlDesigner: Update texture editor and browser after source reset Fixes: QDS-8520 Change-Id: I9ceca64d75ef1c41633a89a79748265beecd16ea Reviewed-by: Miikka Heikkinen Reviewed-by: Ali Kianian --- .../components/materialbrowser/materialbrowserview.cpp | 8 ++++++++ .../components/materialbrowser/materialbrowserview.h | 1 + .../components/textureeditor/textureeditorview.cpp | 8 +++++++- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp index 99f858744d0..199852dc1af 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp @@ -334,6 +334,14 @@ void MaterialBrowserView::variantPropertiesChanged(const QList } } +void MaterialBrowserView::propertiesRemoved(const QList &propertyList) +{ + for (const AbstractProperty &prop : propertyList) { + if (isTexture(prop.parentModelNode()) && prop.name() == "source") + m_widget->materialBrowserTexturesModel()->updateTextureSource(prop.parentModelNode()); + } +} + void MaterialBrowserView::nodeReparented(const ModelNode &node, const NodeAbstractProperty &newPropertyParent, const NodeAbstractProperty &oldPropertyParent, diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h index 22e6c6a8d8f..0c1d216aa33 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h @@ -37,6 +37,7 @@ public: const QList &lastSelectedNodeList) override; void modelNodePreviewPixmapChanged(const ModelNode &node, const QPixmap &pixmap) override; void variantPropertiesChanged(const QList &propertyList, PropertyChangeFlags propertyChange) override; + void propertiesRemoved(const QList &propertyList) override; void nodeReparented(const ModelNode &node, const NodeAbstractProperty &newPropertyParent, const NodeAbstractProperty &oldPropertyParent, AbstractView::PropertyChangeFlags propertyChange) override; diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp b/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp index b1d3c210ecb..6ff4f43cfef 100644 --- a/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp @@ -555,7 +555,13 @@ void TextureEditorView::propertiesRemoved(const QList &propert m_qmlBackEnd->contextObject()->setHasAliasExport(QmlObjectNode(m_selectedTexture).isAliasExported()); if (node == m_selectedTexture || QmlObjectNode(m_selectedTexture).propertyChangeForCurrentState() == node) { - setValue(m_selectedTexture, property.name(), QmlObjectNode(m_selectedTexture).instanceValue(property.name())); + // TODO: workaround for bug QDS-8539. To be removed once it is fixed. + if (node.metaInfo().property(property.name()).propertyType().isUrl()) { + resetPuppet(); + } else { + setValue(m_selectedTexture, property.name(), + QmlObjectNode(m_selectedTexture).instanceValue(property.name())); + } } if (property.name() == "materials" && (node == m_selectedModel From 636c9524f994f32bd20a4683836ad2acd0c6893f Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Thu, 8 Dec 2022 12:35:57 +0200 Subject: [PATCH 107/131] QmlDesigner: Work around the issue in QtQuick3D 6.4 geometry caching QQuick3DGeometry::updateSpatialNode can create QSSGRenderGeometry object with exact same address as a previously created and subsequently deleted QSSGRenderGeometry object (i.e. the memory location is reused). If the previous node was not used for the exact same logical geometry, then you get these artifacts, as QSSGBufferManager uses QSSGRenderGeometry object pointers in QSSGBufferManager::loadRenderMesh function to determine if it needs to reload the geometry and what geometry gets used for each model. The cache still contains data for the deleted node, which is then matched for the new node with same address. This workaround ensures that none of our grid geometries will have the same generation id and thus will never get improperly matched in QSSGBufferManager cache. Task-number: QDS-8516 Change-Id: I017a4ae4a59eef2bb26ade5abf13e8f74f57c4af Reviewed-by: Mahmoud Badri Reviewed-by: Qt CI Bot --- .../qml2puppet/editor3d/gridgeometry.cpp | 32 +++++++++++++++++++ .../qml2puppet/editor3d/gridgeometry.h | 3 ++ 2 files changed, 35 insertions(+) diff --git a/src/tools/qml2puppet/qml2puppet/editor3d/gridgeometry.cpp b/src/tools/qml2puppet/qml2puppet/editor3d/gridgeometry.cpp index d2f04ef38f5..6bbe2365447 100644 --- a/src/tools/qml2puppet/qml2puppet/editor3d/gridgeometry.cpp +++ b/src/tools/qml2puppet/qml2puppet/editor3d/gridgeometry.cpp @@ -5,6 +5,10 @@ #include "gridgeometry.h" +#if QT_VERSION_MAJOR == 6 && QT_VERSION_MINOR == 4 +#include +#endif + namespace QmlDesigner { namespace Internal { @@ -82,6 +86,34 @@ void GridGeometry::doUpdateGeometry() QVector3D(vertexPtr[lastIndex][0], vertexPtr[lastIndex][1], 0.0)); } +#if QT_VERSION_MAJOR == 6 && QT_VERSION_MINOR == 4 +QSSGRenderGraphObject *GridGeometry::updateSpatialNode(QSSGRenderGraphObject *node) +{ + if (!node) { + markAllDirty(); + auto geometryNode = new QSSGRenderGeometry(); + node = geometryNode; + emit geometryNodeDirty(); + + // This is a work around for the issue of incorrect geometry objects getting matched for + // cached mesh data in QSSGBufferManager::loadRenderMesh in QtQuick3D in 6.4 (see QDS-8516). + // Each setting of stride value increments the generation id of the geometry node. + // By incrementing generation id by different amounts for each grid geometry node we have, + // we can ensure QSSGBufferManager cache never matches wrong mesh data. + // The cache should be cleared of old objects after they are unused for one frame, + // and we use 4 grid objects in total, so max of 8 different generation ids should ensure no + // invalid cache matches. + static int dirtyCount = 0; + if (++dirtyCount > 8) + dirtyCount = 0; + for (int i = 0; i < dirtyCount; ++i) + geometryNode->setStride(stride()); + } + + return QQuick3DGeometry::updateSpatialNode(node); +} +#endif + void GridGeometry::fillVertexData(QByteArray &vertexData) { const int numSubdivs = 1; // number of subdivision lines (i.e. lines between main grid lines) diff --git a/src/tools/qml2puppet/qml2puppet/editor3d/gridgeometry.h b/src/tools/qml2puppet/qml2puppet/editor3d/gridgeometry.h index 376c9d3bf4b..6df03a38efe 100644 --- a/src/tools/qml2puppet/qml2puppet/editor3d/gridgeometry.h +++ b/src/tools/qml2puppet/qml2puppet/editor3d/gridgeometry.h @@ -39,6 +39,9 @@ signals: protected: void doUpdateGeometry() override; +#if QT_VERSION_MAJOR == 6 && QT_VERSION_MINOR == 4 + QSSGRenderGraphObject *updateSpatialNode(QSSGRenderGraphObject *node) override; +#endif private: void fillVertexData(QByteArray &vertexData); From 37564d267bd1476753ab7cd31eafe6548a8d37e1 Mon Sep 17 00:00:00 2001 From: Aleksei German Date: Tue, 6 Dec 2022 14:42:29 +0100 Subject: [PATCH 108/131] QmlDesigner: Fix Connections status in ContextMenu Task-number: QDS-8521 Change-Id: Id0b835fb14b18563d4ecc0c1b2039b9bb181c32c Reviewed-by: Qt CI Bot Reviewed-by: Reviewed-by: Aleksei German --- .../componentcore/designeractionmanager.cpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp index 20027265685..488d7d2bb99 100644 --- a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #include @@ -610,31 +611,29 @@ public: { menu()->clear(); - menu()->setEnabled(true); - const auto selection = selectionContext(); + + bool showMenu = false; + auto cleanup = qScopeGuard([&]{ menu()->setEnabled(showMenu); }); + if (!selection.isValid()) return; if (!selection.singleNodeIsSelected()) return; - if (!action()->isEnabled()) - return; ModelNode currentNode = selection.currentSingleSelectedNode(); if (!currentNode.isValid()) return; + if (!currentNode.hasId()) + return; + showMenu = true; QmlObjectNode currentObjectNode(currentNode); QStringList signalsList = getSignalsList(currentNode); QList slotsLists = getSlotsLists(currentNode); - if (!currentNode.hasId()) { - menu()->setEnabled(false); - return; - } - for (const ModelNode &connectionNode : currentObjectNode.getAllConnections()) { for (const AbstractProperty &property : connectionNode.properties()) { if (property.isSignalHandlerProperty() && property.name() != "target") { From d0c9bc76cbf10c47f0daef2fe26d1f16ec2cb9f1 Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Thu, 8 Dec 2022 13:51:12 +0200 Subject: [PATCH 109/131] QmlDesigner: Update textures sources on state change Also small relevant fixes. Fixes: QDS-8523 Change-Id: I8125b124024ee18de8c70afbc9f633c3b9265e82 Reviewed-by: Miikka Heikkinen --- .../materialbrowsertexturesmodel.cpp | 12 +++++++++++- .../materialbrowsertexturesmodel.h | 5 +++-- .../materialbrowser/materialbrowserview.cpp | 17 +++++++++++++---- .../materialbrowser/materialbrowserview.h | 3 ++- 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp index a24cd06bfa5..571dfff1fb2 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp @@ -33,7 +33,7 @@ QVariant MaterialBrowserTexturesModel::data(const QModelIndex &index, int role) QByteArray roleName = roleNames().value(role); if (roleName == "textureSource") { - QString source = m_textureList.at(index.row()).variantProperty("source").value().toString(); + QString source = QmlObjectNode(m_textureList.at(index.row())).modelValue("source").toString(); if (source.isEmpty()) return {}; if (Utils::FilePath::fromString(source).isAbsolutePath()) @@ -184,6 +184,11 @@ void MaterialBrowserTexturesModel::updateTextureSource(const ModelNode &texture) emit dataChanged(index(idx, 0), index(idx, 0), {roleNames().key("textureSource")}); } +void MaterialBrowserTexturesModel::updateAllTexturesSources() +{ + emit dataChanged(index(0, 0), index(rowCount() - 1, 0), {roleNames().key("textureSource")}); +} + void MaterialBrowserTexturesModel::updateSelectedTexture() { selectTexture(m_selectedIndex, true); @@ -205,6 +210,11 @@ ModelNode MaterialBrowserTexturesModel::textureAt(int idx) const return {}; } +ModelNode MaterialBrowserTexturesModel::selectedTexture() const +{ + return textureAt(m_selectedIndex); +} + bool MaterialBrowserTexturesModel::hasSingleModelSelection() const { return m_hasSingleModelSelection; diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h index 50a07623fa4..112a0ccbb82 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h @@ -37,8 +37,10 @@ public: void deleteSelectedTexture(); void updateSelectedTexture(); void updateTextureSource(const ModelNode &texture); - int textureIndex(const ModelNode &material) const; + void updateAllTexturesSources(); + int textureIndex(const ModelNode &texture) const; ModelNode textureAt(int idx) const; + ModelNode selectedTexture() const; bool hasSingleModelSelection() const; void setHasSingleModelSelection(bool b); @@ -78,7 +80,6 @@ private: QString m_searchText; QList m_textureList; - ModelNode m_copiedMaterial; QHash m_textureIndexHash; // internalId -> index int m_selectedIndex = 0; diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp index 199852dc1af..118c4ca3b91 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp @@ -8,7 +8,6 @@ #include "materialbrowsermodel.h" #include "materialbrowsertexturesmodel.h" #include "materialbrowserwidget.h" -#include "modelnodeoperations.h" #include "nodeabstractproperty.h" #include "nodemetainfo.h" #include "qmlobjectnode.h" @@ -327,10 +326,15 @@ void MaterialBrowserView::variantPropertiesChanged(const QList for (const VariantProperty &property : propertyList) { ModelNode node(property.parentModelNode()); - if (isMaterial(node) && property.name() == "objectName") + if (isMaterial(node) && property.name() == "objectName") { m_widget->materialBrowserModel()->updateMaterialName(node); - else if (isTexture(node) && property.name() == "source") - m_widget->materialBrowserTexturesModel()->updateTextureSource(node); + } else if (property.name() == "source") { + QmlObjectNode selectedTex = m_widget->materialBrowserTexturesModel()->selectedTexture(); + if (isTexture(node)) + m_widget->materialBrowserTexturesModel()->updateTextureSource(node); + else if (selectedTex.propertyChangeForCurrentState() == node) + m_widget->materialBrowserTexturesModel()->updateTextureSource(selectedTex); + } } } @@ -483,6 +487,11 @@ void MaterialBrowserView::active3DSceneChanged(qint32 sceneId) m_sceneId = sceneId; } +void MaterialBrowserView::currentStateChanged([[maybe_unused]] const ModelNode &node) +{ + m_widget->materialBrowserTexturesModel()->updateAllTexturesSources(); +} + void MaterialBrowserView::instancesCompleted(const QVector &completedNodeList) { for (const ModelNode &node : completedNodeList) { diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h index 0c1d216aa33..f2a64e945d0 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h @@ -49,11 +49,12 @@ public: const QList &nodeList, const QList &data) override; void instancesCompleted(const QVector &completedNodeList) override; void instancePropertyChanged(const QList > &propertyList) override; + void active3DSceneChanged(qint32 sceneId) override; + void currentStateChanged(const ModelNode &node) override; void applyTextureToModel3D(const QmlObjectNode &model3D, const ModelNode &texture); void applyTextureToMaterial(const QList &materials, const ModelNode &texture); - void active3DSceneChanged(qint32 sceneId) override; Q_INVOKABLE void updatePropsModel(const QString &matId); Q_INVOKABLE void applyTextureToProperty(const QString &matId, const QString &propName); From 56450a8fe6839a5e50186ad3b92861a04c6a6636 Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Thu, 8 Dec 2022 16:37:07 +0100 Subject: [PATCH 110/131] QmlDesigner: Update application template for QDS projects This updates the application template to download the designer components. * Added a cmake configure option BUILD_QDS_COMPONENTS * We use the qds-3.9 branch of qtquickdesigner-components Change-Id: I046e2c7e648fe9d3cb6cf8d2cec8eb606432bccd Reviewed-by: Tim Jenssen --- .../projects/application-3d/wizard.json | 9 +++++ .../projects/application/wizard.json | 8 +++++ .../projects/common/CMakeLists.main.txt.tpl | 23 ++++++++++--- .../import_qml_components_plugins.h.tpl | 19 +++++++++++ .../projects/common/main.cpp.tpl | 1 + .../projects/common/qmlcomponents.tpl | 33 +++++++++++++++++++ .../projects/common/qmlmodules.tpl | 4 +-- .../projects/desktop-launcher/wizard.json | 8 +++++ .../projects/mobile-scroll/wizard.json | 8 +++++ .../projects/mobile-stack/wizard.json | 8 +++++ .../projects/mobile-swipe/wizard.json | 8 +++++ 11 files changed, 122 insertions(+), 7 deletions(-) create mode 100644 share/qtcreator/qmldesigner/studio_templates/projects/common/import_qml_components_plugins.h.tpl create mode 100644 share/qtcreator/qmldesigner/studio_templates/projects/common/qmlcomponents.tpl diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/application-3d/wizard.json b/share/qtcreator/qmldesigner/studio_templates/projects/application-3d/wizard.json index 6c6949442c6..84690258ea3 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/application-3d/wizard.json +++ b/share/qtcreator/qmldesigner/studio_templates/projects/application-3d/wizard.json @@ -299,6 +299,10 @@ "source": "../common/qmlmodules.tpl", "target": "%{ProjectDirectory}/qmlmodules" }, + { + "source": "../common/qmlcomponents.tpl", + "target": "%{ProjectDirectory}/qmlcomponents" + }, { "source": "../common/main.qml", "target": "%{ProjectDirectory}/main.qml" @@ -319,6 +323,11 @@ "source": "../common/import_qml_plugins.h.tpl", "target": "%{ProjectDirectory}/src/import_qml_plugins.h" }, + { + "source": "../common/import_qml_components_plugins.h.tpl", + "target": "%{ProjectDirectory}/src/import_qml_components_plugins.h" + }, + { "source": "../common/CMakeLists.content.txt.tpl", "target": "%{ProjectDirectory}/content/CMakeLists.txt" diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/application/wizard.json b/share/qtcreator/qmldesigner/studio_templates/projects/application/wizard.json index 235116a1f4c..85f248f957b 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/application/wizard.json +++ b/share/qtcreator/qmldesigner/studio_templates/projects/application/wizard.json @@ -295,6 +295,10 @@ "source": "../common/qmlmodules.tpl", "target": "%{ProjectDirectory}/qmlmodules" }, + { + "source": "../common/qmlcomponents.tpl", + "target": "%{ProjectDirectory}/qmlcomponents" + }, { "source": "../common/main.qml", "target": "%{ProjectDirectory}/main.qml" @@ -315,6 +319,10 @@ "source": "../common/import_qml_plugins.h.tpl", "target": "%{ProjectDirectory}/src/import_qml_plugins.h" }, + { + "source": "../common/import_qml_components_plugins.h.tpl", + "target": "%{ProjectDirectory}/src/import_qml_components_plugins.h" + }, { "source": "../common/CMakeLists.content.txt.tpl", "target": "%{ProjectDirectory}/content/CMakeLists.txt" diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/common/CMakeLists.main.txt.tpl b/share/qtcreator/qmldesigner/studio_templates/projects/common/CMakeLists.main.txt.tpl index f105c83c553..6878a1023c3 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/common/CMakeLists.main.txt.tpl +++ b/share/qtcreator/qmldesigner/studio_templates/projects/common/CMakeLists.main.txt.tpl @@ -1,24 +1,37 @@ -cmake_minimum_required(VERSION 3.18) +cmake_minimum_required(VERSION 3.21.1) + +set(BUILD_QDS_COMPONENTS ON CACHE BOOL "Build design studio components") project(%{ProjectName}App LANGUAGES CXX) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) -find_package(Qt6 COMPONENTS Gui Qml Quick) -qt_add_executable(%{ProjectExecutableName} src/main.cpp) +find_package(QT NAMES Qt6 COMPONENTS Gui Qml Quick) +find_package(Qt6 REQUIRED COMPONENTS Core Qml Quick) -qt_add_resources(%{ProjectExecutableName} "configuration" +qt_add_executable(${CMAKE_PROJECT_NAME} src/main.cpp) + +# qt_standard_project_setup() requires Qt 6.3 or higher. See https://doc.qt.io/qt-6/qt-standard-project-setup.html for details. +if (${QT_VERSION_MINOR} GREATER_EQUAL 3) +qt6_standard_project_setup() +endif() + +qt_add_resources(${CMAKE_PROJECT_NAME} "configuration" PREFIX "/" FILES qtquickcontrols2.conf ) -target_link_libraries(%{ProjectExecutableName} PRIVATE +target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Gui Qt${QT_VERSION_MAJOR}::Quick Qt${QT_VERSION_MAJOR}::Qml ) +if (${BUILD_QDS_COMPONENTS}) + include(${CMAKE_CURRENT_SOURCE_DIR}/qmlcomponents) +endif () + include(${CMAKE_CURRENT_SOURCE_DIR}/qmlmodules) diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/common/import_qml_components_plugins.h.tpl b/share/qtcreator/qmldesigner/studio_templates/projects/common/import_qml_components_plugins.h.tpl new file mode 100644 index 00000000000..25d0a98384e --- /dev/null +++ b/share/qtcreator/qmldesigner/studio_templates/projects/common/import_qml_components_plugins.h.tpl @@ -0,0 +1,19 @@ +/* + * This file is automatically generated by Qt Design Studio. + * Do not change. +*/ + +#include "qqmlextensionplugin.h" + +#ifdef BULD_QDS_COMPONENTS + +Q_IMPORT_QML_PLUGIN(QtQuick_Studio_ComponentsPlugin) +Q_IMPORT_QML_PLUGIN(QtQuick_Studio_EffectsPlugin) +Q_IMPORT_QML_PLUGIN(QtQuick_Studio_ApplicationPlugin) +Q_IMPORT_QML_PLUGIN(FlowViewPlugin) +Q_IMPORT_QML_PLUGIN(QtQuick_Studio_LogicHelperPlugin) +Q_IMPORT_QML_PLUGIN(QtQuick_Studio_MultiTextPlugin) +Q_IMPORT_QML_PLUGIN(QtQuick_Studio_EventSimulatorPlugin) +Q_IMPORT_QML_PLUGIN(QtQuick_Studio_EventSystemPlugin) + +#endif diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/common/main.cpp.tpl b/share/qtcreator/qmldesigner/studio_templates/projects/common/main.cpp.tpl index f10adc57d72..cd28d11f3e5 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/common/main.cpp.tpl +++ b/share/qtcreator/qmldesigner/studio_templates/projects/common/main.cpp.tpl @@ -5,6 +5,7 @@ #include #include "app_environment.h" +#include "import_qml_components_plugins.h" #include "import_qml_plugins.h" int main(int argc, char *argv[]) diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/common/qmlcomponents.tpl b/share/qtcreator/qmldesigner/studio_templates/projects/common/qmlcomponents.tpl new file mode 100644 index 00000000000..3619df33d33 --- /dev/null +++ b/share/qtcreator/qmldesigner/studio_templates/projects/common/qmlcomponents.tpl @@ -0,0 +1,33 @@ +### This file is automatically generated by Qt Design Studio. +### Do not change + +message("Building designer components.") + +set(QT_QML_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/qml") + +include(FetchContent) +FetchContent_Declare( + ds + GIT_TAG qds-3.9 + GIT_REPOSITORY https://code.qt.io/qt-labs/qtquickdesigner-components.git +) + +FetchContent_GetProperties(ds) +FetchContent_Populate(ds) + +target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE + QuickStudioComponentsplugin + QuickStudioEffectsplugin + QuickStudioApplicationplugin + FlowViewplugin + QuickStudioLogicHelperplugin + QuickStudioMultiTextplugin + QuickStudioEventSimulatorplugin + QuickStudioEventSystemplugin +) + +add_subdirectory(${ds_SOURCE_DIR} ${ds_BINARY_DIR}) + +target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE + BULD_QDS_COMPONENTS=true +) diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/common/qmlmodules.tpl b/share/qtcreator/qmldesigner/studio_templates/projects/common/qmlmodules.tpl index 1a3303600d4..fa3069a770a 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/common/qmlmodules.tpl +++ b/share/qtcreator/qmldesigner/studio_templates/projects/common/qmlmodules.tpl @@ -1,7 +1,7 @@ ### This file is automatically generated by Qt Design Studio. ### Do not change -qt6_add_qml_module(%{ProjectExecutableName} +qt6_add_qml_module(${CMAKE_PROJECT_NAME} URI "Main" VERSION 1.0 NO_PLUGIN @@ -11,7 +11,7 @@ qt6_add_qml_module(%{ProjectExecutableName} add_subdirectory(content) add_subdirectory(imports) -target_link_libraries(%{ProjectExecutableName} PRIVATE +target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE contentplugin %{ProjectPluginName} ) diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/desktop-launcher/wizard.json b/share/qtcreator/qmldesigner/studio_templates/projects/desktop-launcher/wizard.json index 851e3780899..92cbd476cdf 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/desktop-launcher/wizard.json +++ b/share/qtcreator/qmldesigner/studio_templates/projects/desktop-launcher/wizard.json @@ -293,6 +293,10 @@ "source": "../common/qmlmodules.tpl", "target": "%{ProjectDirectory}/qmlmodules" }, + { + "source": "../common/qmlcomponents.tpl", + "target": "%{ProjectDirectory}/qmlcomponents" + }, { "source": "../common/main.qml", "target": "%{ProjectDirectory}/main.qml" @@ -313,6 +317,10 @@ "source": "../common/import_qml_plugins.h.tpl", "target": "%{ProjectDirectory}/src/import_qml_plugins.h" }, + { + "source": "../common/import_qml_components_plugins.h.tpl", + "target": "%{ProjectDirectory}/src/import_qml_components_plugins.h" + }, { "source": "../common/CMakeLists.content.txt.tpl", "target": "%{ProjectDirectory}/content/CMakeLists.txt" diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/mobile-scroll/wizard.json b/share/qtcreator/qmldesigner/studio_templates/projects/mobile-scroll/wizard.json index 37e40ff8baf..9b3fe1be77a 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/mobile-scroll/wizard.json +++ b/share/qtcreator/qmldesigner/studio_templates/projects/mobile-scroll/wizard.json @@ -252,6 +252,10 @@ "source": "../common/qmlmodules.tpl", "target": "%{ProjectDirectory}/qmlmodules" }, + { + "source": "../common/qmlcomponents.tpl", + "target": "%{ProjectDirectory}/qmlcomponents" + }, { "source": "../common/main.qml", "target": "%{ProjectDirectory}/main.qml" @@ -272,6 +276,10 @@ "source": "../common/import_qml_plugins.h.tpl", "target": "%{ProjectDirectory}/src/import_qml_plugins.h" }, + { + "source": "../common/import_qml_components_plugins.h.tpl", + "target": "%{ProjectDirectory}/src/import_qml_components_plugins.h" + }, { "source": "../common/CMakeLists.content.txt.tpl", "target": "%{ProjectDirectory}/content/CMakeLists.txt" diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/mobile-stack/wizard.json b/share/qtcreator/qmldesigner/studio_templates/projects/mobile-stack/wizard.json index 642cd7a8e46..a10fe57da6c 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/mobile-stack/wizard.json +++ b/share/qtcreator/qmldesigner/studio_templates/projects/mobile-stack/wizard.json @@ -249,6 +249,10 @@ "source": "../common/qmlmodules.tpl", "target": "%{ProjectDirectory}/qmlmodules" }, + { + "source": "../common/qmlcomponents.tpl", + "target": "%{ProjectDirectory}/qmlcomponents" + }, { "source": "../common/main.qml", "target": "%{ProjectDirectory}/main.qml" @@ -269,6 +273,10 @@ "source": "../common/import_qml_plugins.h.tpl", "target": "%{ProjectDirectory}/src/import_qml_plugins.h" }, + { + "source": "../common/import_qml_components_plugins.h.tpl", + "target": "%{ProjectDirectory}/src/import_qml_components_plugins.h" + }, { "source": "CMakeLists.content.txt.tpl", "target": "%{ProjectDirectory}/content/CMakeLists.txt" diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/mobile-swipe/wizard.json b/share/qtcreator/qmldesigner/studio_templates/projects/mobile-swipe/wizard.json index a2ece501ad2..77c31a7d3bf 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/mobile-swipe/wizard.json +++ b/share/qtcreator/qmldesigner/studio_templates/projects/mobile-swipe/wizard.json @@ -249,6 +249,10 @@ "source": "../common/qmlmodules.tpl", "target": "%{ProjectDirectory}/qmlmodules" }, + { + "source": "../common/qmlcomponents.tpl", + "target": "%{ProjectDirectory}/qmlcomponents" + }, { "source": "../common/main.qml", "target": "%{ProjectDirectory}/main.qml" @@ -269,6 +273,10 @@ "source": "../common/import_qml_plugins.h.tpl", "target": "%{ProjectDirectory}/src/import_qml_plugins.h" }, + { + "source": "../common/import_qml_components_plugins.h.tpl", + "target": "%{ProjectDirectory}/src/import_qml_components_plugins.h" + }, { "source": "CMakeLists.content.txt.tpl", "target": "%{ProjectDirectory}/content/CMakeLists.txt" From 59505310a5b46e49ccd1db8e2e3e1d15aa1f3e70 Mon Sep 17 00:00:00 2001 From: Burak Hancerli Date: Fri, 9 Dec 2022 09:43:01 +0100 Subject: [PATCH 111/131] QmlDesigner: fix QDS does not quit when closing it while Splash screen is open macOS Setting modality prevents the window closing even the mainwindow is being closed. Task-number: QDS-8540 Change-Id: Ifa2b185c0548128b35ba680d2bc8bf8c856d7c37 Reviewed-by: Tim Jenssen --- src/plugins/studiowelcome/studiowelcomeplugin.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/plugins/studiowelcome/studiowelcomeplugin.cpp b/src/plugins/studiowelcome/studiowelcomeplugin.cpp index 818aeffb9d7..f8a9b5c331d 100644 --- a/src/plugins/studiowelcome/studiowelcomeplugin.cpp +++ b/src/plugins/studiowelcome/studiowelcomeplugin.cpp @@ -535,7 +535,6 @@ void StudioWelcomePlugin::extensionsInitialized() s_viewWindow->setFlag(Qt::FramelessWindowHint); - s_viewWindow->setModality(Qt::ApplicationModal); s_viewWindow->engine()->addImportPath("qrc:/studiofonts"); #ifdef QT_DEBUG s_viewWindow->engine()->addImportPath(QLatin1String(STUDIO_QML_PATH) @@ -561,9 +560,13 @@ void StudioWelcomePlugin::extensionsInitialized() s_viewWindow->setPosition((mainWindow->width() - s_viewWindow->width()) / 2, (mainWindow->height() - s_viewWindow->height()) / 2); - s_viewWindow->show(); - s_viewWindow->raise(); + Core::ICore::mainWindow()->setEnabled(false); + connect(s_viewWindow, &QObject::destroyed, []() { + if (Core::ICore::mainWindow()) + Core::ICore::mainWindow()->setEnabled(true); + }); + s_viewWindow->show(); s_viewWindow->requestActivate(); } else { s_viewWidget = new QQuickWidget(Core::ICore::dialogParent()); From e990b828a99be74d8afd88b900039b9cfba85310 Mon Sep 17 00:00:00 2001 From: Mats Honkamaa Date: Fri, 9 Dec 2022 11:16:04 +0200 Subject: [PATCH 112/131] Doc: Document password protection function for shared online apps You can now password protect applications that you share online. Task-number: QDS-8538 Change-Id: I856946de3407d1e0b833a8cbf8c3e881ae45414c Reviewed-by: Leena Miettinen --- doc/qtdesignstudio/images/share-online.webp | Bin 6472 -> 7362 bytes .../src/overviews/qt-design-viewer.qdoc | 8 ++++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/doc/qtdesignstudio/images/share-online.webp b/doc/qtdesignstudio/images/share-online.webp index c91661f18cbbd2aeae884ccaf05c6bc0e8bf8b42..7ba08aa4271b901a5a8b3f57385d642ea4225d19 100644 GIT binary patch literal 7362 zcmWIYbaUG!!@v;k>J$(bU=hK^00Fv;3<8V{VF4BjZZZrE8;Tg_GHU5D3NqT|noaju zqO?d+rI53ufwwa??@!H)zYiB&Ut;y~=$)mp6}9{mWVGrSsyqKzU%q_d{0x5c`XBq2 z+pn*3uQIJa_y7Mp`M>#pr~m)|PoClaPPuLIe{B2rAFuNMf9EU1|NpNT|8HJfzxMw5 z-_ifp{#1M}|7gDc-}X<|)7Io!~fKM zW%y|Ki~T$Q*Za@yrRomYPpr55@A^;u`}9BSFO|Qy|G59q|9O88e%tvo|NH!(`|sCZ z{CDYZ(|^@}&Hpa{UjH-x-~3nh|NsC0UHAX?+tuHG-#GqF`oH`i!7Uq}`n1L7nV;70 zak;uj<@(lak!^(rE7|8=6Fcy8dEPcz&u{(_0vkWx zgNS({)GZ2vb&$y>|D7cDLL5cFV*Ua#dA6I#UsNF~+v}U47oJETf-& zNz0e^m8`3~(XF3zh#?95P_bdp4Oh+9oXosO#Uu94(7EvGywkR0 zkLPa6{9al*^`2MbIrSAnmT!e#l}z2fyCA5oZ|j4DwiSNrPdXqREUW4>_M6 zs*A7l&KBK!#_r_lL<*J)pvD&h1jjml3R16cwyI>s|T0LJI@GzsPx~a=Tq%W0YR$?dG#B7 z*_QjB^?SDI{{J@PkY~}U?Nk4ks%z?etrgzMpy}ytb~ochld{6o`D#4(6Wz*XvNu0v zn6&cFtPQn4{T{29UeAmDQS`#Cx>e_MJe%<0pEuX8vA^NS@8Zw0@4vKbMds5pOkDC! zZ*KW(cx_~!$XO+mXtUutR@>FF7_E8H-W

HF-a%pH--i{Qp5d;in* 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 delta 6443 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 diff --git a/doc/qtdesignstudio/src/overviews/qt-design-viewer.qdoc b/doc/qtdesignstudio/src/overviews/qt-design-viewer.qdoc index 9d869a72d0d..069ad71f274 100644 --- a/doc/qtdesignstudio/src/overviews/qt-design-viewer.qdoc +++ b/doc/qtdesignstudio/src/overviews/qt-design-viewer.qdoc @@ -31,11 +31,15 @@ \list 1 \li Open the application in \QDS. \li Select \uicontrol File > \uicontrol {Share Application Online}. - \li In the dialog, select \uicontrol Share. + \li Optionally, select \uicontrol {Protect with password}, and enter a password to prevent + unauthorized viewing of the application. + \note If you share the same application again, you must set the password + again. Otherwise, the application is not password protected any longer. + \li Select \uicontrol Share. \image share-online.webp \endlist - In the dialog, you can now open the application in a web + You can now open the application in a web browser, copy the link to share with others, or manage your shared applications. From 944ba4a5be06393f887a06e0e22597e4ec90c407 Mon Sep 17 00:00:00 2001 From: Amr Essam Date: Fri, 9 Dec 2022 02:52:31 -0800 Subject: [PATCH 113/131] QmlDesigner: fix adding effect to 2D does not work on windows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Effect cannot be added in windows, during to latest changes in QDS-7344 The QFileSystemModel::dataChanged doesn't emit with some file types So I used Utils::FileSystemWatcher for watching files changes Task-number: QDS-8452 Change-Id: Id381a78556a3dad56268cec506a0182d4343f0a2 Reviewed-by: Reviewed-by: Tomi Korpipää Reviewed-by: Tim Jenssen --- .../assetslibrary/assetslibrarymodel.cpp | 28 +++++++++---------- .../assetslibrary/assetslibrarymodel.h | 3 +- .../componentcore/modelnodeoperations.cpp | 2 +- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp index 864660a1476..6587c89cf32 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp @@ -35,24 +35,17 @@ void AssetsLibraryModel::createBackendModel() setSourceModel(m_sourceFsModel); QObject::connect(m_sourceFsModel, &QFileSystemModel::directoryLoaded, this, &AssetsLibraryModel::directoryLoaded); - QObject::connect(m_sourceFsModel, &QFileSystemModel::dataChanged, this, &AssetsLibraryModel::onDataChanged); QObject::connect(m_sourceFsModel, &QFileSystemModel::directoryLoaded, this, [this]([[maybe_unused]] const QString &dir) { - syncHaveFiles(); - }); -} + syncHaveFiles(); + }); -void AssetsLibraryModel::onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, - [[maybe_unused]] const QList &roles) -{ - for (int i = topLeft.row(); i <= bottomRight.row(); ++i) { - QModelIndex index = m_sourceFsModel->index(i, 0, topLeft.parent()); - QString path = m_sourceFsModel->filePath(index); - - if (!isDirectory(path)) - emit fileChanged(path); - } + m_fileWatcher = new Utils::FileSystemWatcher(parent()); + QObject::connect(m_fileWatcher, &Utils::FileSystemWatcher::fileChanged, this, + [this] (const QString &path) { + emit fileChanged(path); + }); } void AssetsLibraryModel::destroyBackendModel() @@ -61,6 +54,10 @@ void AssetsLibraryModel::destroyBackendModel() m_sourceFsModel->disconnect(this); m_sourceFsModel->deleteLater(); m_sourceFsModel = nullptr; + + m_fileWatcher->disconnect(this); + m_fileWatcher->deleteLater(); + m_fileWatcher = nullptr; } void AssetsLibraryModel::setSearchText(const QString &searchText) @@ -196,6 +193,9 @@ bool AssetsLibraryModel::filterAcceptsRow(int sourceRow, const QModelIndex &sour QModelIndex sourceIdx = m_sourceFsModel->index(sourceRow, 0, sourceParent); QString sourcePath = m_sourceFsModel->filePath(sourceIdx); + if (QFileInfo(sourcePath).isFile() && !m_fileWatcher->watchesFile(sourcePath)) + m_fileWatcher->addFile(sourcePath, Utils::FileSystemWatcher::WatchModifiedDate); + if (!m_searchText.isEmpty() && path.startsWith(m_rootPath) && QFileInfo{path}.isDir()) { QString sourceName = m_sourceFsModel->fileName(sourceIdx); diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h index f73d7fb7e3f..99c96017084 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h @@ -8,6 +8,7 @@ #include #include +#include namespace QmlDesigner { @@ -73,7 +74,6 @@ signals: private: void setHaveFiles(bool value); bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; - void onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList &roles); void resetModel(); void createBackendModel(); void destroyBackendModel(); @@ -84,6 +84,7 @@ private: QString m_rootPath; QFileSystemModel *m_sourceFsModel = nullptr; bool m_haveFiles = false; + Utils::FileSystemWatcher *m_fileWatcher = nullptr; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp index 3a3dd7d6838..711b84c3798 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp +++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp @@ -1725,7 +1725,7 @@ bool validateEffect(const QString &effectPath) msgBox.setDefaultButton(QMessageBox::Yes); msgBox.setIcon(QMessageBox::Question); if (msgBox.exec() == QMessageBox::Yes) - ModelNodeOperations::openEffectMaker(effectName); + ModelNodeOperations::openEffectMaker(effectPath); return false; } return true; From df622c9c3cb22268c0d21bfcecb8f5be9590afa8 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Fri, 9 Dec 2022 14:52:35 +0200 Subject: [PATCH 114/131] QmlDesigner: Fix expand all and collapse all in content library Fixes: QDS-8413 Change-Id: I98931c52f63bbbfd7a42e522a762d77b469df5da Reviewed-by: Mahmoud Badri --- .../contentLibraryQmlSource/ContentLibraryMaterialsView.qml | 2 ++ .../contentLibraryQmlSource/ContentLibraryTexturesView.qml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialsView.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialsView.qml index 57c507c3ff1..50c0336a483 100644 --- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialsView.qml +++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialsView.qml @@ -61,6 +61,8 @@ HelperWidgets.ScrollView { expanded: bundleCategoryExpanded expandOnClick: false onToggleExpand: bundleCategoryExpanded = !bundleCategoryExpanded + onExpand: bundleCategoryExpanded = true + onCollapse: bundleCategoryExpanded = false function expandSection() { bundleCategoryExpanded = true diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexturesView.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexturesView.qml index b80ef7471ae..c17dcd3a6fb 100644 --- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexturesView.qml +++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexturesView.qml @@ -57,6 +57,8 @@ HelperWidgets.ScrollView { expanded: bundleCategoryExpanded expandOnClick: false onToggleExpand: bundleCategoryExpanded = !bundleCategoryExpanded + onExpand: bundleCategoryExpanded = true + onCollapse: bundleCategoryExpanded = false function expandSection() { bundleCategoryExpanded = true From 6c1545bcb3df22031663dd65807cd75410a5d94b Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Fri, 9 Dec 2022 16:19:17 +0200 Subject: [PATCH 115/131] QmlDesigner: Apply gamma correction on generated icons of HDR images Fixes: QDS-8438 Change-Id: Ibd1c1951c8c5e7f1c050b145167f17c8fefadeaa Reviewed-by: Mahmoud Badri --- src/plugins/qmldesigner/utils/hdrimage.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/plugins/qmldesigner/utils/hdrimage.cpp b/src/plugins/qmldesigner/utils/hdrimage.cpp index 4c1c49b08e6..291784efd0b 100644 --- a/src/plugins/qmldesigner/utils/hdrimage.cpp +++ b/src/plugins/qmldesigner/utils/hdrimage.cpp @@ -19,6 +19,8 @@ typedef unsigned char RGBE[4]; #define B 2 #define E 3 +constexpr float GAMMA = 1.f / 2.2f; + QByteArray fileToByteArray(QString const &filename) { QFile file(filename); @@ -38,7 +40,11 @@ float convertComponent(int exponent, int val) { float v = val / (256.0f); float d = powf(2.0f, (float)exponent - 128.0f); - return v * d; + float converted = v * d; + + // Apply gamma correction to brighten the image. + // This will approximately match the default (i.e. linear) tonemapping of SceneEnvironment + return powf(converted, GAMMA); } void decrunchScanline(const char *&p, const char *pEnd, RGBE *scanline, int w) From c2f8677e9fae366a56510b041eb7f1d6b57741ad Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Tue, 29 Nov 2022 17:26:55 +0100 Subject: [PATCH 116/131] QmlDesigner: Move actions from toolbar to context menu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change-Id: Ia8513f21ecd82d238e94bf9e59d54685c52c0d46 Reviewed-by: Henning Gründl --- .../componentcore/componentcore_constants.h | 1 + .../componentcore/designeractionmanager.cpp | 15 ++-- .../formeditor/formeditorwidget.cpp | 76 ++++++++++++------- .../components/formeditor/formeditorwidget.h | 6 +- 4 files changed, 64 insertions(+), 34 deletions(-) diff --git a/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h b/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h index 090d4740fe3..6c3ced148df 100644 --- a/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h +++ b/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h @@ -19,6 +19,7 @@ const char editCategory[] = "Edit"; const char anchorsCategory[] = "Anchors"; const char positionCategory[] = "Position"; const char groupCategory[] = "Group"; +const char snappingCategory[] = "Snapping"; const char layoutCategory[] = "Layout"; const char flowCategory[] = "Flow"; const char flowEffectCategory[] = "FlowEffect"; diff --git a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp index 488d7d2bb99..1e71935244b 100644 --- a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp @@ -1529,11 +1529,16 @@ void DesignerActionManager::createDefaultDesignerActions() priorityPositionCategory, &positionOptionVisible)); - addDesignerAction(new ActionGroup( - layoutCategoryDisplayName, - layoutCategory, - priorityLayoutCategory, - &layoutOptionVisible)); + addDesignerAction(new ActionGroup(layoutCategoryDisplayName, + layoutCategory, + priorityLayoutCategory, + &layoutOptionVisible)); + + addDesignerAction(new ActionGroup("Snapping", + snappingCategory, + priorityLayoutCategory + 10, + &selectionEnabled, + &selectionEnabled)); addDesignerAction(new ActionGroup(groupCategoryDisplayName, groupCategory, diff --git a/src/plugins/qmldesigner/components/formeditor/formeditorwidget.cpp b/src/plugins/qmldesigner/components/formeditor/formeditorwidget.cpp index ec847d04245..0be675b957d 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditorwidget.cpp +++ b/src/plugins/qmldesigner/components/formeditor/formeditorwidget.cpp @@ -6,6 +6,7 @@ #include "designersettings.h" #include "formeditoritem.h" #include "formeditorscene.h" +#include "modelnodecontextmenu_helper.h" #include "qmldesignerconstants.h" #include "qmldesignericons.h" #include "qmldesignerplugin.h" @@ -67,47 +68,49 @@ FormEditorWidget::FormEditorWidget(FormEditorView *view) auto layoutActionGroup = new QActionGroup(this); layoutActionGroup->setExclusive(true); - m_noSnappingAction = layoutActionGroup->addAction(tr("No snapping.")); + m_noSnappingAction = layoutActionGroup->addAction(tr("No snapping")); m_noSnappingAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); m_noSnappingAction->setCheckable(true); m_noSnappingAction->setChecked(true); - m_noSnappingAction->setIcon(Icons::NO_SNAPPING.icon()); - registerActionAsCommand(m_noSnappingAction, Constants::FORMEDITOR_NO_SNAPPING, QKeySequence(Qt::Key_T)); - m_snappingAndAnchoringAction = layoutActionGroup->addAction(tr("Snap to parent or sibling components and generate anchors.")); + registerActionAsCommand(m_noSnappingAction, + Constants::FORMEDITOR_NO_SNAPPING, + QKeySequence(Qt::Key_T), + ComponentCoreConstants::snappingCategory, + 40); + + m_snappingAndAnchoringAction = layoutActionGroup->addAction(tr("Snap with Anchors")); m_snappingAndAnchoringAction->setCheckable(true); m_snappingAndAnchoringAction->setChecked(true); - m_snappingAndAnchoringAction->setIcon(Icons::NO_SNAPPING_AND_ANCHORING.icon()); - registerActionAsCommand(m_snappingAndAnchoringAction, Constants::FORMEDITOR_NO_SNAPPING_AND_ANCHORING, QKeySequence(Qt::Key_W)); - m_snappingAction = layoutActionGroup->addAction(tr("Snap to parent or sibling components but do not generate anchors.")); + registerActionAsCommand(m_snappingAndAnchoringAction, + Constants::FORMEDITOR_NO_SNAPPING_AND_ANCHORING, + QKeySequence(Qt::Key_W), + ComponentCoreConstants::snappingCategory, + 10); + + m_snappingAction = layoutActionGroup->addAction(tr("Snap without anchors")); m_snappingAction->setCheckable(true); m_snappingAction->setChecked(true); - m_snappingAction->setIcon(Icons::SNAPPING.icon()); - registerActionAsCommand(m_snappingAction, Constants::FORMEDITOR_SNAPPING, QKeySequence(Qt::Key_E)); + + registerActionAsCommand(m_snappingAction, + Constants::FORMEDITOR_SNAPPING, + QKeySequence(Qt::Key_E), + ComponentCoreConstants::snappingCategory, + 20); addActions(layoutActionGroup->actions()); - upperActions.append(layoutActionGroup->actions()); - auto separatorAction = new QAction(this); - separatorAction->setSeparator(true); - addAction(separatorAction); - upperActions.append(separatorAction); - - m_showBoundingRectAction = new QAction(Utils::Icons::BOUNDING_RECT.icon(), - tr("Show bounding rectangles and stripes for empty components."), - this); + m_showBoundingRectAction = new QAction(tr("Show bounds"), this); m_showBoundingRectAction->setCheckable(true); m_showBoundingRectAction->setChecked(false); - registerActionAsCommand(m_showBoundingRectAction, Constants::FORMEDITOR_NO_SHOW_BOUNDING_RECTANGLE, QKeySequence(Qt::Key_A)); + registerActionAsCommand(m_showBoundingRectAction, + Constants::FORMEDITOR_NO_SHOW_BOUNDING_RECTANGLE, + QKeySequence(Qt::Key_A), + ComponentCoreConstants::rootCategory, + 10); addAction(m_showBoundingRectAction.data()); - upperActions.append(m_showBoundingRectAction.data()); - - separatorAction = new QAction(this); - separatorAction->setSeparator(true); - addAction(separatorAction); - upperActions.append(separatorAction); m_rootWidthAction = new LineEditAction(tr("Override Width"), this); m_rootWidthAction->setToolTip(tr("Override width of root component.")); @@ -266,7 +269,11 @@ FormEditorWidget::FormEditorWidget(FormEditorView *view) connect(m_zoomSelectionAction.data(), &QAction::triggered, frameSelection); m_resetAction = new QAction(Utils::Icons::RESET_TOOLBAR.icon(), tr("Reset View"), this); - registerActionAsCommand(m_resetAction, Constants::FORMEDITOR_REFRESH, QKeySequence(Qt::Key_R)); + registerActionAsCommand(m_resetAction, + Constants::FORMEDITOR_REFRESH, + QKeySequence(Qt::Key_R), + ComponentCoreConstants::rootCategory, + 0); addAction(m_resetAction.data()); upperActions.append(m_resetAction.data()); @@ -334,12 +341,25 @@ void FormEditorWidget::changeBackgound(const QColor &color) } } -void FormEditorWidget::registerActionAsCommand(QAction *action, Utils::Id id, const QKeySequence &keysequence) +void FormEditorWidget::registerActionAsCommand( + QAction *action, Utils::Id id, const QKeySequence &, const QByteArray &category, int priority) { Core::Context context(Constants::C_QMLFORMEDITOR); Core::Command *command = Core::ActionManager::registerAction(action, id, context); - command->setDefaultKeySequence(keysequence); + + DesignerActionManager &designerActionManager = QmlDesignerPlugin::instance() + ->viewManager() + .designerActionManager(); + + designerActionManager.addCreatorCommand(command, category, priority); + + connect(command->action(), &QAction::enabledChanged, command, [command](bool b) { + command->action()->setVisible(b); + }); + + command->action()->setVisible(command->action()->isEnabled()); + command->augmentActionWithShortcutToolTip(action); } diff --git a/src/plugins/qmldesigner/components/formeditor/formeditorwidget.h b/src/plugins/qmldesigner/components/formeditor/formeditorwidget.h index 7931af6ec51..413b5f16c24 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditorwidget.h +++ b/src/plugins/qmldesigner/components/formeditor/formeditorwidget.h @@ -82,7 +82,11 @@ private: void changeRootItemWidth(const QString &widthText); void changeRootItemHeight(const QString &heightText); void changeBackgound(const QColor &color); - void registerActionAsCommand(QAction *action, Utils::Id id, const QKeySequence &keysequence); + void registerActionAsCommand(QAction *action, + Utils::Id id, + const QKeySequence &keysequence, + const QByteArray &category = {}, + int priority = 0); QPointer m_formEditorView; QPointer m_graphicsView; From 56e242c9f9aaab8cf5a25f41b77b26f9304ff0d5 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Mon, 12 Dec 2022 14:13:31 +0200 Subject: [PATCH 117/131] QmlDesigner: Disable 'add to selected model' option when it's not valid MaterialBrowserTexturesModel's hasSingleModelSelection property value is now updated at context menu open, as that's the only place using it. This is similar to how hasSceneEnv is handled. Fixes: QDS-8582 Change-Id: I63871a5557c90a06633eee52840b267d808bfe27 Reviewed-by: Mahmoud Badri --- .../TextureBrowserContextMenu.qml | 1 + .../materialbrowsertexturesmodel.cpp | 5 +++ .../materialbrowsertexturesmodel.h | 5 +-- .../materialbrowser/materialbrowserview.cpp | 34 +++++++++++++++---- .../materialbrowser/materialbrowserview.h | 1 + 5 files changed, 37 insertions(+), 9 deletions(-) diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureBrowserContextMenu.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureBrowserContextMenu.qml index 4ac0d72635f..4323998ee0f 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureBrowserContextMenu.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureBrowserContextMenu.qml @@ -16,6 +16,7 @@ StudioControls.Menu { { this.targetTexture = targetTexture materialBrowserTexturesModel.updateSceneEnvState() + materialBrowserTexturesModel.updateModelSelectionState() popup() } diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp index 571dfff1fb2..e3ed0870b22 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp @@ -316,4 +316,9 @@ void MaterialBrowserTexturesModel::applyAsLightProbe(qint64 internalId) } } +void MaterialBrowserTexturesModel::updateModelSelectionState() +{ + emit updateModelSelectionStateRequested(); +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h index 112a0ccbb82..c99d7511ea9 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h @@ -16,8 +16,7 @@ class MaterialBrowserTexturesModel : public QAbstractListModel Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged) Q_PROPERTY(int selectedIndex MEMBER m_selectedIndex NOTIFY selectedIndexChanged) - Q_PROPERTY(bool hasSingleModelSelection READ hasSingleModelSelection - WRITE setHasSingleModelSelection NOTIFY hasSingleModelSelectionChanged) + Q_PROPERTY(bool hasSingleModelSelection READ hasSingleModelSelection NOTIFY hasSingleModelSelectionChanged) Q_PROPERTY(bool hasSceneEnv READ hasSceneEnv NOTIFY hasSceneEnvChanged) public: @@ -60,6 +59,7 @@ public: Q_INVOKABLE void applyToSelectedModel(qint64 internalId); Q_INVOKABLE void openTextureEditor(); Q_INVOKABLE void updateSceneEnvState(); + Q_INVOKABLE void updateModelSelectionState(); Q_INVOKABLE void applyAsLightProbe(qint64 internalId); signals: @@ -71,6 +71,7 @@ signals: void applyToSelectedModelTriggered(const QmlDesigner::ModelNode &texture); void addNewTextureTriggered(); void updateSceneEnvStateRequested(); + void updateModelSelectionStateRequested(); void hasSceneEnvChanged(); void applyAsLightProbeRequested(const QmlDesigner::ModelNode &texture); diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp index 118c4ca3b91..be3a45ad2ab 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp @@ -193,6 +193,13 @@ WidgetInfo MaterialBrowserView::widgetInfo() m_widget->materialBrowserTexturesModel()->setHasSceneEnv(sceneEnvExists); }); + connect(texturesModel, &MaterialBrowserTexturesModel::updateModelSelectionStateRequested, this, [&]() { + bool hasModel = false; + if (m_selectedModels.size() == 1) + hasModel = getMaterialOfModel(m_selectedModels.at(0)).isValid(); + m_widget->materialBrowserTexturesModel()->setHasSingleModelSelection(hasModel); + }); + connect(texturesModel, &MaterialBrowserTexturesModel::applyAsLightProbeRequested, this, [&] (const ModelNode &texture) { executeInTransaction(__FUNCTION__, [&] { @@ -290,7 +297,6 @@ void MaterialBrowserView::selectedNodesChanged(const QList &selectedN }); m_widget->materialBrowserModel()->setHasModelSelection(!m_selectedModels.isEmpty()); - m_widget->materialBrowserTexturesModel()->setHasSingleModelSelection(m_selectedModels.size() == 1); // the logic below selects the material of the first selected model if auto selection is on if (!m_autoSelectModelMaterial) @@ -299,13 +305,8 @@ void MaterialBrowserView::selectedNodesChanged(const QList &selectedN if (selectedNodeList.size() > 1 || m_selectedModels.isEmpty()) return; - QmlObjectNode qmlObjNode(m_selectedModels.at(0)); - QString matExp = qmlObjNode.expression("materials"); - if (matExp.isEmpty()) - return; + ModelNode mat = getMaterialOfModel(m_selectedModels.at(0)); - QString matId = matExp.remove('[').remove(']').split(',', Qt::SkipEmptyParts).at(0); - ModelNode mat = modelNodeForId(matId); if (!mat.isValid()) return; @@ -435,6 +436,25 @@ void MaterialBrowserView::requestPreviews() m_previewRequests.clear(); } +ModelNode MaterialBrowserView::getMaterialOfModel(const ModelNode &model) +{ + QmlObjectNode qmlObjNode(model); + QString matExp = qmlObjNode.expression("materials"); + if (matExp.isEmpty()) + return {}; + + const QStringList mats = matExp.remove('[').remove(']').split(',', Qt::SkipEmptyParts); + if (mats.isEmpty()) + return {}; + + for (const auto &matId : mats) { + ModelNode mat = modelNodeForId(matId); + if (mat.isValid()) + return mat; + } + return {}; +} + void MaterialBrowserView::importsChanged([[maybe_unused]] const QList &addedImports, [[maybe_unused]] const QList &removedImports) { diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h index f2a64e945d0..b78d54e1660 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h @@ -70,6 +70,7 @@ private: void loadPropertyGroups(); void requestPreviews(); ModelNode resolveSceneEnv(); + ModelNode getMaterialOfModel(const ModelNode &model); AsynchronousImageCache &m_imageCache; QPointer m_widget; From 430998d7698193fc41d807f164223c839b23c1af Mon Sep 17 00:00:00 2001 From: Vikas Pachdha Date: Mon, 12 Dec 2022 17:03:26 +0100 Subject: [PATCH 118/131] Update interface to let refreshing files in code model The use case is to wait for the udpate to finish Task-number: QDS-8469 Change-Id: Ia3871a5557c90a06b33eee52840b267d808cfe21 Reviewed-by: Thomas Hartmann --- src/libs/qmljs/qmljsmodelmanagerinterface.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/qmljs/qmljsmodelmanagerinterface.h b/src/libs/qmljs/qmljsmodelmanagerinterface.h index 52af12d6c0e..9ea231923f1 100644 --- a/src/libs/qmljs/qmljsmodelmanagerinterface.h +++ b/src/libs/qmljs/qmljsmodelmanagerinterface.h @@ -188,6 +188,9 @@ public: void removeProjectInfo(ProjectExplorer::Project *project); void maybeQueueCppQmlTypeUpdate(const CPlusPlus::Document::Ptr &doc); + QFuture refreshSourceFiles(const QList &sourceFiles, + bool emitDocumentOnDiskChanged); + signals: void documentUpdated(QmlJS::Document::Ptr doc); void documentChangedOnDisk(QmlJS::Document::Ptr doc); @@ -207,9 +210,6 @@ protected: virtual void addTaskInternal(const QFuture &result, const QString &msg, const char *taskId) const; - QFuture refreshSourceFiles(const QList &sourceFiles, - bool emitDocumentOnDiskChanged); - static void parseLoop(QSet &scannedPaths, QSet &newLibraries, const WorkingCopy &workingCopyInternal, From ef8802487ed6ac6fbbf72f3794d7feaedadc5872 Mon Sep 17 00:00:00 2001 From: Ali Kianian Date: Thu, 1 Dec 2022 13:17:49 +0200 Subject: [PATCH 119/131] QmlDesigner: Implement new context menu structure The 2D Context Menu is rearranged Task-number: QDS-8388 Change-Id: Idca46da89a876f2a9a90acb9072be896d3e74468 Reviewed-by: Qt CI Bot Reviewed-by: Thomas Hartmann Reviewed-by: --- .../components/colortool/colortool.cpp | 45 ---- .../componentcore/actioninterface.h | 8 +- .../componentcore/componentcore_constants.h | 97 +++++---- .../componentcore/designeractionmanager.cpp | 201 +++++++++--------- .../componentcore/modelnodecontextmenu.cpp | 2 +- .../components/componentcore/viewmanager.cpp | 4 +- .../components/eventlist/eventlistactions.cpp | 6 +- .../eventlist/eventlistpluginview.cpp | 2 +- .../formeditor/formeditorwidget.cpp | 16 +- .../components/sourcetool/sourcetool.cpp | 51 ----- .../components/texttool/texttool.cpp | 48 ----- .../timelineeditor/timelineconstants.h | 3 +- .../timelineeditor/timelineview.cpp | 10 +- .../qmldesigner/qmldesignerconstants.h | 1 + .../qmlpreviewplugin/qmlpreviewactions.cpp | 2 +- .../qmlpreviewplugin/qmlpreviewplugin.cpp | 2 +- src/plugins/qmldesigner/shortcutmanager.cpp | 51 +++-- src/plugins/qmldesigner/shortcutmanager.h | 2 + 18 files changed, 231 insertions(+), 320 deletions(-) diff --git a/src/plugins/qmldesigner/components/colortool/colortool.cpp b/src/plugins/qmldesigner/components/colortool/colortool.cpp index d4eaf42d0bc..4efb23a1817 100644 --- a/src/plugins/qmldesigner/components/colortool/colortool.cpp +++ b/src/plugins/qmldesigner/components/colortool/colortool.cpp @@ -27,53 +27,8 @@ namespace QmlDesigner { -class ColorToolAction : public AbstractAction -{ -public: - ColorToolAction() : AbstractAction(QCoreApplication::translate("ColorToolAction","Edit Color")) {} - - QByteArray category() const override - { - return QByteArray(); - } - - QByteArray menuId() const override - { - return "ColorTool"; - } - - int priority() const override - { - return CustomActionsPriority; - } - - Type type() const override - { - return FormEditorAction; - } - -protected: - bool isVisible(const SelectionContext &selectionContext) const override - { - if (selectionContext.singleNodeIsSelected()) - return selectionContext.currentSingleSelectedNode().metaInfo().hasProperty("color"); - - return false; - } - - bool isEnabled(const SelectionContext &selectionContext) const override - { - return isVisible(selectionContext); - } -}; - ColorTool::ColorTool() { - auto colorToolAction = new ColorToolAction; - QmlDesignerPlugin::instance()->designerActionManager().addDesignerAction(colorToolAction); - connect(colorToolAction->action(), &QAction::triggered, [=]() { - view()->changeCurrentToolTo(this); - }); } ColorTool::~ColorTool() = default; diff --git a/src/plugins/qmldesigner/components/componentcore/actioninterface.h b/src/plugins/qmldesigner/components/componentcore/actioninterface.h index bad16f6d91a..5f487ef4724 100644 --- a/src/plugins/qmldesigner/components/componentcore/actioninterface.h +++ b/src/plugins/qmldesigner/components/componentcore/actioninterface.h @@ -25,10 +25,10 @@ public: }; enum Priorities { - HighestPriority = ComponentCoreConstants::priorityFirst, - CustomActionsPriority = ComponentCoreConstants::priorityCustomActions, - RefactoringActionsPriority = ComponentCoreConstants::priorityRefactoring, - LowestPriority = ComponentCoreConstants::priorityLast + HighestPriority = ComponentCoreConstants::Priorities::Top, + CustomActionsPriority = ComponentCoreConstants::Priorities::CustomActionsSection, + RefactoringActionsPriority = ComponentCoreConstants::Priorities::Refactoring, + LowestPriority = ComponentCoreConstants::Priorities::Last }; enum class TargetView { diff --git a/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h b/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h index 6c3ced148df..6664eec8556 100644 --- a/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h +++ b/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h @@ -17,7 +17,7 @@ const char arrangeCategory[] = "Arrange"; const char qmlPreviewCategory[] = "QmlPreview"; const char editCategory[] = "Edit"; const char anchorsCategory[] = "Anchors"; -const char positionCategory[] = "Position"; +const char positionerCategory[] = "Position"; const char groupCategory[] = "Group"; const char snappingCategory[] = "Snapping"; const char layoutCategory[] = "Layout"; @@ -59,7 +59,7 @@ const char goIntoComponentCommandId[] = "GoIntoComponent"; const char mergeTemplateCommandId[] = "MergeTemplate"; const char goToImplementationCommandId[] = "GoToImplementation"; const char addSignalHandlerCommandId[] = "AddSignalHandler"; -const char moveToComponentCommandId[] = "MoveToComponent"; +const char makeComponentCommandId[] = "MakeComponent"; const char editMaterialCommandId[] = "EditMaterial"; const char addItemToStackedContainerCommandId[] = "AddItemToStackedContainer"; const char addTabBarToStackedContainerCommandId[] = "AddTabBarToStackedContainer"; @@ -71,7 +71,7 @@ const char addToGroupItemCommandId[] = "AddToGroupItem"; const char removeGroupItemCommandId[] = "RemoveToGroupItem"; const char fitRootToScreenCommandId[] = "FitRootToScreen"; const char fitSelectionToScreenCommandId[] = "FitSelectionToScreen"; -const char editAnnotationCommandId[] = "EditAnnotation"; +const char editAnnotationsCommandId[] = "EditAnnotation"; const char addMouseAreaFillCommandId[] = "AddMouseAreaFill"; const char openSignalDialogCommandId[] = "OpenSignalDialog"; @@ -84,8 +84,9 @@ const char selectEffectDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu const char arrangeCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Arrange"); const char editCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Edit"); const char anchorsCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Anchors"); -const char positionCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Position"); +const char positionerCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Positioner"); const char groupCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Group"); +const char snappingCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Snapping"); const char layoutCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Layout"); const char flowCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Flow"); const char flowEffectCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Flow Effects"); @@ -112,13 +113,13 @@ const char resetPositionDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMen const char copyFormatDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Copy Formatting"); const char applyFormatDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Apply Formatting"); -const char goIntoComponentDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Go into Component"); -const char mergeTemplateDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Merge File With Template"); +const char enterComponentDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Enter Component"); +const char mergeTemplateDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Merge with Template"); const char goToImplementationDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Go to Implementation"); const char addSignalHandlerDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Add New Signal Handler"); -const char moveToComponentDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Move Component into Separate File"); +const char makeComponentDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Make Component"); const char editMaterialDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Edit Material"); -const char editAnnotationDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Edit Annotation"); +const char editAnnotationsDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Edit Annotations"); const char addMouseAreaFillDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Add Mouse Area"); const char openSignalDialogDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Open Signal Dialog"); @@ -130,13 +131,13 @@ const char resetZDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Re const char reverseDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Reverse"); -const char anchorsFillDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Fill"); -const char anchorsResetDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Reset"); +const char anchorsFillDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Fill Parent"); +const char anchorsResetDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "No Anchors"); -const char layoutColumnPositionerDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Position in Column"); -const char layoutRowPositionerDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Position in Row"); -const char layoutGridPositionerDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Position in Grid"); -const char layoutFlowPositionerDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Position in Flow"); +const char layoutColumnPositionerDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Column Positioner"); +const char layoutRowPositionerDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Row Positioner"); +const char layoutGridPositionerDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Grid Positioner"); +const char layoutFlowPositionerDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Flow Positioner"); const char removePositionerDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Remove Positioner"); const char createFlowActionAreaDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Create Flow Action"); const char setFlowStartDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Set Flow Start"); @@ -151,9 +152,9 @@ const char addTabBarToStackedContainerDisplayName[] = QT_TRANSLATE_NOOP("QmlDesi const char increaseIndexToStackedContainerDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Increase Index"); const char decreaseIndexToStackedContainerDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Decrease Index"); -const char layoutColumnLayoutDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Layout in Column Layout"); -const char layoutRowLayoutDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Layout in Row Layout"); -const char layoutGridLayoutDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Layout in Grid Layout"); +const char layoutColumnLayoutDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Column Layout"); +const char layoutRowLayoutDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Row Layout"); +const char layoutGridLayoutDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Grid Layout"); const char layoutFillWidthDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Fill Width"); const char layoutFillHeightDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Fill Height"); @@ -183,26 +184,48 @@ const char addFlowActionToolTip[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", const char editListModelDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Edit List Model..."); - -const int priorityFirst = 280; -const int prioritySelectionCategory = 220; -const int priorityConnectionsCategory = 210; -const int priorityQmlPreviewCategory = 200; -const int priorityStackCategory = 180; -const int priorityEditCategory = 160; -const int priorityAnchorsCategory = 140; -const int priorityFlowCategory = 240; -const int priorityGroupCategory = 140; -const int priorityPositionCategory = 130; -const int priorityLayoutCategory = 120; -const int priorityStackedContainerCategory = priorityLayoutCategory; -const int priorityEventListCategory = 105; -const int priorityTopLevelSeperator = 100; -const int priorityCustomActions = 80; -const int priorityRefactoring = 60; -const int priorityGoIntoComponent = 40; -const int priorityGenericToolBar = 50; -const int priorityLast = 60; +namespace Priorities { +enum PrioritiesEnum : int { + Top = 0, + FlowCategory, + ComponentActions, + /******** Section *****************************/ + ModifySection = 1000, + ConnectionsCategory, + SelectionCategory, + ArrangeCategory, + EditCategory, + /******** Section *****************************/ + PositionSection = 2000, + SnappingCategory, + AnchorsCategory, + LayoutCategory, + PositionCategory, + StackedContainerCategory, + /******** Section *****************************/ + EventSection = 3000, + TimelineCategory, + EventListCategory, + /******** Section *****************************/ + AdditionsSection = 4000, + EditAnnotations, + AddMouseArea, + MergeWithTemplate, + /******** Section *****************************/ + ViewOprionsSection = 5000, + ResetView, + Group, + Visibility, + ShowBoundingRect, + /******** Section *****************************/ + CustomActionsSection = 6000, + QmlPreviewCategory, + SignalsDialog, + Refactoring, + GenericToolBar, + Last +}; +}; const char addImagesDisplayString[] = QT_TRANSLATE_NOOP("QmlDesignerAddResources", "Image Files"); const char addFontsDisplayString[] = QT_TRANSLATE_NOOP("QmlDesignerAddResources", "Font Files"); diff --git a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp index 1e71935244b..9df8ad0b511 100644 --- a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp @@ -73,7 +73,7 @@ DesignerActionToolBar *DesignerActionManager::createToolBar(QWidget *parent) con }); Utils::sort(categories, [](ActionInterface *l, ActionInterface *r) { - return l->priority() > r->priority(); + return l->priority() < r->priority(); }); for (auto *categoryAction : std::as_const(categories)) { @@ -82,7 +82,7 @@ DesignerActionToolBar *DesignerActionManager::createToolBar(QWidget *parent) con }); Utils::sort(actions, [](ActionInterface *l, ActionInterface *r) { - return l->priority() > r->priority(); + return l->priority() < r->priority(); }); bool addSeparator = false; @@ -142,7 +142,7 @@ QGraphicsWidget *DesignerActionManager::createFormEditorToolBar(QGraphicsItem *p }); Utils::sort(actions, [](ActionInterface *l, ActionInterface *r) { - return l->priority() > r->priority(); + return l->priority() < r->priority(); }); QGraphicsWidget *toolbar = new QGraphicsWidget(parent); @@ -401,8 +401,7 @@ public: if (!ModelNode::isThisOrAncestorLocked(parentNode)) { ActionTemplate *selectionAction = new ActionTemplate("SELECTION", {}, &ModelNodeOperations::select); selectionAction->setParent(menu()); - selectionAction->setText(QString(QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Select parent: %1")).arg( - captionForModelNode(parentNode))); + selectionAction->setText(QString(QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Parent"))); SelectionContext nodeSelectionContext = selectionContext(); nodeSelectionContext.setTargetNode(parentNode); @@ -1341,26 +1340,28 @@ void DesignerActionManager::createDefaultDesignerActions() addDesignerAction(new SelectionModelNodeAction( selectionCategoryDisplayName, selectionCategory, - prioritySelectionCategory)); + Priorities::SelectionCategory)); addDesignerAction(new ConnectionsModelNodeActionGroup( connectionsCategoryDisplayName, connectionsCategory, - priorityConnectionsCategory)); + Priorities::ConnectionsCategory)); addDesignerAction(new ActionGroup( arrangeCategoryDisplayName, arrangeCategory, - priorityStackCategory, + Priorities::ArrangeCategory, &selectionNotEmpty)); + addDesignerAction(new SeperatorDesignerAction(arrangeCategory, 10)); + addDesignerAction(new ModelNodeContextMenuAction( toFrontCommandId, toFrontDisplayName, {}, arrangeCategory, QKeySequence(), - 200, + 1, &toFront, &raiseAvailable)); @@ -1370,7 +1371,7 @@ void DesignerActionManager::createDefaultDesignerActions() Utils::Icon({{":/qmldesigner/icon/designeractions/images/raise.png", Utils::Theme::IconsBaseColor}}).icon(), arrangeCategory, QKeySequence(), - 180, + 11, &raise, &raiseAvailable)); @@ -1380,7 +1381,7 @@ void DesignerActionManager::createDefaultDesignerActions() Utils::Icon({{":/qmldesigner/icon/designeractions/images/lower.png", Utils::Theme::IconsBaseColor}}).icon(), arrangeCategory, QKeySequence(), - 160, + 12, &lower, &lowerAvailable)); @@ -1390,23 +1391,25 @@ void DesignerActionManager::createDefaultDesignerActions() {}, arrangeCategory, QKeySequence(), - 140, + 2, &toBack, &lowerAvailable)); + addDesignerAction(new SeperatorDesignerAction(arrangeCategory, 20)); + addDesignerAction(new ModelNodeContextMenuAction( reverseCommandId, reverseDisplayName, {}, arrangeCategory, QKeySequence(), - 100, + 21, &reverse, &multiSelectionAndHasSameParent)); - addDesignerAction(new ActionGroup(editCategoryDisplayName, editCategory, priorityEditCategory, &selectionNotEmpty)); + addDesignerAction(new ActionGroup(editCategoryDisplayName, editCategory, Priorities::EditCategory, &selectionNotEmpty)); - addDesignerAction(new SeperatorDesignerAction(editCategory, 220)); + addDesignerAction(new SeperatorDesignerAction(editCategory, 30)); addDesignerAction( new ModelNodeAction(resetPositionCommandId, @@ -1418,7 +1421,7 @@ void DesignerActionManager::createDefaultDesignerActions() resetPositionTooltip, editCategory, QKeySequence("Ctrl+d"), - 200, + 32, &resetPosition, &selectionNotEmptyAndHasXorYProperty)); @@ -1456,7 +1459,7 @@ void DesignerActionManager::createDefaultDesignerActions() copyFormatTooltip, editCategory, QKeySequence(), - 120, + 41, ©Format, &propertiesCopyable)); @@ -1466,7 +1469,7 @@ void DesignerActionManager::createDefaultDesignerActions() applyFormatTooltip, editCategory, QKeySequence(), - 120, + 42, &applyFormat, &propertiesApplyable)); @@ -1478,24 +1481,24 @@ void DesignerActionManager::createDefaultDesignerActions() resetSizeToolTip, editCategory, QKeySequence("shift+s"), - 180, + 31, &resetSize, &selectionNotEmptyAndHasWidthOrHeightProperty)); - addDesignerAction(new SeperatorDesignerAction(editCategory, 170)); + addDesignerAction(new SeperatorDesignerAction(editCategory, 40)); addDesignerAction(new VisiblityModelNodeAction( visiblityCommandId, visibilityDisplayName, - editCategory, + rootCategory, QKeySequence("Ctrl+g"), - 160, + Priorities::Visibility, &setVisible, &singleSelectedItem)); addDesignerAction(new ActionGroup(anchorsCategoryDisplayName, anchorsCategory, - priorityAnchorsCategory, + Priorities::AnchorsCategory, &anchorsMenuEnabled)); addDesignerAction(new ModelNodeAction( @@ -1505,7 +1508,7 @@ void DesignerActionManager::createDefaultDesignerActions() anchorsFillToolTip, anchorsCategory, QKeySequence(QKeySequence("shift+f")), - 200, + 2, &anchorsFill, &singleSelectionItemIsNotAnchoredAndSingleSelectionNotRoot)); @@ -1517,46 +1520,49 @@ void DesignerActionManager::createDefaultDesignerActions() anchorsResetToolTip, anchorsCategory, QKeySequence(QKeySequence("Ctrl+Shift+r")), - 180, + 1, &anchorsReset, &singleSelectionItemIsAnchored)); - addDesignerAction(new SeperatorDesignerAction(anchorsCategory, 170)); + addDesignerAction(new SeperatorDesignerAction(anchorsCategory, 10)); addDesignerAction(new ActionGroup( - positionCategoryDisplayName, - positionCategory, - priorityPositionCategory, + positionerCategoryDisplayName, + positionerCategory, + Priorities::PositionCategory, &positionOptionVisible)); - addDesignerAction(new ActionGroup(layoutCategoryDisplayName, - layoutCategory, - priorityLayoutCategory, - &layoutOptionVisible)); - - addDesignerAction(new ActionGroup("Snapping", - snappingCategory, - priorityLayoutCategory + 10, - &selectionEnabled, - &selectionEnabled)); - - addDesignerAction(new ActionGroup(groupCategoryDisplayName, - groupCategory, - priorityGroupCategory, - &studioComponentsAvailableAndSelectionCanBeLayouted)); + addDesignerAction(new ActionGroup( + layoutCategoryDisplayName, + layoutCategory, + Priorities::LayoutCategory, + &layoutOptionVisible)); addDesignerAction(new ActionGroup( - flowCategoryDisplayName, - flowCategory, - priorityFlowCategory, - &isFlowTargetOrTransition, - &flowOptionVisible)); + snappingCategoryDisplayName, + snappingCategory, + Priorities::SnappingCategory, + &selectionEnabled, + &selectionEnabled)); + + addDesignerAction(new ActionGroup( + groupCategoryDisplayName, + groupCategory, + Priorities::Group, + &studioComponentsAvailableAndSelectionCanBeLayouted)); + + addDesignerAction(new ActionGroup( + flowCategoryDisplayName, + flowCategory, + Priorities::FlowCategory, + &isFlowTargetOrTransition, + &flowOptionVisible)); auto effectMenu = new ActionGroup( flowEffectCategoryDisplayName, flowEffectCategory, - priorityFlowCategory, + Priorities::FlowCategory, &isFlowTransitionItem, &flowOptionVisible); @@ -1570,7 +1576,7 @@ void DesignerActionManager::createDefaultDesignerActions() addFlowActionToolTip, flowCategory, {}, - priorityFirst, + 1, &createFlowActionArea, &isFlowItem, &flowOptionVisible)); @@ -1580,7 +1586,7 @@ void DesignerActionManager::createDefaultDesignerActions() setFlowStartDisplayName, {}, flowCategory, - priorityFirst, + 2, {}, &setFlowStartItem, &isFlowItem, @@ -1589,7 +1595,7 @@ void DesignerActionManager::createDefaultDesignerActions() addDesignerAction(new FlowActionConnectAction( flowConnectionCategoryDisplayName, flowConnectionCategory, - priorityFlowCategory)); + Priorities::FlowCategory)); const QList transitionTypes = {"FlowFadeEffect", @@ -1608,23 +1614,23 @@ void DesignerActionManager::createDefaultDesignerActions() {}, flowCategory, {}, - priorityFlowCategory, + 2, &selectFlowEffect, &isFlowTransitionItemWithEffect)); addDesignerAction(new ActionGroup( stackedContainerCategoryDisplayName, stackedContainerCategory, - priorityStackedContainerCategory, + Priorities::StackedContainerCategory, &isStackedContainer)); addDesignerAction(new ModelNodeContextMenuAction( removePositionerCommandId, removePositionerDisplayName, {}, - positionCategory, + positionerCategory, QKeySequence("Ctrl+Shift+p"), - 210, + 1, &removePositioner, &isPositioner, &isPositioner)); @@ -1633,9 +1639,9 @@ void DesignerActionManager::createDefaultDesignerActions() layoutRowPositionerCommandId, layoutRowPositionerDisplayName, {}, - positionCategory, + positionerCategory, QKeySequence(), - 200, + 2, &layoutRowPositioner, &selectionCanBeLayouted, &selectionCanBeLayouted)); @@ -1644,9 +1650,9 @@ void DesignerActionManager::createDefaultDesignerActions() layoutColumnPositionerCommandId, layoutColumnPositionerDisplayName, {}, - positionCategory, + positionerCategory, QKeySequence(), - 180, + 3, &layoutColumnPositioner, &selectionCanBeLayouted, &selectionCanBeLayouted)); @@ -1655,9 +1661,9 @@ void DesignerActionManager::createDefaultDesignerActions() layoutGridPositionerCommandId, layoutGridPositionerDisplayName, {}, - positionCategory, + positionerCategory, QKeySequence(), - 160, + 4, &layoutGridPositioner, &selectionCanBeLayouted, &selectionCanBeLayouted)); @@ -1666,14 +1672,14 @@ void DesignerActionManager::createDefaultDesignerActions() layoutFlowPositionerCommandId, layoutFlowPositionerDisplayName, {}, - positionCategory, + positionerCategory, QKeySequence("Ctrl+m"), - 140, + 5, &layoutFlowPositioner, &selectionCanBeLayouted, &selectionCanBeLayouted)); - addDesignerAction(new SeperatorDesignerAction(layoutCategory, 120)); + addDesignerAction(new SeperatorDesignerAction(layoutCategory, 0)); addDesignerAction(new ModelNodeContextMenuAction( removeLayoutCommandId, @@ -1681,7 +1687,7 @@ void DesignerActionManager::createDefaultDesignerActions() {}, layoutCategory, QKeySequence(), - 110, + 1, &removeLayout, &isLayout, &isLayout)); @@ -1691,7 +1697,7 @@ void DesignerActionManager::createDefaultDesignerActions() {}, groupCategory, QKeySequence("Ctrl+Shift+g"), - 110, + 1, &addToGroupItem, &selectionCanBeLayouted)); @@ -1700,7 +1706,7 @@ void DesignerActionManager::createDefaultDesignerActions() {}, groupCategory, QKeySequence(), - 110, + 2, &removeGroup, &isGroup)); @@ -1710,7 +1716,7 @@ void DesignerActionManager::createDefaultDesignerActions() addItemToStackedContainerToolTip, stackedContainerCategory, QKeySequence("Ctrl+Shift+a"), - 110, + 1, &addItemToStackedContainer, &isStackedContainer, &isStackedContainer)); @@ -1721,7 +1727,7 @@ void DesignerActionManager::createDefaultDesignerActions() {}, stackedContainerCategory, QKeySequence("Ctrl+Shift+t"), - 100, + 2, &addTabBarToStackedContainer, &isStackedContainerWithoutTabBar, &isStackedContainer)); @@ -1733,7 +1739,7 @@ void DesignerActionManager::createDefaultDesignerActions() decreaseIndexOfStackedContainerToolTip, stackedContainerCategory, QKeySequence("Ctrl+Shift+Left"), - 80, + 3, &decreaseIndexOfStackedContainer, &isStackedContainerAndIndexCanBeDecreased, &isStackedContainer)); @@ -1745,7 +1751,7 @@ void DesignerActionManager::createDefaultDesignerActions() increaseIndexOfStackedContainerToolTip, stackedContainerCategory, QKeySequence("Ctrl+Shift+Right"), - 80, + 4, &increaseIndexOfStackedContainer, &isStackedContainerAndIndexCanBeIncreased, &isStackedContainer)); @@ -1757,7 +1763,7 @@ void DesignerActionManager::createDefaultDesignerActions() layoutRowLayoutToolTip, layoutCategory, QKeySequence("Ctrl+u"), - 100, + 2, &layoutRowLayout, &selectionCanBeLayoutedAndQtQuickLayoutPossible)); @@ -1768,7 +1774,7 @@ void DesignerActionManager::createDefaultDesignerActions() layoutColumnLayoutToolTip, layoutCategory, QKeySequence("Ctrl+l"), - 80, + 3, &layoutColumnLayout, &selectionCanBeLayoutedAndQtQuickLayoutPossible)); @@ -1779,18 +1785,18 @@ void DesignerActionManager::createDefaultDesignerActions() layoutGridLayoutToolTip, layoutCategory, QKeySequence("shift+g"), - 60, + 4, &layoutGridLayout, &selectionCanBeLayoutedAndQtQuickLayoutPossibleAndNotMCU)); - addDesignerAction(new SeperatorDesignerAction(layoutCategory, 50)); + addDesignerAction(new SeperatorDesignerAction(layoutCategory, 10)); addDesignerAction(new FillWidthModelNodeAction( layoutFillWidthCommandId, layoutFillWidthDisplayName, layoutCategory, QKeySequence(), - 40, + 11, &setFillWidth, &singleSelectionAndInQtQuickLayout, &singleSelectionAndInQtQuickLayout)); @@ -1800,30 +1806,35 @@ void DesignerActionManager::createDefaultDesignerActions() layoutFillHeightDisplayName, layoutCategory, QKeySequence(), - 20, + 12, &setFillHeight, &singleSelectionAndInQtQuickLayout, &singleSelectionAndInQtQuickLayout)); - addDesignerAction(new SeperatorDesignerAction(rootCategory, priorityTopLevelSeperator)); + addDesignerAction(new SeperatorDesignerAction(rootCategory, Priorities::ModifySection)); + addDesignerAction(new SeperatorDesignerAction(rootCategory, Priorities::PositionSection)); + addDesignerAction(new SeperatorDesignerAction(rootCategory, Priorities::EventSection)); + addDesignerAction(new SeperatorDesignerAction(rootCategory, Priorities::AdditionsSection)); + addDesignerAction(new SeperatorDesignerAction(rootCategory, Priorities::ViewOprionsSection)); + addDesignerAction(new SeperatorDesignerAction(rootCategory, Priorities::CustomActionsSection)); addDesignerAction(new ModelNodeContextMenuAction( goIntoComponentCommandId, - goIntoComponentDisplayName, + enterComponentDisplayName, {}, rootCategory, QKeySequence(Qt::Key_F2), - priorityGoIntoComponent, + Priorities::ComponentActions + 2, &goIntoComponentOperation, &selectionIsComponent)); addDesignerAction(new ModelNodeContextMenuAction( - editAnnotationCommandId, - editAnnotationDisplayName, + editAnnotationsCommandId, + editAnnotationsDisplayName, {}, rootCategory, QKeySequence(), - (priorityLast+6), + Priorities::EditAnnotations, &editAnnotation, &singleSelection, &singleSelection)); @@ -1834,7 +1845,7 @@ void DesignerActionManager::createDefaultDesignerActions() {}, rootCategory, QKeySequence(), - (priorityLast+7), + Priorities::AddMouseArea, &addMouseAreaFill, &addMouseAreaFillCheck, &singleSelection)); @@ -1863,12 +1874,12 @@ void DesignerActionManager::createDefaultDesignerActions() &singleSelectedAndUiFile)); addDesignerAction(new ModelNodeContextMenuAction( - moveToComponentCommandId, - moveToComponentDisplayName, + makeComponentCommandId, + makeComponentDisplayName, {}, rootCategory, QKeySequence(), - 44, + Priorities::ComponentActions + 1, &moveToComponent, &singleSelection, &singleSelection)); @@ -1889,14 +1900,14 @@ void DesignerActionManager::createDefaultDesignerActions() {}, rootCategory, {}, - 50, + Priorities::MergeWithTemplate, [&] (const SelectionContext& context) { mergeWithTemplate(context, m_externalDependencies); }, &SelectionContextFunctors::always)); addDesignerAction(new ActionGroup( "", genericToolBarCategory, - priorityGenericToolBar)); + Priorities::GenericToolBar)); addDesignerAction(new ChangeStyleAction()); @@ -1908,7 +1919,7 @@ void DesignerActionManager::createDefaultDesignerActions() {}, rootCategory, QKeySequence(), - 66, + Priorities::SignalsDialog, &openSignalDialog, &singleSelectionAndHasSlotTrigger)); @@ -1918,7 +1929,7 @@ void DesignerActionManager::createDefaultDesignerActions() {}, rootCategory, QKeySequence(), - priorityGenericToolBar, + Priorities::GenericToolBar, &updateImported3DAsset, &selectionIsImported3DAsset, &selectionIsImported3DAsset)); @@ -2048,7 +2059,7 @@ void DesignerActionManager::addTransitionEffectAction(const TypeName &typeName) {}, ComponentCoreConstants::flowEffectCategory, {}, - typeName == "None" ? 100 : 140, + typeName == "None" ? 11 : 1, [typeName](const SelectionContext &context) { ModelNodeOperations::addFlowEffect(context, typeName); }, &isFlowTransitionItem)); @@ -2062,7 +2073,7 @@ void DesignerActionManager::addCustomTransitionEffectAction() {}, ComponentCoreConstants::flowEffectCategory, {}, - 80, + 21, &ModelNodeOperations::addCustomFlowEffect, &isFlowTransitionItem)); } diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu.cpp b/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu.cpp index 1b3fbac3f47..ebe51d8a3a8 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu.cpp +++ b/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu.cpp @@ -42,7 +42,7 @@ void populateMenu(QSet &actionInterfaces, QList matchingFactoriesList = Utils::toList(matchingFactories); Utils::sort(matchingFactoriesList, [](ActionInterface *l, ActionInterface *r) { - return l->priority() > r->priority(); + return l->priority() < r->priority(); }); for (ActionInterface* actionInterface : std::as_const(matchingFactoriesList)) { diff --git a/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp b/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp index 438ceb9c3a8..15ea827e6ff 100644 --- a/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp @@ -258,7 +258,7 @@ void ViewManager::registerNanotraceActions() QObject::tr("Start Nanotrace"), ComponentCoreConstants::eventListCategory, QKeySequence(), - 220, + 22, handleShutdownNanotraceAction); QObject::connect(startNanotraceAction->defaultAction(), &QAction::triggered, [&]() { @@ -273,7 +273,7 @@ void ViewManager::registerNanotraceActions() QObject::tr("Shut Down Nanotrace"), ComponentCoreConstants::eventListCategory, QKeySequence(), - 220, + 23, handleShutdownNanotraceAction); QObject::connect(shutDownNanotraceAction->defaultAction(), &QAction::triggered, [&]() { diff --git a/src/plugins/qmldesigner/components/eventlist/eventlistactions.cpp b/src/plugins/qmldesigner/components/eventlist/eventlistactions.cpp index a9ca7936aed..fd6485118a7 100644 --- a/src/plugins/qmldesigner/components/eventlist/eventlistactions.cpp +++ b/src/plugins/qmldesigner/components/eventlist/eventlistactions.cpp @@ -55,7 +55,7 @@ EventListAction::EventListAction() QObject::tr("Show Event List"), ComponentCoreConstants::eventListCategory, QKeySequence("Alt+e"), - 230, + 11, &handleAction, &eventListEnabled) {} @@ -77,7 +77,7 @@ AssignEventEditorAction::AssignEventEditorAction() QObject::tr("Assign Events to Actions"), ComponentCoreConstants::eventListCategory, QKeySequence("Alt+a"), - 220, + 21, &handleAssignEventActionOperation, &eventListEnabled) {} @@ -88,7 +88,7 @@ ConnectSignalAction::ConnectSignalAction() assignEventListIcon(), ComponentCoreConstants::eventListCategory, QKeySequence(), - 210, + 31, &handleAssignEventActionOperation) {} diff --git a/src/plugins/qmldesigner/components/eventlist/eventlistpluginview.cpp b/src/plugins/qmldesigner/components/eventlist/eventlistpluginview.cpp index 47b0043b571..f45d609d927 100644 --- a/src/plugins/qmldesigner/components/eventlist/eventlistpluginview.cpp +++ b/src/plugins/qmldesigner/components/eventlist/eventlistpluginview.cpp @@ -45,7 +45,7 @@ void EventListPluginView::registerActions() designerActionManager.addDesignerAction(new ActionGroup(tr("Event List"), ComponentCoreConstants::eventListCategory, - ComponentCoreConstants::priorityEventListCategory, + ComponentCoreConstants::Priorities::EventListCategory, &SelectionContextFunctors::always, &SelectionContextFunctors::always)); auto eventListAction = new EventListAction(); diff --git a/src/plugins/qmldesigner/components/formeditor/formeditorwidget.cpp b/src/plugins/qmldesigner/components/formeditor/formeditorwidget.cpp index 0be675b957d..331f7fbe97b 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditorwidget.cpp +++ b/src/plugins/qmldesigner/components/formeditor/formeditorwidget.cpp @@ -68,7 +68,7 @@ FormEditorWidget::FormEditorWidget(FormEditorView *view) auto layoutActionGroup = new QActionGroup(this); layoutActionGroup->setExclusive(true); - m_noSnappingAction = layoutActionGroup->addAction(tr("No snapping")); + m_noSnappingAction = layoutActionGroup->addAction(tr("No Snapping")); m_noSnappingAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); m_noSnappingAction->setCheckable(true); m_noSnappingAction->setChecked(true); @@ -77,7 +77,7 @@ FormEditorWidget::FormEditorWidget(FormEditorView *view) Constants::FORMEDITOR_NO_SNAPPING, QKeySequence(Qt::Key_T), ComponentCoreConstants::snappingCategory, - 40); + 1); m_snappingAndAnchoringAction = layoutActionGroup->addAction(tr("Snap with Anchors")); m_snappingAndAnchoringAction->setCheckable(true); @@ -87,9 +87,9 @@ FormEditorWidget::FormEditorWidget(FormEditorView *view) Constants::FORMEDITOR_NO_SNAPPING_AND_ANCHORING, QKeySequence(Qt::Key_W), ComponentCoreConstants::snappingCategory, - 10); + 2); - m_snappingAction = layoutActionGroup->addAction(tr("Snap without anchors")); + m_snappingAction = layoutActionGroup->addAction(tr("Snap without Anchors")); m_snappingAction->setCheckable(true); m_snappingAction->setChecked(true); @@ -97,18 +97,18 @@ FormEditorWidget::FormEditorWidget(FormEditorView *view) Constants::FORMEDITOR_SNAPPING, QKeySequence(Qt::Key_E), ComponentCoreConstants::snappingCategory, - 20); + 3); addActions(layoutActionGroup->actions()); - m_showBoundingRectAction = new QAction(tr("Show bounds"), this); + m_showBoundingRectAction = new QAction(tr("Show Bounds"), this); m_showBoundingRectAction->setCheckable(true); m_showBoundingRectAction->setChecked(false); registerActionAsCommand(m_showBoundingRectAction, Constants::FORMEDITOR_NO_SHOW_BOUNDING_RECTANGLE, QKeySequence(Qt::Key_A), ComponentCoreConstants::rootCategory, - 10); + ComponentCoreConstants::Priorities::ShowBoundingRect); addAction(m_showBoundingRectAction.data()); @@ -273,7 +273,7 @@ FormEditorWidget::FormEditorWidget(FormEditorView *view) Constants::FORMEDITOR_REFRESH, QKeySequence(Qt::Key_R), ComponentCoreConstants::rootCategory, - 0); + ComponentCoreConstants::Priorities::ResetView); addAction(m_resetAction.data()); upperActions.append(m_resetAction.data()); diff --git a/src/plugins/qmldesigner/components/sourcetool/sourcetool.cpp b/src/plugins/qmldesigner/components/sourcetool/sourcetool.cpp index 5732235b508..cfae70b4cc3 100644 --- a/src/plugins/qmldesigner/components/sourcetool/sourcetool.cpp +++ b/src/plugins/qmldesigner/components/sourcetool/sourcetool.cpp @@ -40,59 +40,8 @@ bool modelNodeHasUrlSource(const QmlDesigner::ModelNode &modelNode) namespace QmlDesigner { -class SourceToolAction : public AbstractAction -{ -public: - SourceToolAction() : AbstractAction(QCoreApplication::translate("SourceToolAction","Change Source URL...")) - { - const Utils::Icon prevIcon({ - {":/utils/images/fileopen.png", Utils::Theme::OutputPanes_NormalMessageTextColor}}, Utils::Icon::MenuTintedStyle); - - action()->setIcon(prevIcon.icon()); - } - - QByteArray category() const override - { - return QByteArray(); - } - - QByteArray menuId() const override - { - return "SourceTool"; - } - - int priority() const override - { - return CustomActionsPriority; - } - - Type type() const override - { - return FormEditorAction; - } - -protected: - bool isVisible(const SelectionContext &selectionContext) const override - { - if (selectionContext.singleNodeIsSelected()) - return modelNodeHasUrlSource(selectionContext.currentSingleSelectedNode()); - return false; - } - - bool isEnabled(const SelectionContext &selectionContext) const override - { - return isVisible(selectionContext); - } -}; - - SourceTool::SourceTool() { - auto sourceToolAction = new SourceToolAction; - QmlDesignerPlugin::instance()->designerActionManager().addDesignerAction(sourceToolAction); - connect(sourceToolAction->action(), &QAction::triggered, [=]() { - view()->changeCurrentToolTo(this); - }); } SourceTool::~SourceTool() = default; diff --git a/src/plugins/qmldesigner/components/texttool/texttool.cpp b/src/plugins/qmldesigner/components/texttool/texttool.cpp index 596ef44efb9..ffd2875ca78 100644 --- a/src/plugins/qmldesigner/components/texttool/texttool.cpp +++ b/src/plugins/qmldesigner/components/texttool/texttool.cpp @@ -28,56 +28,8 @@ namespace QmlDesigner { -class TextToolAction : public AbstractAction -{ -public: - TextToolAction() : AbstractAction(QCoreApplication::translate("TextToolAction","Edit Text")) {} - - QByteArray category() const override - { - return QByteArray(); - } - - QByteArray menuId() const override - { - return "TextTool"; - } - - int priority() const override - { - return CustomActionsPriority; - } - - Type type() const override - { - return ContextMenuAction; - } - -protected: - bool isVisible(const SelectionContext &selectionContext) const override - { - if (selectionContext.scenePosition().isNull()) - return false; - - if (selectionContext.singleNodeIsSelected()) - return selectionContext.currentSingleSelectedNode().metaInfo().hasProperty("text"); - - return false; - } - - bool isEnabled(const SelectionContext &selectionContext) const override - { - return isVisible(selectionContext); - } -}; - TextTool::TextTool() { - auto textToolAction = new TextToolAction; - QmlDesignerPlugin::instance()->designerActionManager().addDesignerAction(textToolAction); - connect(textToolAction->action(), &QAction::triggered, [=]() { - view()->changeCurrentToolTo(this); - }); } TextTool::~TextTool() = default; diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineconstants.h b/src/plugins/qmldesigner/components/timelineeditor/timelineconstants.h index 6324372d5b7..38c16e41bbc 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelineconstants.h +++ b/src/plugins/qmldesigner/components/timelineeditor/timelineconstants.h @@ -20,7 +20,6 @@ const int timelineBounds = 8; const int timelineLeftOffset = 10; const char timelineCategory[] = "Timeline"; -const int priorityTimelineCategory = 110; const char timelineCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Timeline"); const char timelineCopyKeyframesDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", @@ -28,7 +27,7 @@ const char timelineCopyKeyframesDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerCo const char timelinePasteKeyframesDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Paste Keyframes"); const char timelineInsertKeyframesDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", - "Add Keyframes at Current Frame"); + "Add Keyframe"); const char timelineDeleteKeyframesDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Delete All Keyframes"); diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineview.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelineview.cpp index c3aa2a18283..6249ce94484 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelineview.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/timelineview.cpp @@ -584,7 +584,7 @@ void TimelineView::registerActions() actionManager.addDesignerAction(new ActionGroup(TimelineConstants::timelineCategoryDisplayName, TimelineConstants::timelineCategory, - TimelineConstants::priorityTimelineCategory, + ComponentCoreConstants::Priorities::TimelineCategory, timelineEnabled, &SelectionContextFunctors::always)); @@ -594,7 +594,7 @@ void TimelineView::registerActions() {}, TimelineConstants::timelineCategory, QKeySequence(), - 160, + 3, deleteKeyframes, timelineHasKeyframes)); @@ -604,7 +604,7 @@ void TimelineView::registerActions() {}, TimelineConstants::timelineCategory, QKeySequence(), - 140, + 1, insertKeyframes, timelineHasKeyframes)); @@ -614,7 +614,7 @@ void TimelineView::registerActions() {}, TimelineConstants::timelineCategory, QKeySequence(), - 120, + 4, copyKeyframes, timelineHasKeyframes)); @@ -624,7 +624,7 @@ void TimelineView::registerActions() {}, TimelineConstants::timelineCategory, QKeySequence(), - 100, + 5, pasteKeyframes, timelineHasClipboard)); } diff --git a/src/plugins/qmldesigner/qmldesignerconstants.h b/src/plugins/qmldesigner/qmldesignerconstants.h index b178bf1837b..cb682355af7 100644 --- a/src/plugins/qmldesigner/qmldesignerconstants.h +++ b/src/plugins/qmldesigner/qmldesignerconstants.h @@ -8,6 +8,7 @@ namespace Constants { const char C_BACKSPACE[] = "QmlDesigner.Backspace"; const char C_DELETE[] = "QmlDesigner.Delete"; +const char C_DUPLICATE[] = "QmlDesigner.Duplicate"; // Context const char C_QMLDESIGNER[] = "QmlDesigner::QmlDesignerMain"; diff --git a/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp b/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp index 2ef0a0cc860..d7c314a085d 100644 --- a/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp +++ b/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp @@ -65,7 +65,7 @@ QmlPreviewAction::QmlPreviewAction() : ModelNodeAction(livePreviewId, QmlPreviewWidgetPlugin::tr("Show Live Preview"), ComponentCoreConstants::qmlPreviewCategory, QKeySequence("Alt+p"), - 20, + 1, &handleAction, &SelectionContextFunctors::always) { diff --git a/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewplugin.cpp b/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewplugin.cpp index 0b526338de7..fb7296ec947 100644 --- a/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewplugin.cpp +++ b/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewplugin.cpp @@ -40,7 +40,7 @@ QmlPreviewWidgetPlugin::QmlPreviewWidgetPlugin() designerActionManager.addDesignerAction(new ActionGroup( QString(), ComponentCoreConstants::qmlPreviewCategory, - ComponentCoreConstants::priorityQmlPreviewCategory, + ComponentCoreConstants::Priorities::QmlPreviewCategory, &SelectionContextFunctors::always)); s_previewPlugin = getPreviewPlugin(); diff --git a/src/plugins/qmldesigner/shortcutmanager.cpp b/src/plugins/qmldesigner/shortcutmanager.cpp index 04edfd72094..326977c4af5 100644 --- a/src/plugins/qmldesigner/shortcutmanager.cpp +++ b/src/plugins/qmldesigner/shortcutmanager.cpp @@ -31,6 +31,7 @@ #include +#include "modelnodecontextmenu_helper.h" #include "qmldesignerconstants.h" #include "qmldesignerplugin.h" @@ -40,16 +41,17 @@ namespace QmlDesigner { ShortCutManager::ShortCutManager() - : QObject() - , m_exportAsImageAction(tr("Export as Image...")) - , m_undoAction(tr("&Undo")) - , m_redoAction(tr("&Redo")) - , m_deleteAction(tr("Delete")) - , m_cutAction(tr("Cu&t")) - , m_copyAction(tr("&Copy")) - , m_pasteAction(tr("&Paste")) - , m_selectAllAction(tr("Select &All")) - , m_escapeAction(this) + : QObject(), + m_exportAsImageAction(tr("Export as &Image...")), + m_undoAction(tr("&Undo")), + m_redoAction(tr("&Redo")), + m_deleteAction(tr("Delete")), + m_cutAction(tr("Cu&t")), + m_copyAction(tr("&Copy")), + m_pasteAction(tr("&Paste")), + m_duplicateAction(tr("&Duplicate")), + m_selectAllAction(tr("Select &All")), + m_escapeAction(this) { } @@ -70,6 +72,8 @@ void ShortCutManager::registerActions(const Core::Context &qmlDesignerMainContex connect(&m_deleteAction, &QAction::triggered, this, &ShortCutManager::deleteSelected); + connect(&m_duplicateAction, &QAction::triggered, this, &ShortCutManager::duplicateSelected); + connect(&m_cutAction, &QAction::triggered, this, &ShortCutManager::cutSelected); connect(&m_copyAction, &QAction::triggered, this, &ShortCutManager::copySelected); @@ -126,10 +130,13 @@ void ShortCutManager::registerActions(const Core::Context &qmlDesignerMainContex // Undo / Redo command = Core::ActionManager::registerAction(&m_undoAction, Core::Constants::UNDO, qmlDesignerMainContext); - designerActionManager.addCreatorCommand(command, ComponentCoreConstants::editCategory, 310, Utils::Icons::UNDO_TOOLBAR.icon()); + command->setDefaultKeySequence(QKeySequence::Undo); + designerActionManager.addCreatorCommand(command, ComponentCoreConstants::editCategory, 1, Utils::Icons::UNDO_TOOLBAR.icon()); command = Core::ActionManager::registerAction(&m_redoAction, Core::Constants::REDO, qmlDesignerMainContext); - designerActionManager.addCreatorCommand(command, ComponentCoreConstants::editCategory, 300, Utils::Icons::REDO_TOOLBAR.icon()); + command->setDefaultKeySequence(QKeySequence::Redo); + designerActionManager.addCreatorCommand(command, ComponentCoreConstants::editCategory, 2, Utils::Icons::REDO_TOOLBAR.icon()); + designerActionManager.addDesignerAction(new SeperatorDesignerAction(ComponentCoreConstants::editCategory, 10)); //Edit Menu m_deleteAction.setIcon(QIcon::fromTheme(QLatin1String("edit-cut"), Utils::Icons::EDIT_CLEAR_TOOLBAR.icon())); @@ -140,28 +147,34 @@ void ShortCutManager::registerActions(const Core::Context &qmlDesignerMainContex command->setAttribute(Core::Command::CA_Hide); // don't show delete in other modes if (!Utils::HostOsInfo::isMacHost()) editMenu->addAction(command, Core::Constants::G_EDIT_COPYPASTE); - designerActionManager.addCreatorCommand(command, ComponentCoreConstants::editCategory, 280); + designerActionManager.addCreatorCommand(command, ComponentCoreConstants::editCategory, 14); Core::ActionManager::registerAction(&m_cutAction, Core::Constants::CUT, qmlDesignerFormEditorContext); Core::ActionManager::registerAction(&m_cutAction, Core::Constants::CUT, qmlDesignerEditor3DContext); command = Core::ActionManager::registerAction(&m_cutAction, Core::Constants::CUT, qmlDesignerNavigatorContext); command->setDefaultKeySequence(QKeySequence::Cut); editMenu->addAction(command, Core::Constants::G_EDIT_COPYPASTE); - designerActionManager.addCreatorCommand(command, ComponentCoreConstants::editCategory, 260, Utils::Icons::CUT_TOOLBAR.icon()); + designerActionManager.addCreatorCommand(command, ComponentCoreConstants::editCategory, 13, Utils::Icons::CUT_TOOLBAR.icon()); Core::ActionManager::registerAction(&m_copyAction, Core::Constants::COPY, qmlDesignerFormEditorContext); Core::ActionManager::registerAction(&m_copyAction, Core::Constants::COPY, qmlDesignerEditor3DContext); command = Core::ActionManager::registerAction(&m_copyAction, Core::Constants::COPY, qmlDesignerNavigatorContext); command->setDefaultKeySequence(QKeySequence::Copy); editMenu->addAction(command, Core::Constants::G_EDIT_COPYPASTE); - designerActionManager.addCreatorCommand(command, ComponentCoreConstants::editCategory, 250, Utils::Icons::COPY_TOOLBAR.icon()); + designerActionManager.addCreatorCommand(command, ComponentCoreConstants::editCategory, 11, Utils::Icons::COPY_TOOLBAR.icon()); Core::ActionManager::registerAction(&m_pasteAction, Core::Constants::PASTE, qmlDesignerFormEditorContext); Core::ActionManager::registerAction(&m_pasteAction, Core::Constants::PASTE, qmlDesignerEditor3DContext); command = Core::ActionManager::registerAction(&m_pasteAction, Core::Constants::PASTE, qmlDesignerNavigatorContext); command->setDefaultKeySequence(QKeySequence::Paste); editMenu->addAction(command, Core::Constants::G_EDIT_COPYPASTE); - designerActionManager.addCreatorCommand(command, ComponentCoreConstants::editCategory, 240, Utils::Icons::PASTE_TOOLBAR.icon()); + designerActionManager.addCreatorCommand(command, ComponentCoreConstants::editCategory, 12, Utils::Icons::PASTE_TOOLBAR.icon()); + + Core::ActionManager::registerAction(&m_duplicateAction, Constants::C_DUPLICATE, qmlDesignerFormEditorContext); + Core::ActionManager::registerAction(&m_duplicateAction, Constants::C_DUPLICATE, qmlDesignerEditor3DContext); + command = Core::ActionManager::registerAction(&m_duplicateAction, Constants::C_DUPLICATE, qmlDesignerMainContext); + editMenu->addAction(command, Core::Constants::G_EDIT_COPYPASTE); + designerActionManager.addCreatorCommand(command, ComponentCoreConstants::editCategory, 15); Core::ActionManager::registerAction(&m_selectAllAction, Core::Constants::SELECTALL, qmlDesignerFormEditorContext); command = Core::ActionManager::registerAction(&m_selectAllAction, Core::Constants::SELECTALL, qmlDesignerNavigatorContext); @@ -258,6 +271,12 @@ void ShortCutManager::copySelected() currentDesignDocument()->copySelected(); } +void ShortCutManager::duplicateSelected() +{ + if (currentDesignDocument()) + currentDesignDocument()->duplicateSelected(); +} + void ShortCutManager::paste() { if (currentDesignDocument()) diff --git a/src/plugins/qmldesigner/shortcutmanager.h b/src/plugins/qmldesigner/shortcutmanager.h index 564a2dbbd7e..63c724934c2 100644 --- a/src/plugins/qmldesigner/shortcutmanager.h +++ b/src/plugins/qmldesigner/shortcutmanager.h @@ -41,6 +41,7 @@ private: void deleteSelected(); void cutSelected(); void copySelected(); + void duplicateSelected(); void paste(); void selectAll(); void undoAvailable(bool isAvailable); @@ -61,6 +62,7 @@ private: QAction m_cutAction; QAction m_copyAction; QAction m_pasteAction; + QAction m_duplicateAction; QAction m_selectAllAction; QAction m_escapeAction; From fcf70d69576f5849cf72573498fe025e90bf6234 Mon Sep 17 00:00:00 2001 From: Ali Kianian Date: Mon, 5 Dec 2022 17:09:07 +0200 Subject: [PATCH 120/131] QmlDesigner: Add checkable and checked properties to AbstractAction AbstractAction could be checkable and it is also able to be checked. The default action will follow isChecked overridden method when an update is required. Task-number: QDS-8502 Change-Id: Idf379ba544da57b138b43aa963174f43b750e34f Reviewed-by: Thomas Hartmann Reviewed-by: Qt CI Bot Reviewed-by: Miikka Heikkinen Reviewed-by: Mahmoud Badri Reviewed-by: --- .../components/componentcore/abstractaction.cpp | 12 ++++++++++++ .../components/componentcore/abstractaction.h | 3 +++ 2 files changed, 15 insertions(+) diff --git a/src/plugins/qmldesigner/components/componentcore/abstractaction.cpp b/src/plugins/qmldesigner/components/componentcore/abstractaction.cpp index db0cfc591bb..88d5991b129 100644 --- a/src/plugins/qmldesigner/components/componentcore/abstractaction.cpp +++ b/src/plugins/qmldesigner/components/componentcore/abstractaction.cpp @@ -38,9 +38,21 @@ void AbstractAction::updateContext() if (m_selectionContext.isValid()) { m_defaultAction->setEnabled(isEnabled(m_selectionContext)); m_defaultAction->setVisible(isVisible(m_selectionContext)); + if (m_defaultAction->isCheckable()) + m_defaultAction->setChecked(isChecked(m_selectionContext)); } } +bool AbstractAction::isChecked(const SelectionContext &) const +{ + return false; +} + +void AbstractAction::setCheckable(bool checkable) +{ + m_defaultAction->setCheckable(checkable); +} + DefaultAction *AbstractAction::defaultAction() const { return m_defaultAction.data(); diff --git a/src/plugins/qmldesigner/components/componentcore/abstractaction.h b/src/plugins/qmldesigner/components/componentcore/abstractaction.h index 081740b03dc..1d66f947982 100644 --- a/src/plugins/qmldesigner/components/componentcore/abstractaction.h +++ b/src/plugins/qmldesigner/components/componentcore/abstractaction.h @@ -38,8 +38,11 @@ public: protected: virtual void updateContext(); + virtual bool isChecked(const SelectionContext &selectionContext) const; virtual bool isVisible(const SelectionContext &selectionContext) const = 0; virtual bool isEnabled(const SelectionContext &selectionContext) const = 0; + + void setCheckable(bool checkable); SelectionContext selectionContext() const; private: From 1839de431cfa2c75fcb4e4a26bf8abdbc5a8cbee Mon Sep 17 00:00:00 2001 From: Ali Kianian Date: Wed, 7 Dec 2022 17:03:28 +0200 Subject: [PATCH 121/131] QmlDesigner: Implement "Anchor to parent" for 2D-View Context Menu ParentAnchorAction is added to handle "Anchor to parent" actions. Task-number: QDS-8502 Change-Id: I629055fcf389c6c8dff3fc308180a9cdb57f26c9 Reviewed-by: Mahmoud Badri Reviewed-by: Miikka Heikkinen Reviewed-by: Reviewed-by: Qt CI Bot Reviewed-by: Thomas Hartmann --- src/plugins/qmldesigner/CMakeLists.txt | 1 + .../components/componentcore/anchoraction.cpp | 133 ++++++++++++++++++ .../components/componentcore/anchoraction.h | 27 ++++ .../componentcore/componentcore_constants.h | 15 ++ .../componentcore/designeractionmanager.cpp | 67 +++++++++ 5 files changed, 243 insertions(+) create mode 100644 src/plugins/qmldesigner/components/componentcore/anchoraction.cpp create mode 100644 src/plugins/qmldesigner/components/componentcore/anchoraction.h diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 5daf2e72f80..9e040dba0e6 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -618,6 +618,7 @@ extend_qtc_plugin(QmlDesigner selectioncontext.cpp selectioncontext.h theme.cpp theme.h zoomaction.cpp zoomaction.h + anchoraction.cpp anchoraction.h svgpasteaction.cpp svgpasteaction.h viewmanager.cpp viewmanager.h ) diff --git a/src/plugins/qmldesigner/components/componentcore/anchoraction.cpp b/src/plugins/qmldesigner/components/componentcore/anchoraction.cpp new file mode 100644 index 00000000000..630a53dc39e --- /dev/null +++ b/src/plugins/qmldesigner/components/componentcore/anchoraction.cpp @@ -0,0 +1,133 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "anchoraction.h" +#include "nodeabstractproperty.h" + +#include + +using namespace QmlDesigner; + +static void setAnchorToTheSameOnTarget(AbstractView *view, + const ModelNode &objectNode, + const AnchorLineType &objectAnchorType, + const ModelNode &targetNode, + double margin = 0) +{ + QmlItemNode node = objectNode; + QmlItemNode dstNode = targetNode; + if (!node.isValid() || !dstNode.isValid()) + return; + + view->executeInTransaction("QmlAnchorAction|setAnchorToTheSameOnTarget", [&] { + int maxFlagBits = 8 * sizeof(AnchorLineType); + for (int i = 0; i < maxFlagBits; ++i) { + AnchorLineType requiredAnchor = AnchorLineType(0x1 << i); + if (requiredAnchor & objectAnchorType) { + node.anchors().setAnchor(requiredAnchor, dstNode, requiredAnchor); + if (qFuzzyIsNull(margin)) + node.anchors().removeMargin(requiredAnchor); + else + node.anchors().setMargin(requiredAnchor, margin); + } + } + }); +} + +static void anchorToParent(const SelectionContext &selectionState, AnchorLineType anchorType) +{ + if (!selectionState.view()) + return; + + ModelNode modelNode = selectionState.currentSingleSelectedNode(); + ModelNode parentModelNode = modelNode.parentProperty().parentModelNode(); + setAnchorToTheSameOnTarget(selectionState.view(), modelNode, anchorType, parentModelNode); +} + +static void removeAnchor(const SelectionContext &selectionState, + const AnchorLineType &objectAnchorType, + double margin = 0) +{ + ModelNode srcModelNode = selectionState.currentSingleSelectedNode(); + QmlItemNode node = srcModelNode; + AbstractView *view = srcModelNode.view(); + if (!node.isValid() || !view) + return; + + view->executeInTransaction("QmlAnchorAction|removeAnchor", [&] { + + int maxFlagBits = 8 * sizeof(AnchorLineType); + for (int i = 0; i < maxFlagBits; ++i) { + AnchorLineType singleAnchor = AnchorLineType(0x1 << i); + if (singleAnchor & objectAnchorType) { + node.anchors().removeAnchor(singleAnchor); + if (qFuzzyIsNull(margin)) + node.anchors().removeMargin(singleAnchor); + else + node.anchors().setMargin(singleAnchor, margin); + } + } + }); +} + +static bool singleSelectionIsAnchoredToParentBy(const SelectionContext &selectionState, + const AnchorLineType &lineType) +{ + QmlItemNode itemNode(selectionState.currentSingleSelectedNode()); + QmlItemNode itemNodeParent(itemNode.modelParentItem()); + if (itemNode.isValid() && itemNodeParent.isValid()) { + bool allSet = false; + int maxFlagBits = 8 * sizeof(AnchorLineType); + for (int i = 0; i < maxFlagBits; ++i) { + AnchorLineType singleAnchor = AnchorLineType(0x1 << i); + if (singleAnchor & lineType) { + if (itemNode.anchors().modelAnchor(singleAnchor).qmlItemNode() == itemNodeParent) + allSet = true; + else + return false; + } + } + return allSet; + } + return false; +} + +static void toggleParentAnchor(const SelectionContext &selectionState, AnchorLineType anchorType) +{ + if (singleSelectionIsAnchoredToParentBy(selectionState, anchorType)) + removeAnchor(selectionState, anchorType); + else + anchorToParent(selectionState, anchorType); +} + +static SelectionContextOperation getSuitableAnchorFunction(AnchorLineType lineType) +{ + return std::bind(toggleParentAnchor, std::placeholders::_1, lineType); +} + +ParentAnchorAction::ParentAnchorAction(const QByteArray &id, + const QString &description, + const QIcon &icon, + const QString &tooltip, + const QByteArray &category, + const QKeySequence &key, + int priority, + AnchorLineType lineType) + : ModelNodeAction(id, + description, + icon, + tooltip, + category, + key, + priority, + getSuitableAnchorFunction(lineType), + &SelectionContextFunctors::singleSelectedItem) + , m_lineType(lineType) +{ + setCheckable(true); +} + +bool ParentAnchorAction::isChecked(const SelectionContext &selectionState) const +{ + return singleSelectionIsAnchoredToParentBy(selectionState, m_lineType); +} diff --git a/src/plugins/qmldesigner/components/componentcore/anchoraction.h b/src/plugins/qmldesigner/components/componentcore/anchoraction.h new file mode 100644 index 00000000000..29a190c0b73 --- /dev/null +++ b/src/plugins/qmldesigner/components/componentcore/anchoraction.h @@ -0,0 +1,27 @@ +// Copyright (C) 2022 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 +namespace QmlDesigner { + +class ParentAnchorAction : public ModelNodeAction +{ +public: + ParentAnchorAction(const QByteArray &id, + const QString &description, + const QIcon &icon, + const QString &tooltip, + const QByteArray &category, + const QKeySequence &key, + int priority, + AnchorLineType lineType); + + bool isChecked(const SelectionContext &selectionState) const override; + +private: + const AnchorLineType m_lineType; +}; + +} diff --git a/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h b/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h index 6664eec8556..3a53555ccdd 100644 --- a/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h +++ b/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h @@ -41,6 +41,14 @@ const char applyFormatCommandId[] = "ApplyFormat"; const char visiblityCommandId[] = "ToggleVisiblity"; const char anchorsFillCommandId[] = "AnchorsFill"; const char anchorsResetCommandId[] = "AnchorsReset"; + +const char anchorParentTopAndBottomCommandId[] = "AnchorParentTopAndBottom"; +const char anchorParentLeftAndRightCommandId[] = "AnchorParentLeftAndRight"; +const char anchorParentTopCommandId[] = "AnchorParentTop"; +const char anchorParentRightCommandId[] = "AnchorParentRight"; +const char anchorParentBottomCommandId[] = "AnchorParentBottom"; +const char anchorParentLeftCommandId[] = "AnchorParentLeft"; + const char removePositionerCommandId[] = "RemovePositioner"; const char createFlowActionAreaCommandId[] = "CreateFlowActionArea"; const char setFlowStartCommandId[] = "SetFlowStart"; @@ -134,6 +142,13 @@ const char reverseDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "R const char anchorsFillDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Fill Parent"); const char anchorsResetDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "No Anchors"); +const char anchorParentTopAndBottomDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Top And Bottom"); +const char anchorParentLeftAndRightDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Left And Right"); +const char anchorParentTopDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Top"); +const char anchorParentRightDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Right"); +const char anchorParentBottomDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Bottom"); +const char anchorParentLeftDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Left"); + const char layoutColumnPositionerDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Column Positioner"); const char layoutRowPositionerDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Row Positioner"); const char layoutGridPositionerDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Grid Positioner"); diff --git a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp index 9df8ad0b511..dd07f433ca5 100644 --- a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp @@ -3,6 +3,7 @@ #include "designeractionmanager.h" +#include "anchoraction.h" #include "changestyleaction.h" #include "designeractionmanagerview.h" #include "designermcumanager.h" @@ -1526,6 +1527,72 @@ void DesignerActionManager::createDefaultDesignerActions() addDesignerAction(new SeperatorDesignerAction(anchorsCategory, 10)); + addDesignerAction(new ParentAnchorAction( + anchorParentTopAndBottomCommandId, + anchorParentTopAndBottomDisplayName, + {}, + {}, + anchorsCategory, + QKeySequence(), + 11, + AnchorLineType(AnchorLineTop | AnchorLineBottom))); + + addDesignerAction(new ParentAnchorAction( + anchorParentLeftAndRightCommandId, + anchorParentLeftAndRightDisplayName, + {}, + {}, + anchorsCategory, + QKeySequence(), + 12, + AnchorLineType(AnchorLineLeft | AnchorLineRight))); + + addDesignerAction(new SeperatorDesignerAction(anchorsCategory, 20)); + + addDesignerAction(new ParentAnchorAction( + anchorParentTopCommandId, + anchorParentTopDisplayName, + Utils::Icon({{":/qmldesigner/images/anchor_top.png", Utils::Theme::IconsBaseColor}, + {":/utils/images/iconoverlay_reset.png", Utils::Theme::IconsStopToolBarColor}}).icon(), + {}, + anchorsCategory, + QKeySequence(), + 21, + AnchorLineTop)); + + addDesignerAction(new ParentAnchorAction( + anchorParentBottomCommandId, + anchorParentBottomDisplayName, + Utils::Icon({{":/qmldesigner/images/anchor_bottom.png", Utils::Theme::IconsBaseColor}, + {":/utils/images/iconoverlay_reset.png", Utils::Theme::IconsStopToolBarColor}}).icon(), + {}, + anchorsCategory, + QKeySequence(), + 22, + AnchorLineBottom)); + + addDesignerAction(new ParentAnchorAction( + anchorParentLeftCommandId, + anchorParentLeftDisplayName, + Utils::Icon({{":/qmldesigner/images/anchor_left.png", Utils::Theme::IconsBaseColor}, + {":/utils/images/iconoverlay_reset.png", Utils::Theme::IconsStopToolBarColor}}).icon(), + {}, + anchorsCategory, + QKeySequence(), + 23, + AnchorLineLeft)); + + addDesignerAction(new ParentAnchorAction( + anchorParentRightCommandId, + anchorParentRightDisplayName, + Utils::Icon({{":/qmldesigner/images/anchor_right.png", Utils::Theme::IconsBaseColor}, + {":/utils/images/iconoverlay_reset.png", Utils::Theme::IconsStopToolBarColor}}).icon(), + {}, + anchorsCategory, + QKeySequence(), + 24, + AnchorLineRight)); + addDesignerAction(new ActionGroup( positionerCategoryDisplayName, positionerCategory, From 2a0a1181f9cf3f5dee7ee15d4752dc99119bf4e2 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Mon, 12 Dec 2022 17:39:47 +0200 Subject: [PATCH 122/131] QmlDesigner: Resolve active scene id also at model attach When coming back to previously loaded document, the sceneId will be available at model attach time and there will not be separate changed notification for it. Fixes: QDS-8585 Change-Id: Ic8fcd4e2ec9123adc39d0c1cdca3bdb86d3a7924 Reviewed-by: Mahmoud Badri Reviewed-by: --- .../components/assetslibrary/assetslibraryview.cpp | 2 ++ .../components/contentlibrary/contentlibraryview.cpp | 2 ++ .../components/materialbrowser/materialbrowserview.cpp | 2 ++ src/plugins/qmldesigner/designercore/include/model.h | 1 + src/plugins/qmldesigner/designercore/model/model.cpp | 8 ++++++++ 5 files changed, 15 insertions(+) diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryview.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryview.cpp index ef6aa9f610e..c43bd485334 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryview.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryview.cpp @@ -82,6 +82,8 @@ void AssetsLibraryView::modelAttached(Model *model) m_widget->setModel(model); setResourcePath(DocumentManager::currentResourcePath().toFileInfo().absoluteFilePath()); + + m_sceneId = model->active3DSceneId(); } void AssetsLibraryView::modelAboutToBeDetached(Model *model) diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp index 856d4077e12..764bb7f155a 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp @@ -130,6 +130,8 @@ void ContentLibraryView::modelAttached(Model *model) const bool hasLibrary = materialLibraryNode().isValid(); m_widget->setHasMaterialLibrary(hasLibrary); m_widget->setHasQuick3DImport(m_hasQuick3DImport); + + m_sceneId = model->active3DSceneId(); } void ContentLibraryView::modelAboutToBeDetached(Model *model) diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp index be3a45ad2ab..534ee4c4922 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp @@ -229,6 +229,8 @@ void MaterialBrowserView::modelAttached(Model *model) refreshModel(true); loadPropertyGroups(); // Needs the delay because it uses metaInfo }); + + m_sceneId = model->active3DSceneId(); } void MaterialBrowserView::refreshModel(bool updateImages) diff --git a/src/plugins/qmldesigner/designercore/include/model.h b/src/plugins/qmldesigner/designercore/include/model.h index e22dbe70b6e..336a5b2d280 100644 --- a/src/plugins/qmldesigner/designercore/include/model.h +++ b/src/plugins/qmldesigner/designercore/include/model.h @@ -149,6 +149,7 @@ public: QString generateIdFromName(const QString &name, const QString &fallbackId = "element") const; void setActive3DSceneId(qint32 sceneId); + qint32 active3DSceneId() const; void startDrag(QMimeData *mimeData, const QPixmap &icon); void endDrag(); diff --git a/src/plugins/qmldesigner/designercore/model/model.cpp b/src/plugins/qmldesigner/designercore/model/model.cpp index f3d8f403d61..0909afabac1 100644 --- a/src/plugins/qmldesigner/designercore/model/model.cpp +++ b/src/plugins/qmldesigner/designercore/model/model.cpp @@ -1596,6 +1596,14 @@ void Model::setActive3DSceneId(qint32 sceneId) d->notifyActive3DSceneIdChanged(sceneId); } +qint32 Model::active3DSceneId() const +{ + auto sceneId = d->rootNode()->auxiliaryData(active3dSceneProperty); + if (sceneId) + return sceneId->toInt(); + return -1; +} + void Model::startDrag(QMimeData *mimeData, const QPixmap &icon) { d->notifyDragStarted(mimeData); From 521f220efb7cfcab86b0378884d07bca76a24ea9 Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Mon, 12 Dec 2022 16:58:04 +0200 Subject: [PATCH 123/131] QmlDesigner: Add basic texture's info to tool tip Fixes: QDS-8488 Change-Id: If4672863babef03bdc108109c514837a2587c6a8 Reviewed-by: Miikka Heikkinen --- .../materialBrowserQmlSource/TextureItem.qml | 12 ++++- .../materialbrowsertexturesmodel.cpp | 45 ++++++++++++++----- .../materialbrowsertexturesmodel.h | 6 +++ .../materialbrowser/materialbrowserview.cpp | 8 +++- .../materialbrowser/materialbrowserview.h | 1 + 5 files changed, 58 insertions(+), 14 deletions(-) diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml index 2ad2a317ed4..a223668dc9b 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml @@ -44,8 +44,18 @@ Rectangle { ToolTip { visible: mouseArea.containsMouse - text: textureSource ? textureSource : qsTr("Texture has no source image.") + // contentWidth is not calculated correctly by the toolTip (resulting in a wider tooltip than + // needed). Using a helper Text to calculate the correct width + contentWidth: helperText.width + bottomInset: -2 + text: textureToolTip delay: 1000 + + Text { + id: helperText + text: textureToolTip + visible: false + } } Image { diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp index e3ed0870b22..f348580937d 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp @@ -3,6 +3,7 @@ #include "materialbrowsertexturesmodel.h" +#include "designeractionmanager.h" #include "designmodewidget.h" #include "qmldesignerplugin.h" #include "qmlobjectnode.h" @@ -31,8 +32,7 @@ QVariant MaterialBrowserTexturesModel::data(const QModelIndex &index, int role) QTC_ASSERT(index.isValid() && index.row() < m_textureList.count(), return {}); QTC_ASSERT(roleNames().contains(role), return {}); - QByteArray roleName = roleNames().value(role); - if (roleName == "textureSource") { + if (role == RoleTexSource) { QString source = QmlObjectNode(m_textureList.at(index.row())).modelValue("source").toString(); if (source.isEmpty()) return {}; @@ -42,15 +42,36 @@ QVariant MaterialBrowserTexturesModel::data(const QModelIndex &index, int role) ->fileName().absolutePath().pathAppended(source).cleanPath().toString()); } - if (roleName == "textureVisible") + if (role == RoleTexVisible) return isTextureVisible(index.row()); - if (roleName == "hasDynamicProperties") + if (role == RoleTexHasDynamicProps) return !m_textureList.at(index.row()).dynamicProperties().isEmpty(); - if (roleName == "textureInternalId") + if (role == RoleTexInternalId) return m_textureList.at(index.row()).internalId(); + if (role == RoleTexToolTip) { + QString source = QmlObjectNode(m_textureList.at(index.row())).modelValue("source").toString(); + if (source.isEmpty()) + return tr("Texture has no source image."); + + const QString noData = tr("Texture has no data."); + + auto op = QmlDesignerPlugin::instance()->viewManager().designerActionManager() + .modelNodePreviewOperation(m_textureList.at(index.row())); + if (!op) + return noData; + + QVariantMap imgMap = op(m_textureList.at(index.row())).toMap(); + if (imgMap.isEmpty()) + return noData; + + return QLatin1String("%1\n%2\n%3").arg(imgMap.value("id").toString(), + source.split('/').last(), + imgMap.value("info").toString()); + } + return {}; } @@ -68,14 +89,14 @@ bool MaterialBrowserTexturesModel::isValidIndex(int idx) const return idx > -1 && idx < rowCount(); } - QHash MaterialBrowserTexturesModel::roleNames() const { static const QHash roles { - {Qt::UserRole + 1, "textureSource"}, - {Qt::UserRole + 2, "textureVisible"}, - {Qt::UserRole + 3, "hasDynamicProperties"}, - {Qt::UserRole + 4, "textureInternalId"} + {RoleTexHasDynamicProps, "hasDynamicProperties"}, + {RoleTexInternalId, "textureInternalId"}, + {RoleTexSource, "textureSource"}, + {RoleTexToolTip, "textureToolTip"}, + {RoleTexVisible, "textureVisible"} }; return roles; } @@ -181,12 +202,12 @@ void MaterialBrowserTexturesModel::updateTextureSource(const ModelNode &texture) { int idx = textureIndex(texture); if (idx != -1) - emit dataChanged(index(idx, 0), index(idx, 0), {roleNames().key("textureSource")}); + emit dataChanged(index(idx, 0), index(idx, 0), {RoleTexSource, RoleTexToolTip}); } void MaterialBrowserTexturesModel::updateAllTexturesSources() { - emit dataChanged(index(0, 0), index(rowCount() - 1, 0), {roleNames().key("textureSource")}); + emit dataChanged(index(0, 0), index(rowCount() - 1, 0), {RoleTexSource, RoleTexToolTip}); } void MaterialBrowserTexturesModel::updateSelectedTexture() diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h index c99d7511ea9..c12b0be93e2 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h @@ -87,6 +87,12 @@ private: bool m_isEmpty = true; bool m_hasSingleModelSelection = false; bool m_hasSceneEnv = false; + + const static int RoleTexHasDynamicProps = Qt::UserRole + 1; + const static int RoleTexInternalId = Qt::UserRole + 2; + const static int RoleTexSource = Qt::UserRole + 3; + const static int RoleTexToolTip = Qt::UserRole + 4; + const static int RoleTexVisible = Qt::UserRole + 5; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp index 534ee4c4922..1f4df699481 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp @@ -323,12 +323,18 @@ void MaterialBrowserView::modelNodePreviewPixmapChanged(const ModelNode &node, c m_widget->updateMaterialPreview(node, pixmap); } +void MaterialBrowserView::nodeIdChanged(const ModelNode &node, [[maybe_unused]] const QString &newId, + [[maybe_unused]] const QString &oldId) +{ + if (isTexture(node)) + m_widget->materialBrowserTexturesModel()->updateTextureSource(node); +} + void MaterialBrowserView::variantPropertiesChanged(const QList &propertyList, [[maybe_unused]] PropertyChangeFlags propertyChange) { for (const VariantProperty &property : propertyList) { ModelNode node(property.parentModelNode()); - if (isMaterial(node) && property.name() == "objectName") { m_widget->materialBrowserModel()->updateMaterialName(node); } else if (property.name() == "source") { diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h index b78d54e1660..0d2ec42af0d 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h @@ -36,6 +36,7 @@ public: void selectedNodesChanged(const QList &selectedNodeList, const QList &lastSelectedNodeList) override; void modelNodePreviewPixmapChanged(const ModelNode &node, const QPixmap &pixmap) override; + void nodeIdChanged(const ModelNode &node, const QString &newId, const QString &oldId) override; void variantPropertiesChanged(const QList &propertyList, PropertyChangeFlags propertyChange) override; void propertiesRemoved(const QList &propertyList) override; void nodeReparented(const ModelNode &node, const NodeAbstractProperty &newPropertyParent, From 3d3bf4d9234e9a454a3347eec9b08323b070af0c Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Tue, 13 Dec 2022 13:07:16 +0200 Subject: [PATCH 124/131] QmlDesigner: Adjust for scene root transform when finding click pos Fixes: QDS-8590 Change-Id: I0b8a604c4f78f57a64f4720ab676745c5d0db980 Reviewed-by: Mahmoud Badri --- .../qml2puppet/instances/qt5informationnodeinstanceserver.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp b/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp index 70d2571e847..dbbe90c2f35 100644 --- a/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp +++ b/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include "servernodeinstance.h" #include "childrenchangeeventfilter.h" @@ -427,6 +428,9 @@ void Qt5InformationNodeInstanceServer::getNodeAtPos(const QPointF &pos) if (!qFuzzyCompare(planePos.z(), -1.f) && qAbs(planePos.x()) < limit && qAbs(planePos.y()) < limit) pos3d = {planePos.x(), 0, planePos.y()}; } + if (auto rootScene = qobject_cast(m_active3DScene)) + pos3d = rootScene->sceneTransform().inverted().map(pos3d); + QVariantList data; data.append(instanceId); data.append(pos3d); From 87d076d6a6d5a0bf6fe69665ef5e662f468b5e75 Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Tue, 13 Dec 2022 13:58:40 +0200 Subject: [PATCH 125/131] QmlDesigner: Fix possible build fail on some platforms Change-Id: I5d412656cb61e7cc29af27a42922ad6c00bd4c6b Reviewed-by: Amr Elsayed Reviewed-by: Miikka Heikkinen --- .../materialbrowser/materialbrowsertexturesmodel.h | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h index c12b0be93e2..6056431019c 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h @@ -88,11 +88,13 @@ private: bool m_hasSingleModelSelection = false; bool m_hasSceneEnv = false; - const static int RoleTexHasDynamicProps = Qt::UserRole + 1; - const static int RoleTexInternalId = Qt::UserRole + 2; - const static int RoleTexSource = Qt::UserRole + 3; - const static int RoleTexToolTip = Qt::UserRole + 4; - const static int RoleTexVisible = Qt::UserRole + 5; + enum { + RoleTexHasDynamicProps = Qt::UserRole + 1, + RoleTexInternalId, + RoleTexSource, + RoleTexToolTip, + RoleTexVisible + }; }; } // namespace QmlDesigner From ca023553b2d7d75442ba7cd6179a01d10451edcf Mon Sep 17 00:00:00 2001 From: Pranta Dastider Date: Mon, 12 Dec 2022 15:46:47 +0100 Subject: [PATCH 126/131] QmlDesigner: Update the tooltips for QDS Basic Components Update the tooltip text for Qt Design Studio basic components. Fixes: QDS-8309 Change-Id: Ic8bd30db189cbf405ae6ded56c8ed98225009d2d Reviewed-by: Tim Jenssen Reviewed-by: Thomas Hartmann Reviewed-by: Mats Honkamaa --- src/plugins/qmldesigner/qtquickplugin/quick.metainfo | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/plugins/qmldesigner/qtquickplugin/quick.metainfo b/src/plugins/qmldesigner/qtquickplugin/quick.metainfo index d4c02abef42..655a3b0841a 100644 --- a/src/plugins/qmldesigner/qtquickplugin/quick.metainfo +++ b/src/plugins/qmldesigner/qtquickplugin/quick.metainfo @@ -16,6 +16,7 @@ MetaInfo { Property { name: "width"; type: "int"; value: 200; } Property { name: "height"; type: "int"; value: 200; } + toolTip: qsTr("Groups several visual items.") } } @@ -32,6 +33,7 @@ MetaInfo { Property { name: "width"; type: "int"; value: 200; } Property { name: "height"; type: "int"; value: 200; } Property { name: "color"; type: "QColor"; value: "#ffffff"; } + toolTip: qsTr("A rectangle with an optional border.") } } @@ -47,6 +49,7 @@ MetaInfo { Property { name: "font.pixelSize"; type: "int"; value: 12; } Property { name: "text"; type: "binding"; value: "qsTr(\"Text\")"; } + toolTip: qsTr("A read-only text label.") } } @@ -64,6 +67,7 @@ MetaInfo { Property { name: "height"; type: "int"; value: 20; } Property { name: "font.pixelSize"; type: "int"; value: 12; } Property { name: "text"; type: "binding"; value: "qsTr(\"Text Edit\")"; } + toolTip: qsTr("A multi-line block of editable text.") } } @@ -81,6 +85,7 @@ MetaInfo { Property { name: "height"; type: "int"; value: 20; } Property { name: "font.pixelSize"; type: "int"; value: 12; } Property { name: "text"; type: "binding"; value: "qsTr(\"Text Input\")"; } + toolTip: qsTr("An editable line of text.") } } @@ -96,6 +101,7 @@ MetaInfo { Property { name: "width"; type: "int"; value: 100; } Property { name: "height"; type: "int"; value: 100; } + toolTip: qsTr("An area with mouse functionality.") } } @@ -113,6 +119,7 @@ MetaInfo { Property { name: "height"; type: "int"; value: 100; } Property { name: "source"; type: "QUrl"; value:"qrc:/qtquickplugin/images/template_image.png"; } Property { name: "fillMode"; type: "enum"; value: "Image.PreserveAspectFit"; } + toolTip: qsTr("Displays an image.") } } @@ -129,6 +136,7 @@ MetaInfo { Property { name: "width"; type: "int"; value: 100; } Property { name: "height"; type: "int"; value: 100; } Property { name: "source"; type: "QUrl"; value:"qrc:/qtquickplugin/images/template_image.png"; } + toolTip: qsTr("Animates a series of images.") } } @@ -145,6 +153,7 @@ MetaInfo { Property { name: "width"; type: "int"; value: 100; } Property { name: "height"; type: "int"; value: 100; } Property { name: "source"; type: "QUrl"; value:"qrc:/qtquickplugin/images/template_image.png"; } + toolTip: qsTr("A responsive border based on an image.") } } @@ -160,6 +169,7 @@ MetaInfo { Property { name: "width"; type: "int"; value: 300; } Property { name: "height"; type: "int"; value: 300; } + toolTip: qsTr("An area for keeping dragable objects.") } } @@ -217,6 +227,7 @@ MetaInfo { Property { name: "width"; type: "int"; value: 100; } Property { name: "height"; type: "int"; value: 100; } + toolTip: qsTr("A scope to focus on a specific text element.") } } From 34ecdfc68240768e69e123faa101588ae915d8c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cristi=C3=A1n=20Maureira-Fredes?= Date: Mon, 12 Dec 2022 11:21:53 +0100 Subject: [PATCH 127/131] Add QtQuick.Window import for 5.15.x projects Some of the templates were using the Window type but didn't include the import statement for 5.15 Fixes: QDS-8468 Change-Id: I414d2bd09198374835de1ca6c81c8ed62d08dede Reviewed-by: Reviewed-by: Thomas Hartmann --- .../projects/application-3d/contentmodule.main.qml.tpl | 3 +++ .../qmldesigner/studio_templates/projects/common/App.qml.tpl | 2 ++ .../projects/common/contentmodule.main.qml.tpl | 3 +++ .../studio_templates/projects/mobile-stack/App.qml.tpl | 3 +++ .../studio_templates/projects/mobile-swipe/App.qml.tpl | 3 +++ 5 files changed, 14 insertions(+) diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/application-3d/contentmodule.main.qml.tpl b/share/qtcreator/qmldesigner/studio_templates/projects/application-3d/contentmodule.main.qml.tpl index 0aa4cf67d7f..96f5dbaa36c 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/application-3d/contentmodule.main.qml.tpl +++ b/share/qtcreator/qmldesigner/studio_templates/projects/application-3d/contentmodule.main.qml.tpl @@ -1,4 +1,7 @@ import QtQuick %{QtQuickVersion} +@if !%{IsQt6Project} +import QtQuick.Window %{QtQuickVersion} +@endif import %{ApplicationImport} @if %{UseVirtualKeyboard} import QtQuick.VirtualKeyboard %{QtQuickVersion} diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/common/App.qml.tpl b/share/qtcreator/qmldesigner/studio_templates/projects/common/App.qml.tpl index 21794f23e9f..d093ee8586e 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/common/App.qml.tpl +++ b/share/qtcreator/qmldesigner/studio_templates/projects/common/App.qml.tpl @@ -2,7 +2,9 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 import QtQuick %{QtQuickVersion} +@if !%{IsQt6Project} import QtQuick.Window %{QtQuickVersion} +@endif import %{ImportModuleName} %{ImportModuleVersion} @if %{UseVirtualKeyboard} import QtQuick.VirtualKeyboard %{QtQuickVersion} diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/common/contentmodule.main.qml.tpl b/share/qtcreator/qmldesigner/studio_templates/projects/common/contentmodule.main.qml.tpl index 55c40e624f1..ac0b0b28c02 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/common/contentmodule.main.qml.tpl +++ b/share/qtcreator/qmldesigner/studio_templates/projects/common/contentmodule.main.qml.tpl @@ -1,4 +1,7 @@ import QtQuick %{QtQuickVersion} +@if !%{IsQt6Project} +import QtQuick.Window %{QtQuickVersion} +@endif import %{ApplicationImport} Window { diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/mobile-stack/App.qml.tpl b/share/qtcreator/qmldesigner/studio_templates/projects/mobile-stack/App.qml.tpl index 7097157027d..abddf3d0c11 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/mobile-stack/App.qml.tpl +++ b/share/qtcreator/qmldesigner/studio_templates/projects/mobile-stack/App.qml.tpl @@ -1,5 +1,8 @@ import QtQuick %{QtQuickVersion} import QtQuick.Controls %{QtQuickVersion} +@if !%{IsQt6Project} +import QtQuick.Window %{QtQuickVersion} +@endif import %{ImportModuleName} %{ImportModuleVersion} Window { diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/mobile-swipe/App.qml.tpl b/share/qtcreator/qmldesigner/studio_templates/projects/mobile-swipe/App.qml.tpl index 8201708f72b..0937e1288eb 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/mobile-swipe/App.qml.tpl +++ b/share/qtcreator/qmldesigner/studio_templates/projects/mobile-swipe/App.qml.tpl @@ -1,5 +1,8 @@ import QtQuick %{QtQuickVersion} import QtQuick.Controls %{QtQuickVersion} +@if !%{IsQt6Project} +import QtQuick.Window %{QtQuickVersion} +@endif import %{ImportModuleName} %{ImportModuleVersion} Window { From db7d7eb801140142edcca9dea272cffb65f862f0 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Tue, 13 Dec 2022 16:10:19 +0200 Subject: [PATCH 128/131] QmlDesigner: Fix image data caching in previewImageDataForImageNode Change-Id: Iaca7099e2e6bdf365bdb8dfeb26d92e12f7ddd90 Reviewed-by: Mahmoud Badri --- .../qmldesigner/designercore/instances/nodeinstanceview.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp index 67bbc93a10f..661b303a940 100644 --- a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp +++ b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp @@ -1908,6 +1908,7 @@ QVariant NodeInstanceView::previewImageDataForImageNode(const ModelNode &modelNo const int dim = Constants::MODELNODE_PREVIEW_IMAGE_DIMENSIONS * ratio; imageData.pixmap = originalPixmap.scaled(dim, dim, Qt::KeepAspectRatio); imageData.pixmap.setDevicePixelRatio(ratio); + imageData.time = modified; double imgSize = double(imageFi.size()); static QStringList units({::QmlDesigner::NodeInstanceView::tr("B"), From 857ee29c1a177117ed44619a05ff76af6d93d813 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Tue, 13 Dec 2022 16:29:25 +0200 Subject: [PATCH 129/131] QmlDesigner: Add support for dragging textures to materials Dragging textures to materials within material browser now works. Fixes: QDS-8552 Change-Id: I1ec0287020fe2cd347bc0db7cda9d235678e9f04 Reviewed-by: Mahmoud Badri --- .../materialBrowserQmlSource/MaterialItem.qml | 12 ++++++++++++ .../materialbrowser/materialbrowserwidget.cpp | 11 +++++++++++ .../materialbrowser/materialbrowserwidget.h | 1 + 3 files changed, 24 insertions(+) diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialItem.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialItem.qml index bb38fe435ea..ec36e61d854 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialItem.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialItem.qml @@ -43,6 +43,18 @@ Rectangle { color: "transparent" visible: materialVisible + DropArea { + anchors.fill: parent + + onEntered: (drag) => { + drag.accepted = drag.formats[0] === "application/vnd.qtdesignstudio.texture" + } + + onDropped: (drag) => { + rootView.acceptTextureDropOnMaterial(index, drag.getDataAsString(drag.keys[0])) + } + } + MouseArea { id: mouseArea diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp index 901626b5a24..64d285f9222 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp @@ -265,6 +265,17 @@ void MaterialBrowserWidget::acceptBundleTextureDrop() m_materialBrowserView->emitCustomNotification("drop_bundle_texture", {}, {}); // To ContentLibraryView } +void MaterialBrowserWidget::acceptTextureDropOnMaterial(int matIndex, const QString &texId) +{ + ModelNode mat = m_materialBrowserModel->materialAt(matIndex); + ModelNode tex = m_materialBrowserView->modelNodeForInternalId(texId.toInt()); + + if (mat.isValid() && tex.isValid()) { + m_materialBrowserModel->selectMaterial(matIndex); + m_materialBrowserView->applyTextureToMaterial({mat}, tex); + } +} + void MaterialBrowserWidget::focusMaterialSection(bool focusMatSec) { if (focusMatSec != m_materialSectionFocused) { diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h index 4b0541ee560..47930eee0ae 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h @@ -56,6 +56,7 @@ public: Q_INVOKABLE void startDragTexture(int index, const QPointF &mousePos); Q_INVOKABLE void acceptBundleMaterialDrop(); Q_INVOKABLE void acceptBundleTextureDrop(); + Q_INVOKABLE void acceptTextureDropOnMaterial(int matIndex, const QString &texId); Q_INVOKABLE void focusMaterialSection(bool focusMatSec); QQuickWidget *quickWidget() const; From bfffa32f1e1338e17cd1a73d83f2e2e97afb1b95 Mon Sep 17 00:00:00 2001 From: Pranta Dastider Date: Tue, 13 Dec 2022 14:11:46 +0100 Subject: [PATCH 130/131] QmlDesigner: Update the tooltips for QDS Component Components Update the tooltip text for Qt Design Studio component type of components. Fixes: QDS-8313 Change-Id: I503e10a60f1d59f9a2e6b693c9f886254c3dabcd Reviewed-by: Mats Honkamaa Reviewed-by: Thomas Hartmann --- src/plugins/qmldesigner/qtquickplugin/quick.metainfo | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/plugins/qmldesigner/qtquickplugin/quick.metainfo b/src/plugins/qmldesigner/qtquickplugin/quick.metainfo index 655a3b0841a..7dff75aed6c 100644 --- a/src/plugins/qmldesigner/qtquickplugin/quick.metainfo +++ b/src/plugins/qmldesigner/qtquickplugin/quick.metainfo @@ -547,6 +547,7 @@ MetaInfo { version: "2.0" QmlSource { source: ":/qtquickplugin/source/component.qml" } + toolTip: qsTr("Allows you to define components inline, within a QML document.") } } @@ -581,6 +582,7 @@ MetaInfo { version: "2.0" Property { name: "width"; type: "int"; value: 200; } Property { name: "height"; type: "int"; value: 200; } + toolTip: qsTr("Allows you to load components dynamically.") } } @@ -598,6 +600,7 @@ MetaInfo { category: "e.Qt Quick - Component" libraryIcon: ":/qtquickplugin/images/repeater-icon.png" version: "2.0" + toolTip: qsTr("Creates a number of copies of the same item.") } } From e46141dcd0692704b5f26d5669de2d28009abc71 Mon Sep 17 00:00:00 2001 From: Pranta Dastider Date: Tue, 13 Dec 2022 14:22:58 +0100 Subject: [PATCH 131/131] QmlDesigner: Update the tooltips for QDS view Components Update the tooltip text for Qt Design Studio view type of components. Fixes: QDS-8310 Change-Id: I1b56e5bc26c6cc3711c304641cce3cbbd6d8c802 Reviewed-by: Mats Honkamaa Reviewed-by: Thomas Hartmann Reviewed-by: Tim Jenssen --- src/plugins/qmldesigner/qtquickplugin/quick.metainfo | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/plugins/qmldesigner/qtquickplugin/quick.metainfo b/src/plugins/qmldesigner/qtquickplugin/quick.metainfo index 7dff75aed6c..ec420e27200 100644 --- a/src/plugins/qmldesigner/qtquickplugin/quick.metainfo +++ b/src/plugins/qmldesigner/qtquickplugin/quick.metainfo @@ -184,6 +184,7 @@ MetaInfo { version: "2.0" QmlSource { source: ":/qtquickplugin/source/gridviewv2.qml" } + toolTip: qsTr("Organizes dynamic data sets in a grid.") } } @@ -198,6 +199,7 @@ MetaInfo { version: "2.0" QmlSource { source: ":/qtquickplugin/source/listviewv2.qml" } + toolTip: qsTr("Organizes dynamic data sets in a list.") } } @@ -212,6 +214,7 @@ MetaInfo { version: "2.0" QmlSource { source: ":/qtquickplugin/source/pathviewv2.qml" } + toolTip: qsTr("Organizes dynamic data sets along a path.") } }