diff --git a/dist/changes-6.0.0.md b/dist/changes-6.0.0.md index fdf1c71ca57..475250748ce 100644 --- a/dist/changes-6.0.0.md +++ b/dist/changes-6.0.0.md @@ -29,16 +29,24 @@ Editing * Added option for saving open files automatically after refactoring (QTCREATORBUG-25924) * Added information about source to tooltip on diagnostics -* Fixed `Insert Definition` for templates with value parameters - (QTCREATORBUG-26113) +* Added highlighting color option for namespaces (QTCREATORBUG-16580) +* Made pure virtual functions optional in `Create implementations for all member + functions` (QTCREATORBUG-26468) +* Fixed `Insert Definition` for template types (QTCREATORBUG-26113, + QTCREATORBUG-26397) +* Fixed that `Find References` did not work for some template and namespace + combinations (QTCREATORBUG-26520) * Fixed canceling of C++ parsing on configuration change (QTCREATORBUG-24890) * Fixed crash when checking for refactoring actions (QTCREATORBUG-26316) * Fixed wrong target compiler option (QTCREATORBUG-25615) * Fixed parentheses matching (QTCREATORBUG-26400) +* Fixed documentation comment generation for template types (QTCREATORBUG-9620) * Clangd * Added warning for older `clangd` versions * Added support for completion and function hint + * Added option for `Insert header files on completion` * Improved location of generated `compile_commands.json` (QTCREATORBUG-26431) + * Fixed missing reparsing after refactorings (QTCREATORBUG-26523) ### QML @@ -66,6 +74,7 @@ Projects (QTCREATORBUG-26422) * Fixed that re-detecting compilers removed compilers from kits (QTCREATORBUG-25697) +* Fixed GitHub action created by Qt Creator plugin wizard for Qt 6 ### CMake @@ -79,6 +88,7 @@ Projects QTCREATORBUG-26238, QTCREATORBUG-21452, QTCREATORBUG-25644, QTCREATORBUG-25782) * Fixed that generated files were selected for analyzing (QTCREATORBUG-25125) +* Fixed importing of Qt projects (QTCREATORBUG-25767) ### qmake @@ -88,6 +98,11 @@ Projects * Fixed that headers were not shown as part of the project (QTCREATORBUG-26356) +### Conan + +* Added `QT_CREATOR_CONAN_BUILD_POLICY` used for `BUILD` property of + `conan_cmake_run` + Debugging --------- @@ -97,6 +112,11 @@ Debugging * Fixed variable expansion for `Additional Startup Commands` (QTCREATORBUG-26382) +### CDB + +* Added hint for missing Qt debug information +* Improved pretty printing for Qt 6 without debug information + Version Control Systems ----------------------- @@ -117,9 +137,16 @@ Test Integration Platforms --------- +### Windows + +* Added support for MSVC 2022 + ### macOS * Changed prebuilt binaries to universal Intel + ARM +* Made dark theme the default in dark system mode +* Fixed issues with dark system mode (QTCREATORBUG-21520, QTCREATORBUG-26427, + QTCREATORBUG-26428) ### Android @@ -127,11 +154,16 @@ Platforms selector (QTCREATORBUG-23991) * Added details to device settings (QTCREATORBUG-23991) * Added filter field for Android SDK manager +* Fixed that NDK 22 and later could not be added ### WebAssembly * Fixed running applications (QTCREATORBUG-25905, QTCREATORBUG-26189) +### MCU + +* Added preliminary support for SDK 2.0 + ### Docker * Various improvements @@ -147,6 +179,7 @@ André Pönitz Artem Sokolovskii Artur Shepilko Assam Boudjelthia +BogDan Vatra Christiaan Janssen Christian Kandeler Christian Stenger @@ -160,6 +193,7 @@ Ivan Komissarov Jaroslaw Kobus Johanna Vanhatapio Jonas Karlsson +Jonas Singe Kai Köhne Kama Wójcik Knud Dollereder @@ -171,9 +205,11 @@ Marco Bubke Martin Kampas Miikka Heikkinen Miina Puuronen +Oliver Wolff Orgad Shaneh Petar Perisin Piotr Mikolajczyk +Robert Löhning Samuel Ghinet Shantanu Tushar Tapani Mattila @@ -181,6 +217,7 @@ Tasuku Suzuki Thiago Macieira Thomas Hartmann Tim Jenssen +Tomi Korpipaa Tony Leinonen Tor Arne Vestbø Tuomo Pelkonen diff --git a/doc/qtcreator/images/qtcreator-build-cmake-output.png b/doc/qtcreator/images/qtcreator-build-cmake-output.png index 88638020652..4311c61249a 100644 Binary files a/doc/qtcreator/images/qtcreator-build-cmake-output.png and b/doc/qtcreator/images/qtcreator-build-cmake-output.png differ diff --git a/doc/qtcreator/images/qtcreator-cmake-build-settings-initial.png b/doc/qtcreator/images/qtcreator-cmake-build-settings-initial.png index 0fce3dd7116..ce7f75f881f 100644 Binary files a/doc/qtcreator/images/qtcreator-cmake-build-settings-initial.png and b/doc/qtcreator/images/qtcreator-cmake-build-settings-initial.png differ diff --git a/doc/qtcreator/images/qtcreator-git-amend.png b/doc/qtcreator/images/qtcreator-git-amend.png new file mode 100644 index 00000000000..2aa74da5eb7 Binary files /dev/null and b/doc/qtcreator/images/qtcreator-git-amend.png differ diff --git a/doc/qtcreator/images/qtcreator-projects-view-cmake.png b/doc/qtcreator/images/qtcreator-projects-view-cmake.png deleted file mode 100644 index c3752d345b3..00000000000 Binary files a/doc/qtcreator/images/qtcreator-projects-view-cmake.png and /dev/null differ diff --git a/doc/qtcreator/images/qtcreator-projects-view-edit.png b/doc/qtcreator/images/qtcreator-projects-view-edit.png index 3b761ad6a16..f5c1f37f3b8 100644 Binary files a/doc/qtcreator/images/qtcreator-projects-view-edit.png and b/doc/qtcreator/images/qtcreator-projects-view-edit.png differ diff --git a/doc/qtcreator/images/qtcreator-vcs-commit.png b/doc/qtcreator/images/qtcreator-vcs-commit.png index 79ef8bc53ab..fbd1ae73a9e 100644 Binary files a/doc/qtcreator/images/qtcreator-vcs-commit.png and b/doc/qtcreator/images/qtcreator-vcs-commit.png differ diff --git a/doc/qtcreator/src/cmake/creator-projects-cmake-building.qdoc b/doc/qtcreator/src/cmake/creator-projects-cmake-building.qdoc index 66ae154d58c..16dd3cee47f 100644 --- a/doc/qtcreator/src/cmake/creator-projects-cmake-building.qdoc +++ b/doc/qtcreator/src/cmake/creator-projects-cmake-building.qdoc @@ -135,6 +135,9 @@ case-sensitivity. Select \uicontrol {Show Non-matching Lines} to hide the lines that match the filter. + Press \key {Ctrl+F} to \l{Finding and Replacing}{search} for a string from + the output. + To increase or decrease the output text size, select \inlineimage plus.png (\uicontrol {Zoom In}) or \inlineimage minus.png (\uicontrol {Zoom Out}), or press \key Ctrl++ or \key Ctrl+-. @@ -197,5 +200,5 @@ \image qtcreator-cmake-clean-steps.png The build errors and warnings are parsed and displayed in the - \uicontrol Issues output pane. + \l Issues output pane. */ diff --git a/doc/qtcreator/src/cmake/creator-projects-cmake.qdoc b/doc/qtcreator/src/cmake/creator-projects-cmake.qdoc index da691f6f03a..80f0e326ea8 100644 --- a/doc/qtcreator/src/cmake/creator-projects-cmake.qdoc +++ b/doc/qtcreator/src/cmake/creator-projects-cmake.qdoc @@ -57,18 +57,19 @@ a \c CMakeLists.txt configuration file in a project. Project information is also automatically refreshed when you build the project. - The \uicontrol {File System} section in the sidebar \uicontrol Projects view - displays information from the file system. \QC cannot determine whether the - files are part of the project. For example, header files that \QC finds in - the project directories but that are not mentioned in the CMakeLists.txt - files are listed here. + \image qtcreator-projects-view-edit.png "CMake project in Projects view" - \image qtcreator-projects-view-cmake.png "File System section in Projects view" + If \QC cannot load the CMake project, the \l Projects view shows a + \uicontrol {} project node to avoid scanning the file + system and load the project faster. The node shows the same files + as the \l {File System} view. Select \uicontrol Build > + \uicontrol {Clear CMake Configuration}, and then select \uicontrol Build + > \uicontrol {Run CMake} to reconfigure the project. \section1 Adding CMake Tools \QC requires CMake's \l{https://cmake.org/cmake/help/latest/manual/cmake-file-api.7.html} - {file-based API}. Please make sure to use CMake version 3.14, or later. + {file-based API}, and therefore you'll need CMake version 3.14, or later. To view and specify settings for CMake: @@ -144,6 +145,8 @@ \endlist + Warnings and errors are displayed in the \l {Issues} output pane. + \section1 Adding External Libraries to CMake Projects Through external libraries, \QC can support code completion and syntax diff --git a/doc/qtcreator/src/overview/creator-only/creator-advanced.qdoc b/doc/qtcreator/src/overview/creator-only/creator-advanced.qdoc index d383b808cf0..c5eb81598c3 100644 --- a/doc/qtcreator/src/overview/creator-only/creator-advanced.qdoc +++ b/doc/qtcreator/src/overview/creator-only/creator-advanced.qdoc @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2020 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Creator documentation. @@ -57,7 +57,7 @@ applications for multiple desktop and \l{glossary-device}{device} platforms. - \li \l{Using Other Build Systems} + \li \l{Build Systems} \QC is integrated with cross-platform systems for build automation: qmake, Qbs, CMake, and Autotools. In addition, you can import diff --git a/doc/qtcreator/src/projects/creator-only/creator-projects-creating.qdoc b/doc/qtcreator/src/projects/creator-only/creator-projects-creating.qdoc index 34eeb024b05..76a67382562 100644 --- a/doc/qtcreator/src/projects/creator-only/creator-projects-creating.qdoc +++ b/doc/qtcreator/src/projects/creator-only/creator-projects-creating.qdoc @@ -515,7 +515,7 @@ \li \l{Opening Projects} \li \l{Adding Libraries to Projects} \li \l{Adding New Custom Wizards} - \li \l{Using Other Build Systems} + \li \l{Build Systems} \endlist */ diff --git a/doc/qtcreator/src/projects/creator-only/creator-projects-other.qdoc b/doc/qtcreator/src/projects/creator-only/creator-projects-other.qdoc index 635fbd19ba4..e69f953e44a 100644 --- a/doc/qtcreator/src/projects/creator-only/creator-projects-other.qdoc +++ b/doc/qtcreator/src/projects/creator-only/creator-projects-other.qdoc @@ -34,7 +34,7 @@ \page creator-project-other.html \nextpage creator-project-cmake.html - \title Using Other Build Systems + \title Build Systems Most \QC project wizards enable you to choose the build system to use for building the project: qmake, CMake, Meson, or Qbs. qmake is installed and diff --git a/doc/qtcreator/src/projects/creator-only/creator-projects-overview.qdoc b/doc/qtcreator/src/projects/creator-only/creator-projects-overview.qdoc index f2534ba8d86..1b3174b1dc5 100644 --- a/doc/qtcreator/src/projects/creator-only/creator-projects-overview.qdoc +++ b/doc/qtcreator/src/projects/creator-only/creator-projects-overview.qdoc @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2018 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Creator documentation. @@ -75,7 +75,7 @@ \section1 Related Topics \list - \li \l{Using Other Build Systems} + \li \l{Build Systems} \endlist */ diff --git a/doc/qtcreator/src/qtcreator-toc.qdoc b/doc/qtcreator/src/qtcreator-toc.qdoc index f1e06156fd0..421755c3153 100644 --- a/doc/qtcreator/src/qtcreator-toc.qdoc +++ b/doc/qtcreator/src/qtcreator-toc.qdoc @@ -212,7 +212,7 @@ \li \l {Embedded Platforms} \li \l {Mobile Platforms} \endlist - \li \l{Using Other Build Systems} + \li \l{Build Systems} \list \li \l{Setting Up CMake} \li \l{Setting Up Qbs} diff --git a/doc/qtcreator/src/qtcreator.qdoc b/doc/qtcreator/src/qtcreator.qdoc index cb53dc7b6c7..6a38a087760 100644 --- a/doc/qtcreator/src/qtcreator.qdoc +++ b/doc/qtcreator/src/qtcreator.qdoc @@ -113,7 +113,7 @@ \li \b {\l{Advanced Use}} \list \li \l{Supported Platforms} - \li \l{Using Other Build Systems} + \li \l{Build Systems} \li \l{Using Command Line Options} \li \l{Keyboard Shortcuts} \li \l{Using External Tools} diff --git a/doc/qtcreator/src/vcs/creator-vcs-git.qdoc b/doc/qtcreator/src/vcs/creator-vcs-git.qdoc index 5359c964f67..18b9bcc6891 100644 --- a/doc/qtcreator/src/vcs/creator-vcs-git.qdoc +++ b/doc/qtcreator/src/vcs/creator-vcs-git.qdoc @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2020 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Creator documentation. @@ -203,7 +203,20 @@ commit page containing a text editor where you can enter your commit message and a checkable list of modified files to be included. - \image qtcreator-vcs-commit.png + \image qtcreator-vcs-commit.png "Committing changes with Git" + + \uicontrol {General Information} displays the names of the + repository and branch for the commit. + + In \uicontrol {Commit Information}, you can edit information about the + author of the commit. To bypass re-commit and commit message hooks, + select \uicontrol {Bypass hooks}. If signoffs are used for your project, + select \uicontrol {Sign off} to add a \e signed-off-by trailer by the + author at the end of the commit log message. + + In \uicontrol Description, edit the commit message. + + In \uicontrol Files, select the files to include in the commit. When you have finished filling out the commit page information, click on \uicontrol Commit to start committing. @@ -223,10 +236,22 @@ \section2 Amending Commits - To apply latest changes to the last commit, select - \uicontrol {Amend Last Commit}. You can also edit the commit message. + To apply latest changes to the last commit, select \uicontrol Tools > + \uicontrol Git > \uicontrol {Local Repository} > + \uicontrol {Amend Last Commit}. + + \image qtcreator-git-amend.png "Amending a Git commit" + + To view the commit in its current form, before amending, select + \uicontrol {Show HEAD}. + + To view a diff of the changes in the selected files, select + \uicontrol {Diff Selected Files}. + + Select \uicontrol Commit to amend the commit. To amend an earlier comment in a series of related commits, select + \uicontrol Tools > \uicontrol Git > \uicontrol {Local Repository} > \uicontrol {Fixup Previous Commit}. This operation is done using interactive rebase. In case of conflicts, a merge tool is suggested. diff --git a/doc/qtdesignstudio/src/qtdesignstudio-importing-2d.qdoc b/doc/qtdesignstudio/src/qtdesignstudio-importing-2d.qdoc index 176bcc03319..6214adf509a 100644 --- a/doc/qtdesignstudio/src/qtdesignstudio-importing-2d.qdoc +++ b/doc/qtdesignstudio/src/qtdesignstudio-importing-2d.qdoc @@ -30,6 +30,28 @@ \title Importing 2D Assets + You can import 2D assets, such as images, fonts, and sound files, to \QDS to + use them in your projects. + + To import an asset, drag-and-drop the external file containing the asset from, + for example, File Explorer on Windows, to \uicontrol {Form Editor}, + \uicontrol Navigator, or \uicontrol {Text Editor}. Alternatively, select + \l Library > \uicontrol Assets > \inlineimage plus.png + and follow the instructions in the \uicontrol {Asset Import} dialog. You can + also multiselect several external asset files to drag-and-drop them to + \QDS simultaneously. + + The imported images will appear in \uicontrol Library > \uicontrol Assets. + If you initiate the import by dragging the assets to \uicontrol {Form Editor}, + they are also added to your projects as image components, and you can view + them in \uicontrol {Form Editor} and \uicontrol Navigator. If you drag an + external font file to \uicontrol {Form Editor}, it will be added to your + project as a text component. Other imported assets, such as sound files, + will only appear in \uicontrol Library > \uicontrol Assets, and you can then + drag-and-drop them to a suitable view. + + \section1 Importing Designs From Other Design Tools + \image studio-imported-assets.png "UI imported into Qt Design Studio" \QB enables you to export assets and then import them to a \QDS project @@ -45,7 +67,7 @@ information about the options you have, see \l {Creating Projects}. - To import designs to \QDS projects: + To import assets exported in \QB to \QDS projects: \list 1 \li Select \uicontrol File > \uicontrol {New File or Project} > @@ -107,4 +129,5 @@ design tool and export the assets again. \include qtbridge-tutorial-links.qdocinc qtsketchbridge tutorials + */ diff --git a/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-importing.qdoc b/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-importing.qdoc index 624d7f5ecb9..ae12772cf4d 100644 --- a/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-importing.qdoc +++ b/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-importing.qdoc @@ -43,17 +43,27 @@ For more information about exporting 3D graphics, see \l{Exporting 3D Assets}. - During the import, you can optimize the files for \QDS. You can remove - components from meshes to reduce the cache size, find and fix issues in - the files, optimize graphs and meshes, and so on. The available options - depend on whether you are importing files that you created with Qt 3D Studio - or with other 3D graphics tools. See the tooltips in the options dialog - for more information about a particular option. - \image studio-import-3d.png To import 3D assets to \QDS projects: + \list 1 + \li Drag-and-drop an external file containing the 3D asset from, + for example, File Explorer (on Windows), to \uicontrol {3D Editor}. + \li In the \uicontrol {3D Scene Options} tab, select options for + importing the file. + \li Select \uicontrol Import to import the 3D graphics file. + \li When the import is done, select \uicontrol Close. + \endlist + + The 3D asset you dragged-and-dropped to \uicontrol {3D Editor} has now + been added to your scene, and you can see it in the \uicontrol {3D Editor} + view and in \uicontrol Navigator. It is also available in + \uicontrol Library > \uicontrol Components > \uicontrol {My 3D Components}. + + Alternatively, you can initiate the import dialog from the + \uicontrol Library view: + \list 1 \li In the \l{Design Views}{Design mode}, select \l Library > \uicontrol Assets > \inlineimage plus.png @@ -67,6 +77,17 @@ \li When the import is done, select \uicontrol Close. \endlist + The 3D asset now appears in \uicontrol Library > \uicontrol Components > + \uicontrol {My 3D Components}. You can add it to the scene by + drag-and-dropping it to \uicontrol {3D Editor}. + + During the import, you can optimize the files for \QDS. You can remove + components from meshes to reduce the cache size, find and fix issues in + the files, optimize graphs and meshes, and so on. The available options + depend on whether you are importing files that you created with Qt 3D Studio + or with other 3D graphics tools. See the tooltips in the options dialog + for more information about a particular option. + The 3D asset you added to the project now appears in \uicontrol Library > \uicontrol Components > \uicontrol {My 3D Components}. You can add it to your UI by dragging-and-dropping it to \l {3D Editor}. diff --git a/doc/qtdesignstudio/src/views/qtquick-library.qdoc b/doc/qtdesignstudio/src/views/qtquick-library.qdoc index b1d03a72e67..adf66688b16 100644 --- a/doc/qtdesignstudio/src/views/qtquick-library.qdoc +++ b/doc/qtdesignstudio/src/views/qtquick-library.qdoc @@ -119,9 +119,13 @@ \section1 Assets \uicontrol Library > \uicontrol {Assets} displays the images and other files - that you add to the project folder by selecting \inlineimage plus.png - . To add assets to your UI, drag-and-drop them to \l Navigator or - \l {Form Editor}. + that you add to the project folder by dragging-and-dropping external asset + files to \QDS or by selecting \inlineimage plus.png + . For more information about importing assets to \QDS, see + \l {Importing 2D Assets} and \l {Importing 3D Assets}. + + To add assets to your UI, drag-and-drop them from \uicontrol Library > + \uicontrol Assets to \l Navigator, \l {Form Editor}, or \l {3D Editor}. To add multiple assets to your UI simultaneously, multiselect them first by holding \key Ctrl and clicking the asset files you wish to select. diff --git a/share/qtcreator/debugger/dumper.py b/share/qtcreator/debugger/dumper.py index 7bdb028eba3..fdf64d851fc 100644 --- a/share/qtcreator/debugger/dumper.py +++ b/share/qtcreator/debugger/dumper.py @@ -252,8 +252,9 @@ class DumperBase(): #DumperBase.warn('EXPANDED INAMES: %s' % self.expandedINames) #DumperBase.warn('WATCHERS: %s' % self.watchers) - def setFallbackQtVersion(self, version): - self.warn("got fallback qt version %x" % version) + def setFallbackQtVersion(self, args): + version = int(args.get('version', self.fallbackQtVersion)) + DumperBase.warn("got fallback qt version 0x%x" % version) self.fallbackQtVersion = version def resetPerStepCaches(self): @@ -1135,12 +1136,18 @@ class DumperBase(): if displayFormat != DisplayFormat.Raw and p: if innerType.name in ( 'char', + 'int8_t', + 'qint8', 'wchar_t', 'unsigned char', 'uint8_t', + 'quint8', 'signed char', 'CHAR', - 'WCHAR' + 'WCHAR', + 'char8_t', + 'char16_t', + 'char32_t' ): self.putCharArrayHelper(p, n, innerType, self.currentItemFormat(), makeExpandable=False) @@ -1406,11 +1413,17 @@ class DumperBase(): if innerType.name not in ( 'char', 'signed char', + 'int8_t', + 'qint8', 'unsigned char', 'uint8_t', + 'quint8', 'wchar_t', 'CHAR', - 'WCHAR' + 'WCHAR', + 'char8_t', + 'char16_t', + 'char32_t' ): self.putDerefedPointer(value) return @@ -3628,15 +3641,33 @@ class DumperBase(): res = { 'bool': 'int:1', 'char': 'int:1', + 'int8_t': 'int:1', + 'qint8': 'int:1', 'signed char': 'int:1', + 'char8_t': 'uint:1', 'unsigned char': 'uint:1', 'uint8_t': 'uint:1', + 'quint8': 'uint:1', 'short': 'int:2', + 'int16_t': 'int:2', + 'qint16': 'int:2', 'unsigned short': 'uint:2', + 'char16_t': 'uint:2', + 'uint16_t': 'uint:2', + 'quint16': 'uint:2', 'int': 'int:4', + 'int32_t': 'int:4', + 'qint32': 'int:4', 'unsigned int': 'uint:4', + 'char32_t': 'uint:4', + 'uint32_t': 'uint:4', + 'quint32': 'uint:4', 'long long': 'int:8', + 'int64_t': 'int:8', + 'qint64': 'int:8', 'unsigned long long': 'uint:8', + 'uint64_t': 'uint:8', + 'quint64': 'uint:8', 'float': 'float:4', 'double': 'float:8', 'QChar': 'uint:2' diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.cpp index c1c086c58d6..682b825eed2 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.cpp @@ -1393,14 +1393,19 @@ void NodeInstanceServer::setTranslationLanguage(const QString &language) engine()->setUiLanguage(language); #endif static QPointer multilanguageTranslator; - if (!MultiLanguage::databaseFilePath().isEmpty()) { - if (!multilanguageLink) { - multilanguageLink = std::make_unique(); - multilanguageTranslator = multilanguageLink->translator().release(); - QCoreApplication::installTranslator(multilanguageTranslator); + if (!MultiLanguage::databaseFilePath().isEmpty() + && QFileInfo::exists(QString::fromUtf8(MultiLanguage::databaseFilePath()))) { + try { + if (!multilanguageLink) { + multilanguageLink = std::make_unique(); + multilanguageTranslator = multilanguageLink->translator().release(); + QCoreApplication::installTranslator(multilanguageTranslator); + } + if (multilanguageTranslator) + multilanguageTranslator->setLanguage(language); + } catch (std::exception &e) { + qWarning() << "QmlPuppet is unable to initialize MultiLanguage translator:" << e.what(); } - if (multilanguageTranslator) - multilanguageTranslator->setLanguage(language); } } diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml index 5ca714051b0..8444a2c88f6 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml @@ -32,6 +32,8 @@ import StudioControls 1.0 as StudioControls import StudioTheme 1.0 as StudioTheme Item { + id: rootItem + property var selectedAssets: ({}) property int allExpandedState: 0 property string delFilePath: "" @@ -257,4 +259,52 @@ Item { } } } + + // Placeholder when the assets panel is empty + Column { + id: colNoAssets + visible: assetsModel.isEmpty + + spacing: 20 + x: 20 + width: rootItem.width - 2 * x + anchors.verticalCenter: parent.verticalCenter + + Text { + text: qsTr("Looks like you don't have any assets yet.") + color: StudioTheme.Values.themeTextColor + font.pixelSize: 18 + width: colNoAssets.width + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WordWrap + } + + Image { + source: "image://qmldesigner_assets/browse" + anchors.horizontalCenter: parent.horizontalCenter + scale: maBrowse.containsMouse ? 1.2 : 1 + Behavior on scale { + NumberAnimation { + duration: 300 + easing.type: Easing.OutQuad + } + } + + MouseArea { + id: maBrowse + anchors.fill: parent + hoverEnabled: true + onClicked: rootView.handleAddAsset(); + } + } + + Text { + text: qsTr("Drag-and-drop your assets here or click the '+' button to browse assets from the file system.") + color: StudioTheme.Values.themeTextColor + font.pixelSize: 18 + width: colNoAssets.width + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WordWrap + } + } } diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/LibraryHeader.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/LibraryHeader.qml index f5ad0eae3f2..36c823ec631 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/LibraryHeader.qml +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/LibraryHeader.qml @@ -105,8 +105,11 @@ Item { verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter anchors.centerIn: parent - color: tabBar.currentIndex === index ? StudioTheme.Values.themeIconColorSelected - : StudioTheme.Values.themeIconColor + color: !plusButton.enabled + ? StudioTheme.Values.themeIconColorDisabled + : tabBar.currentIndex === index + ? StudioTheme.Values.themeIconColorSelected + : StudioTheme.Values.themeIconColor } HelperWidgets.ToolTipArea { diff --git a/share/qtcreator/qmldesigner/newprojectdialog/NewProjectDialog.qml b/share/qtcreator/qmldesigner/newprojectdialog/NewProjectDialog.qml new file mode 100644 index 00000000000..86234bf5bae --- /dev/null +++ b/share/qtcreator/qmldesigner/newprojectdialog/NewProjectDialog.qml @@ -0,0 +1,192 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick.Window + +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import StudioTheme as StudioTheme +import StudioControls as SC + +import NewProjectDialog + +Item { + width: DialogValues.dialogWidth + height: DialogValues.dialogHeight + + Rectangle { // the main dialog panel + anchors.fill: parent + color: DialogValues.darkPaneColor + + ColumnLayout { + anchors.fill: parent + + Layout.alignment: Qt.AlignHCenter + spacing: 0 + + Item { // Header Item + Layout.fillWidth: true + implicitHeight: 218 + + Column { + anchors.fill: parent + + Item { width: parent.width; height: 74 } // spacer + + Text { + text: qsTr("Welcome to Qt Design Studio. Let's Create Something Wonderful!") + font.pixelSize: 32 + width: parent.width + height: 47 + lineHeight: 49 + lineHeightMode: Text.FixedHeight + color: DialogValues.textColor + horizontalAlignment: Text.AlignHCenter + } + + Item { width: parent.width; height: 11 } // spacer + + Text { + width: parent.width + text: qsTr("Get started by selecting from Presets or start from empty screen. You may also include your design file.") + color: DialogValues.textColor + font.pixelSize: DialogValues.paneTitlePixelSize + lineHeight: DialogValues.paneTitleLineHeight + lineHeightMode: Text.FixedHeight + horizontalAlignment: Text.AlignHCenter + } + } + } // Header Item + + Item { // Content Item + Layout.fillWidth: true + Layout.fillHeight: true + + RowLayout { + x: 35 + width: parent.width - 70 + height: parent.height + spacing: 0 + + Rectangle { // Left pane + color: DialogValues.lightPaneColor + Layout.fillWidth: true + Layout.fillHeight: true + Layout.minimumWidth: 379 // figured out this number visually + Layout.minimumHeight: 326 // figured out this number visually + + Column { + x: DialogValues.defaultPadding // left padding + width: parent.width - DialogValues.defaultPadding * 2 // right padding + height: parent.height + + Text { + text: qsTr("Presets") + width: parent.width + font.weight: Font.DemiBold + font.pixelSize: DialogValues.paneTitlePixelSize + lineHeight: DialogValues.paneTitleLineHeight + lineHeightMode: Text.FixedHeight + color: DialogValues.textColor + } + + NewProjectView { + id: projectViewId + x: 10 // left padding + width: parent.width - 64 // right padding + height: DialogValues.projectViewHeight + loader: projectDetailsLoader + } + + Item { height: 5; width: parent.width } + + Text { + id: descriptionText + text: dialogBox.projectDescription + font.pixelSize: DialogValues.defaultPixelSize + lineHeight: DialogValues.defaultLineHeight + lineHeightMode: Text.FixedHeight + leftPadding: 14 + width: projectViewId.width + color: DialogValues.textColor + wrapMode: Text.WordWrap + maximumLineCount: 4 + elide: Text.ElideRight + } + } + } // Left pane + + Loader { + id: projectDetailsLoader + // we need to specify width because the loaded item needs to use parent sizes + width: DialogValues.loadedPanesWidth + Layout.fillHeight: true + source: "" + } + } // RowLayout + } //Content Item + + Item { // Footer + implicitHeight: DialogValues.footerHeight + implicitWidth: parent.width + RowLayout { + anchors.fill: parent + spacing: DialogValues.defaultPadding + + Item { Layout.fillWidth: true } + + SC.AbstractButton { + implicitWidth: DialogValues.dialogButtonWidth + width: DialogValues.dialogButtonWidth + visible: true + buttonIcon: qsTr("Cancel") + iconSize: DialogValues.defaultPixelSize + iconFont: StudioTheme.Constants.font + + onClicked: { + dialogBox.reject(); + } + } + + SC.AbstractButton { + implicitWidth: DialogValues.dialogButtonWidth + width: DialogValues.dialogButtonWidth + visible: true + buttonIcon: qsTr("Create") + iconSize: DialogValues.defaultPixelSize + enabled: dialogBox.fieldsValid + iconFont: StudioTheme.Constants.font + + onClicked: { + dialogBox.accept(); + } + } + Item { implicitWidth: 35 - DialogValues.defaultPadding } + } // RowLayout + } // Footer + } // ColumnLayout + } // Rectangle +} diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-basic.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-basic.png new file mode 100644 index 00000000000..53d03e476b4 Binary files /dev/null and b/share/qtcreator/qmldesigner/newprojectdialog/image/style-basic.png differ diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-default.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-default.png new file mode 100644 index 00000000000..c3f05108f9e Binary files /dev/null and b/share/qtcreator/qmldesigner/newprojectdialog/image/style-default.png differ diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-error.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-error.png new file mode 100644 index 00000000000..aeb03a574ed Binary files /dev/null and b/share/qtcreator/qmldesigner/newprojectdialog/image/style-error.png differ diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-fusion.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-fusion.png new file mode 100644 index 00000000000..882256b30d4 Binary files /dev/null and b/share/qtcreator/qmldesigner/newprojectdialog/image/style-fusion.png differ diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-imagine.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-imagine.png new file mode 100644 index 00000000000..47d66317b28 Binary files /dev/null and b/share/qtcreator/qmldesigner/newprojectdialog/image/style-imagine.png differ diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-macos.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-macos.png new file mode 100644 index 00000000000..323f4d51b16 Binary files /dev/null and b/share/qtcreator/qmldesigner/newprojectdialog/image/style-macos.png differ diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-material_dark.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-material_dark.png new file mode 100644 index 00000000000..53247a422fd Binary files /dev/null and b/share/qtcreator/qmldesigner/newprojectdialog/image/style-material_dark.png differ diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-material_light.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-material_light.png new file mode 100644 index 00000000000..63c6b7b79f0 Binary files /dev/null and b/share/qtcreator/qmldesigner/newprojectdialog/image/style-material_light.png differ diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_dark.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_dark.png new file mode 100644 index 00000000000..fc21300336e Binary files /dev/null and b/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_dark.png differ diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_light.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_light.png new file mode 100644 index 00000000000..735219f0bb6 Binary files /dev/null and b/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_light.png differ diff --git a/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_system.png b/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_system.png new file mode 100644 index 00000000000..fc21300336e Binary files /dev/null and b/share/qtcreator/qmldesigner/newprojectdialog/image/style-universal_system.png differ diff --git a/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/Details.qml b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/Details.qml new file mode 100644 index 00000000000..c7ed3ec8d64 --- /dev/null +++ b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/Details.qml @@ -0,0 +1,453 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick.Window +import QtQuick.Controls + +import QtQuick +import QtQuick.Layouts +import StudioControls as SC +import StudioTheme as StudioTheme + +Item { + width: DialogValues.detailsPaneWidth + + Component.onCompleted: { + dialogBox.detailsLoaded = true; + } + + Component.onDestruction: { + dialogBox.detailsLoaded = false; + } + + Rectangle { + color: DialogValues.darkPaneColor + anchors.fill: parent + + Item { + x: DialogValues.detailsPanePadding // left padding + width: parent.width - DialogValues.detailsPanePadding * 2 // right padding + + Column { + anchors.fill: parent + spacing: DialogValues.defaultPadding + + Text { + text: qsTr("Details") + width: parent.width; + font.weight: Font.DemiBold + font.pixelSize: DialogValues.paneTitlePixelSize + lineHeight: DialogValues.paneTitleLineHeight + lineHeightMode: Text.FixedHeight + color: DialogValues.textColor + } + + SC.TextField { + id: projectNameTextField + actionIndicatorVisible: false + translationIndicatorVisible: false + text: dialogBox.projectName + width: parent.width + color: DialogValues.textColor + selectByMouse: true + + font.pixelSize: DialogValues.paneTitlePixelSize + } + + Binding { + target: dialogBox + property: "projectName" + value: projectNameTextField.text + } + + Item { width: parent.width; height: DialogValues.narrowSpacing(11) } + + RowLayout { // Project location + width: parent.width + + SC.TextField { + Layout.fillWidth: true + id: projectLocationTextField + actionIndicatorVisible: false + translationIndicatorVisible: false + text: dialogBox.projectLocation + color: DialogValues.textColor + selectByMouse: true + font.pixelSize: DialogValues.defaultPixelSize + } + + Binding { + target: dialogBox + property: "projectLocation" + value: projectLocationTextField.text + } + + SC.AbstractButton { + implicitWidth: 30 + iconSize: 20 + visible: true + buttonIcon: "…" + iconFont: StudioTheme.Constants.font + + onClicked: { + var newLocation = dialogBox.chooseProjectLocation() + if (newLocation) + projectLocationTextField.text = newLocation + } + } // SC.AbstractButton + } // Project location RowLayout + + Item { width: parent.width; height: DialogValues.narrowSpacing(7) } + + RowLayout { // StatusMessage + width: parent.width + spacing: 0 + + Image { + id: statusIcon + asynchronous: false + } + + Text { + id: statusMessage + text: dialogBox.statusMessage + font.pixelSize: DialogValues.defaultPixelSize + lineHeight: DialogValues.defaultLineHeight + lineHeightMode: Text.FixedHeight + color: DialogValues.textColor + wrapMode: Text.Wrap + elide: Text.ElideRight + maximumLineCount: 3 + Layout.fillWidth: true + + states: [ + State { + name: "warning" + when: dialogBox.statusType === "warning" + PropertyChanges { + target: statusMessage + color: DialogValues.textWarning + } + PropertyChanges { + target: statusIcon + source: "image://newprojectdialog_library/status-warning" + } + }, + + State { + name: "error" + when: dialogBox.statusType === "error" + PropertyChanges { + target: statusMessage + color: DialogValues.textError + } + PropertyChanges { + target: statusIcon + source: "image://newprojectdialog_library/status-error" + } + } + ] + } // Text + } // RowLayout + + SC.CheckBox { + id: defaultLocationCheckbox + actionIndicatorVisible: false + text: qsTr("Use as default project location") + checked: false + font.pixelSize: DialogValues.defaultPixelSize + } + + Binding { + target: dialogBox + property: "saveAsDefaultLocation" + value: defaultLocationCheckbox.checked + } + + Rectangle { width: parent.width; height: 1; color: DialogValues.dividerlineColor } + + SC.ComboBox { // Screen Size ComboBox + id: screenSizeComboBox + actionIndicatorVisible: false + currentIndex: 1 + model: screenSizeModel + textRole: "display" + width: parent.width + font.pixelSize: DialogValues.defaultPixelSize + + onActivated: (index) => { + // NOTE: item 0 is activated when the screenSizeModel is reset + dialogBox.setScreenSizeIndex(index); + + var r = screenSizeModel.screenSizes(index); + widthTextField.text = r.width; + heightTextField.text = r.height; + } + + Connections { + target: screenSizeModel + function onModelReset() { + screenSizeComboBox.activated(screenSizeComboBox.currentIndex) + } + } + } // Screen Size ComboBox + + GridLayout { // orientation + width + height + width: parent.width + height: 85 + + columns: 4 + rows: 2 + + columnSpacing: 10 + rowSpacing: 10 + + // header items + Text { + text: qsTr("Width") + font.pixelSize: DialogValues.defaultPixelSize + lineHeight: DialogValues.defaultLineHeight + lineHeightMode: Text.FixedHeight + color: DialogValues.textColor + } + + Text { + text: qsTr("Height") + font.pixelSize: DialogValues.defaultPixelSize + lineHeight: DialogValues.defaultLineHeight + lineHeightMode: Text.FixedHeight + color: DialogValues.textColor + } + + Item { Layout.fillWidth: true } + + Text { + text: qsTr("Orientation") + font.pixelSize: DialogValues.defaultPixelSize + lineHeight: DialogValues.defaultLineHeight + lineHeightMode: Text.FixedHeight + color: DialogValues.textColor + } + + // content items + SC.TextField { + id: widthTextField + actionIndicatorVisible: false + translationIndicatorVisible: false + implicitWidth: 50 + color: DialogValues.textColor + selectByMouse: true + validator: IntValidator { bottom: 1; top: 100000; } + font.pixelSize: DialogValues.defaultPixelSize + + onTextChanged: { + var height = heightTextField.text ? parseInt(heightTextField.text) : 0 + var width = text ? parseInt(text) : 0 + + if (width >= height) + orientationButton.setHorizontal() + else + orientationButton.setVertical() + } + } // Width Text Field + + Binding { + target: dialogBox + property: "customWidth" + value: widthTextField.text + } + + SC.TextField { + id: heightTextField + actionIndicatorVisible: false + translationIndicatorVisible: false + implicitWidth: 50 + color: DialogValues.textColor + selectByMouse: true + validator: IntValidator { bottom: 1; top: 100000; } + font.pixelSize: DialogValues.defaultPixelSize + + onTextChanged: { + var height = text ? parseInt(text) : 0 + var width = widthTextField.text ? parseInt(widthTextField.text) : 0 + + if (width >= height) + orientationButton.setHorizontal() + else + orientationButton.setVertical() + } + } // Height Text Field + + Binding { + target: dialogBox + property: "customHeight" + value: heightTextField.text + } + + Item { Layout.fillWidth: true } + + Button { + id: orientationButton + implicitWidth: 100 + implicitHeight: 50 + + checked: false + hoverEnabled: false + background: Rectangle { + width: parent.width + height: parent.height + color: "transparent" + + Row { + Item { + width: orientationButton.width / 2 + height: orientationButton.height + Rectangle { + id: horizontalBar + color: "white" + width: parent.width + height: orientationButton.height / 2 + anchors.verticalCenter: parent.verticalCenter + } + } + + Item { + width: orientationButton.width / 4 + height: orientationButton.height + } + + Rectangle { + id: verticalBar + width: orientationButton.width / 4 + height: orientationButton.height + color: "white" + } + } + } + + onClicked: { + if (widthTextField.text && heightTextField.text) { + [widthTextField.text, heightTextField.text] = [heightTextField.text, widthTextField.text]; + + checked = !checked + } + } + + function setHorizontal() { + checked = false + horizontalBar.color = DialogValues.textColorInteraction + verticalBar.color = "white" + } + + function setVertical() { + checked = true + horizontalBar.color = "white" + verticalBar.color = DialogValues.textColorInteraction + } + } // Orientation button + + } // GridLayout: orientation + width + height + + Rectangle { width: parent.width; height: 1; color: DialogValues.dividerlineColor } + + SC.Section { + width: parent.width + caption: qsTr("Advanced") + captionPixelSize: DialogValues.defaultPixelSize + captionColor: DialogValues.darkPaneColor + captionTextColor: DialogValues.textColor + leftPadding: 0 + expanded: true + visible: dialogBox.haveVirtualKeyboard || dialogBox.haveTargetQtVersion + + Column { + spacing: DialogValues.defaultPadding + width: parent.width + + /* We need a spacer of -10 in order to have actual 18px spacing between + * section bottom and the checkbox. Otherwise, with Column spacing set to + * 18, without a spacer, the default space to the first item would be 10, + * for some reason. */ + Item { width: parent.width; height: -10 } + + SC.CheckBox { + id: useQtVirtualKeyboard + actionIndicatorVisible: false + text: qsTr("Use Qt Virtual Keyboard") + font.pixelSize: DialogValues.defaultPixelSize + checked: dialogBox.useVirtualKeyboard + visible: dialogBox.haveVirtualKeyboard + } + + RowLayout { // Target Qt Version + width: parent.width + visible: dialogBox.haveTargetQtVersion + + Text { + text: "Target Qt Version:" + font.pixelSize: DialogValues.defaultPixelSize + lineHeight: DialogValues.defaultLineHeight + lineHeightMode: Text.FixedHeight + color: DialogValues.textColor + } + + SC.ComboBox { // Target Qt Version ComboBox + id: qtVersionComboBox + actionIndicatorVisible: false + implicitWidth: 70 + Layout.alignment: Qt.AlignRight + currentIndex: 1 + font.pixelSize: DialogValues.defaultPixelSize + + model: ListModel { + ListElement { + name: "Qt 5" + } + ListElement { + name: "Qt 6" + } + } + + width: parent.width + + onActivated: (index) => { + dialogBox.setTargetQtVersion(index) + } + } // Target Qt Version ComboBox + + } // RowLayout + } // Column + } // SC.Section + + Binding { + target: dialogBox + property: "useVirtualKeyboard" + value: useQtVirtualKeyboard.checked + } + + } // Column + } // Item + } +} diff --git a/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/DialogValues.qml b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/DialogValues.qml new file mode 100644 index 00000000000..5bcfadaa785 --- /dev/null +++ b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/DialogValues.qml @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +pragma Singleton +import QtQml + +import StudioTheme as StudioTheme + +QtObject { + readonly property int dialogWidth: 1522 + readonly property int dialogHeight: 994 + readonly property int projectViewMinimumWidth: 600 + readonly property int projectViewMinimumHeight: projectViewHeight + readonly property int dialogContentHeight: projectViewHeight + 300 // i.e. dialog without header and footer + readonly property int loadedPanesWidth: detailsPaneWidth + stylesPaneWidth + readonly property int detailsPaneWidth: 330 + detailsPanePadding * 2 + readonly property int stylesPaneWidth: styleImageWidth + stylesPanePadding * 2 + styleImageBorderWidth * 2 // i.e. 240px + readonly property int detailsPanePadding: 18 + readonly property int stylesPanePadding: 18 + readonly property int defaultPadding: 18 + + readonly property int styleImageWidth: 200 + readonly property int styleImageBorderWidth: 2 + readonly property int footerHeight: 73 + readonly property int projectItemWidth: 144 + readonly property int projectItemHeight: 144 + readonly property int projectViewHeight: projectItemHeight * 2 + projectViewHeaderHeight + readonly property int projectViewHeaderHeight: 38 + + readonly property int dialogButtonWidth: 100 + + readonly property int loadedPanesHeight: dialogContentHeight + readonly property int detailsPaneHeight: dialogContentHeight + + readonly property string darkPaneColor: StudioTheme.Values.themeBackgroundColorNormal + readonly property string lightPaneColor: StudioTheme.Values.themeBackgroundColorAlternate + + readonly property string textColor: StudioTheme.Values.themeTabInactiveText + readonly property string textColorInteraction: StudioTheme.Values.themeInteraction + readonly property string dividerlineColor: StudioTheme.Values.themeTextColorDisabled + readonly property string textError: StudioTheme.Values.themeError + readonly property string textWarning: StudioTheme.Values.themeWarning + + readonly property real defaultPixelSize: 14 + readonly property real defaultLineHeight: 21 + readonly property real viewHeaderPixelSize: 16 + readonly property real viewHeaderLineHeight: 24 + readonly property real paneTitlePixelSize: 18 + readonly property real paneTitleLineHeight: 27 + + // for a spacer item + function narrowSpacing(value, layoutSpacing = DialogValues.defaultPadding) { + /* e.g. if we want narrow spacing value = 11, then for the spacer item residing inside a + layout with spacing set to 18, we need to realize the fact that by adding the spacer + item, we already have 18 * 2 spacing added implicitly (i.e. spacing before the spacer + item and spacing after it). So we have to subtract 2 x layout spacing before setting + our own, narrower, spacing. + */ + return -layoutSpacing -layoutSpacing + value + } +} diff --git a/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/NewProjectView.qml b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/NewProjectView.qml new file mode 100644 index 00000000000..049b955420c --- /dev/null +++ b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/NewProjectView.qml @@ -0,0 +1,198 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick.Window +import QtQuick.Controls + +import QtQuick +import QtQuick.Layouts +import StudioTheme as StudioTheme + +GridView { + id: projectView + + required property Item loader + + header: TabBar { + id: tabBar + width: parent.width + height: DialogValues.projectViewHeaderHeight + + background: Rectangle { + color: DialogValues.lightPaneColor + } + + Repeater { + model: categoryModel + + TabButton { + padding: 0 + + width: headerText.contentWidth + 36 + + background: Item { // TabButton background + Rectangle { // bottom strip + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + width: headerText.contentWidth + height: 6 + radius: 10 + color: tabBar.currentIndex === index ? DialogValues.textColorInteraction + : "transparent" + } + } // TabButton background + + implicitHeight: headerText.height + DialogValues.defaultPadding - 7 + + contentItem: Item { + Column { + anchors.fill: parent + + Text { + id: headerText + color: tabBar.currentIndex == index ? DialogValues.textColorInteraction + : DialogValues.textColor + text: name + width: parent.width + font.weight: Font.DemiBold + font.pixelSize: DialogValues.viewHeaderPixelSize + lineHeight: DialogValues.viewHeaderLineHeight + lineHeightMode: Text.FixedHeight + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + + Item { width: parent.width; height: 11; } + } // Column + } // Item + + onClicked: { + projectModel.setPage(index) + projectView.currentIndex = 0 + projectView.currentIndexChanged() + } + } // TabButton + } // Repeater + } // Header - TabBar + + cellWidth: DialogValues.projectItemWidth + cellHeight: DialogValues.projectItemHeight + + boundsBehavior: Flickable.StopAtBounds + + children: [ + Rectangle { + color: DialogValues.darkPaneColor + anchors.fill: parent + z: -1 + } + ] + + model: projectModel + + // called by onModelReset and when user clicks on an item, or when the header item is changed. + onCurrentIndexChanged: { + dialogBox.selectedProject = projectView.currentIndex + var source = dialogBox.currentProjectQmlPath() + loader.source = source + } + + Connections { + target: projectModel + + // called when data is set (setWizardFactories) + function onModelReset() { + currentIndex = 0 + currentIndexChanged() + } + } + + delegate: ItemDelegate { + id: delegate + + width: DialogValues.projectItemWidth + height: DialogValues.projectItemHeight + + function fontIconCode(index) { + var code = projectModel.fontIconCode(index) + return code ? code : StudioTheme.Constants.wizardsUnknown + } + + Column { + width: parent.width + height: parent.height + + Label { + id: projectTypeIcon + text: fontIconCode(index) + color: DialogValues.textColor + width: parent.width + height: DialogValues.projectItemHeight / 2 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignBottom + renderType: Text.NativeRendering + font.pixelSize: 65 + font.family: StudioTheme.Constants.iconFont.family + } + + Text { + id: projectTypeLabel + color: DialogValues.textColor + + text: name + font.pixelSize: DialogValues.defaultPixelSize + lineHeight: DialogValues.defaultLineHeight + lineHeightMode: Text.FixedHeight + width: parent.width + height: DialogValues.projectItemHeight / 2 + wrapMode: Text.Wrap + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignTop + } + } // Column + + MouseArea { + anchors.fill: parent + onClicked: { + delegate.GridView.view.currentIndex = index + } + } + + states: [ + State { + when: delegate.GridView.isCurrentItem + PropertyChanges { + target: projectTypeLabel + color: DialogValues.textColorInteraction + } + + PropertyChanges { + target: projectTypeIcon + color: DialogValues.textColorInteraction + } + } // State + ] + } // ItemDelegate +} // GridView diff --git a/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/Styles.qml b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/Styles.qml new file mode 100644 index 00000000000..a1ef6696f3a --- /dev/null +++ b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/Styles.qml @@ -0,0 +1,183 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick.Window +import QtQuick.Controls + +import QtQuick +import QtQuick.Layouts + +import StudioControls as SC + +Item { + width: DialogValues.stylesPaneWidth + + Component.onCompleted: { + dialogBox.stylesLoaded = true; + + /* + * TODO: roleNames is called before the backend model (in the proxy class StyleModel) is + * loaded, which may be buggy. But I found no way to force refresh the model, so as to + * reload the role names afterwards. setting styleModel.dynamicRoles doesn't appear to do + * anything. + */ + } + + Component.onDestruction: { + dialogBox.stylesLoaded = false; + } + + Rectangle { + color: DialogValues.lightPaneColor + anchors.fill: parent + + Item { + x: DialogValues.stylesPanePadding // left padding + width: parent.width - DialogValues.stylesPanePadding * 2 // right padding + height: parent.height + + ColumnLayout { + anchors.fill: parent + spacing: 5 + + Text { + id: styleTitleText + text: qsTr("Style") + width: parent.width; + font.weight: Font.DemiBold + font.pixelSize: DialogValues.paneTitlePixelSize + lineHeight: DialogValues.paneTitleLineHeight + lineHeightMode: Text.FixedHeight + color: DialogValues.textColor + + function refresh() { + text = qsTr("Style") + " (" + styleModel.rowCount() + ")" + } + } + + SC.ComboBox { // Style Filter ComboBox + actionIndicatorVisible: false + currentIndex: 0 + textRole: "text" + valueRole: "value" + font.pixelSize: DialogValues.defaultPixelSize + + model: ListModel { + ListElement { text: qsTr("All"); value: "all" } + ListElement { text: qsTr("Light"); value: "light" } + ListElement { text: qsTr("Dark"); value: "dark" } + } + + implicitWidth: parent.width + + onActivated: (index) => { + styleModel.filter(currentValue.toLowerCase()); + styleTitleText.refresh(); + } + } // Style Filter ComboBox + + ListView { + id: stylesList + Layout.fillWidth: true + Layout.fillHeight: true + clip: true + model: styleModel + + focus: true + boundsBehavior: Flickable.StopAtBounds + + highlightFollowsCurrentItem: false + + onCurrentIndexChanged: { + if (styleModel.rowCount() > 0) + dialogBox.styleIndex = stylesList.currentIndex; + } + + delegate: ItemDelegate { + id: delegateId + height: styleImage.height + DialogValues.styleImageBorderWidth + styleText.height + 1 + width: stylesList.width + + Rectangle { + anchors.fill: parent + color: DialogValues.lightPaneColor + + Column { + spacing: 0 + anchors.fill: parent + + Rectangle { + border.color: index == stylesList.currentIndex ? DialogValues.textColorInteraction : "transparent" + border.width: index == stylesList.currentIndex ? DialogValues.styleImageBorderWidth : 0 + color: "transparent" + width: parent.width + height: parent.height - styleText.height + + Image { + id: styleImage + asynchronous: false + source: "image://newprojectdialog_library/" + styleModel.iconId(model.index) + width: 200 + height: 262 + x: DialogValues.styleImageBorderWidth + y: DialogValues.styleImageBorderWidth + } + } // Rectangle + + Text { + id: styleText + text: model.display + font.pixelSize: DialogValues.defaultPixelSize + lineHeight: DialogValues.defaultLineHeight + height: 18 + lineHeightMode: Text.FixedHeight + horizontalAlignment: Text.AlignHCenter + width: parent.width + color: DialogValues.textColor + } + } // Column + } // Rectangle + + MouseArea { + anchors.fill: parent + onClicked: { + stylesList.currentIndex = index + } + } + } + + Connections { + target: styleModel + function onModelReset() { + stylesList.currentIndex = dialogBox.styleIndex; + stylesList.currentIndexChanged(); + styleTitleText.refresh(); + } + } + } // ListView + } // ColumnLayout + } // Parent Item + } // Rectangle +} diff --git a/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/qmldir b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/qmldir new file mode 100644 index 00000000000..d7c1562164f --- /dev/null +++ b/share/qtcreator/qmldesigner/newprojectdialog/imports/NewProjectDialog/qmldir @@ -0,0 +1,4 @@ +singleton DialogValues 1.0 DialogValues.qml +Details 1.0 Details.qml +Styles 1.0 Styles.qml +NewProjectView 1.0 NewProjectView.qml diff --git a/share/qtcreator/qmldesigner/newprojectdialog/imports/ProjectType/DefaultProject.qml b/share/qtcreator/qmldesigner/newprojectdialog/imports/ProjectType/DefaultProject.qml new file mode 100644 index 00000000000..05bc5fff042 --- /dev/null +++ b/share/qtcreator/qmldesigner/newprojectdialog/imports/ProjectType/DefaultProject.qml @@ -0,0 +1,48 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick.Window +import QtQuick.Controls + +import QtQuick +import QtQuick.Layouts + +import newprojectdialog + +Item { + anchors.fill: parent + + Row { + anchors.fill: parent + + Details { + height: parent.height + } + + Styles { + height: parent.height + } + } +} diff --git a/share/qtcreator/qmldesigner/newprojectdialog/imports/ProjectType/qmldir b/share/qtcreator/qmldesigner/newprojectdialog/imports/ProjectType/qmldir new file mode 100644 index 00000000000..6f3c69e9d42 --- /dev/null +++ b/share/qtcreator/qmldesigner/newprojectdialog/imports/ProjectType/qmldir @@ -0,0 +1 @@ +DefaultProject 1.0 DefaultProject.qml diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/Section.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/Section.qml index 23ee5615700..a6dbcfe11da 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/Section.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/Section.qml @@ -30,6 +30,9 @@ import StudioTheme 1.0 as StudioTheme Item { id: section property alias caption: label.text + property alias captionPixelSize: label.font.pixelSize + property alias captionColor: header.color + property alias captionTextColor: label.color property int leftPadding: 8 property int topPadding: 4 property int rightPadding: 0 diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/InternalConstants.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/InternalConstants.qml index 0dff2cba8ba..8b2e6698bd9 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/InternalConstants.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/InternalConstants.qml @@ -23,7 +23,7 @@ ** ****************************************************************************/ -import QtQuick 2.10 +import QtQuick 2.15 QtObject { readonly property int width: 1920 @@ -95,84 +95,92 @@ QtObject { readonly property string distributeSpacingHorizontal: "\u005A" readonly property string distributeSpacingVertical: "\u005B" readonly property string distributeTop: "\u005C" - readonly property string edit: "\u005D" - readonly property string eyeDropper: "\u005E" - readonly property string flowAction: "\u005F" - readonly property string flowTransition: "\u0060" - readonly property string fontStyleBold: "\u0061" - readonly property string fontStyleItalic: "\u0062" - readonly property string fontStyleStrikethrough: "\u0063" - readonly property string fontStyleUnderline: "\u0064" - readonly property string gradient: "\u0065" - readonly property string gridView: "\u0066" - readonly property string idAliasOff: "\u0067" - readonly property string idAliasOn: "\u0068" - readonly property string infinity: "\u0069" - readonly property string keyframe: "\u006A" - readonly property string linkTriangle: "\u006B" - readonly property string linked: "\u006C" - readonly property string listView: "\u006D" - readonly property string lockOff: "\u006E" - readonly property string lockOn: "\u006F" - readonly property string mergeCells: "\u0070" - readonly property string minus: "\u0071" - readonly property string mirror: "\u0072" - readonly property string orientation: "\u0073" - readonly property string paddingEdge: "\u0074" - readonly property string paddingFrame: "\u0075" - readonly property string pasteStyle: "\u0076" - readonly property string pause: "\u0077" - readonly property string pin: "\u0078" - readonly property string play: "\u0079" - readonly property string plus: "\u007A" - readonly property string promote: "\u007B" - readonly property string readOnly: "\u007C" - readonly property string redo: "\u007D" - readonly property string rotationFill: "\u007E" - readonly property string rotationOutline: "\u007F" - readonly property string search: "\u0080" - readonly property string sectionToggle: "\u0081" - readonly property string splitColumns: "\u0082" - readonly property string splitRows: "\u0083" - readonly property string startNode: "\u0084" - readonly property string testIcon: "\u0085" - readonly property string textAlignBottom: "\u0086" - readonly property string textAlignCenter: "\u0087" - readonly property string textAlignJustified: "\u0088" - readonly property string textAlignLeft: "\u0089" - readonly property string textAlignMiddle: "\u008A" - readonly property string textAlignRight: "\u008B" - readonly property string textAlignTop: "\u008C" - readonly property string textBulletList: "\u008D" - readonly property string textFullJustification: "\u008E" - readonly property string textNumberedList: "\u008F" - readonly property string tickIcon: "\u0090" - readonly property string transparent: "\u0091" - readonly property string triState: "\u0092" - readonly property string triangleArcA: "\u0093" - readonly property string triangleArcB: "\u0094" - readonly property string triangleCornerA: "\u0095" - readonly property string triangleCornerB: "\u0096" - readonly property string unLinked: "\u0097" - readonly property string undo: "\u0098" - readonly property string unpin: "\u0099" - readonly property string upDownIcon: "\u009A" - readonly property string upDownSquare2: "\u009B" - readonly property string visibilityOffBroken: "\u009C" // visibilityOff - readonly property string visibilityOff: "\u009D" // visibilityOff2 - readonly property string visibilityOn: "\u009E" - readonly property string wildcard: "\u009F" - readonly property string wizardsAutomotive: "\u00A0" - readonly property string wizardsDesktop: "\u00A1" - readonly property string wizardsGeneric: "\u00A2" - readonly property string wizardsMcuEmpty: "\u00A3" - readonly property string wizardsMcuGraph: "\u00A4" - readonly property string wizardsMobile: "\u00A5" - readonly property string wizardsUnknown: "\u00A6" - readonly property string zoomAll: "\u00A7" - readonly property string zoomIn: "\u00A8" - readonly property string zoomOut: "\u00A9" - readonly property string zoomSelection: "\u00AA" + readonly property string download: "\u005D" + readonly property string edit: "\u005E" + readonly property string eyeDropper: "\u005F" + readonly property string favorite: "\u0060" + readonly property string flowAction: "\u0061" + readonly property string flowTransition: "\u0062" + readonly property string fontStyleBold: "\u0063" + readonly property string fontStyleItalic: "\u0064" + readonly property string fontStyleStrikethrough: "\u0065" + readonly property string fontStyleUnderline: "\u0066" + readonly property string gradient: "\u0067" + readonly property string gridView: "\u0068" + readonly property string idAliasOff: "\u0069" + readonly property string idAliasOn: "\u006A" + readonly property string infinity: "\u006B" + readonly property string keyframe: "\u006C" + readonly property string linkTriangle: "\u006D" + readonly property string linked: "\u006E" + readonly property string listView: "\u006F" + readonly property string lockOff: "\u0070" + readonly property string lockOn: "\u0071" + readonly property string mergeCells: "\u0072" + readonly property string minus: "\u0073" + readonly property string mirror: "\u0074" + readonly property string orientation: "\u0075" + readonly property string paddingEdge: "\u0076" + readonly property string paddingFrame: "\u0077" + readonly property string pasteStyle: "\u0078" + readonly property string pause: "\u0079" + readonly property string pin: "\u007A" + readonly property string play: "\u007B" + readonly property string plus: "\u007C" + readonly property string promote: "\u007D" + readonly property string readOnly: "\u007E" + readonly property string redo: "\u007F" + readonly property string rotationFill: "\u0080" + readonly property string rotationOutline: "\u0081" + readonly property string search: "\u0082" + readonly property string sectionToggle: "\u0083" + readonly property string splitColumns: "\u0084" + readonly property string splitRows: "\u0085" + readonly property string startNode: "\u0086" + readonly property string testIcon: "\u0087" + readonly property string textAlignBottom: "\u0088" + readonly property string textAlignCenter: "\u0089" + readonly property string textAlignJustified: "\u008A" + readonly property string textAlignLeft: "\u008B" + readonly property string textAlignMiddle: "\u008C" + readonly property string textAlignRight: "\u008D" + readonly property string textAlignTop: "\u008E" + readonly property string textBulletList: "\u008F" + readonly property string textFullJustification: "\u0090" + readonly property string textNumberedList: "\u0091" + readonly property string tickIcon: "\u0092" + readonly property string translationCreateFiles: "\u0093" + readonly property string translationCreateReport: "\u0094" + readonly property string translationExport: "\u0095" + readonly property string translationImport: "\u0096" + readonly property string translationSelectLanguages: "\u0097" + readonly property string translationTest: "\u0098" + readonly property string transparent: "\u0099" + readonly property string triState: "\u009A" + readonly property string triangleArcA: "\u009B" + readonly property string triangleArcB: "\u009C" + readonly property string triangleCornerA: "\u009D" + readonly property string triangleCornerB: "\u009E" + readonly property string unLinked: "\u009F" + readonly property string undo: "\u00A0" + readonly property string unpin: "\u00A1" + readonly property string upDownIcon: "\u00A2" + readonly property string upDownSquare2: "\u00A3" + readonly property string visibilityOffBroken: "\u00A4" // visibilityOff + readonly property string visibilityOff: "\u00A5" // visibilityOff2 + readonly property string visibilityOn: "\u00A6" + readonly property string wildcard: "\u00A7" + readonly property string wizardsAutomotive: "\u00A8" + readonly property string wizardsDesktop: "\u00A9" + readonly property string wizardsGeneric: "\u00AA" + readonly property string wizardsMcuEmpty: "\u00AB" + readonly property string wizardsMcuGraph: "\u00AC" + readonly property string wizardsMobile: "\u00AD" + readonly property string wizardsUnknown: "\u00AE" + readonly property string zoomAll: "\u00AF" + readonly property string zoomIn: "\u00B0" + readonly property string zoomOut: "\u00B1" + readonly property string zoomSelection: "\u00B2" readonly property font iconFont: Qt.font({ "family": controlIcons.name, diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/icons.ttf b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/icons.ttf index 403bb7205ad..24141fcc837 100644 Binary files a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/icons.ttf and b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/icons.ttf differ diff --git a/src/libs/3rdparty/cplusplus/Lexer.cpp b/src/libs/3rdparty/cplusplus/Lexer.cpp index 1d814845fec..c0f8731db0c 100644 --- a/src/libs/3rdparty/cplusplus/Lexer.cpp +++ b/src/libs/3rdparty/cplusplus/Lexer.cpp @@ -1034,7 +1034,7 @@ void Lexer::scanPreprocessorNumber(Token *tok, bool dotAlreadySkipped) yyinp(); if (_yychar == '+' || _yychar == '-') yyinp(); - } else if (std::isalnum(_yychar) || _yychar == '_' || _yychar == '.') { + } else if (std::isalnum(_yychar) || (_yychar == '\'') || _yychar == '_' || _yychar == '.') { yyinp(); } else { scanOptionalUserDefinedLiteral(tok); diff --git a/src/libs/cplusplus/ResolveExpression.cpp b/src/libs/cplusplus/ResolveExpression.cpp index f59dcdac427..99db8cef9df 100644 --- a/src/libs/cplusplus/ResolveExpression.cpp +++ b/src/libs/cplusplus/ResolveExpression.cpp @@ -910,7 +910,7 @@ bool ResolveExpression::visit(CallAST *ast) if (Symbol *declaration = templateTy->declaration()) { if (Function *funTy = declaration->asFunction()) { if (maybeValidPrototype(funTy, actualArgumentCount)) - addResult(funTy->returnType().simplified(), _scope); + addResult(funTy->returnType().simplified(), scope); } } } diff --git a/src/libs/utils/archive.cpp b/src/libs/utils/archive.cpp index 2bbc8eb9eb8..b5521103443 100644 --- a/src/libs/utils/archive.cpp +++ b/src/libs/utils/archive.cpp @@ -136,6 +136,7 @@ static Utils::optional unzipTool(const FilePath &src, const FilePath &dest const QString destStr = dest.toString(); const QString args = result.command.arguments().replace("%{src}", srcStr).replace("%{dest}", destStr); result.command.setArguments(args); + return result; } } return {}; diff --git a/src/libs/utils/theme/theme_mac.mm b/src/libs/utils/theme/theme_mac.mm index b6898f49bbb..3d46938113a 100644 --- a/src/libs/utils/theme/theme_mac.mm +++ b/src/libs/utils/theme/theme_mac.mm @@ -41,7 +41,7 @@ namespace Internal { bool currentAppearanceMatches(bool dark) { -#if __has_builtin(__builtin_available) +#if QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_14) if (__builtin_available(macOS 10.14, *)) { auto appearance = [NSApp.effectiveAppearance bestMatchFromAppearancesWithNames:@[NSAppearanceNameAqua, NSAppearanceNameDarkAqua]]; diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt index e61b8ab4338..800cd3b775d 100644 --- a/src/plugins/CMakeLists.txt +++ b/src/plugins/CMakeLists.txt @@ -57,7 +57,6 @@ add_subdirectory(scxmleditor) add_subdirectory(subversion) add_subdirectory(compilationdatabaseprojectmanager) add_subdirectory(languageclient) -add_subdirectory(studiowelcome) # Level 6: add_subdirectory(cmakeprojectmanager) @@ -93,6 +92,7 @@ if (WIN32 AND CMAKE_CXX_COMPILER_ID MATCHES "Clang") set(qmldesigner_builddir ${PROJECT_BINARY_DIR}/qmldsgnr) endif() add_subdirectory(qmldesigner ${qmldesigner_builddir}) +add_subdirectory(studiowelcome) add_subdirectory(qnx) add_subdirectory(webassembly) add_subdirectory(mcusupport) diff --git a/src/plugins/android/androidconfigurations.cpp b/src/plugins/android/androidconfigurations.cpp index 92d6219f60c..eace8d36eab 100644 --- a/src/plugins/android/androidconfigurations.cpp +++ b/src/plugins/android/androidconfigurations.cpp @@ -562,6 +562,16 @@ FilePath AndroidConfig::gdbPathFromNdk(const Abi &abi, const FilePath &ndkLocati QString(QTC_HOST_EXE_SUFFIX))); } +FilePath AndroidConfig::lldbPathFromNdk(const FilePath &ndkLocation) const +{ + const FilePath path = ndkLocation.pathAppended( + QString("toolchains/llvm/prebuilt/%1/bin/lldb%2").arg(toolchainHostFromNdk(ndkLocation), + QString(QTC_HOST_EXE_SUFFIX))); + if (path.exists()) + return path; + return {}; +} + FilePath AndroidConfig::makePathFromNdk(const FilePath &ndkLocation) const { return ndkLocation.pathAppended( @@ -1240,39 +1250,79 @@ static QString getMultiOrSingleAbiString(const QStringList &abis) return containsAllAbis(abis) ? "Multi-Abi" : abis.join(","); } +static const Debugger::DebuggerItem *existingDebugger(const FilePath &command, + Debugger::DebuggerEngineType type) +{ + // check if the debugger is already registered, but ignoring the display name + const Debugger::DebuggerItem *existing = Debugger::DebuggerItemManager::findByCommand(command); + + // Return existing debugger with same command + if (existing && existing->engineType() == type && existing->isAutoDetected()) + return existing; + return nullptr; +} + static QVariant findOrRegisterDebugger(ToolChain *tc, const QStringList &abisList, bool customDebugger = false) { const auto ¤tConfig = AndroidConfigurations::currentConfig(); const FilePath ndk = static_cast(tc)->ndkLocation(); - const FilePath command = currentConfig.gdbPathFromNdk(tc->targetAbi(), ndk); + const FilePath lldbCommand = currentConfig.lldbPathFromNdk(ndk); + const Debugger::DebuggerItem *existingLldb = existingDebugger(lldbCommand, + Debugger::LldbEngineType); + // Return existing debugger with same command - prefer lldb (limit to sdk/ndk min version?) + if (existingLldb) + return existingLldb->id(); + + const FilePath gdbCommand = currentConfig.gdbPathFromNdk(tc->targetAbi(), ndk); // check if the debugger is already registered, but ignoring the display name - const Debugger::DebuggerItem *existing = Debugger::DebuggerItemManager::findByCommand(command); - + const Debugger::DebuggerItem *existingGdb = existingDebugger(gdbCommand, + Debugger::GdbEngineType); // Return existing debugger with same command - if (existing && existing->engineType() == Debugger::GdbEngineType - && existing->isAutoDetected()) { - return existing->id(); + if (existingGdb) + return existingGdb->id(); + + const QString mainName = AndroidConfigurations::tr("Android Debugger (%1, NDK %2)"); + const QString custom = customDebugger ? QString{"Custom "} : QString{}; + // debugger not found, register a new one + // check lldb + QVariant registeredLldb; + if (!lldbCommand.isEmpty()) { + Debugger::DebuggerItem debugger; + debugger.setCommand(lldbCommand); + debugger.setEngineType(Debugger::LldbEngineType); + debugger.setUnexpandedDisplayName(custom + mainName + .arg(getMultiOrSingleAbiString(allSupportedAbis())) + .arg(AndroidConfigurations::currentConfig().ndkVersion(ndk).toString()) + + ' ' + debugger.engineTypeName()); + debugger.setAutoDetected(true); + debugger.reinitializeFromFile(); + registeredLldb = Debugger::DebuggerItemManager::registerDebugger(debugger); + } + + // we always have a value for gdb (but we shouldn't - we currently use a fallback) + if (!gdbCommand.exists()) { + if (!registeredLldb.isNull()) + return registeredLldb; + return {}; } - // debugger not found, register a new one Debugger::DebuggerItem debugger; - debugger.setCommand(command); + debugger.setCommand(gdbCommand); debugger.setEngineType(Debugger::GdbEngineType); // NDK 10 and older have multiple gdb versions per ABI, so check for that. const bool oldNdkVersion = currentConfig.ndkVersion(ndk) <= QVersionNumber{11}; - QString mainName = AndroidConfigurations::tr("Android Debugger (%1, NDK %2)"); - if (customDebugger) - mainName.prepend("Custom "); - debugger.setUnexpandedDisplayName(mainName + debugger.setUnexpandedDisplayName(custom + mainName .arg(getMultiOrSingleAbiString(oldNdkVersion ? abisList : allSupportedAbis())) - .arg(AndroidConfigurations::currentConfig().ndkVersion(ndk).toString())); + .arg(AndroidConfigurations::currentConfig().ndkVersion(ndk).toString()) + + ' ' + debugger.engineTypeName()); debugger.setAutoDetected(true); debugger.reinitializeFromFile(); - return Debugger::DebuggerItemManager::registerDebugger(debugger); + QVariant registeredGdb = Debugger::DebuggerItemManager::registerDebugger(debugger); + return registeredLldb.isNull() ? registeredGdb : registeredLldb; } void AndroidConfigurations::registerCustomToolChainsAndDebuggers() diff --git a/src/plugins/android/androidconfigurations.h b/src/plugins/android/androidconfigurations.h index 2110125ad70..84d1142cce4 100644 --- a/src/plugins/android/androidconfigurations.h +++ b/src/plugins/android/androidconfigurations.h @@ -140,6 +140,7 @@ public: Utils::FilePath gdbPath(const ProjectExplorer::Abi &abi, const QtSupport::BaseQtVersion *qtVersion) const; Utils::FilePath gdbPathFromNdk(const ProjectExplorer::Abi &abi, const Utils::FilePath &ndkLocation) const; + Utils::FilePath lldbPathFromNdk(const Utils::FilePath &ndkLocation) const; Utils::FilePath makePathFromNdk(const Utils::FilePath &ndkLocation) const; Utils::FilePath keytoolPath() const; diff --git a/src/plugins/android/androiddevice.cpp b/src/plugins/android/androiddevice.cpp index 600c285edf8..7638d0b402f 100644 --- a/src/plugins/android/androiddevice.cpp +++ b/src/plugins/android/androiddevice.cpp @@ -428,7 +428,7 @@ void AndroidDeviceManager::updateDevicesList() void AndroidDeviceManager::updateDevicesListOnce() { if (!m_avdsFutureWatcher.isRunning() && m_androidConfig.adbToolPath().exists()) { - m_avdsFutureWatcher.setFuture((new AndroidAvdManager)->avdList()); + m_avdsFutureWatcher.setFuture(m_avdManager.avdList()); m_devicesFutureWatcher.setFuture(Utils::runAsync([this]() { return m_androidConfig.connectedDevices(); })); diff --git a/src/plugins/android/androidmanager.cpp b/src/plugins/android/androidmanager.cpp index b1a1f15fdee..74695c219b1 100644 --- a/src/plugins/android/androidmanager.cpp +++ b/src/plugins/android/androidmanager.cpp @@ -246,11 +246,6 @@ bool AndroidManager::isQtCreatorGenerated(const FilePath &deploymentFile) return QJsonDocument::fromJson(f.readAll()).object()["_description"].toString() == qtcSignature; } -FilePath AndroidManager::dirPath(const Target *target) -{ - return androidBuildDirectory(target); -} - FilePath AndroidManager::androidBuildDirectory(const Target *target) { return buildDirectory(target) / Constants::ANDROID_BUILD_DIRECTORY; diff --git a/src/plugins/android/androidmanager.h b/src/plugins/android/androidmanager.h index a32bbcacbca..f41dc42be28 100644 --- a/src/plugins/android/androidmanager.h +++ b/src/plugins/android/androidmanager.h @@ -95,8 +95,6 @@ public: static bool isQt5CmakeProject(const ProjectExplorer::Target *target); - // TODO: remove this on 6.0 branch, kept here for binary compatibility for 5.0 release. - static Utils::FilePath dirPath(const ProjectExplorer::Target *target); static Utils::FilePath androidBuildDirectory(const ProjectExplorer::Target *target); static Utils::FilePath buildDirectory(const ProjectExplorer::Target *target); static Utils::FilePath manifestPath(const ProjectExplorer::Target *target); diff --git a/src/plugins/android/androidmanifesteditoriconwidget.cpp b/src/plugins/android/androidmanifesteditoriconwidget.cpp index 3774a5cf00a..1202c8ebc18 100644 --- a/src/plugins/android/androidmanifesteditoriconwidget.cpp +++ b/src/plugins/android/androidmanifesteditoriconwidget.cpp @@ -166,7 +166,7 @@ void AndroidManifestEditorIconWidget::selectIcon() { FilePath file = FileUtils::getOpenFilePath(this, m_iconSelectionText, FileUtils::homePath(), - tr("Images (*.png *.jpg *.webp *.svg)")); + tr("Images (*.png *.jpg *.jpeg *.webp *.svg)")); if (file.isEmpty()) return; setIconFromPath(file); diff --git a/src/plugins/android/splashscreencontainerwidget.cpp b/src/plugins/android/splashscreencontainerwidget.cpp index fbbada112eb..a738b0ed127 100644 --- a/src/plugins/android/splashscreencontainerwidget.cpp +++ b/src/plugins/android/splashscreencontainerwidget.cpp @@ -62,7 +62,7 @@ const char splashscreenFileName[] = "logo"; const char splashscreenPortraitFileName[] = "logo_port"; const char splashscreenLandscapeFileName[] = "logo_land"; const char imageSuffix[] = ".png"; -const QString fileDialogImageFiles = QString(QWidget::tr("Images (*.png *.jpg)")); +const QString fileDialogImageFiles = QString(QWidget::tr("Images (*.png *.jpg *.jpeg)")); const QSize lowDpiImageSize{200, 320}; const QSize mediumDpiImageSize{320, 480}; const QSize highDpiImageSize{480, 800}; @@ -608,8 +608,8 @@ void SplashScreenContainerWidget::checkSplashscreenImage(const QString &name) for (const QString &path : paths) { const FilePath filePath = baseDir.pathAppended(path + name); - if (filePath.stringAppended(".png").exists() - || filePath.stringAppended(".jpg").exists()) { + if (filePath.stringAppended(".png").exists() || filePath.stringAppended(".jpg").exists() + || filePath.stringAppended(".jpeg").exists()) { setCurrentIndex(1); break; } diff --git a/src/plugins/android/splashscreenwidget.cpp b/src/plugins/android/splashscreenwidget.cpp index 23e5ddccb97..386e6802df2 100644 --- a/src/plugins/android/splashscreenwidget.cpp +++ b/src/plugins/android/splashscreenwidget.cpp @@ -189,7 +189,8 @@ void SplashScreenWidget::selectImage() { const FilePath file = FileUtils::getOpenFilePath(this, m_imageSelectionText, FileUtils::homePath(), - QStringLiteral("%1 (*.png *.jpg)").arg(tr("Images"))); + QStringLiteral("%1 (*.png *.jpg *.jpeg)") + .arg(tr("Images"))); if (file.isEmpty()) return; setImageFromPath(file, false); diff --git a/src/plugins/autotest/gtest/gtestconfiguration.cpp b/src/plugins/autotest/gtest/gtestconfiguration.cpp index a4903b075ad..cbdd0c8e8a2 100644 --- a/src/plugins/autotest/gtest/gtestconfiguration.cpp +++ b/src/plugins/autotest/gtest/gtestconfiguration.cpp @@ -55,6 +55,7 @@ QStringList filterInterfering(const QStringList &provided, QStringList *omitted) "--gtest_stream_result_to=", "--gtest_break_on_failure", "--gtest_throw_on_failure", + "--gtest_catch_exceptions=", "--gtest_print_time=" }; @@ -100,6 +101,7 @@ QStringList GTestConfiguration::argumentsForTestRunner(QStringList *omitted) con if (isDebugRunMode()) { if (gSettings->breakOnFailure.value()) arguments << "--gtest_break_on_failure"; + arguments << "--gtest_catch_exceptions=0"; } return arguments; } diff --git a/src/plugins/clangcodemodel/CMakeLists.txt b/src/plugins/clangcodemodel/CMakeLists.txt index ace1b99575c..e8f15a2303b 100644 --- a/src/plugins/clangcodemodel/CMakeLists.txt +++ b/src/plugins/clangcodemodel/CMakeLists.txt @@ -61,3 +61,14 @@ extend_qtc_plugin(ClangCodeModel test/clangdtests.cpp test/clangdtests.h test/data/clangtestdata.qrc ) + +if(MINGW) + set(big_obj_compile_option "-Wa,-mbig-obj") +elseif(MSVC) + set(big_obj_compile_option "/bigobj") +endif() + +extend_qtc_plugin(ClangCodeModel + CONDITION DEFINED big_obj_compile_option + PROPERTIES COMPILE_OPTIONS ${big_obj_compile_option} +) diff --git a/src/plugins/clangcodemodel/clangdclient.cpp b/src/plugins/clangcodemodel/clangdclient.cpp index 17fe2775074..46c85f06cdf 100644 --- a/src/plugins/clangcodemodel/clangdclient.cpp +++ b/src/plugins/clangcodemodel/clangdclient.cpp @@ -252,6 +252,16 @@ public: QString theType = type(); if (theType.endsWith("const")) theType.chop(5); + + // We don't care about the "inner" type of templates. + const int openAngleBracketPos = theType.indexOf('<'); + if (openAngleBracketPos != -1) { + const int closingAngleBracketPos = theType.lastIndexOf('>'); + if (closingAngleBracketPos > openAngleBracketPos) { + theType = theType.left(openAngleBracketPos) + + theType.mid(closingAngleBracketPos + 1); + } + } const int xrefCount = theType.count("&&"); const int refCount = theType.count('&') - 2 * xrefCount; const int ptrRefCount = theType.count('*') + refCount; @@ -2443,6 +2453,8 @@ static void semanticHighlighter(QFutureInterface &future, return true; } + if (it->kind() == "Lambda") + return false; if (it->kind().endsWith("Cast") && it->hasConstType()) return false; if (it->kind() == "Member" && it->arcanaContains("(") @@ -3140,6 +3152,18 @@ void ExtraHighlightingResultsCollector::insertResult(const HighlightingResult &r return; const auto it = std::lower_bound(m_results.begin(), m_results.end(), result, lessThan); if (it == m_results.end() || *it != result) { + + // Prevent inserting expansions for function-like macros. For instance: + // #define TEST() "blubb" + // const char *s = TEST(); + // The macro name is always shorter than the expansion and starts at the same + // location, so it should occur right before the insertion position. + if (it > m_results.begin() && (it - 1)->line == result.line + && (it - 1)->column == result.column + && (it - 1)->textStyles.mainStyle == C_PREPROCESSOR) { + return; + } + qCDebug(clangdLogHighlight) << "adding additional highlighting result" << result.line << result.column << result.length; m_results.insert(it, result); diff --git a/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp b/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp index 090a4549c3b..3bd9d2640f6 100644 --- a/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp +++ b/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp @@ -112,6 +112,7 @@ ClangModelManagerSupport::ClangModelManagerSupport() m_instance = this; watchForExternalChanges(); + watchForInternalChanges(); cppModelManager()->setCurrentDocumentFilter(std::make_unique()); cppModelManager()->setLocatorFilter(std::make_unique()); cppModelManager()->setClassesFilter(std::make_unique()); @@ -488,6 +489,29 @@ void ClangModelManagerSupport::watchForExternalChanges() }); } +void ClangModelManagerSupport::watchForInternalChanges() +{ + connect(Core::DocumentManager::instance(), &Core::DocumentManager::filesChangedInternally, + this, [this](const Utils::FilePaths &filePaths) { + for (const Utils::FilePath &fp : filePaths) { + ClangdClient * const client = clientForFile(fp); + if (!client || client->documentForFilePath(fp)) + continue; + client->openExtraFile(fp); + + // We need to give clangd some time to start re-parsing the file. + // Closing right away does not work, and neither does doing it queued. + // If it turns out that this delay is not always enough, we'll need to come up + // with something more clever. + // Ideally, clangd would implement workspace/didChangeWatchedFiles; let's keep + // any eye on that. + QTimer::singleShot(5000, client, [client, fp] { + if (!client->documentForFilePath(fp)) + client->closeExtraFile(fp); }); + } + }); +} + void ClangModelManagerSupport::onEditorOpened(Core::IEditor *editor) { QTC_ASSERT(editor, return); diff --git a/src/plugins/clangcodemodel/clangmodelmanagersupport.h b/src/plugins/clangcodemodel/clangmodelmanagersupport.h index fc4e1d1a4b4..5d591d6925f 100644 --- a/src/plugins/clangcodemodel/clangmodelmanagersupport.h +++ b/src/plugins/clangcodemodel/clangmodelmanagersupport.h @@ -135,8 +135,8 @@ private: ClangdClient *createClient(ProjectExplorer::Project *project, const Utils::FilePath &jsonDbDir); void claimNonProjectSources(ClangdClient *fallbackClient); void watchForExternalChanges(); + void watchForInternalChanges(); -private: UiHeaderOnDiskManager m_uiHeaderOnDiskManager; BackendCommunicator m_communicator; ClangCompletionAssistProvider m_completionAssistProvider; diff --git a/src/plugins/clangcodemodel/test/clangdtests.cpp b/src/plugins/clangcodemodel/test/clangdtests.cpp index 49dac93f495..82a6eea7524 100644 --- a/src/plugins/clangcodemodel/test/clangdtests.cpp +++ b/src/plugins/clangcodemodel/test/clangdtests.cpp @@ -1249,6 +1249,9 @@ void ClangdTestHighlighting::test_data() QTest::newRow("const argument to unnamed lambda") << 830 << 16 << 830 << 19 << QList{C_LOCAL} << 0; QTest::newRow("simple assignment") << 835 << 5 << 835 << 6 << QList{C_LOCAL} << 0; + QTest::newRow("simple return") << 841 << 12 << 841 << 15 << QList{C_LOCAL} << 0; + QTest::newRow("lambda parameter") << 847 << 49 << 847 << 52 + << QList{C_PARAMETER, C_DECLARATION} << 0; } void ClangdTestHighlighting::test() diff --git a/src/plugins/clangcodemodel/test/data/highlighting/highlighting.cpp b/src/plugins/clangcodemodel/test/data/highlighting/highlighting.cpp index 652d9540c80..7f0db946955 100644 --- a/src/plugins/clangcodemodel/test/data/highlighting/highlighting.cpp +++ b/src/plugins/clangcodemodel/test/data/highlighting/highlighting.cpp @@ -834,3 +834,15 @@ void assignmentTest() { struct S {} s; s = {}; } + +using FooPtrVector = std::vector; +FooPtrVector returnTest() { + FooPtrVector foo; + return foo; +} + +template inline void useContainer(const Container &, Func) {} +void testConstRefAutoLambdaArgs() +{ + useContainer(FooPtrVector(), [](const auto &arg) {}); +} diff --git a/src/plugins/coreplugin/find/basetextfind.cpp b/src/plugins/coreplugin/find/basetextfind.cpp index ce213362230..12afaf8a659 100644 --- a/src/plugins/coreplugin/find/basetextfind.cpp +++ b/src/plugins/coreplugin/find/basetextfind.cpp @@ -421,10 +421,13 @@ QTextCursor BaseTextFind::findOne(const QRegularExpression &expr, QTextCursor found = document()->find(expr, from, options); while (!found.isNull() && !inScope(found)) { if (!found.hasSelection()) { - from = found; - found.movePosition(options & QTextDocument::FindBackward - ? QTextCursor::PreviousCharacter - : QTextCursor::NextCharacter); + if (found.movePosition(options & QTextDocument::FindBackward + ? QTextCursor::PreviousCharacter + : QTextCursor::NextCharacter)) { + from = found; + } else { + return {}; + } } else { from.setPosition(options & QTextDocument::FindBackward ? found.selectionStart() : found.selectionEnd()); diff --git a/src/plugins/coreplugin/icore.cpp b/src/plugins/coreplugin/icore.cpp index c118533426a..82a715f59e2 100644 --- a/src/plugins/coreplugin/icore.cpp +++ b/src/plugins/coreplugin/icore.cpp @@ -32,6 +32,7 @@ #include #include +#include #include #include @@ -165,9 +166,13 @@ namespace Core { // The Core Singleton static ICore *m_instance = nullptr; static MainWindow *m_mainwindow = nullptr; -std::function ICore::m_newDialogFactory = [](QWidget *parent) { + +static NewDialog *defaultDialogFactory(QWidget *parent) +{ return new NewDialogWidget(parent); -}; +} + +static std::function m_newDialogFactory = defaultDialogFactory; /*! Returns the pointer to the instance. Only use for connecting to signals. @@ -253,7 +258,23 @@ void ICore::showNewItemDialog(const QString &title, const QVariantMap &extraVariables) { QTC_ASSERT(!isNewItemDialogRunning(), return); - NewDialog *newDialog = ICore::m_newDialogFactory(dialogParent()); + + /* This is a workaround for QDS: In QDS, we currently have a "New Project" dialog box but we do + * not also have a "New file" dialog box (yet). Therefore, when requested to add a new file, we + * need to use QtCreator's dialog box. In QDS, if `factories` contains project wizard factories + * (even though it may contain file wizard factories as well), then we consider it to be a + * request for "New Project". Otherwise, if we only have file wizard factories, we defer to + * QtCreator's dialog and request "New File" + */ + auto dialogFactory = m_newDialogFactory; + bool haveProjectWizards = Utils::anyOf(factories, [](IWizardFactory *f) { + return f->kind() == IWizardFactory::ProjectWizard; + }); + + if (!haveProjectWizards) + dialogFactory = defaultDialogFactory; + + NewDialog *newDialog = dialogFactory(dialogParent()); connect(newDialog->widget(), &QObject::destroyed, m_instance, &ICore::updateNewItemDialogState); newDialog->setWizardFactories(factories, defaultLocation, extraVariables); newDialog->setWindowTitle(title); diff --git a/src/plugins/coreplugin/icore.h b/src/plugins/coreplugin/icore.h index 589cf49765e..93cf5c71f17 100644 --- a/src/plugins/coreplugin/icore.h +++ b/src/plugins/coreplugin/icore.h @@ -180,8 +180,6 @@ public: private: static void updateNewItemDialogState(); - - static std::function m_newDialogFactory; }; } // namespace Core diff --git a/src/plugins/coreplugin/iwizardfactory.cpp b/src/plugins/coreplugin/iwizardfactory.cpp index 988c4bdd958..ea4ec09e774 100644 --- a/src/plugins/coreplugin/iwizardfactory.cpp +++ b/src/plugins/coreplugin/iwizardfactory.cpp @@ -291,9 +291,10 @@ Wizard *IWizardFactory::runWizard(const FilePath &path, QWidget *parent, Id plat s_reopenData.reopen(); }); s_inspectWizardAction->setEnabled(true); - if (showWizard) + if (showWizard) { wizard->show(); - Core::ICore::registerWindow(wizard, Core::Context("Core.NewWizard")); + Core::ICore::registerWindow(wizard, Core::Context("Core.NewWizard")); + } } else { s_isWizardRunning = false; ICore::updateNewItemDialogState(); diff --git a/src/plugins/coreplugin/mainwindow.cpp b/src/plugins/coreplugin/mainwindow.cpp index bc8e438dcfc..83d5a257a57 100644 --- a/src/plugins/coreplugin/mainwindow.cpp +++ b/src/plugins/coreplugin/mainwindow.cpp @@ -92,6 +92,7 @@ #include #include #include +#include using namespace ExtensionSystem; using namespace Utils; @@ -101,6 +102,14 @@ namespace Internal { enum { debugMainWindow = 0 }; +static bool isQtDesignStudio() +{ + QSettings *settings = Core::ICore::settings(); + const QString qdsStandaloneEntry = "QML/Designer/StandAloneMode"; //entry from qml settings + + return settings->value(qdsStandaloneEntry, false).toBool(); +} + MainWindow::MainWindow() : AppMainWindow() , m_coreImpl(new ICore(this)) @@ -518,7 +527,8 @@ void MainWindow::registerDefaultActions() // New File Action QIcon icon = QIcon::fromTheme(QLatin1String("document-new"), Utils::Icons::NEWFILE.icon()); - m_newAction = new QAction(icon, tr("&New File or Project..."), this); + QString newActionText = isQtDesignStudio() ? tr("&New Project...") : tr("&New File or Project..."); + m_newAction = new QAction(icon, newActionText, this); cmd = ActionManager::registerAction(m_newAction, Constants::NEW); cmd->setDefaultKeySequence(QKeySequence::New); mfile->addAction(cmd, Constants::G_FILE_NEW); @@ -939,6 +949,20 @@ void MainWindow::setFocusToEditor() EditorManagerPrivate::doEscapeKeyFocusMoveMagic(); } +static void acceptModalDialogs() +{ + const QWidgetList topLevels = QApplication::topLevelWidgets(); + QList dialogsToClose; + for (QWidget *topLevel : topLevels) { + if (auto dialog = qobject_cast(topLevel)) { + if (dialog->isModal()) + dialogsToClose.append(dialog); + } + } + for (QDialog *dialog : dialogsToClose) + dialog->accept(); +} + void MainWindow::exit() { // this function is most likely called from a user action @@ -946,7 +970,15 @@ void MainWindow::exit() // since on close we are going to delete everything // so to prevent the deleting of that object we // just append it - QMetaObject::invokeMethod(this, &QWidget::close, Qt::QueuedConnection); + QMetaObject::invokeMethod( + this, + [this] { + // Modal dialogs block the close event. So close them, in case this was triggered from + // a RestartDialog in the settings dialog. + acceptModalDialogs(); + close(); + }, + Qt::QueuedConnection); } void MainWindow::openFileWith() diff --git a/src/plugins/cppeditor/cppcodemodelsettings.cpp b/src/plugins/cppeditor/cppcodemodelsettings.cpp index cf57ee39730..e60d8464687 100644 --- a/src/plugins/cppeditor/cppcodemodelsettings.cpp +++ b/src/plugins/cppeditor/cppcodemodelsettings.cpp @@ -36,6 +36,9 @@ #include #include +#include +#include +#include #include using namespace Utils; @@ -356,11 +359,12 @@ static QVersionNumber getClangdVersion(const FilePath &clangdFilePath) QVersionNumber ClangdSettings::clangdVersion(const FilePath &clangdFilePath) { + static QHash> versionCache; const QDateTime timeStamp = clangdFilePath.lastModified(); - const auto it = m_versionCache.find(clangdFilePath); - if (it == m_versionCache.end()) { + const auto it = versionCache.find(clangdFilePath); + if (it == versionCache.end()) { const QVersionNumber version = getClangdVersion(clangdFilePath); - m_versionCache.insert(clangdFilePath, qMakePair(timeStamp, version)); + versionCache.insert(clangdFilePath, qMakePair(timeStamp, version)); return version; } if (it->first != timeStamp) { diff --git a/src/plugins/cppeditor/cppcodemodelsettings.h b/src/plugins/cppeditor/cppcodemodelsettings.h index 2f3de7dd6bc..7d3ecb27fae 100644 --- a/src/plugins/cppeditor/cppcodemodelsettings.h +++ b/src/plugins/cppeditor/cppcodemodelsettings.h @@ -29,10 +29,7 @@ #include -#include -#include #include -#include #include #include @@ -150,7 +147,6 @@ private: void saveSettings(); Data m_data; - static inline QHash> m_versionCache; }; inline bool operator==(const ClangdSettings::Data &s1, const ClangdSettings::Data &s2) diff --git a/src/plugins/cppeditor/cppquickfix_test.cpp b/src/plugins/cppeditor/cppquickfix_test.cpp index 5a0e21ad693..dd85e95470e 100644 --- a/src/plugins/cppeditor/cppquickfix_test.cpp +++ b/src/plugins/cppeditor/cppquickfix_test.cpp @@ -213,6 +213,7 @@ QuickFixOperationTest::QuickFixOperationTest(const QList &testD QuickFixOperations operations; factory->match(quickFixInterface, operations); if (operations.isEmpty()) { + QEXPECT_FAIL("CompleteSwitchCaseStatement_QTCREATORBUG-25998", "FIXME", Abort); QVERIFY(testDocuments.first()->m_expectedSource.isEmpty()); return; } diff --git a/src/plugins/cppeditor/semantichighlighter.cpp b/src/plugins/cppeditor/semantichighlighter.cpp index ba8b81e7ff1..d1e4e678257 100644 --- a/src/plugins/cppeditor/semantichighlighter.cpp +++ b/src/plugins/cppeditor/semantichighlighter.cpp @@ -148,6 +148,7 @@ void SemanticHighlighter::run() connectWatcher(); m_revision = documentRevision(); + qCDebug(log) << "starting runner for document revision" << m_revision; m_watcher->setFuture(m_highlightingRunner()); } @@ -160,12 +161,17 @@ static Parentheses getClearedParentheses(const QTextBlock &block) void SemanticHighlighter::onHighlighterResultAvailable(int from, int to) { - if (documentRevision() != m_revision) - return; // outdated - if (!m_watcher || m_watcher->isCanceled()) - return; // aborted - qCDebug(log) << "onHighlighterResultAvailable()" << from << to; + if (documentRevision() != m_revision) { + qCDebug(log) << "ignoring results: revision changed from" << m_revision << "to" + << documentRevision(); + return; + } + if (!m_watcher || m_watcher->isCanceled()) { + qCDebug(log) << "ignoring results: future was canceled"; + return; + } + QElapsedTimer t; t.start(); diff --git a/src/plugins/debugger/cdb/cdbengine.cpp b/src/plugins/debugger/cdb/cdbengine.cpp index 37d9b16e230..58604a79cce 100644 --- a/src/plugins/debugger/cdb/cdbengine.cpp +++ b/src/plugins/debugger/cdb/cdbengine.cpp @@ -2832,8 +2832,10 @@ void CdbEngine::setupScripting(const DebuggerResponse &response) runCommand({command, ScriptCommand}); } - const QString qtVersion = QString::number(runParameters().fallbackQtVersion, 16); - runCommand({"theDumper.setFallbackQtVersion(0x" + qtVersion + ")", ScriptCommand}); + DebuggerCommand cmd0("theDumper.setFallbackQtVersion", ScriptCommand); + cmd0.arg("version", runParameters().fallbackQtVersion); + runCommand(cmd0); + runCommand({"theDumper.loadDumpers(None)", ScriptCommand, [this](const DebuggerResponse &response) { watchHandler()->addDumpers(response.data["result"]["dumpers"]); diff --git a/src/plugins/debugger/debuggerengine.cpp b/src/plugins/debugger/debuggerengine.cpp index ebe4ac3c04f..cc1c6d6396b 100644 --- a/src/plugins/debugger/debuggerengine.cpp +++ b/src/plugins/debugger/debuggerengine.cpp @@ -931,22 +931,27 @@ void DebuggerEngine::showStatusMessage(const QString &msg, int timeout) const void DebuggerEngine::updateLocalsWindow(bool showReturn) { + QTC_ASSERT(d->m_returnWindow, return); + QTC_ASSERT(d->m_localsView, return); d->m_returnWindow->setVisible(showReturn); d->m_localsView->resizeColumns(); } bool DebuggerEngine::isRegistersWindowVisible() const { + QTC_ASSERT(d->m_registerWindow, return false); return d->m_registerWindow->isVisible(); } bool DebuggerEngine::isPeripheralRegistersWindowVisible() const { + QTC_ASSERT(d->m_peripheralRegisterWindow, return false); return d->m_peripheralRegisterWindow->isVisible(); } bool DebuggerEngine::isModulesWindowVisible() const { + QTC_ASSERT(d->m_modulesWindow, return false); return d->m_modulesWindow->isVisible(); } diff --git a/src/plugins/debugger/debuggeritem.cpp b/src/plugins/debugger/debuggeritem.cpp index 2f71c8606bf..1517557be95 100644 --- a/src/plugins/debugger/debuggeritem.cpp +++ b/src/plugins/debugger/debuggeritem.cpp @@ -165,9 +165,12 @@ void DebuggerItem::reinitializeFromFile(const Environment &sysEnv, QString *erro return; } + Environment env = sysEnv.size() == 0 ? Environment::systemEnvironment() : sysEnv; // Prevent calling lldb on Windows because the lldb from the llvm package is linked against // python but does not contain a python dll. - if (HostOsInfo::isWindowsHost() && m_command.fileName().startsWith("lldb")) { + const bool isAndroidNdkLldb = DebuggerItem::addAndroidLldbPythonEnv(m_command, env); + if (HostOsInfo::isWindowsHost() && m_command.fileName().startsWith("lldb") + && !isAndroidNdkLldb) { QString errorMessage; m_version = winGetDLLVersion(WinDLLFileVersion, m_command.absoluteFilePath().path(), @@ -178,7 +181,7 @@ void DebuggerItem::reinitializeFromFile(const Environment &sysEnv, QString *erro } QtcProcess proc; - proc.setEnvironment(sysEnv); + proc.setEnvironment(env); proc.setCommand({m_command, {version}}); proc.runBlocking(); const QString output = proc.allOutput().trimmed(); @@ -262,6 +265,22 @@ void DebuggerItem::reinitializeFromFile(const Environment &sysEnv, QString *erro m_engineType = NoEngineType; } +bool DebuggerItem::addAndroidLldbPythonEnv(const Utils::FilePath &lldbCmd, Utils::Environment &env) +{ + if (lldbCmd.baseName().contains("lldb") && + (lldbCmd.path().contains("/ndk/") || lldbCmd.path().contains("/ndk-bundle/"))) { + const FilePath pythonDir = lldbCmd.parentDir().parentDir().pathAppended("python3"); + const FilePath pythonBinDir = + HostOsInfo::isAnyUnixHost() ? pythonDir.pathAppended("bin") : pythonDir; + if (pythonBinDir.exists()) { + env.set("PYTHONHOME", pythonDir.toUserOutput()); + env.prependOrSetPath(pythonBinDir); + return true; + } + } + return false; +} + QString DebuggerItem::engineTypeName() const { switch (m_engineType) { diff --git a/src/plugins/debugger/debuggeritem.h b/src/plugins/debugger/debuggeritem.h index 12bc903bdd0..f8d5e602ded 100644 --- a/src/plugins/debugger/debuggeritem.h +++ b/src/plugins/debugger/debuggeritem.h @@ -107,6 +107,8 @@ public: QString detectionSource() const { return m_detectionSource; } void setDetectionSource(const QString &source) { m_detectionSource = source; } + static bool addAndroidLldbPythonEnv(const Utils::FilePath &lldbCmd, Utils::Environment &env); + private: DebuggerItem(const QVariant &id); void initMacroExpander(); diff --git a/src/plugins/debugger/gdb/gdbengine.cpp b/src/plugins/debugger/gdb/gdbengine.cpp index 90740c5b2f2..d0d769f654b 100644 --- a/src/plugins/debugger/gdb/gdbengine.cpp +++ b/src/plugins/debugger/gdb/gdbengine.cpp @@ -4011,7 +4011,10 @@ void GdbEngine::setupEngine() if (!commands.isEmpty()) runCommand({commands}); - runCommand({"setFallbackQtVersion(0x" + QString::number(rp.fallbackQtVersion, 16) + ")"}); + DebuggerCommand cmd1("setFallbackQtVersion"); + cmd1.arg("version", rp.fallbackQtVersion); + runCommand(cmd1); + runCommand({"loadDumpers", CB(handlePythonSetup)}); // Reload peripheral register description. diff --git a/src/plugins/debugger/lldb/lldbengine.cpp b/src/plugins/debugger/lldb/lldbengine.cpp index 4188966e48b..e98c0598158 100644 --- a/src/plugins/debugger/lldb/lldbengine.cpp +++ b/src/plugins/debugger/lldb/lldbengine.cpp @@ -213,12 +213,7 @@ void LldbEngine::setupEngine() showMessage("STARTING LLDB: " + lldbCmd.toUserOutput()); Environment environment = runParameters().debugger.environment; environment.appendOrSet("PYTHONUNBUFFERED", "1"); // avoid flushing problem on macOS - if (lldbCmd.path().contains("/ndk-bundle/")) { - FilePath androidPythonDir = lldbCmd.parentDir().parentDir().pathAppended("python3"); - if (HostOsInfo::isAnyUnixHost()) - androidPythonDir = androidPythonDir.pathAppended("bin"); - environment.prependOrSetPath(androidPythonDir); - } + DebuggerItem::addAndroidLldbPythonEnv(lldbCmd, environment); m_lldbProc.setEnvironment(environment); if (runParameters().debugger.workingDirectory.isDir()) @@ -269,10 +264,6 @@ void LldbEngine::setupEngine() runCommand(cmd); } - DebuggerCommand cmd0("setFallbackQtVersion"); - cmd0.arg("version", "0x" + QString::number(rp.fallbackQtVersion, 16)); - runCommand(cmd0); - DebuggerCommand cmd1("loadDumpers"); cmd1.callback = [this](const DebuggerResponse &response) { watchHandler()->addDumpers(response.data["dumpers"]); @@ -348,6 +339,10 @@ void LldbEngine::setupEngine() cmd2.flags = Silent; runCommand(cmd2); + + DebuggerCommand cmd0("setFallbackQtVersion"); + cmd0.arg("version", rp.fallbackQtVersion); + runCommand(cmd0); } void LldbEngine::runEngine() diff --git a/src/plugins/debugger/watchdata.cpp b/src/plugins/debugger/watchdata.cpp index 49a75cc86f0..cf49aa250d4 100644 --- a/src/plugins/debugger/watchdata.cpp +++ b/src/plugins/debugger/watchdata.cpp @@ -48,7 +48,11 @@ bool isIntType(const QString &type) case 'b': return type == "bool"; case 'c': - return type == "char"; + return type.startsWith("char") && + ( type == "char" + || type == "char8_t" + || type == "char16_t" + || type == "char32_t" ); case 'i': return type.startsWith("int") && ( type == "int" @@ -63,7 +67,8 @@ bool isIntType(const QString &type) case 'p': return type == "ptrdiff_t"; case 'q': - return type == "qint16" || type == "quint16" + return type == "qint8" || type == "quint8" + || type == "qint16" || type == "quint16" || type == "qint32" || type == "quint32" || type == "qint64" || type == "quint64" || type == "qlonglong" || type == "qulonglong"; diff --git a/src/plugins/debugger/watchhandler.cpp b/src/plugins/debugger/watchhandler.cpp index 2b5373a042f..1e347e67220 100644 --- a/src/plugins/debugger/watchhandler.cpp +++ b/src/plugins/debugger/watchhandler.cpp @@ -717,22 +717,34 @@ static QString formattedValue(const WatchItem *item) // Append quoted, printable character also for decimal. // FIXME: This is unreliable. - if (item->type.endsWith("char") || item->type.endsWith("int8_t")) { + const QString type = item->type; + if (type == "char8_t" || type.endsWith("char") || type.endsWith("int8_t")) { bool ok; const int code = item->value.toInt(&ok); - bool isUnsigned = item->type == "unsigned char" || item->type == "uchar" || item->type == "uint8_t"; + bool isUnsigned = type == "char8_t" + || type == "unsigned char" + || type == "uchar" + || type == "uint8_t"; if (ok) return reformatCharacter(code, 1, !isUnsigned); - } else if (item->type.endsWith("wchar_t")) { + } else if (type == "qint8" || type == "quint8") { + bool ok = false; + const int code = item->value.toInt(&ok); + bool isUnsigned = type == "quint8"; + if (ok) + return reformatCharacter(code, 1, !isUnsigned); + } else if (type == "char32_t" || type.endsWith("wchar_t")) { bool ok; const int code = item->value.toInt(&ok); + bool isUnsigned = type == "char32_t"; if (ok) - return reformatCharacter(code, 4, false); - } else if (item->type.endsWith("QChar")) { + return reformatCharacter(code, 4, !isUnsigned); + } else if (type == "char16_t" || type.endsWith("QChar")) { bool ok; const int code = item->value.toInt(&ok); + bool isUnsigned = type == "char16_t"; if (ok) - return reformatCharacter(code, 2, false); + return reformatCharacter(code, 2, !isUnsigned); } if (format == HexadecimalIntegerFormat diff --git a/src/plugins/languageclient/client.cpp b/src/plugins/languageclient/client.cpp index 03b75680abd..b452341d0d3 100644 --- a/src/plugins/languageclient/client.cpp +++ b/src/plugins/languageclient/client.cpp @@ -937,7 +937,6 @@ void Client::handleCodeActionResponse(const CodeActionRequest::Response &respons void Client::executeCommand(const Command &command) { - const QString method(ExecuteCommandRequest::methodName); bool serverSupportsExecuteCommand = m_serverCapabilities.executeCommandProvider().has_value(); serverSupportsExecuteCommand = m_dynamicCapabilities .isRegistered(ExecuteCommandRequest::methodName) @@ -953,7 +952,18 @@ ProjectExplorer::Project *Client::project() const void Client::setCurrentProject(ProjectExplorer::Project *project) { + if (m_project == project) + return; + if (m_project) + m_project->disconnect(this); m_project = project; + if (m_project) { + connect(m_project, &ProjectExplorer::Project::destroyed, this, [this]() { + // the project of the client should already be null since we expect the session and + // the language client manager to reset it before it gets deleted. + QTC_ASSERT(m_project == nullptr, projectClosed(m_project)); + }); + } } void Client::projectOpened(ProjectExplorer::Project *project) diff --git a/src/plugins/languageclient/languageclientmanager.cpp b/src/plugins/languageclient/languageclientmanager.cpp index 63a4f1c9da5..f4b4439dae9 100644 --- a/src/plugins/languageclient/languageclientmanager.cpp +++ b/src/plugins/languageclient/languageclientmanager.cpp @@ -53,6 +53,8 @@ using namespace LanguageServerProtocol; namespace LanguageClient { +static Q_LOGGING_CATEGORY(Log, "qtc.languageclient.manager", QtWarningMsg) + static LanguageClientManager *managerInstance = nullptr; LanguageClientManager::LanguageClientManager(QObject *parent) @@ -111,6 +113,7 @@ void LanguageClient::LanguageClientManager::addClient(Client *client) if (managerInstance->m_clients.contains(client)) return; + qCDebug(Log) << "add client: " << client->name() << client; managerInstance->m_clients << client; connect(client, &Client::finished, managerInstance, [client]() { clientFinished(client); }); connect(client, @@ -130,6 +133,7 @@ void LanguageClient::LanguageClientManager::addClient(Client *client) void LanguageClientManager::clientStarted(Client *client) { + qCDebug(Log) << "client started: " << client->name() << client; QTC_ASSERT(managerInstance, return); QTC_ASSERT(client, return); if (managerInstance->m_shuttingDown) @@ -150,6 +154,7 @@ void LanguageClientManager::clientFinished(Client *client) const QList &clientDocs = managerInstance->m_clientForDocument.keys(client); if (client->reset()) { + qCDebug(Log) << "restart unexpectedly finished client: " << client->name() << client; client->disconnect(managerInstance); client->log( tr("Unexpectedly finished. Restarting in %1 seconds.").arg(restartTimeoutS)); @@ -158,6 +163,7 @@ void LanguageClientManager::clientFinished(Client *client) client->deactivateDocument(document); return; } + qCDebug(Log) << "client finished unexpectedly: " << client->name() << client; client->log(tr("Unexpectedly finished.")); for (TextEditor::TextDocument *document : clientDocs) managerInstance->m_clientForDocument.remove(document); @@ -174,6 +180,7 @@ Client *LanguageClientManager::startClient(BaseSettings *setting, ProjectExplore QTC_ASSERT(setting, return nullptr); QTC_ASSERT(setting->isValid(), return nullptr); Client *client = setting->createClient(project); + qCDebug(Log) << "start client: " << client->name() << client; QTC_ASSERT(client, return nullptr); client->start(); managerInstance->m_clientsForSetting[setting->m_id].append(client); @@ -206,6 +213,7 @@ void LanguageClientManager::shutdownClient(Client *client) { if (!client) return; + qCDebug(Log) << "request client shutdown: " << client->name() << client; // reset the documents for that client already when requesting the shutdown so they can get // reassigned to another server right after this request to another server for (TextEditor::TextDocument *document : managerInstance->m_clientForDocument.keys(client)) @@ -220,6 +228,7 @@ void LanguageClientManager::deleteClient(Client *client) { QTC_ASSERT(managerInstance, return); QTC_ASSERT(client, return); + qCDebug(Log) << "delete client: " << client->name() << client; client->disconnect(); managerInstance->m_clients.removeAll(client); for (QVector &clients : managerInstance->m_clientsForSetting) @@ -237,6 +246,7 @@ void LanguageClientManager::shutdown() QTC_ASSERT(managerInstance, return); if (managerInstance->m_shuttingDown) return; + qCDebug(Log) << "shutdown manager"; managerInstance->m_shuttingDown = true; for (Client *client : qAsConst(managerInstance->m_clients)) shutdownClient(client); @@ -412,6 +422,7 @@ void LanguageClientManager::openDocumentWithClient(TextEditor::TextDocument *doc currentClient->deactivateDocument(document); managerInstance->m_clientForDocument[document] = client; if (client) { + qCDebug(Log) << "open" << document->filePath() << "with" << client->name() << client; if (!client->documentOpen(document)) client->openDocument(document); else diff --git a/src/plugins/projectexplorer/jsonwizard/jsonprojectpage.h b/src/plugins/projectexplorer/jsonwizard/jsonprojectpage.h index b362b988945..ced13950aac 100644 --- a/src/plugins/projectexplorer/jsonwizard/jsonprojectpage.h +++ b/src/plugins/projectexplorer/jsonwizard/jsonprojectpage.h @@ -25,12 +25,13 @@ #pragma once +#include "../projectexplorer_export.h" #include namespace ProjectExplorer { // Documentation inside. -class JsonProjectPage : public Utils::ProjectIntroPage +class PROJECTEXPLORER_EXPORT JsonProjectPage : public Utils::ProjectIntroPage { Q_OBJECT diff --git a/src/plugins/qmldesigner/components/componentcore/svgpasteaction.cpp b/src/plugins/qmldesigner/components/componentcore/svgpasteaction.cpp index dac06f73e69..7c29fcd6429 100644 --- a/src/plugins/qmldesigner/components/componentcore/svgpasteaction.cpp +++ b/src/plugins/qmldesigner/components/componentcore/svgpasteaction.cpp @@ -603,7 +603,7 @@ double round(double value, int decimal_places) { } static const std::initializer_list tagAllowList{ - u"path", u"rect", u"polygon", u"circle", u"ellipse" + u"path", u"rect", u"line", u"polygon", u"polyline", u"circle", u"ellipse" }; // fillOpacity and strokeOpacity aren't actual QML properties, but get mapped anyways @@ -840,6 +840,8 @@ QVariant convertValue(const QByteArray &key, const QString &value) return value.toInt(); } else if (key == "opacity") { return value.toFloat(); + } else if ((key == "fillColor" || key == "strokeColor") && value == "none") { + return "transparent"; } return value; @@ -1015,6 +1017,28 @@ PropertyMap generateRectProperties(const QDomElement &e, const CSSRules &cssRule return properties; } +PropertyMap generateLineProperties(const QDomElement &e, const CSSRules &cssRules) +{ + QLineF line(e.attribute("x1").toFloat(), + e.attribute("y1").toFloat(), + e.attribute("x2").toFloat(), + e.attribute("y2").toFloat()); + + QPainterPath path(line.p1()); + path.lineTo(line.p2()); + + PropertyMap properties; + QTransform transform; + flattenTransformsAndStyles(e, cssRules, transform, properties); + + path = transform.map(path); + + if (!applyMinimumBoundingBox(path, properties)) + return {}; + + return properties; +} + PropertyMap generateEllipseProperties(const QDomElement &e, const CSSRules &cssRules) { const QPointF center(e.attribute("cx").toFloat(), e.attribute("cy").toFloat()); @@ -1085,7 +1109,7 @@ PropertyMap generatePolygonProperties(const QDomElement &e, const CSSRules &cssR for (int i = 0; i < pointList.length(); i += 2) polygon.push_back({pointList[i].toFloat(), pointList[i + 1].toFloat()}); - if (!polygon.isClosed() && polygon.size()) + if (e.tagName() != "polyline" && !polygon.isClosed() && polygon.size()) polygon.push_back(polygon.front()); QPainterPath path; @@ -1178,6 +1202,11 @@ QmlObjectNode SVGPasteAction::createQmlObjectNode(QmlDesigner::ModelNode &target round(tmp[2].toFloat(), 2), round(tmp[3].toFloat(), 2)); } + + viewBoxProperties.insert("clip", true); + } else { + viewBox.setWidth(round(rootElement.attribute("width").toFloat(), 2)); + viewBox.setHeight(round(rootElement.attribute("height").toFloat(), 2)); } viewBoxProperties.insert("x", viewBox.x()); @@ -1202,8 +1231,6 @@ QmlObjectNode SVGPasteAction::createQmlObjectNode(QmlDesigner::ModelNode &target depthFirstTraversal(node, processStyleAndCollectShapes); - viewBoxProperties.insert("clip", true); - ModelNode groupNode = createGroupNode(targetNode, viewBoxProperties); for (const QDomElement &e : shapeElements) { @@ -1213,7 +1240,9 @@ QmlObjectNode SVGPasteAction::createQmlObjectNode(QmlDesigner::ModelNode &target pathProperties = generatePathProperties(e, cssRules); else if (e.tagName() == "rect") pathProperties = generateRectProperties(e, cssRules); - else if (e.tagName() == "polygon") + else if (e.tagName() == "line") + pathProperties = generateLineProperties(e, cssRules); + else if (e.tagName() == "polygon" || e.tagName() == "polyline") pathProperties = generatePolygonProperties(e, cssRules); else if (e.tagName() == "circle" || e.tagName() == "ellipse") pathProperties = generateEllipseProperties(e, cssRules); diff --git a/src/plugins/qmldesigner/components/componentcore/theme.h b/src/plugins/qmldesigner/components/componentcore/theme.h index 2ef1f82cbb6..159824b1f77 100644 --- a/src/plugins/qmldesigner/components/componentcore/theme.h +++ b/src/plugins/qmldesigner/components/componentcore/theme.h @@ -103,8 +103,10 @@ public: distributeSpacingHorizontal, distributeSpacingVertical, distributeTop, + download, edit, eyeDropper, + favorite, flowAction, flowTransition, fontStyleBold, @@ -155,6 +157,12 @@ public: textFullJustification, textNumberedList, tickIcon, + translationCreateFiles, + translationCreateReport, + translationExport, + translationImport, + translationSelectLanguages, + translationTest, transparent, triState, triangleArcA, diff --git a/src/plugins/qmldesigner/components/formeditor/resizeindicator.cpp b/src/plugins/qmldesigner/components/formeditor/resizeindicator.cpp index 4a2426f29f6..ad5f2feffff 100644 --- a/src/plugins/qmldesigner/components/formeditor/resizeindicator.cpp +++ b/src/plugins/qmldesigner/components/formeditor/resizeindicator.cpp @@ -63,7 +63,7 @@ static bool itemIsResizable(const QmlItemNode &qmlItemNode) && qmlItemNode.instanceIsResizable() && qmlItemNode.modelIsMovable() && qmlItemNode.modelIsResizable() - && !qmlItemNode.instanceHasRotationTransform() + && !qmlItemNode.instanceHasScaleOrRotationTransform() && !qmlItemNode.instanceIsInLayoutable(); } diff --git a/src/plugins/qmldesigner/components/integration/designdocument.cpp b/src/plugins/qmldesigner/components/integration/designdocument.cpp index 0c0268f4ca5..a5d904c495a 100644 --- a/src/plugins/qmldesigner/components/integration/designdocument.cpp +++ b/src/plugins/qmldesigner/components/integration/designdocument.cpp @@ -311,6 +311,7 @@ void DesignDocument::changeToDocumentModel() viewManager().detachViewsExceptRewriterAndComponetView(); m_inFileComponentModel.reset(); + m_inFileComponentTextModifier.reset(); viewManager().attachRewriterView(); viewManager().attachViewsExceptRewriterAndComponetView(); diff --git a/src/plugins/qmldesigner/components/itemlibrary/images/browse.png b/src/plugins/qmldesigner/components/itemlibrary/images/browse.png new file mode 100644 index 00000000000..003b9e5d81b Binary files /dev/null and b/src/plugins/qmldesigner/components/itemlibrary/images/browse.png differ diff --git a/src/plugins/qmldesigner/components/itemlibrary/images/browse@2x.png b/src/plugins/qmldesigner/components/itemlibrary/images/browse@2x.png new file mode 100644 index 00000000000..b13400f788e Binary files /dev/null and b/src/plugins/qmldesigner/components/itemlibrary/images/browse@2x.png differ diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrary.qrc b/src/plugins/qmldesigner/components/itemlibrary/itemlibrary.qrc index 197dd87655b..b1777bbbec6 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrary.qrc +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrary.qrc @@ -31,5 +31,7 @@ images/asset_sound_384.png images/x.png images/x@2x.png + images/browse.png + images/browse@2x.png diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsiconprovider.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsiconprovider.cpp index deb8312b8f2..ece6e8abaab 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsiconprovider.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsiconprovider.cpp @@ -42,16 +42,18 @@ QPixmap ItemLibraryAssetsIconProvider::requestPixmap(const QString &id, QSize *s { QPixmap pixmap; const QString suffix = "*." + id.split('.').last().toLower(); - if (ItemLibraryAssetsModel::supportedFontSuffixes().contains(suffix)) + if (id == "browse") + pixmap = Utils::StyleHelper::dpiSpecificImageFile(":/ItemLibrary/images/browse.png"); + else if (ItemLibraryAssetsModel::supportedFontSuffixes().contains(suffix)) pixmap = generateFontIcons(id); else if (ItemLibraryAssetsModel::supportedImageSuffixes().contains(suffix)) pixmap = Utils::StyleHelper::dpiSpecificImageFile(id); else if (ItemLibraryAssetsModel::supportedTexture3DSuffixes().contains(suffix)) pixmap = HdrImage{id}.toPixmap(); else if (ItemLibraryAssetsModel::supportedShaderSuffixes().contains(suffix)) - pixmap = QPixmap(Utils::StyleHelper::dpiSpecificImageFile(":/ItemLibrary/images/asset_shader_48.png")); + pixmap = Utils::StyleHelper::dpiSpecificImageFile(":/ItemLibrary/images/asset_shader_48.png"); else if (ItemLibraryAssetsModel::supportedAudioSuffixes().contains(suffix)) - pixmap = QPixmap(Utils::StyleHelper::dpiSpecificImageFile(":/ItemLibrary/images/asset_sound_48.png")); + pixmap = Utils::StyleHelper::dpiSpecificImageFile(":/ItemLibrary/images/asset_sound_48.png"); if (size) { size->setWidth(pixmap.width()); diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsmodel.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsmodel.cpp index db3ea452d2b..caaaa374528 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsmodel.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsmodel.cpp @@ -275,7 +275,8 @@ void ItemLibraryAssetsModel::setRootPath(const QString &path) beginResetModel(); m_assetsDir = new ItemLibraryAssetsDir(path, 0, true, this); - parseDirRecursive(m_assetsDir, 1); + bool noAssets = parseDirRecursive(m_assetsDir, 1); + setIsEmpty(noAssets); endResetModel(); } @@ -304,6 +305,19 @@ const QSet &ItemLibraryAssetsModel::supportedSuffixes() const return allSuffixes; } +bool ItemLibraryAssetsModel::isEmpty() const +{ + return m_isEmpty; +}; + +void ItemLibraryAssetsModel::setIsEmpty(bool empty) +{ + if (m_isEmpty != empty) { + m_isEmpty = empty; + emit isEmptyChanged(); + } +}; + const QSet &ItemLibraryAssetsModel::previewableSuffixes() const { static QSet previewableSuffixes; diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsmodel.h b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsmodel.h index 294a760a6b5..c1dcc845da0 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsmodel.h +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsmodel.h @@ -46,13 +46,15 @@ class ItemLibraryAssetsModel : public QAbstractListModel { Q_OBJECT + Q_PROPERTY(bool isEmpty READ isEmpty WRITE setIsEmpty NOTIFY isEmptyChanged) + public: ItemLibraryAssetsModel(QmlDesigner::SynchronousImageCache &fontImageCache, Utils::FileSystemWatcher *fileSystemWatcher, QObject *parent = nullptr); - QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override; - int rowCount(const QModelIndex & parent = QModelIndex()) const override; + 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(); @@ -82,15 +84,22 @@ public: Q_INVOKABLE DirExpandState getAllExpandedState() const; Q_INVOKABLE void removeFile(const QString &filePath); +signals: + void isEmptyChanged(); + private: const QSet &supportedSuffixes() const; + bool isEmpty() const; + void setIsEmpty(bool empty); + SynchronousImageCache &m_fontImageCache; QHash> m_iconCache; QString m_searchText; Utils::FileSystemWatcher *m_fileSystemWatcher = nullptr; ItemLibraryAssetsDir *m_assetsDir = nullptr; + bool m_isEmpty = true; QHash m_roleNames; inline static QHash m_expandedStateHash; // diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp index 95f1fc3faa5..151be411ad1 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp @@ -118,6 +118,8 @@ void ItemLibraryView::modelAttached(Model *model) m_widget->clearSearchFilter(); m_widget->setModel(model); updateImports(); + if (model) + m_widget->updatePossibleImports(model->possibleImports()); m_hasErrors = !rewriterView()->errors().isEmpty(); m_widget->setFlowMode(QmlItemNode(rootModelNode()).isFlowView()); setResourcePath(DocumentManager::currentResourcePath().toFileInfo().absoluteFilePath()); diff --git a/src/plugins/qmldesigner/components/navigator/choosefrompropertylistdialog.cpp b/src/plugins/qmldesigner/components/navigator/choosefrompropertylistdialog.cpp index 598f2ade758..b7d9fb6b9e9 100644 --- a/src/plugins/qmldesigner/components/navigator/choosefrompropertylistdialog.cpp +++ b/src/plugins/qmldesigner/components/navigator/choosefrompropertylistdialog.cpp @@ -29,12 +29,12 @@ namespace QmlDesigner { -// This dialog displays all given type properties of an object and allows the user to choose one -ChooseFromPropertyListDialog::ChooseFromPropertyListDialog(const ModelNode &node, TypeName type, QWidget *parent) +// This dialog displays specified properties and allows the user to choose one +ChooseFromPropertyListDialog::ChooseFromPropertyListDialog(const QStringList &propNames, + QWidget *parent) : QDialog(parent) , m_ui(new Ui::ChooseFromPropertyListDialog) { - m_propertyTypeName = type; m_ui->setupUi(this); setWindowTitle(tr("Select property")); m_ui->label->setText(tr("Bind to property:")); @@ -50,7 +50,7 @@ ChooseFromPropertyListDialog::ChooseFromPropertyListDialog(const ModelNode &node QDialog::accept(); }); - fillList(node); + fillList(propNames); } ChooseFromPropertyListDialog::~ChooseFromPropertyListDialog() @@ -63,31 +63,80 @@ TypeName ChooseFromPropertyListDialog::selectedProperty() const return m_selectedProperty; } -void ChooseFromPropertyListDialog::fillList(const ModelNode &node) +// Create dialog for selecting any property matching newNode type +// Subclass type matches are also valid +ChooseFromPropertyListDialog *ChooseFromPropertyListDialog::createIfNeeded( + const ModelNode &targetNode, const ModelNode &newNode, QWidget *parent) { - // Fill the list with all properties of given type - const auto metaInfo = node.metaInfo(); - const auto propNames = metaInfo.propertyNames(); - const TypeName property(m_propertyTypeName); - QStringList nameList; + TypeName typeName = newNode.type(); + + // Component matches cases where you don't want to insert a plain component, + // such as layer.effect. Also, default property is often a Component (typically 'delegate'), + // and inserting into such property will silently overwrite implicit component, if any. + if (typeName == "QtQml.Component") + return nullptr; + + const NodeMetaInfo metaInfo = targetNode.metaInfo(); + const PropertyNameList propNames = metaInfo.propertyNames(); + QStringList matchingNames; + + // Common base types cause too many rarely valid matches, so they are ignored + const QSet ignoredTypes {".QObject", + ".QQuickItem", + "QtQuick.Item", + "QtQuick3D.Object3D", + "QtQuick3D.Node"}; + for (const auto &propName : propNames) { - if (metaInfo.propertyTypeName(propName) == property) - nameList.append(QString::fromLatin1(propName)); - } - - if (!nameList.isEmpty()) { - QString defaultProp = nameList.first(); - - nameList.sort(); - for (const auto &propName : qAsConst(nameList)) { - QListWidgetItem *newItem = new QListWidgetItem(propName); - m_ui->listProps->addItem(newItem); + const TypeName testType = metaInfo.propertyTypeName(propName); + if (!ignoredTypes.contains(testType) + && metaInfo.propertyIsWritable(propName) + && (testType == typeName || newNode.isSubclassOf(testType))) { + matchingNames.append(QString::fromLatin1(propName)); } - - // Select the default prop - m_ui->listProps->setCurrentRow(nameList.indexOf(defaultProp)); - m_selectedProperty = defaultProp.toLatin1(); } + + if (!matchingNames.isEmpty()) + return new ChooseFromPropertyListDialog(matchingNames, parent); + + return nullptr; +} + +// Create dialog for selecting writable properties of exact property type +ChooseFromPropertyListDialog *ChooseFromPropertyListDialog::createIfNeeded( + const ModelNode &targetNode, TypeName type, QWidget *parent) +{ + const NodeMetaInfo metaInfo = targetNode.metaInfo(); + const PropertyNameList propNames = metaInfo.propertyNames(); + const TypeName property(type); + QStringList matchingNames; + for (const auto &propName : propNames) { + if (metaInfo.propertyTypeName(propName) == property && metaInfo.propertyIsWritable(propName)) + matchingNames.append(QString::fromLatin1(propName)); + } + + if (!matchingNames.isEmpty()) + return new ChooseFromPropertyListDialog(matchingNames, parent); + + return nullptr; +} + +void ChooseFromPropertyListDialog::fillList(const QStringList &propNames) +{ + if (propNames.isEmpty()) + return; + + QString defaultProp = propNames.first(); + QStringList sortedNames = propNames; + sortedNames.sort(); + for (const auto &propName : qAsConst(sortedNames)) { + QListWidgetItem *newItem = new QListWidgetItem(propName); + m_ui->listProps->addItem(newItem); + } + + // Select the default prop + m_ui->listProps->setCurrentRow(sortedNames.indexOf(defaultProp)); + m_selectedProperty = defaultProp.toLatin1(); } } diff --git a/src/plugins/qmldesigner/components/navigator/choosefrompropertylistdialog.h b/src/plugins/qmldesigner/components/navigator/choosefrompropertylistdialog.h index 2d2612e0a8a..4cc59242f19 100644 --- a/src/plugins/qmldesigner/components/navigator/choosefrompropertylistdialog.h +++ b/src/plugins/qmldesigner/components/navigator/choosefrompropertylistdialog.h @@ -40,16 +40,22 @@ class ChooseFromPropertyListDialog : public QDialog Q_OBJECT public: - explicit ChooseFromPropertyListDialog(const ModelNode &node, TypeName type, QWidget *parent = 0); ~ChooseFromPropertyListDialog(); TypeName selectedProperty() const; + static ChooseFromPropertyListDialog *createIfNeeded(const ModelNode &targetNode, + const ModelNode &newNode, + QWidget *parent = 0); + static ChooseFromPropertyListDialog *createIfNeeded(const ModelNode &targetNode, + TypeName type, + QWidget *parent = 0); + private: - void fillList(const ModelNode &node); + explicit ChooseFromPropertyListDialog(const QStringList &propNames, QWidget *parent = 0); + void fillList(const QStringList &propNames); Ui::ChooseFromPropertyListDialog *m_ui; TypeName m_selectedProperty; - TypeName m_propertyTypeName; }; } diff --git a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp index cb98389b171..0eed7dec4af 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp @@ -739,25 +739,13 @@ void NavigatorTreeModel::handleItemLibraryItemDrop(const QMimeData *mimeData, in } } else { ModelNode targetNode = targetProperty.parentModelNode(); - NodeMetaInfo metaInfo = targetNode.metaInfo(); - TypeName typeName = newModelNode.type(); - - // Empty components are not supported and having one as property value is generally - // unstable, so let's not offer user to put a fresh Component into a property - if (typeName != "QtQml.Component") { - const PropertyNameList nameList = targetNode.metaInfo().directPropertyNames(); - for (const auto &propertyName : nameList) { - auto testType = metaInfo.propertyTypeName(propertyName); - if (testType == typeName || newModelNode.isSubclassOf(testType)) { - ChooseFromPropertyListDialog *dialog = nullptr; - dialog = new ChooseFromPropertyListDialog(targetNode, testType, Core::ICore::dialogParent()); - dialog->exec(); - if (dialog->result() == QDialog::Accepted) - targetNode.bindingProperty(dialog->selectedProperty()).setExpression(newModelNode.validId()); - delete dialog; - break; - } - } + ChooseFromPropertyListDialog *dialog = ChooseFromPropertyListDialog::createIfNeeded( + targetNode, newModelNode, Core::ICore::dialogParent()); + if (dialog) { + dialog->exec(); + if (dialog->result() == QDialog::Accepted) + targetNode.bindingProperty(dialog->selectedProperty()).setExpression(newModelNode.validId()); + delete dialog; } } @@ -1015,10 +1003,13 @@ bool NavigatorTreeModel::dropAsImage3dTexture(const ModelNode &targetNode, if (targetNode.isSubclassOf("QtQuick3D.Material")) { // if dropping an image on a default material, create a texture instead of image ChooseFromPropertyListDialog *dialog = nullptr; - if (targetNode.isSubclassOf("QtQuick3D.DefaultMaterial") || targetNode.isSubclassOf("QtQuick3D.PrincipledMaterial")) { + if (targetNode.isSubclassOf("QtQuick3D.DefaultMaterial") + || targetNode.isSubclassOf("QtQuick3D.PrincipledMaterial")) { // Show texture property selection dialog - dialog = new ChooseFromPropertyListDialog(targetNode, "QtQuick3D.Texture", Core::ICore::dialogParent()); - dialog->exec(); + dialog = ChooseFromPropertyListDialog::createIfNeeded(targetNode, "QtQuick3D.Texture", + Core::ICore::dialogParent()); + if (dialog) + dialog->exec(); } if (!dialog || dialog->result() == QDialog::Accepted) { m_view->executeInTransaction("NavigatorTreeModel::dropAsImage3dTexture", [&] { diff --git a/src/plugins/qmldesigner/designercore/include/qmlitemnode.h b/src/plugins/qmldesigner/designercore/include/qmlitemnode.h index 9be07edfbfa..3a3498e6c9a 100644 --- a/src/plugins/qmldesigner/designercore/include/qmlitemnode.h +++ b/src/plugins/qmldesigner/designercore/include/qmlitemnode.h @@ -99,7 +99,7 @@ public: bool instanceIsMovable() const; bool instanceIsResizable() const; bool instanceIsInLayoutable() const; - bool instanceHasRotationTransform() const; + bool instanceHasScaleOrRotationTransform() const; bool modelIsMovable() const; bool modelIsResizable() const; diff --git a/src/plugins/qmldesigner/designercore/model/componenttextmodifier.cpp b/src/plugins/qmldesigner/designercore/model/componenttextmodifier.cpp index f9255225058..6aca50c4334 100644 --- a/src/plugins/qmldesigner/designercore/model/componenttextmodifier.cpp +++ b/src/plugins/qmldesigner/designercore/model/componenttextmodifier.cpp @@ -111,7 +111,8 @@ void ComponentTextModifier::commitGroup() { m_originalModifier->commitGroup(); - int textLength = m_originalModifier->text().length(); + m_originalText = m_originalModifier->text(); + int textLength = m_originalText.length(); m_componentEndOffset += (textLength - m_startLength); m_startLength = textLength; } @@ -157,40 +158,42 @@ void ComponentTextModifier::handleOriginalTextChanged() { // Update offsets when original text changes, if necessary - // Detect and adjust for removal/addition of unrelated text before the subcomponent code, - // as that can happen even without user editing the text (e.g. whitespace removal at save time) - const QString currentText = m_originalModifier->text(); - if (m_originalText.left(m_componentStartOffset) != currentText.left(m_componentStartOffset)) { - // Subcomponent item id is the only reliable indicator for adjustment - const int idIndex = m_originalText.indexOf("id:", m_componentStartOffset); - if (idIndex != -1 && idIndex < m_componentEndOffset) { - int newLineIndex = m_originalText.indexOf('\n', idIndex); - if (newLineIndex != -1) { - const QString checkLine = m_originalText.mid(idIndex, newLineIndex - idIndex); - int lineIndex = currentText.indexOf(checkLine); - if (lineIndex != -1) { - // Paranoia check - This shouldn't happen except when modifying text manually, - // but it's possible something was inserted between id and start - // of the component, which would throw off the calculation, so check that - // the first line is still correct even with new offset. - const int diff = idIndex - lineIndex; - newLineIndex = m_originalText.indexOf('\n', m_componentStartOffset); - if (newLineIndex != -1) { - const QString firstLine = m_originalText.mid(m_componentStartOffset, - newLineIndex - m_componentStartOffset); - const int newStart = m_componentStartOffset - diff; - if (firstLine == currentText.mid(newStart, firstLine.size())) { - m_componentEndOffset -= diff; - m_componentStartOffset = newStart; - m_originalText = currentText; - } - } - } - } + // Adjust for removal/addition of whitespace in the document + // Check that non-whitespace portion of the text is the same and count the whitespace diff + const int oldLen = m_originalText.size(); + const int newLen = currentText.size(); + int newSpace = 0; + int oldSpace = 0; + int newIdx = 0; + for (int oldIdx = 0; oldIdx < oldLen; ++oldIdx) { + const QChar oldChar = m_originalText[oldIdx]; + if (oldIdx == m_componentStartOffset) + m_componentStartOffset += newSpace - oldSpace; + if (oldIdx == m_componentEndOffset) { + m_componentEndOffset += newSpace - oldSpace; + break; + } + + while (newIdx < newLen && currentText[newIdx].isSpace()) { + ++newSpace; + ++newIdx; + } + + if (oldChar.isSpace()) { + ++oldSpace; + continue; + } + + if (currentText[newIdx] != oldChar) { + // Non-whitespace difference, we can't determine a valid offset in this case + // TODO: Needs proper handling to deal with undo/redo/arbitrary edits somehow (QDS-5392) + break; + } else { + ++newIdx; } } - + m_originalText = currentText; emit textChanged(); } diff --git a/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp b/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp index c65f281f64a..bc5089f5305 100644 --- a/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp +++ b/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp @@ -303,9 +303,9 @@ bool QmlItemNode::instanceIsInLayoutable() const return nodeInstance().isInLayoutable(); } -bool QmlItemNode::instanceHasRotationTransform() const +bool QmlItemNode::instanceHasScaleOrRotationTransform() const { - return nodeInstance().transform().type() > QTransform::TxScale; + return nodeInstance().transform().type() > QTransform::TxTranslate; } bool itemIsMovable(const ModelNode &modelNode) diff --git a/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp b/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp index 04ac06ce392..bcdd87b749a 100644 --- a/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp +++ b/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp @@ -217,14 +217,13 @@ SwitchLanguageComboboxAction::SwitchLanguageComboboxAction(QObject *parent) QWidget *SwitchLanguageComboboxAction::createWidget(QWidget *parent) { QPointer comboBox = new QComboBox(parent); - // FIXME: this combobox does not work at the moment - comboBox->setDisabled(true); const QString toolTip(tr("Switch the language used by preview.")); comboBox->setToolTip(toolTip); comboBox->addItem(tr("Default")); auto refreshComboBoxFunction = [this, comboBox, toolTip] (ProjectExplorer::Project *project) { if (comboBox && project) { + comboBox->setDisabled(true); QString errorMessage; auto locales = project->availableQmlPreviewTranslations(&errorMessage); if (!errorMessage.isEmpty()) @@ -234,6 +233,7 @@ QWidget *SwitchLanguageComboboxAction::createWidget(QWidget *parent) comboBox->addItem(tr("Default")); comboBox->addItems(locales); m_previousLocales = locales; + comboBox->setEnabled(true); } } }; diff --git a/src/plugins/qmljseditor/qmljseditor.cpp b/src/plugins/qmljseditor/qmljseditor.cpp index a3c0d8ee2e3..265b59ca1a7 100644 --- a/src/plugins/qmljseditor/qmljseditor.cpp +++ b/src/plugins/qmljseditor/qmljseditor.cpp @@ -131,8 +131,6 @@ void QmlJSEditorWidget::finalizeInitialization() connect(&m_updateOutlineIndexTimer, &QTimer::timeout, this, &QmlJSEditorWidget::updateOutlineIndexNow); - textDocument()->setCodec(QTextCodec::codecForName("UTF-8")); // qml files are defined to be utf-8 - m_modelManager = ModelManagerInterface::instance(); m_contextPane = Internal::QmlJSEditorPlugin::quickToolBar(); diff --git a/src/plugins/qmljseditor/qmljseditordocument.cpp b/src/plugins/qmljseditor/qmljseditordocument.cpp index 59270b2ae0d..13d29e72ccc 100644 --- a/src/plugins/qmljseditor/qmljseditordocument.cpp +++ b/src/plugins/qmljseditor/qmljseditordocument.cpp @@ -45,6 +45,7 @@ #include #include +#include const char QML_UI_FILE_WARNING[] = "QmlJSEditor.QmlUiFileWarning"; @@ -655,9 +656,15 @@ QmlJSEditorDocument::QmlJSEditorDocument(Utils::Id id) connect(this, &TextEditor::TextDocument::tabSettingsChanged, d, &Internal::QmlJSEditorDocumentPrivate::invalidateFormatterCache); setSyntaxHighlighter(new QmlJSHighlighter(document())); + setCodec(QTextCodec::codecForName("UTF-8")); // qml files are defined to be utf-8 setIndenter(new Internal::Indenter(document())); } +bool QmlJSEditorDocument::supportsCodec(const QTextCodec *codec) const +{ + return codec == QTextCodec::codecForName("UTF-8"); +} + QmlJSEditorDocument::~QmlJSEditorDocument() { delete d; diff --git a/src/plugins/qmljseditor/qmljseditordocument.h b/src/plugins/qmljseditor/qmljseditordocument.h index a42b367885d..1c73ed1e32e 100644 --- a/src/plugins/qmljseditor/qmljseditordocument.h +++ b/src/plugins/qmljseditor/qmljseditordocument.h @@ -46,6 +46,8 @@ public: QmlJSEditorDocument(Utils::Id id); ~QmlJSEditorDocument() override; + bool supportsCodec(const QTextCodec *codec) const override; + const QmlJSTools::SemanticInfo &semanticInfo() const; bool isSemanticInfoOutdated() const; QVector diagnosticRanges() const; diff --git a/src/plugins/qtsupport/qtoptionspage.cpp b/src/plugins/qtsupport/qtoptionspage.cpp index 0a8d55818a4..b026363b34a 100644 --- a/src/plugins/qtsupport/qtoptionspage.cpp +++ b/src/plugins/qtsupport/qtoptionspage.cpp @@ -177,7 +177,7 @@ public: QtOptionsPageWidget(); ~QtOptionsPageWidget(); - static bool linkWithQt(); + static void linkWithQt(); private: void apply() final; @@ -857,16 +857,7 @@ void QtOptionsPageWidget::setupLinkWithQtButton() QString tip; canLinkWithQt(&tip); m_ui.linkWithQtButton->setToolTip(tip); - connect(m_ui.linkWithQtButton, &QPushButton::clicked, this, [this] { - if (linkWithQt()) { - QWidget *w = window(); - // close options dialog - if (QDialog *dialog = qobject_cast(w)) - dialog->accept(); - else - window()->close(); - } - }); + connect(m_ui.linkWithQtButton, &QPushButton::clicked, this, &QtOptionsPage::linkWithQt); } void QtOptionsPageWidget::updateCurrentQtName() @@ -959,7 +950,7 @@ static FilePath defaultQtInstallationPath() return FileUtils::homePath() / "Qt"; } -bool QtOptionsPageWidget::linkWithQt() +void QtOptionsPageWidget::linkWithQt() { const QString title = tr("Choose Qt Installation"); const QString restartText = tr("The change will take effect after restart."); @@ -1028,9 +1019,8 @@ bool QtOptionsPageWidget::linkWithQt() } if (askForRestart) { Core::RestartDialog restartDialog(Core::ICore::dialogParent(), restartText); - return restartDialog.exec() == QDialog::Accepted; + restartDialog.exec(); } - return false; } // QtOptionsPage diff --git a/src/plugins/studiowelcome/CMakeLists.txt b/src/plugins/studiowelcome/CMakeLists.txt index 2ae3bdcc9a0..e1f899d2fd4 100644 --- a/src/plugins/studiowelcome/CMakeLists.txt +++ b/src/plugins/studiowelcome/CMakeLists.txt @@ -1,12 +1,20 @@ add_qtc_plugin(StudioWelcome - CONDITION TARGET Qt5::QuickWidgets + CONDITION TARGET Qt5::QuickWidgets AND TARGET QmlDesigner DEPENDS Qt5::QuickWidgets - PLUGIN_DEPENDS Core ProjectExplorer QtSupport + PLUGIN_DEPENDS Core ProjectExplorer QtSupport QmlDesigner DEFINES STUDIO_QML_PATH="${CMAKE_CURRENT_SOURCE_DIR}/qml/" SOURCES studiowelcomeplugin.cpp studiowelcomeplugin.h + newprojectdialogimageprovider.cpp newprojectdialogimageprovider.h + newprojectmodel.cpp newprojectmodel.h examplecheckout.cpp examplecheckout.h studiowelcome_global.h + qdsnewdialog.cpp qdsnewdialog.h + wizardfactories.cpp wizardfactories.h + createproject.cpp createproject.h + wizardhandler.cpp wizardhandler.h + screensizemodel.h + stylemodel.h stylemodel.cpp studiowelcome.qrc "${PROJECT_SOURCE_DIR}/src/share/3rdparty/studiofonts/studiofonts.qrc" EXTRA_TRANSLATIONS diff --git a/src/plugins/studiowelcome/createproject.cpp b/src/plugins/studiowelcome/createproject.cpp new file mode 100644 index 00000000000..1b6dd93e9d7 --- /dev/null +++ b/src/plugins/studiowelcome/createproject.cpp @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "createproject.h" + +#include + +#include +#include + +using namespace StudioWelcome; + +void CreateProject::execute() +{ + m_wizard.run([&](QWizardPage *page) { + if (auto *p = dynamic_cast(page)) + processProjectPage(p); + else if (auto *p = dynamic_cast(page)) + processFieldPage(p); + }); +} + +void CreateProject::processProjectPage(ProjectExplorer::JsonProjectPage *page) +{ + page->setProjectName(m_projectName); + page->setFilePath(m_projectLocation); + + page->setUseAsDefaultPath(m_saveAsDefaultLocation); + page->fieldsUpdated(); +} + +void CreateProject::processFieldPage(ProjectExplorer::JsonFieldPage *page) +{ + if (page->jsonField("ScreenFactor")) + m_wizard.setScreenSizeIndex(m_screenSizeIndex); + + if (page->jsonField("TargetQtVersion") && m_targetQtVersionIndex > -1) + m_wizard.setTargetQtVersionIndex(m_targetQtVersionIndex); + + if (page->jsonField("ControlsStyle")) + m_wizard.setStyleIndex(m_styleIndex); + + if (page->jsonField("UseVirtualKeyboard")) + m_wizard.setUseVirtualKeyboard(m_useVirtualKeyboard); + + auto widthField = dynamic_cast(page->jsonField("CustomScreenWidth")); + auto heightField = dynamic_cast(page->jsonField("CustomScreenHeight")); + + if (widthField && heightField) { + if (!m_customWidth.isEmpty() && !m_customHeight.isEmpty()) { + widthField->setText(m_customWidth); + heightField->setText(m_customHeight); + } + } +} diff --git a/src/plugins/studiowelcome/createproject.h b/src/plugins/studiowelcome/createproject.h new file mode 100644 index 00000000000..9709bbee30e --- /dev/null +++ b/src/plugins/studiowelcome/createproject.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "wizardhandler.h" + +namespace ProjectExplorer { +class JsonProjectPage; +} + +namespace StudioWelcome { + +class CreateProject +{ +public: + CreateProject(WizardHandler &wizard): m_wizard{wizard} {} + + CreateProject &withName(const QString &name) { m_projectName = name; return *this; } + CreateProject &atLocation(const Utils::FilePath &location) { m_projectLocation = location; return *this; } + CreateProject &withScreenSizes(int screenSizeIndex, const QString &customWidth, const QString &customHeight) + { + m_screenSizeIndex = screenSizeIndex; + m_customWidth = customWidth; + m_customHeight = customHeight; + return *this; + } + + CreateProject &withStyle(int styleIndex) { m_styleIndex = styleIndex; return *this; } + CreateProject &useQtVirtualKeyboard(bool value) { m_useVirtualKeyboard = value; return *this; } + CreateProject &saveAsDefaultLocation(bool value) { m_saveAsDefaultLocation = value; return *this; } + CreateProject &withTargetQtVersion(int targetQtVersionIndex) + { m_targetQtVersionIndex = targetQtVersionIndex; return *this; } + + void execute(); + +private: + void processProjectPage(ProjectExplorer::JsonProjectPage *page); + void processFieldPage(ProjectExplorer::JsonFieldPage *page); + +private: + WizardHandler &m_wizard; + + QString m_projectName; + Utils::FilePath m_projectLocation; + int m_screenSizeIndex = -1; + QString m_customWidth; + QString m_customHeight; + int m_styleIndex = -1; + bool m_useVirtualKeyboard = false; + bool m_saveAsDefaultLocation = false; + int m_targetQtVersionIndex = -1; +}; + +} // StudioWelcome diff --git a/src/plugins/studiowelcome/newprojectdialogimageprovider.cpp b/src/plugins/studiowelcome/newprojectdialogimageprovider.cpp new file mode 100644 index 00000000000..2c7e454f5b0 --- /dev/null +++ b/src/plugins/studiowelcome/newprojectdialogimageprovider.cpp @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "newprojectdialogimageprovider.h" + +#include +#include +#include + +namespace StudioWelcome { + +namespace Internal { + +NewProjectDialogImageProvider::NewProjectDialogImageProvider() + : QQuickImageProvider(QQuickImageProvider::Pixmap) +{} + +QPixmap NewProjectDialogImageProvider::invalidStyleIcon() +{ + QString iconPath = Core::ICore::resourcePath("qmldesigner/newprojectdialog/image/style-error.png").toString(); + QString file = Utils::StyleHelper::dpiSpecificImageFile(iconPath); + return QPixmap{file}; +} + +QPixmap NewProjectDialogImageProvider::requestStatusPixmap(const QString &id, QSize *size, const QSize &requestedSize) +{ + QPixmap pixmap; + + if (id == "status-warning") { + static const QPixmap warning = Utils::Icons::WARNING.pixmap(); + pixmap = warning; + } else if (id == "status-error") { + static const QPixmap error = Utils::Icons::CRITICAL.pixmap(); + pixmap = error; + } + + if (requestedSize.isValid()) + return pixmap.scaled(requestedSize); + + return pixmap; +} + +QPixmap NewProjectDialogImageProvider::requestStylePixmap(const QString &id, QSize *size, const QSize &requestedSize) +{ + QString realPath = Core::ICore::resourcePath("qmldesigner/newprojectdialog/image/" + id).toString(); + + QPixmap pixmap{realPath}; + + if (size) { + size->setWidth(pixmap.width()); + size->setHeight(pixmap.height()); + } + + if (pixmap.isNull()) + pixmap = invalidStyleIcon(); + + if (requestedSize.isValid()) + return pixmap.scaled(requestedSize); + + return pixmap; +} + +QPixmap NewProjectDialogImageProvider::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) +{ + if (id.startsWith("style-")) + return requestStylePixmap(id, size, requestedSize); + + if (id.startsWith("status-")) + return requestStatusPixmap(id, size, requestedSize); + + return QPixmap{}; +} + +} // namespace Internal + +} // namespace StudioWelcome + diff --git a/src/plugins/studiowelcome/newprojectdialogimageprovider.h b/src/plugins/studiowelcome/newprojectdialogimageprovider.h new file mode 100644 index 00000000000..f149d082c21 --- /dev/null +++ b/src/plugins/studiowelcome/newprojectdialogimageprovider.h @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +namespace StudioWelcome { + +namespace Internal { + +class NewProjectDialogImageProvider : public QQuickImageProvider +{ +public: + NewProjectDialogImageProvider(); + + QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override; + +private: + QPixmap requestStatusPixmap(const QString &id, QSize *size, const QSize &requestedSize); + QPixmap requestStylePixmap(const QString &id, QSize *size, const QSize &requestedSize); + + static QPixmap invalidStyleIcon(); +}; + +} // namespace Internal + +} // namespace StudioWelcome diff --git a/src/plugins/studiowelcome/newprojectmodel.cpp b/src/plugins/studiowelcome/newprojectmodel.cpp new file mode 100644 index 00000000000..07589a4c041 --- /dev/null +++ b/src/plugins/studiowelcome/newprojectmodel.cpp @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "newprojectmodel.h" + +using namespace StudioWelcome; + +/****************** BaseNewProjectModel ******************/ + +BaseNewProjectModel::BaseNewProjectModel(QObject *parent) + : QAbstractListModel(parent) +{} + +QHash BaseNewProjectModel::roleNames() const +{ + QHash roleNames; + roleNames[Qt::UserRole] = "name"; + return roleNames; +} + +void BaseNewProjectModel::setProjects(const ProjectsByCategory &projectsByCategory) +{ + beginResetModel(); + + for (auto &[id, category] : projectsByCategory) { + m_categories.push_back(category.name); + m_projects.push_back(category.items); + } + + endResetModel(); +} + +/****************** NewProjectCategoryModel ******************/ + +NewProjectCategoryModel::NewProjectCategoryModel(QObject *parent) + : BaseNewProjectModel(parent) +{} + +int NewProjectCategoryModel::rowCount(const QModelIndex &) const +{ + return static_cast(categories().size()); +} + +QVariant NewProjectCategoryModel::data(const QModelIndex &index, int role) const +{ + return categories().at(index.row()); +} + +/****************** NewProjectModel ******************/ + +NewProjectModel::NewProjectModel(QObject *parent) + : BaseNewProjectModel(parent) +{} + +int NewProjectModel::rowCount(const QModelIndex &) const +{ + if (projects().empty()) + return 0; + + return static_cast(projectsOfCurrentCategory().size()); +} + +QVariant NewProjectModel::data(const QModelIndex &index, int role) const +{ + return projectsOfCurrentCategory().at(index.row()).name; +} + +void NewProjectModel::setPage(int index) +{ + beginResetModel(); + + m_page = static_cast(index); + + endResetModel(); +} + +QString NewProjectModel::fontIconCode(int index) const +{ + Utils::optional projectItem = project(index); + if (!projectItem) + return ""; + + return projectItem->fontIconCode; +} diff --git a/src/plugins/studiowelcome/newprojectmodel.h b/src/plugins/studiowelcome/newprojectmodel.h new file mode 100644 index 00000000000..ec7cc005add --- /dev/null +++ b/src/plugins/studiowelcome/newprojectmodel.h @@ -0,0 +1,146 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include +#include +#include + +#include +#include + +namespace Utils { +class Wizard; +} + +namespace StudioWelcome { + +struct ProjectItem +{ + QString name; + QString categoryId; + QString description; + QUrl qmlPath; + QString fontIconCode; + std::function create; +}; + +inline QDebug &operator<<(QDebug &d, const ProjectItem &item) +{ + d << "name=" << item.name; + d << "; category = " << item.categoryId; + + return d; +} + +struct ProjectCategory +{ + QString id; + QString name; + std::vector items; +}; + +inline QDebug &operator<<(QDebug &d, const ProjectCategory &cat) +{ + d << "id=" << cat.id; + d << "; name=" << cat.name; + d << "; items=" << cat.items; + + return d; +} + +using ProjectsByCategory = std::map; + +/****************** BaseNewProjectModel ******************/ + +class BaseNewProjectModel : public QAbstractListModel +{ + using ProjectItems = std::vector>; + using Categories = std::vector; + +public: + explicit BaseNewProjectModel(QObject *parent = nullptr); + QHash roleNames() const override; + void setProjects(const ProjectsByCategory &projects); + +protected: + const ProjectItems &projects() const { return m_projects; } + const Categories &categories() const { return m_categories; } + +private: + ProjectItems m_projects; + Categories m_categories; +}; + +/****************** NewProjectCategoryModel ******************/ + +class NewProjectCategoryModel : public BaseNewProjectModel +{ +public: + explicit NewProjectCategoryModel(QObject *parent = nullptr); + int rowCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; +}; + +/****************** NewProjectModel ******************/ + +class NewProjectModel : public BaseNewProjectModel +{ + Q_OBJECT +public: + explicit NewProjectModel(QObject *parent = nullptr); + int rowCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + + Q_INVOKABLE void setPage(int index); // called from QML when view's header item is clicked + Q_INVOKABLE QString fontIconCode(int index) const; + + int page() const { return static_cast(m_page); } + + Utils::optional project(size_t selection) const + { + if (projects().empty()) + return {}; + + if (m_page < projects().size()) { + const std::vector projectsOfCategory = projects().at(m_page); + if (selection < projectsOfCategory.size()) + return projects().at(m_page).at(selection); + } + return {}; + } + + bool empty() const { return projects().empty(); } + +private: + const std::vector projectsOfCurrentCategory() const + { return projects().at(m_page); } + +private: + size_t m_page = 0; +}; + +} // namespace StudioWelcome diff --git a/src/plugins/studiowelcome/qdsnewdialog.cpp b/src/plugins/studiowelcome/qdsnewdialog.cpp new file mode 100644 index 00000000000..b031a083bcf --- /dev/null +++ b/src/plugins/studiowelcome/qdsnewdialog.cpp @@ -0,0 +1,343 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include +#include + +#include "qdsnewdialog.h" + +#include +#include +#include +#include + +#include "createproject.h" +#include "wizardfactories.h" +#include "newprojectdialogimageprovider.h" + +using namespace StudioWelcome; + +namespace { + +/* + * NOTE: copied from projectexplorer/jsonwizard/jsonprojectpage.h +*/ +QString uniqueProjectName(const QString &path) +{ + const QDir pathDir(path); + //: File path suggestion for a new project. If you choose + //: to translate it, make sure it is a valid path name without blanks + //: and using only ascii chars. + const QString prefix = QObject::tr("UntitledProject"); + + QString name = prefix; + int i = 0; + while (pathDir.exists(name)) + name = prefix + QString::number(++i); + + return name; +} + +} + +/***********************/ + +QdsNewDialog::QdsNewDialog(QWidget *parent) + : m_dialog{new QQuickWidget(parent)} + , m_categoryModel{new NewProjectCategoryModel(this)} + , m_projectModel{new NewProjectModel(this)} + , m_screenSizeModel{new ScreenSizeModel(this)} + , m_styleModel{new StyleModel(this)} +{ + setParent(m_dialog); + + m_dialog->rootContext()->setContextProperties(QVector{ + {{"categoryModel"}, QVariant::fromValue(m_categoryModel.data())}, + {{"projectModel"}, QVariant::fromValue(m_projectModel.data())}, + {{"screenSizeModel"}, QVariant::fromValue(m_screenSizeModel.data())}, + {{"styleModel"}, QVariant::fromValue(m_styleModel.data())}, + {{"dialogBox"}, QVariant::fromValue(this)}, + }); + + m_dialog->setResizeMode(QQuickWidget::SizeRootObjectToView); // SizeViewToRootObject + m_dialog->engine()->addImageProvider(QStringLiteral("newprojectdialog_library"), + new Internal::NewProjectDialogImageProvider()); + QmlDesigner::Theme::setupTheme(m_dialog->engine()); + m_dialog->engine()->addImportPath(Core::ICore::resourcePath("qmldesigner/propertyEditorQmlSources/imports").toString()); + m_dialog->engine()->addImportPath(Core::ICore::resourcePath("qmldesigner/newprojectdialog/imports").toString()); + QString sourcesPath = qmlPath(); + m_dialog->setSource(QUrl::fromLocalFile(sourcesPath)); + + m_dialog->setWindowModality(Qt::ApplicationModal); + m_dialog->setWindowFlags(Qt::Dialog); + m_dialog->setAttribute(Qt::WA_DeleteOnClose); + m_dialog->setMinimumSize(1155, 804); + + QObject::connect(&m_wizard, &WizardHandler::deletingWizard, this, &QdsNewDialog::onDeletingWizard); + QObject::connect(&m_wizard, &WizardHandler::wizardCreated, this, &QdsNewDialog::onWizardCreated); + QObject::connect(&m_wizard, &WizardHandler::statusMessageChanged, this, &QdsNewDialog::onStatusMessageChanged); + QObject::connect(&m_wizard, &WizardHandler::projectCanBeCreated, this, &QdsNewDialog::onProjectCanBeCreatedChanged); + + QObject::connect(&m_wizard, &WizardHandler::wizardCreationFailed, this, [this]() { + QMessageBox::critical(m_dialog, "New project", "Failed to initialize data"); + reject(); + delete this; + }); + + QObject::connect(m_styleModel.data(), &StyleModel::modelAboutToBeReset, this, [this]() { + this->m_qmlStyleIndex = -1; + }); +} + +void QdsNewDialog::onDeletingWizard() +{ + m_screenSizeModel->setBackendModel(nullptr); + m_qmlScreenSizeIndex = -1; + m_screenSizeModel->reset(); + + m_styleModel->setBackendModel(nullptr); + m_qmlStyleIndex = -1; +} + +void QdsNewDialog::setProjectName(const QString &name) +{ + m_qmlProjectName = name; + m_wizard.setProjectName(name); +} + +void QdsNewDialog::setProjectLocation(const QString &location) +{ + m_qmlProjectLocation = Utils::FilePath::fromString(QDir::toNativeSeparators(location)); + m_wizard.setProjectLocation(m_qmlProjectLocation); +} + +void QdsNewDialog::onStatusMessageChanged(Utils::InfoLabel::InfoType type, const QString &message) +{ + switch (type) { + case Utils::InfoLabel::Warning: + m_qmlStatusType = "warning"; + break; + case Utils::InfoLabel::Error: + m_qmlStatusType = "error"; + break; + default: + m_qmlStatusType = "normal"; + break; + } + + emit statusTypeChanged(); + + m_qmlStatusMessage = message; + emit statusMessageChanged(); +} + +void QdsNewDialog::onProjectCanBeCreatedChanged(bool value) +{ + if (m_qmlFieldsValid == value) + return; + + m_qmlFieldsValid = value; + + emit fieldsValidChanged(); +} + +void QdsNewDialog::onWizardCreated(QStandardItemModel *screenSizeModel, QStandardItemModel *styleModel) +{ + m_screenSizeModel->setBackendModel(screenSizeModel); + m_styleModel->setBackendModel(styleModel); + + if (m_qmlDetailsLoaded) { + m_screenSizeModel->reset(); + emit haveVirtualKeyboardChanged(); + emit haveTargetQtVersionChanged(); + + setProjectName(m_qmlProjectName); + setProjectLocation(m_qmlProjectLocation.toString()); + } + + if (m_qmlStylesLoaded) + m_styleModel->reset(); +} + +QString QdsNewDialog::currentProjectQmlPath() const +{ + if (!m_currentProject || m_currentProject->qmlPath.isEmpty()) + return ""; + + return m_currentProject->qmlPath.toString(); +} + +void QdsNewDialog::setScreenSizeIndex(int index) +{ + m_wizard.setScreenSizeIndex(index); + m_qmlScreenSizeIndex = index; +} + +void QdsNewDialog::setTargetQtVersion(int index) +{ + m_wizard.setTargetQtVersionIndex(index); + m_qmlTargetQtVersionIndex = index; +} + +void QdsNewDialog::setStyleIndex(int index) +{ + if (!m_qmlStylesLoaded) + return; + + if (index == -1) { + m_qmlStyleIndex = index; + return; + } + + m_qmlStyleIndex = index; + int actualIndex = m_styleModel->actualIndex(m_qmlStyleIndex); + QTC_ASSERT(actualIndex >= 0, return); + + m_wizard.setStyleIndex(actualIndex); +} + +int QdsNewDialog::getStyleIndex() const +{ + /** + * m_wizard.styleIndex property is the wizard's (backend's) value of the style index. + * The initial value (saved in the wizard.json) is read from there. Any subsequent reads of + * the style index should use m_styleIndex, which is the QML's style index property. Setting + * the style index should update both the m_styleIndex and the backend. In this regard, the + * QdsNewDialog's m_styleIndex acts as some kind of cache. + */ + + if (!m_qmlStylesLoaded) + return -1; + + if (m_qmlStyleIndex == -1) { + int actualIndex = m_wizard.styleIndex(); + // Not nice, get sets the property... m_qmlStyleIndex acts like a cache. + m_qmlStyleIndex = m_styleModel->filteredIndex(actualIndex); + return m_qmlStyleIndex; + } + + return m_styleModel->actualIndex(m_qmlStyleIndex); +} + +void QdsNewDialog::setWizardFactories(QList factories_, + const Utils::FilePath &defaultLocation, + const QVariantMap &) +{ + Utils::Id platform = Utils::Id::fromSetting("Desktop"); + + WizardFactories factories{factories_, m_dialog, platform}; + + m_categoryModel->setProjects(factories.projectsGroupedByCategory()); // calls model reset + m_projectModel->setProjects(factories.projectsGroupedByCategory()); // calls model reset + + if (m_qmlSelectedProject > -1) + setSelectedProject(m_qmlSelectedProject); + + if (factories.empty()) + return; // TODO: some message box? + + const Core::IWizardFactory *first = factories.front(); + Utils::FilePath projectLocation = first->runPath(defaultLocation); + + m_qmlProjectName = uniqueProjectName(projectLocation.toString()); + emit projectNameChanged(); // So that QML knows to update the field + + m_qmlProjectLocation = Utils::FilePath::fromString(QDir::toNativeSeparators(projectLocation.toString())); + emit projectLocationChanged(); // So that QML knows to update the field + + if (m_qmlDetailsLoaded) + m_screenSizeModel->reset(); + + if (m_qmlStylesLoaded) + m_styleModel->reset(); +} + +QString QdsNewDialog::qmlPath() const +{ + return Core::ICore::resourcePath("qmldesigner/newprojectdialog/NewProjectDialog.qml").toString(); +} + +void QdsNewDialog::showDialog() +{ + m_dialog->show(); +} + +bool QdsNewDialog::getHaveVirtualKeyboard() const +{ + return m_wizard.haveVirtualKeyboard(); +} + +bool QdsNewDialog::getHaveTargetQtVersion() const +{ + return m_wizard.haveTargetQtVersion(); +} + +void QdsNewDialog::accept() +{ + CreateProject create{m_wizard}; + + create.withName(m_qmlProjectName) + .atLocation(m_qmlProjectLocation) + .withScreenSizes(m_qmlScreenSizeIndex, m_qmlCustomWidth, m_qmlCustomHeight) + .withStyle(m_qmlStyleIndex) + .useQtVirtualKeyboard(m_qmlUseVirtualKeyboard) + .saveAsDefaultLocation(m_qmlSaveAsDefaultLocation) + .withTargetQtVersion(m_qmlTargetQtVersionIndex) + .execute(); + + m_dialog->close(); +} + +void QdsNewDialog::reject() +{ + m_screenSizeModel->setBackendModel(nullptr); + m_styleModel->setBackendModel(nullptr); + m_wizard.destroyWizard(); + + m_dialog->close(); +} + +QString QdsNewDialog::chooseProjectLocation() +{ + Utils::FilePath newPath = Utils::FileUtils::getExistingDirectory(m_dialog, tr("Choose Directory"), + m_qmlProjectLocation); + + return QDir::toNativeSeparators(newPath.toString()); +} + +void QdsNewDialog::setSelectedProject(int selection) +{ + if (m_qmlSelectedProject != selection || m_projectPage != m_projectModel->page()) { + m_qmlSelectedProject = selection; + + m_currentProject = m_projectModel->project(m_qmlSelectedProject); + if (m_currentProject) { + setProjectDescription(m_currentProject->description); + + m_projectPage = m_projectModel->page(); + m_wizard.reset(m_currentProject.value(), m_qmlSelectedProject, m_qmlProjectLocation); + } + } +} diff --git a/src/plugins/studiowelcome/qdsnewdialog.h b/src/plugins/studiowelcome/qdsnewdialog.h new file mode 100644 index 00000000000..ede50bc97a7 --- /dev/null +++ b/src/plugins/studiowelcome/qdsnewdialog.h @@ -0,0 +1,170 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +#include +#include +#include + +#include "wizardhandler.h" +#include "newprojectmodel.h" +#include "screensizemodel.h" +#include "stylemodel.h" + +QT_BEGIN_NAMESPACE +class QStandardItemModel; +QT_END_NAMESPACE + +namespace StudioWelcome { +class QdsNewDialog : public QObject, public Core::NewDialog +{ + Q_OBJECT + +public: + Q_PROPERTY(int selectedProject MEMBER m_qmlSelectedProject WRITE setSelectedProject) + Q_PROPERTY(QString projectName MEMBER m_qmlProjectName WRITE setProjectName NOTIFY projectNameChanged) + Q_PROPERTY(QString projectLocation MEMBER m_qmlProjectLocation READ projectLocation WRITE setProjectLocation NOTIFY projectLocationChanged) + Q_PROPERTY(QString projectDescription MEMBER m_qmlProjectDescription READ projectDescription WRITE setProjectDescription NOTIFY projectDescriptionChanged) + Q_PROPERTY(QString customWidth MEMBER m_qmlCustomWidth) + Q_PROPERTY(QString customHeight MEMBER m_qmlCustomHeight) + Q_PROPERTY(int styleIndex MEMBER m_qmlStyleIndex READ getStyleIndex WRITE setStyleIndex) + Q_PROPERTY(bool useVirtualKeyboard MEMBER m_qmlUseVirtualKeyboard READ getUseVirtualKeyboard WRITE setUseVirtualKeyboard NOTIFY useVirtualKeyboardChanged) + Q_PROPERTY(bool haveVirtualKeyboard MEMBER m_qmlHaveVirtualKeyboard READ getHaveVirtualKeyboard NOTIFY haveVirtualKeyboardChanged) + Q_PROPERTY(bool haveTargetQtVersion MEMBER m_qmlHaveTargetQtVersion READ getHaveTargetQtVersion NOTIFY haveTargetQtVersionChanged) + Q_PROPERTY(bool saveAsDefaultLocation MEMBER m_qmlSaveAsDefaultLocation WRITE setSaveAsDefaultLocation) + Q_PROPERTY(QString statusMessage MEMBER m_qmlStatusMessage READ getStatusMessage NOTIFY statusMessageChanged) + Q_PROPERTY(QString statusType MEMBER m_qmlStatusType READ getStatusType NOTIFY statusTypeChanged) + Q_PROPERTY(bool fieldsValid MEMBER m_qmlFieldsValid READ getFieldsValid NOTIFY fieldsValidChanged) + + Q_PROPERTY(bool detailsLoaded MEMBER m_qmlDetailsLoaded) + Q_PROPERTY(bool stylesLoaded MEMBER m_qmlStylesLoaded) + + Q_INVOKABLE QString currentProjectQmlPath() const; + Q_INVOKABLE void setScreenSizeIndex(int index); // called when ComboBox item is "activated" + Q_INVOKABLE void setTargetQtVersion(int index); + + Q_INVOKABLE QString chooseProjectLocation(); + + explicit QdsNewDialog(QWidget *parent); + + QWidget *widget() override { return m_dialog; } + + void setWizardFactories(QList factories, const Utils::FilePath &defaultLocation, + const QVariantMap &extraVariables) override; + void setWindowTitle(const QString &title) override { m_dialog->setWindowTitle(title); } + void showDialog() override; + void setSelectedProject(int selection); + + void setStyleIndex(int index); + int getStyleIndex() const; + void setUseVirtualKeyboard(bool value) { m_qmlUseVirtualKeyboard = value; } + bool getUseVirtualKeyboard() const { return m_qmlUseVirtualKeyboard; } + + bool getFieldsValid() const { return m_qmlFieldsValid; } + bool getHaveVirtualKeyboard() const; + bool getHaveTargetQtVersion() const; + + void setSaveAsDefaultLocation(bool value) { m_qmlSaveAsDefaultLocation = value; } + + QString getStatusMessage() const { return m_qmlStatusMessage; } + QString getStatusType() const { return m_qmlStatusType; } + +public slots: + void accept(); + void reject(); + +signals: + void projectNameChanged(); + void projectLocationChanged(); + void projectDescriptionChanged(); + void useVirtualKeyboardChanged(); + void haveVirtualKeyboardChanged(); + void haveTargetQtVersionChanged(); + void statusMessageChanged(); + void statusTypeChanged(); + void fieldsValidChanged(); + +private slots: + void onStatusMessageChanged(Utils::InfoLabel::InfoType type, const QString &message); + void onProjectCanBeCreatedChanged(bool value); + +private: + QString qmlPath() const; + + void setProjectName(const QString &name); + void setProjectLocation(const QString &location); + QString projectLocation() const { return m_qmlProjectLocation.toString(); } + + void setProjectDescription(const QString &description) + { + m_qmlProjectDescription = description; + emit projectDescriptionChanged(); + } + + QString projectDescription() const { return m_qmlProjectDescription; } + +private slots: + void onDeletingWizard(); + void onWizardCreated(QStandardItemModel *screenSizeModel, QStandardItemModel *styleModel); + +private: + QQuickWidget *m_dialog = nullptr; + QPointer m_categoryModel; + QPointer m_projectModel; + QPointer m_screenSizeModel; + QPointer m_styleModel; + QString m_qmlProjectName; + Utils::FilePath m_qmlProjectLocation; + QString m_qmlProjectDescription; + int m_qmlSelectedProject = -1; + int m_qmlScreenSizeIndex = -1; + int m_qmlTargetQtVersionIndex = -1; + // m_qmlStyleIndex is like a cache, so it needs to be updated on get() + mutable int m_qmlStyleIndex = -1; + bool m_qmlUseVirtualKeyboard = false; + bool m_qmlHaveVirtualKeyboard = false; + bool m_qmlHaveTargetQtVersion = false; + bool m_qmlSaveAsDefaultLocation = false; + bool m_qmlFieldsValid = false; + QString m_qmlStatusMessage; + QString m_qmlStatusType; + + int m_projectPage = -1; // i.e. the page in the Presets View + + QString m_qmlCustomWidth; + QString m_qmlCustomHeight; + + bool m_qmlDetailsLoaded = false; + bool m_qmlStylesLoaded = false; + + Utils::optional m_currentProject; + + WizardHandler m_wizard; +}; + +} //namespace StudioWelcome diff --git a/src/plugins/studiowelcome/screensizemodel.h b/src/plugins/studiowelcome/screensizemodel.h new file mode 100644 index 00000000000..4e38f7acf85 --- /dev/null +++ b/src/plugins/studiowelcome/screensizemodel.h @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include +#include +#include + +class ScreenSizeModel : public QAbstractListModel +{ + Q_OBJECT + +public: + explicit ScreenSizeModel(QObject *parent = nullptr) + : QAbstractListModel(parent) + {} + + Q_INVOKABLE QSize screenSizes(int index) const + { + constexpr auto invalid = QSize{0, 0}; + if (!m_backendModel) + return invalid; + + auto *item = m_backendModel->item(index, 0); + // Matches strings like "1024 x 768" or "1080 x 1920 (FullHD)" + QRegularExpression re{R"__(^(\d+)\s*x\s*(\d+).*)__"}; + + if (!item) + return invalid; + + auto m = re.match(item->text()); + if (!m.hasMatch()) + return invalid; + + bool ok = false; + int width = m.captured(1).toInt(&ok); + if (!ok) + return invalid; + + int height = m.captured(2).toInt(&ok); + if (!ok) + return invalid; + + return QSize{width, height}; + } + + int rowCount(const QModelIndex &/*parent*/) const override + { + if (m_backendModel) + return m_backendModel->rowCount(); + + return 0; + } + + QVariant data(const QModelIndex &index, int /*role*/) const override + { + if (m_backendModel) { + auto *item = m_backendModel->item(index.row(), index.column()); + return item->text(); + } + + return ""; + } + + QHash roleNames() const override + { + if (m_backendModel) + return m_backendModel->roleNames(); + + QHash roleNames; + roleNames[Qt::UserRole] = "name"; + return roleNames; + } + + void reset() { + beginResetModel(); + endResetModel(); + } + + void setBackendModel(QStandardItemModel *model) + { + m_backendModel = model; + } + +private: + QStandardItemModel *m_backendModel = nullptr; +}; + diff --git a/src/plugins/studiowelcome/studiowelcome.pro b/src/plugins/studiowelcome/studiowelcome.pro index e2ccee8576f..9b7e756a568 100644 --- a/src/plugins/studiowelcome/studiowelcome.pro +++ b/src/plugins/studiowelcome/studiowelcome.pro @@ -12,11 +12,26 @@ DEFINES += STUDIO_QML_PATH=\\\"$$PWD/qml/\\\" HEADERS += \ studiowelcome_global.h \ studiowelcomeplugin.h \ - examplecheckout.h + newprojectdialogimageprovider.h \ + qdsnewdialog.h \ + wizardfactories.h \ + wizardhandler.h \ + createproject.h \ + newprojectmodel.h \ + examplecheckout.h \ + screensizemodel.h \ + stylemodel.h SOURCES += \ studiowelcomeplugin.cpp \ - examplecheckout.cpp + qdsnewdialog.cpp \ + wizardfactories.cpp \ + wizardhandler.cpp \ + createproject.cpp \ + newprojectdialogimageprovider.cpp \ + newprojectmodel.cpp \ + examplecheckout.cpp \ + stylemodel.cpp OTHER_FILES += \ StudioWelcome.json.in diff --git a/src/plugins/studiowelcome/studiowelcome.qbs b/src/plugins/studiowelcome/studiowelcome.qbs index f2bec88af22..a0b81b872da 100644 --- a/src/plugins/studiowelcome/studiowelcome.qbs +++ b/src/plugins/studiowelcome/studiowelcome.qbs @@ -9,17 +9,33 @@ QtcPlugin { Depends { name: "Core" } Depends { name: "ProjectExplorer" } Depends { name: "QtSupport" } + Depends { name: "QmlDesigner" } Depends { name: "app_version_header" } cpp.defines: 'STUDIO_QML_PATH="' + FileInfo.joinPaths(sourceDirectory, "qml") + '"' files: [ + "createproject.cpp", + "createproject.h", + "examplecheckout.h", + "examplecheckout.cpp", + "newprojectdialogimageprovider.h", + "newprojectdialogimageprovider.cpp", + "newprojectmodel.cpp", + "newprojectmodel.h", + "qdsnewdialog.cpp", + "qdsnewdialog.h", + "screensizemodel.h", "studiowelcome_global.h", "studiowelcomeplugin.h", "studiowelcomeplugin.cpp", - "examplecheckout.h", - "examplecheckout.cpp", "studiowelcome.qrc", + "stylemodel.cpp", + "stylemodel.h", + "wizardfactories.cpp", + "wizardfactories.h", + "wizardhandler.cpp", + "wizardhandler.h", ] Group { diff --git a/src/plugins/studiowelcome/studiowelcome_dependencies.pri b/src/plugins/studiowelcome/studiowelcome_dependencies.pri index 51eb0d02565..bb8286bc7b3 100644 --- a/src/plugins/studiowelcome/studiowelcome_dependencies.pri +++ b/src/plugins/studiowelcome/studiowelcome_dependencies.pri @@ -6,5 +6,6 @@ QTC_LIB_DEPENDS += \ QTC_PLUGIN_DEPENDS += \ coreplugin \ projectexplorer \ + qmldesigner \ qtsupport diff --git a/src/plugins/studiowelcome/studiowelcomeplugin.cpp b/src/plugins/studiowelcome/studiowelcomeplugin.cpp index 85f601cc98e..1a577a46ce8 100644 --- a/src/plugins/studiowelcome/studiowelcomeplugin.cpp +++ b/src/plugins/studiowelcome/studiowelcomeplugin.cpp @@ -26,6 +26,8 @@ #include "studiowelcomeplugin.h" #include "examplecheckout.h" +#include "qdsnewdialog.h" + #include #include #include @@ -368,6 +370,9 @@ void StudioWelcomePlugin::extensionsInitialized() s_view->setSource(QUrl("qrc:/qml/splashscreen/main.qml")); #endif + // disabled by default + Core::ICore::setNewDialogFactory([](QWidget *parent) { return new QdsNewDialog(parent); }); + QTC_ASSERT(s_view->rootObject(), qWarning() << "The StudioWelcomePlugin has a runtime depdendency on " "qt/qtquicktimeline."; diff --git a/src/plugins/studiowelcome/stylemodel.cpp b/src/plugins/studiowelcome/stylemodel.cpp new file mode 100644 index 00000000000..9aee4ff7d32 --- /dev/null +++ b/src/plugins/studiowelcome/stylemodel.cpp @@ -0,0 +1,130 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "stylemodel.h" + +#include "utils/algorithm.h" +#include "utils/qtcassert.h" + +#include + +StyleModel::StyleModel(QObject *parent) + : QAbstractListModel(parent) + , m_backendModel(nullptr) +{} + +QString StyleModel::iconId(int index) const +{ + if (!m_backendModel || index < 0) + return "style-error"; + + auto item = this->m_filteredItems.at(index); + QString styleName = item->text(); + QString id{"style-"}; + id += styleName.toLower().replace(' ', '_') + ".png"; + + return id; +} + +void StyleModel::filter(const QString &what) +{ + if (what.toLower() == "all") + m_filteredItems = this->filterItems(m_items, ""); + else if (what.toLower() == "light") + m_filteredItems = this->filterItems(m_items, "light"); + else if (what.toLower() == "dark") + m_filteredItems = this->filterItems(m_items, "dark"); + else + m_filteredItems.clear(); + + reset(); +} + +StyleModel::Items StyleModel::filterItems(const Items &items, const QString &kind) +{ + if (kind.isEmpty()) + return items; + + return Utils::filtered(items, [&kind](auto *item) { + QString pattern{"\\S "}; + pattern += kind; + + QRegularExpression re{pattern, QRegularExpression::CaseInsensitiveOption}; + return re.match(item->text()).hasMatch(); + }); +} + +int StyleModel::filteredIndex(int actualIndex) +{ + if (actualIndex < 0) + return actualIndex; + + QStandardItem *item = m_items.at(actualIndex); + // TODO: perhaps should add this kind of find to utils/algorithm.h + auto it = std::find(std::cbegin(m_filteredItems), std::cend(m_filteredItems), item); + if (it == std::cend(m_filteredItems)) + return -1; + + return std::distance(std::cbegin(m_filteredItems), it); +} + +int StyleModel::actualIndex(int filteredIndex) +{ + if (filteredIndex < 0) + return filteredIndex; + + QTC_ASSERT(filteredIndex < static_cast(m_filteredItems.size()), return -1); + + QStandardItem *item = m_filteredItems.at(filteredIndex); + auto it = std::find(std::cbegin(m_items), std::cend(m_items), item); + if (it == std::cend(m_items)) + return -1; + + auto result = std::distance(std::cbegin(m_items), it); + QTC_ASSERT(result >= 0, return -1); + QTC_ASSERT(result <= static_cast(m_items.size()), return -1); + + return result; +} + +void StyleModel::setBackendModel(QStandardItemModel *model) +{ + m_backendModel = model; + + if (m_backendModel) { + m_count = model->rowCount(); + m_roles = model->roleNames(); + m_items.clear(); + + for (int i = 0; i < m_count; ++i) + m_items.push_back(model->item(i, 0)); + + m_filteredItems = filterItems(m_items, ""); + } else { + m_count = 0; + m_items.clear(); + m_filteredItems.clear(); + } +} diff --git a/src/plugins/studiowelcome/stylemodel.h b/src/plugins/studiowelcome/stylemodel.h new file mode 100644 index 00000000000..e311eded6fb --- /dev/null +++ b/src/plugins/studiowelcome/stylemodel.h @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include +#include + +class StyleModel : public QAbstractListModel +{ + Q_OBJECT + +private: + using Items = std::vector; + +public: + explicit StyleModel(QObject *parent = nullptr); + + Q_INVOKABLE QString iconId(int index) const; + Q_INVOKABLE void filter(const QString &what = "all"); + + int rowCount(const QModelIndex &/*parent*/) const override + { + if (m_backendModel) + return static_cast(m_filteredItems.size()); + + return 0; + } + + QVariant data(const QModelIndex &index, int /*role*/) const override + { + if (m_backendModel) { + auto *item = m_filteredItems.at(index.row()); + return item->text(); + } + + return ""; + } + + QHash roleNames() const override + { + if (m_backendModel) + return m_roles; + + /** + * TODO: roleNames is called before the backend model is loaded, which may be buggy. But I + * found no way to force refresh the model, so as to reload the role names afterwards. + */ + + QHash roleNames; + roleNames[Qt::UserRole] = "display"; + return roleNames; + } + + void reset() + { + beginResetModel(); + endResetModel(); + } + + int filteredIndex(int actualIndex); + int actualIndex(int filteredIndex); + void setBackendModel(QStandardItemModel *model); + +private: + static Items filterItems(const Items &items, const QString &kind); + +private: + QStandardItemModel *m_backendModel; + Items m_items, m_filteredItems; + int m_count = -1; + QHash m_roles; +}; + diff --git a/src/plugins/studiowelcome/wizardfactories.cpp b/src/plugins/studiowelcome/wizardfactories.cpp new file mode 100644 index 00000000000..a7cb20b0c62 --- /dev/null +++ b/src/plugins/studiowelcome/wizardfactories.cpp @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include +#include +#include + +#include "wizardfactories.h" + +namespace { +// TODO: should be extern, check coreplugin/dialogs/newdialogwidget.cpp +const char BLACKLISTED_CATEGORIES_KEY[] = "Core/NewDialog/BlacklistedCategories"; +} + +using namespace StudioWelcome; + +WizardFactories::WizardFactories(QList &factories, QWidget *wizardParent, const Utils::Id &platform) + : m_wizardParent{wizardParent} + , m_platform{platform} + , m_factories{factories} +{ + QVariant value = Core::ICore::settings()->value(BLACKLISTED_CATEGORIES_KEY); + m_blacklist = Utils::Id::fromStringList(value.toStringList()); + + sortByCategoryAndId(); + filter(); + m_projectItems = makeProjectItemsGroupedByCategory(); +} + +void WizardFactories::sortByCategoryAndId() +{ + Utils::sort(m_factories, [](Core::IWizardFactory *lhs, Core::IWizardFactory *rhs) { + if (lhs->category() == rhs->category()) + return lhs->id().toString() < rhs->id().toString(); + else + return lhs->category() < rhs->category(); + }); +} + +void WizardFactories::filter() +{ + QList acceptedFactories; + // TODO: perhaps I could use Utils::filtered here. + std::copy_if(std::begin(m_factories), std::end(m_factories), std::back_inserter(acceptedFactories), + [&](auto *wizard) { + return wizard->isAvailable(m_platform) + && wizard->kind() == Core::IWizardFactory::ProjectWizard + && !m_blacklist.contains(Utils::Id::fromString(wizard->category())); + }); + + m_factories = acceptedFactories; +} + +ProjectItem WizardFactories::makeProjectItem(Core::IWizardFactory *f, QWidget *parent, + const Utils::Id &platform) +{ + using namespace std::placeholders; + + return { + /*.name =*/f->displayName(), + /*.categoryId =*/f->category(), + /*. description =*/f->description(), + /*.qmlPath =*/f->detailsPageQmlPath(), + /*.fontIconCode =*/f->fontIcondCode(), + /*.create =*/ std::bind(&Core::IWizardFactory::runWizard, f, _1, parent, platform, + QVariantMap(), false), + }; +} + +std::map WizardFactories::makeProjectItemsGroupedByCategory() +{ + QMap categories; + + for (auto *f : std::as_const(m_factories)) { + if (!categories.contains(f->category())) { + categories[f->category()] = { + /*.id =*/ f->category(), + /*.name =*/ f->displayCategory(), + /*.items = */ + { + makeProjectItem(f, m_wizardParent, m_platform), + }, + }; + } else { + auto projectItem = makeProjectItem(f, m_wizardParent, m_platform); + categories[f->category()].items.push_back(projectItem); + } + } + + return categories.toStdMap(); +} diff --git a/src/plugins/studiowelcome/wizardfactories.h b/src/plugins/studiowelcome/wizardfactories.h new file mode 100644 index 00000000000..c41300ff091 --- /dev/null +++ b/src/plugins/studiowelcome/wizardfactories.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "newprojectmodel.h" + +#include + +namespace Core { +class IWizardFactory; +} + +namespace StudioWelcome { + +class WizardFactories +{ +public: + WizardFactories(QList &factories, QWidget *wizardParent, + const Utils::Id &platform); + + const Core::IWizardFactory *front() const { return m_factories.front(); } + const std::map &projectsGroupedByCategory() const + { return m_projectItems; } + + bool empty() const { return m_factories.empty(); } + +private: + void sortByCategoryAndId(); + void filter(); + + ProjectItem makeProjectItem(Core::IWizardFactory *f, QWidget *parent, const Utils::Id &platform); + std::map makeProjectItemsGroupedByCategory(); + +private: + QSet m_blacklist; + QWidget *m_wizardParent; + Utils::Id m_platform; + + QList m_factories; + std::map m_projectItems; +}; + +} // namespace StudioWelcome diff --git a/src/plugins/studiowelcome/wizardhandler.cpp b/src/plugins/studiowelcome/wizardhandler.cpp new file mode 100644 index 00000000000..fce7aa120f9 --- /dev/null +++ b/src/plugins/studiowelcome/wizardhandler.cpp @@ -0,0 +1,249 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include +#include + +#include "wizardhandler.h" + +#include +#include + +#include + +#include "utils/wizard.h" +#include + +using namespace StudioWelcome; + +void WizardHandler::reset(const ProjectItem &projectInfo, int projectSelection, const Utils::FilePath &location) +{ + m_projectItem = projectInfo; + m_projectLocation = location; + m_selectedProject = projectSelection; + + if (!m_wizard) { + setupWizard(); + } else { + QObject::connect(m_wizard, &QObject::destroyed, this, &WizardHandler::onWizardResetting); + + // DON'T SET `m_selectedProject = -1` --- we are switching now to a separate project. + emit deletingWizard(); + + m_wizard->deleteLater(); + } +} + +void WizardHandler::destroyWizard() +{ + emit deletingWizard(); + + m_selectedProject = -1; + m_wizard->deleteLater(); + m_wizard = nullptr; +} + +void WizardHandler::setupWizard() +{ + m_wizard = m_projectItem.create(m_projectLocation); + if (!m_wizard) { + emit wizardCreationFailed(); + return; + } + + initializeProjectPage(m_wizard->page(0)); + initializeFieldsPage(m_wizard->page(1)); + + auto *screenFactorModel = getScreenFactorModel(m_detailsPage); + auto *styleModel = getStyleModel(m_detailsPage); + + emit wizardCreated(screenFactorModel, styleModel); +} + +void WizardHandler::setProjectName(const QString &name) +{ + QTC_ASSERT(m_wizard, return); + + QWizardPage *projectPage = m_wizard->page(0); + auto *jpp = dynamic_cast(projectPage); + QTC_ASSERT(jpp, return); + + jpp->setProjectName(name); +} + +void WizardHandler::setProjectLocation(const Utils::FilePath &location) +{ + QTC_ASSERT(m_wizard, return); + + QWizardPage *projectPage = m_wizard->page(0); + auto *jpp = dynamic_cast(projectPage); + QTC_ASSERT(jpp, return); + + jpp->setFilePath(location); +} + +void WizardHandler::initializeProjectPage(QWizardPage *page) +{ + auto *jpp = dynamic_cast(page); + QTC_ASSERT(jpp, return); + + QObject::connect(jpp, &ProjectExplorer::JsonProjectPage::statusMessageChanged, this, &WizardHandler::statusMessageChanged); + QObject::connect(jpp, &ProjectExplorer::JsonProjectPage::completeChanged, this, &WizardHandler::onProjectIntroCompleteChanged); +} + +void WizardHandler::initializeFieldsPage(QWizardPage *page) +{ + auto fieldsPage = dynamic_cast(page); // required for page->jsonField + QTC_ASSERT(fieldsPage, return); + m_detailsPage = fieldsPage; + + fieldsPage->initializePage(); +} + +void WizardHandler::onProjectIntroCompleteChanged() +{ + auto *page = dynamic_cast(QObject::sender()); + QTC_ASSERT(page, return); + + emit projectCanBeCreated(page->isComplete()); +} + +QStandardItemModel *WizardHandler::getScreenFactorModel(ProjectExplorer::JsonFieldPage *page) +{ + auto *field = page->jsonField("ScreenFactor"); + if (!field) + return nullptr; + + auto *cbfield = dynamic_cast(field); + QTC_ASSERT(cbfield, return nullptr); + + return cbfield->model(); +} + +QStandardItemModel *WizardHandler::getStyleModel(ProjectExplorer::JsonFieldPage *page) +{ + auto *field = page->jsonField("ControlsStyle"); + if (!field) + return nullptr; + + auto *cbfield = dynamic_cast(field); + QTC_ASSERT(cbfield, return nullptr); + + return cbfield->model(); +} + +void WizardHandler::onWizardResetting() +{ + m_wizard = nullptr; + + // if have a wizard request pending => create new wizard + // note: we always have a wizard request pending here, unless the dialogbox was requested to be destroyed. + // if m_selectedProject != -1 => the wizard was destroyed as a result of reset to a different project type + if (m_selectedProject > -1) + setupWizard(); +} + +void WizardHandler::setScreenSizeIndex(int index) +{ + auto *field = m_detailsPage->jsonField("ScreenFactor"); + auto *cbfield = dynamic_cast(field); + QTC_ASSERT(cbfield, return); + + cbfield->selectRow(index); +} + +void WizardHandler::setTargetQtVersionIndex(int index) +{ + auto *field = m_detailsPage->jsonField("TargetQtVersion"); + auto *cbfield = dynamic_cast(field); + QTC_ASSERT(cbfield, return); + + cbfield->selectRow(index); +} + +bool WizardHandler::haveTargetQtVersion() const +{ + return m_wizard->hasField("TargetQtVersion"); +} + +void WizardHandler::setStyleIndex(int index) +{ + auto *field = m_detailsPage->jsonField("ControlsStyle"); + auto *cbfield = dynamic_cast(field); + QTC_ASSERT(cbfield, return); + + cbfield->selectRow(index); +} + +int WizardHandler::styleIndex() const +{ + auto *field = m_detailsPage->jsonField("ControlsStyle"); + auto *cbfield = dynamic_cast(field); + QTC_ASSERT(cbfield, return -1); + + return cbfield->selectedRow(); +} + +void WizardHandler::setUseVirtualKeyboard(bool value) +{ + auto *field = m_detailsPage->jsonField("UseVirtualKeyboard"); + auto *cbfield = dynamic_cast(field); + QTC_ASSERT(cbfield, return); + + cbfield->setChecked(value); +} + +bool WizardHandler::haveVirtualKeyboard() const +{ + return m_wizard->hasField("UseVirtualKeyboard"); +} + +void WizardHandler::run(const std::function &processPage) +{ + m_wizard->restart(); + + int nextId = 0; + do { + QWizardPage *page = m_wizard->currentPage(); + QTC_ASSERT(page, return); + + processPage(page); + + if (!page->validatePage() || !page->isComplete()) { + QMessageBox::warning(m_wizard, "New project", "Could not create the project because fields are invalid"); + return; + } + + nextId = m_wizard->nextId(); + m_wizard->next(); + } while (-1 != nextId); + + m_selectedProject = -1; + + // Note: don't call `emit deletingWizard()` here. + + // Note: QWizard::accept calls QObject::deleteLater on the wizard + m_wizard->accept(); +} diff --git a/src/plugins/studiowelcome/wizardhandler.h b/src/plugins/studiowelcome/wizardhandler.h new file mode 100644 index 00000000000..a828d595f66 --- /dev/null +++ b/src/plugins/studiowelcome/wizardhandler.h @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "newprojectmodel.h" + +#include +#include + +QT_BEGIN_NAMESPACE +class QWizard; +class QWizardPage; +class QStandardItemModel; +QT_END_NAMESPACE + +namespace ProjectExplorer { +class JsonFieldPage; +} + +namespace StudioWelcome { + +class WizardHandler: public QObject +{ + Q_OBJECT + +public: + //TODO: location should not be needed in reset() -- only when creating the project + void reset(const ProjectItem &projectInfo, int projectSelection, const Utils::FilePath &location); + void setScreenSizeIndex(int index); + void setTargetQtVersionIndex(int index); + bool haveTargetQtVersion() const; + void setStyleIndex(int index); + int styleIndex() const; + void destroyWizard(); + + void setUseVirtualKeyboard(bool value); + bool haveVirtualKeyboard() const; + + void setProjectName(const QString &name); + void setProjectLocation(const Utils::FilePath &location); + + void run(const std::function &processPage); + +signals: + void deletingWizard(); + void wizardCreated(QStandardItemModel *screenSizeModel, QStandardItemModel *styleModel); + void statusMessageChanged(Utils::InfoLabel::InfoType type, const QString &message); + void projectCanBeCreated(bool value); + void wizardCreationFailed(); + +private: + void setupWizard(); + void initializeProjectPage(QWizardPage *page); + void initializeFieldsPage(QWizardPage *page); + + QStandardItemModel *getScreenFactorModel(ProjectExplorer::JsonFieldPage *page); + QStandardItemModel *getStyleModel(ProjectExplorer::JsonFieldPage *page); + +private slots: + void onWizardResetting(); + void onProjectIntroCompleteChanged(); + +private: + Utils::Wizard *m_wizard = nullptr; + ProjectExplorer::JsonFieldPage *m_detailsPage = nullptr; + + int m_selectedProject = -1; + + ProjectItem m_projectItem; + Utils::FilePath m_projectLocation; +}; + +} // StudioWelcome diff --git a/src/tools/qml2puppet/qml2puppet.qbs b/src/tools/qml2puppet/qml2puppet.qbs index 626aed22255..9f90a0e29a0 100644 --- a/src/tools/qml2puppet/qml2puppet.qbs +++ b/src/tools/qml2puppet/qml2puppet.qbs @@ -16,6 +16,8 @@ QtcTool { ] } Depends { name: "Qt.quick3d-private"; required: false } + Depends { name: "Qt.quick3dparticles-private"; required: false } + property bool useQuick3d: Utilities.versionCompare(Qt.core.version, "5.15") >= 0 && Qt["quick3d-private"].present property bool useParticle3d: Utilities.versionCompare(Qt.core.version, "6.2") >= 0 diff --git a/tests/auto/cplusplus/findusages/tst_findusages.cpp b/tests/auto/cplusplus/findusages/tst_findusages.cpp index cee57808e61..e43d1692b8f 100644 --- a/tests/auto/cplusplus/findusages/tst_findusages.cpp +++ b/tests/auto/cplusplus/findusages/tst_findusages.cpp @@ -112,6 +112,7 @@ private Q_SLOTS: void using_insideNamespace(); void using_insideFunction(); void templatedFunction_QTCREATORBUG9749(); + void templateInNamespaceTypeOutside(); void usingInDifferentNamespace_QTCREATORBUG7978(); @@ -1228,6 +1229,45 @@ void tst_FindUsages::templatedFunction_QTCREATORBUG9749() QCOMPARE(findUsages.usages().at(1).type, Usage::Type::Other); } +void tst_FindUsages::templateInNamespaceTypeOutside() +{ + const QByteArray src = R"( +struct S { int v; }; +namespace N { +template struct Ptr { + T* operator->() { return {}; } +}; +template Ptr makePtr() { return {}; } +} +int func() +{ + auto s = N::makePtr(); + return s->v; +})"; + + Document::Ptr doc = Document::create("templateInNamespaceTypeOutside"); + doc->setUtf8Source(src); + doc->parse(); + doc->check(); + + QVERIFY(doc->diagnosticMessages().isEmpty()); + QCOMPARE(doc->globalSymbolCount(), 3); + + Snapshot snapshot; + snapshot.insert(doc); + + Class * const structDecl = doc->globalSymbolAt(0)->asClass(); + QVERIFY(structDecl); + QCOMPARE(structDecl->memberCount(), 1); + Symbol * const v = structDecl->memberAt(0); + + FindUsages findUsages(src, doc, snapshot, true); + findUsages(v); + QCOMPARE(findUsages.usages().size(), 2); + QCOMPARE(findUsages.usages().at(0).type, Usage::Type::Declaration); + QCOMPARE(findUsages.usages().at(1).type, Usage::Type::Read); +} + void tst_FindUsages::usingInDifferentNamespace_QTCREATORBUG7978() { const QByteArray src = "\n"