aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins')
-rw-r--r--src/plugins/CMakeLists.txt7
-rw-r--r--src/plugins/android/Android.json.in10
-rw-r--r--src/plugins/android/androidavdmanager.cpp267
-rw-r--r--src/plugins/android/androidavdmanager.h30
-rw-r--r--src/plugins/android/androidbuildapkstep.cpp15
-rw-r--r--src/plugins/android/androidconfigurations.cpp928
-rw-r--r--src/plugins/android/androidconfigurations.h180
-rw-r--r--src/plugins/android/androidcreatekeystorecertificate.cpp6
-rw-r--r--src/plugins/android/androiddebugsupport.cpp5
-rw-r--r--src/plugins/android/androiddeployqtstep.cpp166
-rw-r--r--src/plugins/android/androiddevice.cpp823
-rw-r--r--src/plugins/android/androiddevice.h44
-rw-r--r--src/plugins/android/androidmanager.cpp202
-rw-r--r--src/plugins/android/androidmanager.h1
-rw-r--r--src/plugins/android/androidmanifesteditorwidget.cpp2
-rw-r--r--src/plugins/android/androidplugin.cpp8
-rw-r--r--src/plugins/android/androidqmlpreviewworker.cpp149
-rw-r--r--src/plugins/android/androidqtversion.cpp15
-rw-r--r--src/plugins/android/androidrunner.cpp16
-rw-r--r--src/plugins/android/androidrunnerworker.cpp181
-rw-r--r--src/plugins/android/androidrunnerworker.h35
-rw-r--r--src/plugins/android/androidsdkdownloader.cpp12
-rw-r--r--src/plugins/android/androidsdkmanager.cpp44
-rw-r--r--src/plugins/android/androidsdkmanagerdialog.cpp26
-rw-r--r--src/plugins/android/androidsettingswidget.cpp93
-rw-r--r--src/plugins/android/androidsignaloperation.cpp2
-rw-r--r--src/plugins/android/androidtoolchain.cpp27
-rw-r--r--src/plugins/android/avddialog.cpp121
-rw-r--r--src/plugins/android/avddialog.h5
-rw-r--r--src/plugins/android/avdmanageroutputparser.cpp8
-rw-r--r--src/plugins/android/avdmanageroutputparser.h8
-rw-r--r--src/plugins/android/javalanguageserver.cpp2
-rw-r--r--src/plugins/android/javaparser.cpp2
-rw-r--r--src/plugins/appstatisticsmonitor/AppStatisticsMonitor.json.in (renamed from src/plugins/luatemplates/LuaTemplates.json.in)13
-rw-r--r--src/plugins/appstatisticsmonitor/CMakeLists.txt14
-rw-r--r--src/plugins/appstatisticsmonitor/appstatisticsmonitor.qbs20
-rw-r--r--src/plugins/appstatisticsmonitor/appstatisticsmonitorplugin.cpp32
-rw-r--r--src/plugins/appstatisticsmonitor/appstatisticsmonitortr.h15
-rw-r--r--src/plugins/appstatisticsmonitor/chart.cpp266
-rw-r--r--src/plugins/appstatisticsmonitor/chart.h70
-rw-r--r--src/plugins/appstatisticsmonitor/idataprovider.cpp237
-rw-r--r--src/plugins/appstatisticsmonitor/idataprovider.h44
-rw-r--r--src/plugins/appstatisticsmonitor/manager.cpp220
-rw-r--r--src/plugins/appstatisticsmonitor/manager.h60
-rw-r--r--src/plugins/autotest/AutoTest.json.in8
-rw-r--r--src/plugins/autotest/ctest/ctesttool.cpp12
-rw-r--r--src/plugins/autotest/ctest/ctesttreeitem.cpp2
-rw-r--r--src/plugins/autotest/projectsettingswidget.cpp12
-rw-r--r--src/plugins/autotest/testnavigationwidget.cpp4
-rw-r--r--src/plugins/autotest/testresult.cpp19
-rw-r--r--src/plugins/autotest/testresultdelegate.cpp9
-rw-r--r--src/plugins/autotest/testresultspane.cpp4
-rw-r--r--src/plugins/autotest/testrunner.cpp2
-rw-r--r--src/plugins/autotest/testsettingspage.cpp2
-rw-r--r--src/plugins/autotoolsprojectmanager/AutotoolsProjectManager.json.in9
-rw-r--r--src/plugins/axivion/Axivion.json.in5
-rw-r--r--src/plugins/axivion/axivionoutputpane.cpp4
-rw-r--r--src/plugins/axivion/axivionplugin.cpp61
-rw-r--r--src/plugins/axivion/axivionplugin.h2
-rw-r--r--src/plugins/axivion/axivionprojectsettings.cpp52
-rw-r--r--src/plugins/axivion/axivionprojectsettings.h6
-rw-r--r--src/plugins/axivion/axivionsettings.cpp197
-rw-r--r--src/plugins/axivion/axivionsettings.h14
-rw-r--r--src/plugins/axivion/dynamiclistmodel.cpp2
-rw-r--r--src/plugins/baremetal/BareMetal.json.in8
-rw-r--r--src/plugins/baremetal/debugservers/gdb/eblinkgdbserverprovider.cpp2
-rw-r--r--src/plugins/baremetal/debugservers/gdb/gdbserverprovider.cpp10
-rw-r--r--src/plugins/baremetal/debugservers/gdb/stlinkutilgdbserverprovider.cpp2
-rw-r--r--src/plugins/baremetal/debugservers/uvsc/uvscserverprovider.cpp12
-rw-r--r--src/plugins/baremetal/iarewparser.cpp4
-rw-r--r--src/plugins/baremetal/keilparser.cpp12
-rw-r--r--src/plugins/baremetal/keiltoolchain.cpp6
-rw-r--r--src/plugins/baremetal/sdccparser.cpp8
-rw-r--r--src/plugins/bazaar/Bazaar.json.in8
-rw-r--r--src/plugins/bazaar/bazaarplugin.cpp15
-rw-r--r--src/plugins/beautifier/Beautifier.json.in10
-rw-r--r--src/plugins/beautifier/clangformat/clangformat.cpp3
-rw-r--r--src/plugins/beautifier/generalsettings.cpp2
-rw-r--r--src/plugins/bineditor/BinEditor.json.in5
-rw-r--r--src/plugins/boot2qt/Boot2Qt.json.in10
-rw-r--r--src/plugins/boot2qt/qdbdevice.cpp2
-rw-r--r--src/plugins/boot2qt/qdbplugin.cpp2
-rw-r--r--src/plugins/clangcodemodel/ClangCodeModel.json.in7
-rw-r--r--src/plugins/clangcodemodel/clangcodemodelplugin.cpp16
-rw-r--r--src/plugins/clangcodemodel/clangdclient.cpp138
-rw-r--r--src/plugins/clangcodemodel/clangdclient.h2
-rw-r--r--src/plugins/clangcodemodel/clangdfollowsymbol.cpp56
-rw-r--r--src/plugins/clangcodemodel/clangdfollowsymbol.h6
-rw-r--r--src/plugins/clangcodemodel/clangdlocatorfilters.cpp3
-rw-r--r--src/plugins/clangcodemodel/clangdquickfixes.h2
-rw-r--r--src/plugins/clangcodemodel/clangdsemantichighlighting.cpp762
-rw-r--r--src/plugins/clangcodemodel/clangdsemantichighlighting.h3
-rw-r--r--src/plugins/clangcodemodel/clangfixitoperation.cpp5
-rw-r--r--src/plugins/clangcodemodel/clangmodelmanagersupport.cpp16
-rw-r--r--src/plugins/clangcodemodel/clangutils.cpp30
-rw-r--r--src/plugins/clangcodemodel/clangutils.h25
-rw-r--r--src/plugins/clangcodemodel/test/clangdtests.cpp7
-rw-r--r--src/plugins/clangformat/ClangFormat.json.in7
-rw-r--r--src/plugins/clangformat/clangformatbaseindenter.cpp83
-rw-r--r--src/plugins/clangformat/clangformatbaseindenter.h4
-rw-r--r--src/plugins/clangformat/clangformatglobalconfigwidget.cpp5
-rw-r--r--src/plugins/clangformat/clangformatindenter.cpp2
-rw-r--r--src/plugins/clangformat/clangformatutils.cpp1
-rw-r--r--src/plugins/clangformat/tests/clangformat-test.cpp166
-rw-r--r--src/plugins/clangtools/ClangTools.json.in9
-rw-r--r--src/plugins/clangtools/clangtool.cpp4
-rw-r--r--src/plugins/clangtools/clangtoolrunner.cpp13
-rw-r--r--src/plugins/clangtools/clangtoolsdiagnosticview.cpp19
-rw-r--r--src/plugins/clangtools/documentquickfixfactory.h2
-rw-r--r--src/plugins/clangtools/executableinfo.cpp5
-rw-r--r--src/plugins/classview/ClassView.json.in5
-rw-r--r--src/plugins/clearcase/ClearCase.json.in8
-rw-r--r--src/plugins/clearcase/clearcaseplugin.cpp6
-rw-r--r--src/plugins/cmakeprojectmanager/CMakeLists.txt2
-rw-r--r--src/plugins/cmakeprojectmanager/CMakeProjectManager.json.in10
-rw-r--r--src/plugins/cmakeprojectmanager/builddirparameters.cpp6
-rw-r--r--src/plugins/cmakeprojectmanager/builddirparameters.h5
-rw-r--r--src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp128
-rw-r--r--src/plugins/cmakeprojectmanager/cmakebuildstep.cpp11
-rw-r--r--src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp32
-rw-r--r--src/plugins/cmakeprojectmanager/cmakebuildsystem.h3
-rw-r--r--src/plugins/cmakeprojectmanager/cmakeformatter.cpp2
-rw-r--r--src/plugins/cmakeprojectmanager/cmakekitaspect.cpp10
-rw-r--r--src/plugins/cmakeprojectmanager/cmakeparser.cpp21
-rw-r--r--src/plugins/cmakeprojectmanager/cmakeprocess.cpp24
-rw-r--r--src/plugins/cmakeprojectmanager/cmakeproject.cpp14
-rw-r--r--src/plugins/cmakeprojectmanager/cmakeproject.h4
-rw-r--r--src/plugins/cmakeprojectmanager/cmakeprojectconstants.h3
-rw-r--r--src/plugins/cmakeprojectmanager/cmakeprojectimporter.cpp58
-rw-r--r--src/plugins/cmakeprojectmanager/cmakeprojectmanager.cpp45
-rw-r--r--src/plugins/cmakeprojectmanager/cmakeprojectmanager.h1
-rw-r--r--src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs1
-rw-r--r--src/plugins/cmakeprojectmanager/cmakeprojectplugin.cpp2
-rw-r--r--src/plugins/cmakeprojectmanager/cmakespecificsettings.cpp159
-rw-r--r--src/plugins/cmakeprojectmanager/cmakespecificsettings.h14
-rw-r--r--src/plugins/cmakeprojectmanager/cmaketool.cpp8
-rw-r--r--src/plugins/cmakeprojectmanager/cmaketool.h3
-rw-r--r--src/plugins/cmakeprojectmanager/cmaketoolmanager.cpp21
-rw-r--r--src/plugins/cmakeprojectmanager/cmaketoolmanager.h6
-rw-r--r--src/plugins/cmakeprojectmanager/cmaketoolsettingsaccessor.cpp6
-rw-r--r--src/plugins/cmakeprojectmanager/configmodel.cpp4
-rw-r--r--src/plugins/cmakeprojectmanager/fileapidataextractor.cpp54
-rw-r--r--src/plugins/cmakeprojectmanager/fileapiparser.cpp7
-rw-r--r--src/plugins/cmakeprojectmanager/fileapireader.cpp7
-rw-r--r--src/plugins/cmakeprojectmanager/presetsmacros.cpp17
-rw-r--r--src/plugins/cmakeprojectmanager/presetsparser.cpp41
-rw-r--r--src/plugins/cmakeprojectmanager/presetsparser.h6
-rw-r--r--src/plugins/coco/Coco.json.in7
-rw-r--r--src/plugins/compilationdatabaseprojectmanager/CompilationDatabaseProjectManager.json.in10
-rw-r--r--src/plugins/compilationdatabaseprojectmanager/compilationdatabaseproject.cpp6
-rw-r--r--src/plugins/compilationdatabaseprojectmanager/compilationdatabaseutils.h3
-rw-r--r--src/plugins/compilationdatabaseprojectmanager/compilationdbparser.cpp24
-rw-r--r--src/plugins/compilationdatabaseprojectmanager/compilationdbparser.h1
-rw-r--r--src/plugins/compilerexplorer/CompilerExplorer.json.in2
-rw-r--r--src/plugins/compilerexplorer/compilerexplorer.qrc2
-rw-r--r--src/plugins/compilerexplorer/compilerexploreraspects.cpp4
-rw-r--r--src/plugins/compilerexplorer/compilerexploreraspects.h2
-rw-r--r--src/plugins/compilerexplorer/compilerexplorereditor.cpp53
-rw-r--r--src/plugins/compilerexplorer/wizard/qtcpp/file.qtce18
-rw-r--r--src/plugins/compilerexplorer/wizard/qtcpp/wizard.json37
-rw-r--r--src/plugins/conan/Conan.json.in8
-rw-r--r--src/plugins/copilot/Copilot.json.in9
-rw-r--r--src/plugins/copilot/copilotsettings.cpp73
-rw-r--r--src/plugins/coreplugin/Core.json.in2
-rw-r--r--src/plugins/coreplugin/actionmanager/actionmanager.cpp2
-rw-r--r--src/plugins/coreplugin/actionsfilter.cpp3
-rw-r--r--src/plugins/coreplugin/coreconstants.h1
-rw-r--r--src/plugins/coreplugin/corejsextensions.cpp5
-rw-r--r--src/plugins/coreplugin/corejsextensions.h1
-rw-r--r--src/plugins/coreplugin/coreplugin.cpp3
-rw-r--r--src/plugins/coreplugin/designmode.cpp12
-rw-r--r--src/plugins/coreplugin/designmode.h7
-rw-r--r--src/plugins/coreplugin/dialogs/shortcutsettings.cpp15
-rw-r--r--src/plugins/coreplugin/editormanager/editormanager.cpp113
-rw-r--r--src/plugins/coreplugin/editormanager/editormanager_p.h8
-rw-r--r--src/plugins/coreplugin/editormanager/editorview.cpp253
-rw-r--r--src/plugins/coreplugin/editormanager/editorview.h12
-rw-r--r--src/plugins/coreplugin/editormanager/ieditorfactory.cpp39
-rw-r--r--src/plugins/coreplugin/editortoolbar.cpp74
-rw-r--r--src/plugins/coreplugin/editortoolbar.h13
-rw-r--r--src/plugins/coreplugin/fancyactionbar.cpp24
-rw-r--r--src/plugins/coreplugin/fancytabwidget.cpp24
-rw-r--r--src/plugins/coreplugin/find/findplugin.cpp137
-rw-r--r--src/plugins/coreplugin/find/findtoolbar.cpp33
-rw-r--r--src/plugins/coreplugin/find/findtoolbar.h9
-rw-r--r--src/plugins/coreplugin/find/findtoolwindow.cpp24
-rw-r--r--src/plugins/coreplugin/find/findtoolwindow.h7
-rw-r--r--src/plugins/coreplugin/find/highlightscrollbarcontroller.cpp2
-rw-r--r--src/plugins/coreplugin/find/ifindfilter.cpp72
-rw-r--r--src/plugins/coreplugin/find/ifindfilter.h10
-rw-r--r--src/plugins/coreplugin/find/searchresultwidget.cpp4
-rw-r--r--src/plugins/coreplugin/find/searchresultwindow.cpp11
-rw-r--r--src/plugins/coreplugin/generalsettings.cpp2
-rw-r--r--src/plugins/coreplugin/helpmanager.cpp6
-rw-r--r--src/plugins/coreplugin/helpmanager.h6
-rw-r--r--src/plugins/coreplugin/helpmanager_implementation.h1
-rw-r--r--src/plugins/coreplugin/icore.cpp13
-rw-r--r--src/plugins/coreplugin/iversioncontrol.cpp2
-rw-r--r--src/plugins/coreplugin/iwizardfactory.cpp4
-rw-r--r--src/plugins/coreplugin/locator/directoryfilter.cpp39
-rw-r--r--src/plugins/coreplugin/locator/directoryfilter.h1
-rw-r--r--src/plugins/coreplugin/locator/filesystemfilter.cpp24
-rw-r--r--src/plugins/coreplugin/locator/filesystemfilter.h1
-rw-r--r--src/plugins/coreplugin/locator/ilocatorfilter.cpp191
-rw-r--r--src/plugins/coreplugin/locator/ilocatorfilter.h6
-rw-r--r--src/plugins/coreplugin/locator/locatorsettingspage.cpp25
-rw-r--r--src/plugins/coreplugin/locator/locatorwidget.cpp4
-rw-r--r--src/plugins/coreplugin/locator/opendocumentsfilter.cpp3
-rw-r--r--src/plugins/coreplugin/locator/spotlightlocatorfilter.cpp3
-rw-r--r--src/plugins/coreplugin/locator/urllocatorfilter.cpp28
-rw-r--r--src/plugins/coreplugin/locator/urllocatorfilter.h1
-rw-r--r--src/plugins/coreplugin/loggingviewer.cpp4
-rw-r--r--src/plugins/coreplugin/manhattanstyle.cpp51
-rw-r--r--src/plugins/coreplugin/messagemanager.cpp107
-rw-r--r--src/plugins/coreplugin/messagemanager.h49
-rw-r--r--src/plugins/coreplugin/minisplitter.cpp2
-rw-r--r--src/plugins/coreplugin/outputpanemanager.cpp24
-rw-r--r--src/plugins/coreplugin/plugindialog.cpp2
-rw-r--r--src/plugins/coreplugin/plugininstallwizard.cpp17
-rw-r--r--src/plugins/coreplugin/plugininstallwizard.h12
-rw-r--r--src/plugins/coreplugin/progressmanager/futureprogress.cpp2
-rw-r--r--src/plugins/coreplugin/progressmanager/progressbar.cpp9
-rw-r--r--src/plugins/coreplugin/progressmanager/progressmanager.cpp2
-rw-r--r--src/plugins/coreplugin/welcomepagehelper.cpp26
-rw-r--r--src/plugins/coreplugin/welcomepagehelper.h5
-rw-r--r--src/plugins/cpaster/CodePaster.json.in7
-rw-r--r--src/plugins/cppcheck/Cppcheck.json.in8
-rw-r--r--src/plugins/cppcheck/cppcheckrunner.cpp13
-rw-r--r--src/plugins/cppcheck/cppchecksettings.cpp2
-rw-r--r--src/plugins/cppcheck/cppchecksettings.h2
-rw-r--r--src/plugins/cppcheck/cppchecktool.cpp10
-rw-r--r--src/plugins/cppcheck/cppchecktool.h1
-rw-r--r--src/plugins/cppeditor/CMakeLists.txt48
-rw-r--r--src/plugins/cppeditor/CppEditor.json.in5
-rw-r--r--src/plugins/cppeditor/clangdsettings.cpp2
-rw-r--r--src/plugins/cppeditor/compileroptionsbuilder.cpp4
-rw-r--r--src/plugins/cppeditor/cppbuiltinmodelmanagersupport.cpp3
-rw-r--r--src/plugins/cppeditor/cppcodegen_test.cpp2
-rw-r--r--src/plugins/cppeditor/cppcodemodelinspectordialog.cpp4
-rw-r--r--src/plugins/cppeditor/cppcodemodelsettings.cpp11
-rw-r--r--src/plugins/cppeditor/cppcodemodelsettings.h16
-rw-r--r--src/plugins/cppeditor/cppcodestylesettingspage.cpp6
-rw-r--r--src/plugins/cppeditor/cppeditor.qbs103
-rw-r--r--src/plugins/cppeditor/cppeditor.qrc53
-rw-r--r--src/plugins/cppeditor/cppeditordocument.cpp2
-rw-r--r--src/plugins/cppeditor/cppeditorplugin.cpp14
-rw-r--r--src/plugins/cppeditor/cppeditorwidget.cpp3
-rw-r--r--src/plugins/cppeditor/cppfunctiondecldeflink.cpp10
-rw-r--r--src/plugins/cppeditor/cpplocatorfilter.cpp4
-rw-r--r--src/plugins/cppeditor/cppmodelmanager.cpp10
-rw-r--r--src/plugins/cppeditor/cppmodelmanager_test.cpp25
-rw-r--r--src/plugins/cppeditor/cppoutlinemodel.cpp2
-rw-r--r--src/plugins/cppeditor/cppquickfix.cpp26
-rw-r--r--src/plugins/cppeditor/cppquickfix_test.cpp9951
-rw-r--r--src/plugins/cppeditor/cppquickfix_test.h236
-rw-r--r--src/plugins/cppeditor/cppquickfixes.cpp10104
-rw-r--r--src/plugins/cppeditor/cppquickfixes.h630
-rw-r--r--src/plugins/cppeditor/cpprefactoringchanges.cpp50
-rw-r--r--src/plugins/cppeditor/cpprefactoringchanges.h5
-rw-r--r--src/plugins/cppeditor/cpprenaming_test.cpp2
-rw-r--r--src/plugins/cppeditor/cppsemanticinfoupdater.cpp4
-rw-r--r--src/plugins/cppeditor/cpptoolsreuse.cpp11
-rw-r--r--src/plugins/cppeditor/cpptoolstestcase.cpp15
-rw-r--r--src/plugins/cppeditor/cpptoolstestcase.h12
-rw-r--r--src/plugins/cppeditor/cppuseselectionsupdater.cpp4
-rw-r--r--src/plugins/cppeditor/cursorineditor.h9
-rw-r--r--src/plugins/cppeditor/fileandtokenactions_test.cpp6
-rw-r--r--src/plugins/cppeditor/quickfixes/assigntolocalvariable.cpp510
-rw-r--r--src/plugins/cppeditor/quickfixes/assigntolocalvariable.h8
-rw-r--r--src/plugins/cppeditor/quickfixes/bringidentifierintoscope.cpp1497
-rw-r--r--src/plugins/cppeditor/quickfixes/bringidentifierintoscope.h8
-rw-r--r--src/plugins/cppeditor/quickfixes/completeswitchstatement.cpp793
-rw-r--r--src/plugins/cppeditor/quickfixes/completeswitchstatement.h8
-rw-r--r--src/plugins/cppeditor/quickfixes/convertfromandtopointer.cpp704
-rw-r--r--src/plugins/cppeditor/quickfixes/convertfromandtopointer.h8
-rw-r--r--src/plugins/cppeditor/quickfixes/convertnumericliteral.cpp199
-rw-r--r--src/plugins/cppeditor/quickfixes/convertnumericliteral.h8
-rw-r--r--src/plugins/cppeditor/quickfixes/convertqt4connect.cpp505
-rw-r--r--src/plugins/cppeditor/quickfixes/convertqt4connect.h8
-rw-r--r--src/plugins/cppeditor/quickfixes/convertstringliteral.cpp746
-rw-r--r--src/plugins/cppeditor/quickfixes/convertstringliteral.h8
-rw-r--r--src/plugins/cppeditor/quickfixes/converttocamelcase.cpp184
-rw-r--r--src/plugins/cppeditor/quickfixes/converttocamelcase.h8
-rw-r--r--src/plugins/cppeditor/quickfixes/converttometamethodcall.cpp273
-rw-r--r--src/plugins/cppeditor/quickfixes/converttometamethodcall.h8
-rw-r--r--src/plugins/cppeditor/quickfixes/cppcodegenerationquickfixes.cpp5067
-rw-r--r--src/plugins/cppeditor/quickfixes/cppcodegenerationquickfixes.h8
-rw-r--r--src/plugins/cppeditor/quickfixes/cppinsertvirtualmethods.cpp (renamed from src/plugins/cppeditor/cppinsertvirtualmethods.cpp)43
-rw-r--r--src/plugins/cppeditor/quickfixes/cppinsertvirtualmethods.h (renamed from src/plugins/cppeditor/cppinsertvirtualmethods.h)16
-rw-r--r--src/plugins/cppeditor/quickfixes/cppquickfix.cpp187
-rw-r--r--src/plugins/cppeditor/quickfixes/cppquickfix.h (renamed from src/plugins/cppeditor/cppquickfix.h)28
-rw-r--r--src/plugins/cppeditor/quickfixes/cppquickfix_test.cpp251
-rw-r--r--src/plugins/cppeditor/quickfixes/cppquickfix_test.h94
-rw-r--r--src/plugins/cppeditor/quickfixes/cppquickfixassistant.cpp (renamed from src/plugins/cppeditor/cppquickfixassistant.cpp)6
-rw-r--r--src/plugins/cppeditor/quickfixes/cppquickfixassistant.h (renamed from src/plugins/cppeditor/cppquickfixassistant.h)2
-rw-r--r--src/plugins/cppeditor/quickfixes/cppquickfixhelpers.cpp199
-rw-r--r--src/plugins/cppeditor/quickfixes/cppquickfixhelpers.h47
-rw-r--r--src/plugins/cppeditor/quickfixes/cppquickfixprojectsettings.cpp (renamed from src/plugins/cppeditor/cppquickfixprojectsettings.cpp)4
-rw-r--r--src/plugins/cppeditor/quickfixes/cppquickfixprojectsettings.h (renamed from src/plugins/cppeditor/cppquickfixprojectsettings.h)0
-rw-r--r--src/plugins/cppeditor/quickfixes/cppquickfixprojectsettingswidget.cpp (renamed from src/plugins/cppeditor/cppquickfixprojectsettingswidget.cpp)4
-rw-r--r--src/plugins/cppeditor/quickfixes/cppquickfixprojectsettingswidget.h (renamed from src/plugins/cppeditor/cppquickfixprojectsettingswidget.h)0
-rw-r--r--src/plugins/cppeditor/quickfixes/cppquickfixsettings.cpp (renamed from src/plugins/cppeditor/cppquickfixsettings.cpp)4
-rw-r--r--src/plugins/cppeditor/quickfixes/cppquickfixsettings.h (renamed from src/plugins/cppeditor/cppquickfixsettings.h)0
-rw-r--r--src/plugins/cppeditor/quickfixes/cppquickfixsettingspage.cpp (renamed from src/plugins/cppeditor/cppquickfixsettingspage.cpp)4
-rw-r--r--src/plugins/cppeditor/quickfixes/cppquickfixsettingspage.h (renamed from src/plugins/cppeditor/cppquickfixsettingspage.h)0
-rw-r--r--src/plugins/cppeditor/quickfixes/cppquickfixsettingswidget.cpp (renamed from src/plugins/cppeditor/cppquickfixsettingswidget.cpp)6
-rw-r--r--src/plugins/cppeditor/quickfixes/cppquickfixsettingswidget.h (renamed from src/plugins/cppeditor/cppquickfixsettingswidget.h)0
-rw-r--r--src/plugins/cppeditor/quickfixes/createdeclarationfromuse.cpp1208
-rw-r--r--src/plugins/cppeditor/quickfixes/createdeclarationfromuse.h8
-rw-r--r--src/plugins/cppeditor/quickfixes/extractfunction.cpp762
-rw-r--r--src/plugins/cppeditor/quickfixes/extractfunction.h7
-rw-r--r--src/plugins/cppeditor/quickfixes/extractliteralasparameter.cpp560
-rw-r--r--src/plugins/cppeditor/quickfixes/extractliteralasparameter.h8
-rw-r--r--src/plugins/cppeditor/quickfixes/insertfunctiondefinition.cpp2123
-rw-r--r--src/plugins/cppeditor/quickfixes/insertfunctiondefinition.h8
-rw-r--r--src/plugins/cppeditor/quickfixes/logicaloperationquickfixes.cpp381
-rw-r--r--src/plugins/cppeditor/quickfixes/logicaloperationquickfixes.h8
-rw-r--r--src/plugins/cppeditor/quickfixes/moveclasstoownfile.cpp722
-rw-r--r--src/plugins/cppeditor/quickfixes/moveclasstoownfile.h8
-rw-r--r--src/plugins/cppeditor/quickfixes/movefunctiondefinition.cpp1889
-rw-r--r--src/plugins/cppeditor/quickfixes/movefunctiondefinition.h8
-rw-r--r--src/plugins/cppeditor/quickfixes/rearrangeparamdeclarationlist.cpp121
-rw-r--r--src/plugins/cppeditor/quickfixes/rearrangeparamdeclarationlist.h8
-rw-r--r--src/plugins/cppeditor/quickfixes/reformatpointerdeclaration.cpp186
-rw-r--r--src/plugins/cppeditor/quickfixes/reformatpointerdeclaration.h8
-rw-r--r--src/plugins/cppeditor/quickfixes/removeusingnamespace.cpp957
-rw-r--r--src/plugins/cppeditor/quickfixes/removeusingnamespace.h8
-rw-r--r--src/plugins/cppeditor/quickfixes/rewritecomment.cpp878
-rw-r--r--src/plugins/cppeditor/quickfixes/rewritecomment.h8
-rw-r--r--src/plugins/cppeditor/quickfixes/rewritecontrolstatements.cpp1323
-rw-r--r--src/plugins/cppeditor/quickfixes/rewritecontrolstatements.h8
-rw-r--r--src/plugins/cppeditor/quickfixes/splitsimpledeclaration.cpp140
-rw-r--r--src/plugins/cppeditor/quickfixes/splitsimpledeclaration.h8
-rw-r--r--src/plugins/cppeditor/quickfixes/synchronizememberfunctionorder.cpp340
-rw-r--r--src/plugins/cppeditor/quickfixes/synchronizememberfunctionorder.h8
-rw-r--r--src/plugins/cppeditor/symbolsfindfilter.cpp26
-rw-r--r--src/plugins/cppeditor/symbolsfindfilter.h7
-rw-r--r--src/plugins/cppeditor/testcases/move-class/complex/complex.pro2
-rw-r--r--src/plugins/cppeditor/testcases/move-class/complex/complex.pro_expected4
-rw-r--r--src/plugins/cppeditor/testcases/move-class/complex/main.cpp11
-rw-r--r--src/plugins/cppeditor/testcases/move-class/complex/main.cpp_expected11
-rw-r--r--src/plugins/cppeditor/testcases/move-class/complex/theclass.cpp_expected20
-rw-r--r--src/plugins/cppeditor/testcases/move-class/complex/theclass.h_expected29
-rw-r--r--src/plugins/cppeditor/testcases/move-class/complex/theheader.h30
-rw-r--r--src/plugins/cppeditor/testcases/move-class/complex/theheader.h_expected18
-rw-r--r--src/plugins/cppeditor/testcases/move-class/complex/thesource.cpp19
-rw-r--r--src/plugins/cppeditor/testcases/move-class/complex/thesource.cpp_expected15
-rw-r--r--src/plugins/cppeditor/testcases/move-class/decl-in-source/decl-in-source.pro2
-rw-r--r--src/plugins/cppeditor/testcases/move-class/decl-in-source/decl-in-source.pro_expected3
-rw-r--r--src/plugins/cppeditor/testcases/move-class/decl-in-source/theclass.h_expected11
-rw-r--r--src/plugins/cppeditor/testcases/move-class/decl-in-source/theheader.h4
-rw-r--r--src/plugins/cppeditor/testcases/move-class/decl-in-source/thesource.cpp8
-rw-r--r--src/plugins/cppeditor/testcases/move-class/decl-in-source/thesource.cpp_expected6
-rw-r--r--src/plugins/cppeditor/testcases/move-class/header-only/header-only.pro2
-rw-r--r--src/plugins/cppeditor/testcases/move-class/header-only/header-only.pro_expected3
-rw-r--r--src/plugins/cppeditor/testcases/move-class/header-only/theclass.h_expected10
-rw-r--r--src/plugins/cppeditor/testcases/move-class/header-only/theheader.h7
-rw-r--r--src/plugins/cppeditor/testcases/move-class/header-only/theheader.h_expected7
-rw-r--r--src/plugins/cppeditor/testcases/move-class/header-only/thesource.cpp3
-rw-r--r--src/plugins/cppeditor/testcases/move-class/header-only/thesource.cpp_expected3
-rw-r--r--src/plugins/cppeditor/testcases/move-class/match1/TheClass.h2
-rw-r--r--src/plugins/cppeditor/testcases/move-class/match1/match1.pro1
-rw-r--r--src/plugins/cppeditor/testcases/move-class/match2/match2.pro1
-rw-r--r--src/plugins/cppeditor/testcases/move-class/match2/theclass.h2
-rw-r--r--src/plugins/cppeditor/testcases/move-class/match3/match3.pro1
-rw-r--r--src/plugins/cppeditor/testcases/move-class/match3/the_class.h2
-rw-r--r--src/plugins/cppeditor/testcases/move-class/nested/main.cpp6
-rw-r--r--src/plugins/cppeditor/testcases/move-class/nested/nested.pro1
-rw-r--r--src/plugins/cppeditor/testcases/move-class/single/single.pro1
-rw-r--r--src/plugins/cppeditor/testcases/move-class/single/theheader.h2
-rw-r--r--src/plugins/cppeditor/testcases/move-class/template/template.pro1
-rw-r--r--src/plugins/cppeditor/testcases/move-class/template/template.pro_expected2
-rw-r--r--src/plugins/cppeditor/testcases/move-class/template/theclass.h_expected11
-rw-r--r--src/plugins/cppeditor/testcases/move-class/template/theheader.h7
-rw-r--r--src/plugins/cppeditor/testcases/move-class/template/theheader.h_expected6
-rw-r--r--src/plugins/cppeditor/testcases/reorder-member-impls/already-sorted/already-sorted.pro1
-rw-r--r--src/plugins/cppeditor/testcases/reorder-member-impls/already-sorted/header.h9
-rw-r--r--src/plugins/cppeditor/testcases/reorder-member-impls/already-sorted/header.h_expected9
-rw-r--r--src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/different-locations.pro2
-rw-r--r--src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/header.h15
-rw-r--r--src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/header.h_expected15
-rw-r--r--src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/impl1.cpp5
-rw-r--r--src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/impl1.cpp_expected5
-rw-r--r--src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/impl2.cpp6
-rw-r--r--src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/impl2.cpp_expected6
-rw-r--r--src/plugins/cppeditor/testcases/reorder-member-impls/no-out-of-line/header.h5
-rw-r--r--src/plugins/cppeditor/testcases/reorder-member-impls/no-out-of-line/header.h_expected5
-rw-r--r--src/plugins/cppeditor/testcases/reorder-member-impls/no-out-of-line/no-out-of-line.pro1
-rw-r--r--src/plugins/cppeditor/testcases/reorder-member-impls/templates/header.h10
-rw-r--r--src/plugins/cppeditor/testcases/reorder-member-impls/templates/header.h_expected10
-rw-r--r--src/plugins/cppeditor/testcases/reorder-member-impls/templates/templates.pro1
-rw-r--r--src/plugins/ctfvisualizer/CtfVisualizer.json.in6
-rw-r--r--src/plugins/cvs/CVS.json.in5
-rw-r--r--src/plugins/cvs/cvsplugin.cpp2
-rw-r--r--src/plugins/debugger/Debugger.json.in10
-rw-r--r--src/plugins/debugger/cdb/cdbengine.cpp10
-rw-r--r--src/plugins/debugger/commonoptionspage.cpp2
-rw-r--r--src/plugins/debugger/commonoptionspage.h2
-rw-r--r--src/plugins/debugger/console/consoleitemdelegate.cpp11
-rw-r--r--src/plugins/debugger/console/consoleview.cpp3
-rw-r--r--src/plugins/debugger/debuggerengine.h3
-rw-r--r--src/plugins/debugger/debuggeritemmanager.cpp6
-rw-r--r--src/plugins/debugger/debuggerkitaspect.cpp4
-rw-r--r--src/plugins/debugger/debuggerrunconfigurationaspect.cpp2
-rw-r--r--src/plugins/debugger/debuggerruncontrol.cpp2
-rw-r--r--src/plugins/debugger/debuggersourcepathmappingwidget.cpp2
-rw-r--r--src/plugins/debugger/gdb/gdbengine.cpp8
-rw-r--r--src/plugins/debugger/lldb/lldbengine.cpp13
-rw-r--r--src/plugins/debugger/logwindow.cpp15
-rw-r--r--src/plugins/debugger/qml/qmlengine.cpp14
-rw-r--r--src/plugins/debugger/qml/qmlinspectoragent.cpp6
-rw-r--r--src/plugins/debugger/watchdata.h2
-rw-r--r--src/plugins/debugger/watchdelegatewidgets.cpp50
-rw-r--r--src/plugins/debugger/watchdelegatewidgets.h2
-rw-r--r--src/plugins/debugger/watchhandler.cpp26
-rw-r--r--src/plugins/designer/CMakeLists.txt6
-rw-r--r--src/plugins/designer/Designer.json.in7
-rw-r--r--src/plugins/designer/cpp/formclasswizarddialog.cpp2
-rw-r--r--src/plugins/designer/designer.qbs4
-rw-r--r--src/plugins/designer/designerplugin.cpp8
-rw-r--r--src/plugins/designer/formeditor.cpp4
-rw-r--r--src/plugins/designer/formeditor.h2
-rw-r--r--src/plugins/designer/formeditorstack.cpp13
-rw-r--r--src/plugins/designer/formeditorstack.h5
-rw-r--r--src/plugins/designer/formtemplatewizardpage.cpp2
-rw-r--r--src/plugins/designer/formtemplatewizardpage.h2
-rw-r--r--src/plugins/designer/qtcreatorintegration.cpp5
-rw-r--r--src/plugins/designer/qtdesignerformclasscodegenerator.cpp2
-rw-r--r--src/plugins/diffeditor/DiffEditor.json.in5
-rw-r--r--src/plugins/diffeditor/diffeditorplugin.cpp3
-rw-r--r--src/plugins/diffeditor/selectabletexteditorwidget.cpp10
-rw-r--r--src/plugins/diffeditor/sidebysidediffeditorwidget.cpp3
-rw-r--r--src/plugins/diffeditor/unifieddiffeditorwidget.cpp3
-rw-r--r--src/plugins/docker/Docker.json.in8
-rw-r--r--src/plugins/docker/dockerapi.cpp5
-rw-r--r--src/plugins/docker/dockerdevice.cpp6
-rw-r--r--src/plugins/effectcomposer/EffectComposer.json.in4
-rw-r--r--src/plugins/effectcomposer/compositionnode.cpp2
-rw-r--r--src/plugins/effectcomposer/effectcomposermodel.cpp69
-rw-r--r--src/plugins/effectcomposer/effectcomposermodel.h7
-rw-r--r--src/plugins/emacskeys/EmacsKeys.json.in2
-rw-r--r--src/plugins/extensionmanager/CMakeLists.txt10
-rw-r--r--src/plugins/extensionmanager/ExtensionManager.json.in2
-rw-r--r--src/plugins/extensionmanager/extensionmanager.qbs12
-rw-r--r--src/plugins/extensionmanager/extensionmanager.qrc2
-rw-r--r--src/plugins/extensionmanager/extensionmanager_test.cpp40
-rw-r--r--src/plugins/extensionmanager/extensionmanager_test.h14
-rw-r--r--src/plugins/extensionmanager/extensionmanager_test.qrc6
-rw-r--r--src/plugins/extensionmanager/extensionmanagerplugin.cpp11
-rw-r--r--src/plugins/extensionmanager/extensionmanagerwidget.cpp488
-rw-r--r--src/plugins/extensionmanager/extensionmanagerwidget.h18
-rw-r--r--src/plugins/extensionmanager/extensionsbrowser.cpp671
-rw-r--r--src/plugins/extensionmanager/extensionsbrowser.h48
-rw-r--r--src/plugins/extensionmanager/extensionsmodel.cpp410
-rw-r--r--src/plugins/extensionmanager/extensionsmodel.h70
-rw-r--r--src/plugins/extensionmanager/images/download.pngbin0 -> 137 bytes
-rw-r--r--src/plugins/extensionmanager/images/download@2x.pngbin0 -> 207 bytes
-rw-r--r--src/plugins/extensionmanager/images/extensionsmall.pngbin164 -> 314 bytes
-rw-r--r--src/plugins/extensionmanager/images/extensionsmall@2x.pngbin196 -> 504 bytes
-rw-r--r--src/plugins/extensionmanager/images/packsmall.pngbin271 -> 260 bytes
-rw-r--r--src/plugins/extensionmanager/images/packsmall@2x.pngbin454 -> 418 bytes
-rw-r--r--src/plugins/extensionmanager/testdata/defaultpacks.json161
-rw-r--r--src/plugins/extensionmanager/testdata/thirdpartyplugins.json38
-rw-r--r--src/plugins/fakevim/FakeVim.json.in5
-rw-r--r--src/plugins/fakevim/fakevimactions.cpp8
-rw-r--r--src/plugins/fakevim/fakevimhandler.cpp2
-rw-r--r--src/plugins/fossil/Fossil.json.in8
-rw-r--r--src/plugins/fossil/fossilclient.cpp2
-rw-r--r--src/plugins/fossil/fossilplugin.cpp26
-rw-r--r--src/plugins/genericprojectmanager/GenericProjectManager.json.in2
-rw-r--r--src/plugins/git/Git.json.in8
-rw-r--r--src/plugins/git/changeselectiondialog.cpp6
-rw-r--r--src/plugins/git/gerrit/gerritpushdialog.cpp2
-rw-r--r--src/plugins/git/gitclient.cpp6
-rw-r--r--src/plugins/git/gitgrep.cpp9
-rw-r--r--src/plugins/git/gitgrep.h4
-rw-r--r--src/plugins/git/gitplugin.cpp6
-rw-r--r--src/plugins/git/gitsettings.cpp2
-rw-r--r--src/plugins/git/gitsubmiteditor.cpp3
-rw-r--r--src/plugins/git/gitsubmiteditorwidget.cpp3
-rw-r--r--src/plugins/git/instantblame.cpp7
-rw-r--r--src/plugins/git/mergetool.cpp4
-rw-r--r--src/plugins/gitlab/GitLab.json.in8
-rw-r--r--src/plugins/gitlab/gitlaboptionspage.cpp2
-rw-r--r--src/plugins/glsleditor/GLSLEditor.json.in7
-rw-r--r--src/plugins/haskell/Haskell.json.in3
-rw-r--r--src/plugins/helloworld/HelloWorld.json.in2
-rw-r--r--src/plugins/help/Help.json.in8
-rw-r--r--src/plugins/help/generalsettingspage.cpp9
-rw-r--r--src/plugins/help/helpindexfilter.cpp2
-rw-r--r--src/plugins/help/helpmanager.cpp12
-rw-r--r--src/plugins/help/helpmanager.h2
-rw-r--r--src/plugins/help/helpplugin.cpp4
-rw-r--r--src/plugins/help/helpwidget.cpp26
-rw-r--r--src/plugins/help/localhelpmanager.cpp65
-rw-r--r--src/plugins/help/localhelpmanager.h6
-rw-r--r--src/plugins/imageviewer/ImageViewer.json.in5
-rw-r--r--src/plugins/incredibuild/IncrediBuild.json.in8
-rw-r--r--src/plugins/incredibuild/commandbuilderaspect.cpp2
-rw-r--r--src/plugins/incredibuild/commandbuilderaspect.h2
-rw-r--r--src/plugins/insight/Insight.json.in9
-rw-r--r--src/plugins/ios/Ios.json.in10
-rw-r--r--src/plugins/ios/iosconfigurations.cpp7
-rw-r--r--src/plugins/ios/iosrunconfiguration.cpp2
-rw-r--r--src/plugins/ios/iosrunconfiguration.h2
-rw-r--r--src/plugins/ios/iosrunner.cpp22
-rw-r--r--src/plugins/ios/simulatorcontrol.cpp6
-rw-r--r--src/plugins/languageclient/LanguageClient.json.in7
-rw-r--r--src/plugins/languageclient/client.cpp23
-rw-r--r--src/plugins/languageclient/client.h1
-rw-r--r--src/plugins/languageclient/clientrequest.cpp3
-rw-r--r--src/plugins/languageclient/languageclientcompletionassist.cpp18
-rw-r--r--src/plugins/languageclient/languageclientformatter.cpp3
-rw-r--r--src/plugins/languageclient/languageclienthoverhandler.cpp5
-rw-r--r--src/plugins/languageclient/languageclientmanager.cpp6
-rw-r--r--src/plugins/languageclient/languageclientmanager.h4
-rw-r--r--src/plugins/languageclient/languageclientoutline.cpp16
-rw-r--r--src/plugins/languageclient/languageclientsettings.cpp10
-rw-r--r--src/plugins/languageclient/languageclientsettings.h5
-rw-r--r--src/plugins/languageclient/languageclientsymbolsupport.cpp80
-rw-r--r--src/plugins/languageclient/languageclientutils.cpp30
-rw-r--r--src/plugins/languageclient/locatorfilter.cpp4
-rw-r--r--src/plugins/languageclient/lualanguageclient/LuaLanguageClient.json.in2
-rw-r--r--src/plugins/languageclient/lualanguageclient/lualanguageclient.cpp83
-rw-r--r--src/plugins/languageclient/progressmanager.cpp8
-rw-r--r--src/plugins/languageclient/snippet.cpp8
-rw-r--r--src/plugins/lua/CMakeLists.txt51
-rw-r--r--src/plugins/lua/Lua.json.in10
-rw-r--r--src/plugins/lua/bindings/async.cpp4
-rw-r--r--src/plugins/lua/bindings/fetch.cpp263
-rw-r--r--src/plugins/lua/bindings/gui.cpp392
-rw-r--r--src/plugins/lua/bindings/hook.cpp22
-rw-r--r--src/plugins/lua/bindings/inheritance.h53
-rw-r--r--src/plugins/lua/bindings/install.cpp390
-rw-r--r--src/plugins/lua/bindings/layout.cpp177
-rw-r--r--src/plugins/lua/bindings/qtcprocess.cpp36
-rw-r--r--src/plugins/lua/bindings/settings.cpp11
-rw-r--r--src/plugins/lua/bindings/utils.cpp58
-rw-r--r--src/plugins/lua/images/settingscategory_lua.pngbin0 -> 513 bytes
-rw-r--r--src/plugins/lua/images/settingscategory_lua@2x.pngbin0 -> 1152 bytes
-rw-r--r--src/plugins/lua/lua.qbs9
-rw-r--r--src/plugins/lua/luaengine.cpp168
-rw-r--r--src/plugins/lua/luaengine.h17
-rw-r--r--src/plugins/lua/luaplugin.cpp74
-rw-r--r--src/plugins/lua/luapluginspec.cpp46
-rw-r--r--src/plugins/lua/luapluginspec.h8
-rw-r--r--src/plugins/lua/meta/gui.lua181
-rw-r--r--src/plugins/lua/meta/install.lua29
-rw-r--r--src/plugins/lua/meta/layout.lua186
-rw-r--r--src/plugins/lua/meta/lsp.lua1
-rw-r--r--src/plugins/lua/meta/process.lua6
-rw-r--r--src/plugins/lua/meta/qtc.lua1
-rw-r--r--src/plugins/lua/meta/settings.lua4
-rw-r--r--src/plugins/lua/meta/utils.lua30
-rw-r--r--src/plugins/lua/meta/wizard.lua61
-rw-r--r--src/plugins/luals/CMakeLists.txt4
-rw-r--r--src/plugins/luals/luals.qbs8
-rw-r--r--src/plugins/luals/luals/init.lua (renamed from src/plugins/lualsp/lualsp/init.lua)171
-rw-r--r--src/plugins/luals/luals/luals.lua (renamed from src/plugins/lualsp/lualsp/lualsp.lua)6
-rw-r--r--src/plugins/lualsp/CMakeLists.txt4
-rw-r--r--src/plugins/luatemplates/CMakeLists.txt10
-rw-r--r--src/plugins/luatemplates/luatemplates.cpp402
-rw-r--r--src/plugins/luatemplates/templates/CMakeLists.txt4
-rw-r--r--src/plugins/luatemplates/templates/basic_templates/basic_templates.lua13
-rw-r--r--src/plugins/luatemplates/templates/basic_templates/init.lua65
-rw-r--r--src/plugins/luatests/luatests.qbs13
-rw-r--r--src/plugins/luatests/luatests/luatests.lua1
-rw-r--r--src/plugins/luatests/luatests/tst_utils.lua8
-rw-r--r--src/plugins/macros/Macros.json.in7
-rw-r--r--src/plugins/marketplace/Marketplace.json.in5
-rw-r--r--src/plugins/marketplace/qtmarketplacewelcomepage.cpp4
-rw-r--r--src/plugins/mcusupport/McuSupport.json.in9
-rw-r--r--src/plugins/mcusupport/mcukitaspect.cpp6
-rw-r--r--src/plugins/mcusupport/mcupackage.cpp22
-rw-r--r--src/plugins/mcusupport/mcupackage.h2
-rw-r--r--src/plugins/mcusupport/settingshandler.cpp16
-rw-r--r--src/plugins/mercurial/Mercurial.json.in8
-rw-r--r--src/plugins/mercurial/mercurialplugin.cpp12
-rw-r--r--src/plugins/mercurial/mercurialsettings.cpp2
-rw-r--r--src/plugins/mesonprojectmanager/MesonProjectManager.json.in10
-rw-r--r--src/plugins/mesonprojectmanager/buildoptionsmodel.cpp10
-rw-r--r--src/plugins/mesonprojectmanager/mesonbuildconfiguration.cpp2
-rw-r--r--src/plugins/mesonprojectmanager/mesonoutputparser.cpp2
-rw-r--r--src/plugins/mesonprojectmanager/toolkitaspectwidget.h8
-rw-r--r--src/plugins/modeleditor/ModelEditor.json.in7
-rw-r--r--src/plugins/modeleditor/componentviewcontroller.cpp15
-rw-r--r--src/plugins/modeleditor/elementtasks.cpp43
-rw-r--r--src/plugins/modeleditor/elementtasks.h9
-rw-r--r--src/plugins/modeleditor/extdocumentcontroller.cpp7
-rw-r--r--src/plugins/modeleditor/extdocumentcontroller.h4
-rw-r--r--src/plugins/modeleditor/extpropertiesmview.cpp86
-rw-r--r--src/plugins/modeleditor/jsextension.cpp4
-rw-r--r--src/plugins/modeleditor/jsextension.h4
-rw-r--r--src/plugins/modeleditor/modeldocument.cpp30
-rw-r--r--src/plugins/modeleditor/modeldocument.h3
-rw-r--r--src/plugins/modeleditor/modeleditor.cpp12
-rw-r--r--src/plugins/modeleditor/modelindexer.cpp12
-rw-r--r--src/plugins/modeleditor/modelsmanager.cpp8
-rw-r--r--src/plugins/modeleditor/pxnodecontroller.cpp17
-rw-r--r--src/plugins/modeleditor/pxnodeutilities.cpp10
-rw-r--r--src/plugins/nim/Nim.json.in8
-rw-r--r--src/plugins/nim/project/nimbuildsystem.cpp15
-rw-r--r--src/plugins/nim/project/nimoutputtaskparser.cpp2
-rw-r--r--src/plugins/nim/settings/nimsettings.cpp2
-rw-r--r--src/plugins/perforce/Perforce.json.in8
-rw-r--r--src/plugins/perforce/perforcechecker.cpp8
-rw-r--r--src/plugins/perforce/perforcesettings.cpp2
-rw-r--r--src/plugins/perfprofiler/PerfProfiler.json.in9
-rw-r--r--src/plugins/perfprofiler/perfprofilerflamegraphview.cpp2
-rw-r--r--src/plugins/perfprofiler/perfprofilerruncontrol.cpp16
-rw-r--r--src/plugins/perfprofiler/perftimelinemodel.cpp8
-rw-r--r--src/plugins/perfprofiler/perftimelineresourcesrenderpass.cpp2
-rw-r--r--src/plugins/perfprofiler/perftracepointdialog.cpp2
-rw-r--r--src/plugins/plugins.qbs7
-rw-r--r--src/plugins/projectexplorer/CMakeLists.txt2
-rw-r--r--src/plugins/projectexplorer/ProjectExplorer.json.in9
-rw-r--r--src/plugins/projectexplorer/allprojectsfind.cpp22
-rw-r--r--src/plugins/projectexplorer/allprojectsfind.h8
-rw-r--r--src/plugins/projectexplorer/buildaspects.cpp2
-rw-r--r--src/plugins/projectexplorer/buildaspects.h2
-rw-r--r--src/plugins/projectexplorer/buildconfiguration.cpp4
-rw-r--r--src/plugins/projectexplorer/buildstep.cpp8
-rw-r--r--src/plugins/projectexplorer/clangparser.cpp8
-rw-r--r--src/plugins/projectexplorer/codestylesettingspropertiespage.cpp1
-rw-r--r--src/plugins/projectexplorer/currentprojectfind.cpp29
-rw-r--r--src/plugins/projectexplorer/customexecutablerunconfiguration.cpp15
-rw-r--r--src/plugins/projectexplorer/customexecutablerunconfiguration.h1
-rw-r--r--src/plugins/projectexplorer/customparser.cpp2
-rw-r--r--src/plugins/projectexplorer/customparserconfigdialog.cpp6
-rw-r--r--src/plugins/projectexplorer/devicesupport/desktopdevice.cpp4
-rw-r--r--src/plugins/projectexplorer/devicesupport/devicesettingspage.cpp5
-rw-r--r--src/plugins/projectexplorer/devicesupport/devicetestdialog.cpp4
-rw-r--r--src/plugins/projectexplorer/environmentwidget.cpp12
-rw-r--r--src/plugins/projectexplorer/extracompiler.cpp5
-rw-r--r--src/plugins/projectexplorer/filesinallprojectsfind.cpp25
-rw-r--r--src/plugins/projectexplorer/filesinallprojectsfind.h7
-rw-r--r--src/plugins/projectexplorer/gccparser.cpp386
-rw-r--r--src/plugins/projectexplorer/gccparser.h32
-rw-r--r--src/plugins/projectexplorer/gnumakeparser.cpp2
-rw-r--r--src/plugins/projectexplorer/ioutputparser.cpp97
-rw-r--r--src/plugins/projectexplorer/ioutputparser.h20
-rw-r--r--src/plugins/projectexplorer/jsonwizard/jsonfieldpage.cpp24
-rw-r--r--src/plugins/projectexplorer/jsonwizard/jsonkitspage.cpp6
-rw-r--r--src/plugins/projectexplorer/jsonwizard/jsonwizard.cpp8
-rw-r--r--src/plugins/projectexplorer/jsonwizard/jsonwizardfactory.cpp27
-rw-r--r--src/plugins/projectexplorer/jsonwizard/jsonwizardfilegenerator.cpp2
-rw-r--r--src/plugins/projectexplorer/jsonwizard/jsonwizardpagefactory_p.cpp8
-rw-r--r--src/plugins/projectexplorer/jsonwizard/jsonwizardscannergenerator.cpp2
-rw-r--r--src/plugins/projectexplorer/jsonwizard/wizarddebug.h4
-rw-r--r--src/plugins/projectexplorer/kitaspects.cpp16
-rw-r--r--src/plugins/projectexplorer/kitmanager.cpp2
-rw-r--r--src/plugins/projectexplorer/kitmanager.h4
-rw-r--r--src/plugins/projectexplorer/kitmanagerconfigwidget.cpp2
-rw-r--r--src/plugins/projectexplorer/kitmanagerconfigwidget.h2
-rw-r--r--src/plugins/projectexplorer/ldparser.cpp81
-rw-r--r--src/plugins/projectexplorer/ldparser.h5
-rw-r--r--src/plugins/projectexplorer/linuxiccparser.cpp52
-rw-r--r--src/plugins/projectexplorer/linuxiccparser.h4
-rw-r--r--src/plugins/projectexplorer/lldparser.cpp2
-rw-r--r--src/plugins/projectexplorer/makestep.cpp2
-rw-r--r--src/plugins/projectexplorer/miniprojecttargetselector.cpp11
-rw-r--r--src/plugins/projectexplorer/msvcparser.cpp192
-rw-r--r--src/plugins/projectexplorer/msvcparser.h10
-rw-r--r--src/plugins/projectexplorer/msvctoolchain.cpp6
-rw-r--r--src/plugins/projectexplorer/processparameters.cpp2
-rw-r--r--src/plugins/projectexplorer/project.cpp8
-rw-r--r--src/plugins/projectexplorer/project.h4
-rw-r--r--src/plugins/projectexplorer/projectexplorer.cpp63
-rw-r--r--src/plugins/projectexplorer/projectexplorer.h2
-rw-r--r--src/plugins/projectexplorer/projectexplorer.qbs2
-rw-r--r--src/plugins/projectexplorer/projectexplorerconstants.h1
-rw-r--r--src/plugins/projectexplorer/projectexplorersettings.cpp29
-rw-r--r--src/plugins/projectexplorer/projectexplorersettings.h2
-rw-r--r--src/plugins/projectexplorer/projectmodels.cpp2
-rw-r--r--src/plugins/projectexplorer/projectnodeshelper.h42
-rw-r--r--src/plugins/projectexplorer/projecttreewidget.cpp6
-rw-r--r--src/plugins/projectexplorer/projectwelcomepage.cpp27
-rw-r--r--src/plugins/projectexplorer/projectwindow.cpp2
-rw-r--r--src/plugins/projectexplorer/projectwizardpage.cpp10
-rw-r--r--src/plugins/projectexplorer/projectwizardpage.h4
-rw-r--r--src/plugins/projectexplorer/runconfiguration.cpp11
-rw-r--r--src/plugins/projectexplorer/runconfigurationaspects.cpp25
-rw-r--r--src/plugins/projectexplorer/runconfigurationaspects.h8
-rw-r--r--src/plugins/projectexplorer/runcontrol.cpp19
-rw-r--r--src/plugins/projectexplorer/runcontrol.h3
-rw-r--r--src/plugins/projectexplorer/sanitizerparser.cpp5
-rw-r--r--src/plugins/projectexplorer/targetsettingspanel.cpp80
-rw-r--r--src/plugins/projectexplorer/treescanner.cpp45
-rw-r--r--src/plugins/projectexplorer/treescanner.h11
-rw-r--r--src/plugins/projectexplorer/userfileaccessor.cpp22
-rw-r--r--src/plugins/projectexplorer/workspaceproject.cpp337
-rw-r--r--src/plugins/projectexplorer/workspaceproject.h16
-rw-r--r--src/plugins/projectexplorer/xcodebuildparser.cpp3
-rw-r--r--src/plugins/python/Python.json.in9
-rw-r--r--src/plugins/python/pythonbuildconfiguration.cpp4
-rw-r--r--src/plugins/python/pythonkitaspect.cpp2
-rw-r--r--src/plugins/python/pythonlanguageclient.cpp2
-rw-r--r--src/plugins/python/pythonrunconfiguration.cpp21
-rw-r--r--src/plugins/python/pythonsettings.cpp2
-rw-r--r--src/plugins/python/pythonutils.cpp6
-rw-r--r--src/plugins/qbsprojectmanager/QbsProjectManager.json.in7
-rw-r--r--src/plugins/qbsprojectmanager/customqbspropertiesdialog.cpp2
-rw-r--r--src/plugins/qbsprojectmanager/qbsbuildstep.cpp2
-rw-r--r--src/plugins/qbsprojectmanager/qbsbuildstep.h2
-rw-r--r--src/plugins/qbsprojectmanager/qbskitaspect.cpp2
-rw-r--r--src/plugins/qbsprojectmanager/qbsprofilemanager.cpp6
-rw-r--r--src/plugins/qbsprojectmanager/qbsprofilessettingspage.cpp4
-rw-r--r--src/plugins/qbsprojectmanager/qbssession.cpp28
-rw-r--r--src/plugins/qbsprojectmanager/qbssession.h2
-rw-r--r--src/plugins/qmakeprojectmanager/QmakeProjectManager.json.in7
-rw-r--r--src/plugins/qmakeprojectmanager/customwidgetwizard/customwidgetwizarddialog.cpp4
-rw-r--r--src/plugins/qmakeprojectmanager/qmakekitaspect.cpp2
-rw-r--r--src/plugins/qmakeprojectmanager/qmakeparser.cpp2
-rw-r--r--src/plugins/qmakeprojectmanager/qmakestep.cpp6
-rw-r--r--src/plugins/qmldesigner/CMakeLists.txt36
-rw-r--r--src/plugins/qmldesigner/QmlDesigner.json.in2
-rw-r--r--src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.cpp2
-rw-r--r--src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp48
-rw-r--r--src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h2
-rw-r--r--src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp32
-rw-r--r--src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h5
-rw-r--r--src/plugins/qmldesigner/components/collectioneditor/collectiondatatypemodel.cpp86
-rw-r--r--src/plugins/qmldesigner/components/collectioneditor/collectiondatatypemodel.h34
-rw-r--r--src/plugins/qmldesigner/components/collectioneditor/collectiondetails.cpp965
-rw-r--r--src/plugins/qmldesigner/components/collectioneditor/collectiondetails.h152
-rw-r--r--src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.cpp627
-rw-r--r--src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.h106
-rw-r--r--src/plugins/qmldesigner/components/collectioneditor/collectiondetailssortfiltermodel.cpp163
-rw-r--r--src/plugins/qmldesigner/components/collectioneditor/collectiondetailssortfiltermodel.h58
-rw-r--r--src/plugins/qmldesigner/components/collectioneditor/collectioneditorconstants.h19
-rw-r--r--src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.cpp345
-rw-r--r--src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.h46
-rw-r--r--src/plugins/qmldesigner/components/collectioneditor/collectionlistmodel.cpp521
-rw-r--r--src/plugins/qmldesigner/components/collectioneditor/collectionlistmodel.h79
-rw-r--r--src/plugins/qmldesigner/components/collectioneditor/collectionview.cpp501
-rw-r--r--src/plugins/qmldesigner/components/collectioneditor/collectionview.h124
-rw-r--r--src/plugins/qmldesigner/components/collectioneditor/collectionwidget.cpp320
-rw-r--r--src/plugins/qmldesigner/components/collectioneditor/collectionwidget.h81
-rw-r--r--src/plugins/qmldesigner/components/collectioneditor/datastoremodelnode.cpp511
-rw-r--r--src/plugins/qmldesigner/components/collectioneditor/datastoremodelnode.h63
-rw-r--r--src/plugins/qmldesigner/components/componentcore/abstractaction.cpp4
-rw-r--r--src/plugins/qmldesigner/components/componentcore/abstractaction.h5
-rw-r--r--src/plugins/qmldesigner/components/componentcore/abstractactiongroup.cpp10
-rw-r--r--src/plugins/qmldesigner/components/componentcore/abstractactiongroup.h5
-rw-r--r--src/plugins/qmldesigner/components/componentcore/componentcore_constants.h3
-rw-r--r--src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp19
-rw-r--r--src/plugins/qmldesigner/components/componentcore/designeractionmanager.h2
-rw-r--r--src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu_helper.h21
-rw-r--r--src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp48
-rw-r--r--src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h1
-rw-r--r--src/plugins/qmldesigner/components/componentcore/propertycomponentgenerator.cpp4
-rw-r--r--src/plugins/qmldesigner/components/componentcore/viewmanager.cpp18
-rw-r--r--src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp28
-rw-r--r--src/plugins/qmldesigner/components/connectioneditor/bindingmodel.h3
-rw-r--r--src/plugins/qmldesigner/components/connectioneditor/connectioneditorutils.cpp3
-rw-r--r--src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp31
-rw-r--r--src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.h3
-rw-r--r--src/plugins/qmldesigner/components/contentlibrary/contentlibrarybundleimporter.cpp159
-rw-r--r--src/plugins/qmldesigner/components/contentlibrary/contentlibrarybundleimporter.h38
-rw-r--r--src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffect.cpp78
-rw-r--r--src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectscategory.cpp10
-rw-r--r--src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectscategory.h12
-rw-r--r--src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectsmodel.cpp155
-rw-r--r--src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectsmodel.h35
-rw-r--r--src/plugins/qmldesigner/components/contentlibrary/contentlibraryitem.cpp76
-rw-r--r--src/plugins/qmldesigner/components/contentlibrary/contentlibraryitem.h (renamed from src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffect.h)18
-rw-r--r--src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterial.cpp7
-rw-r--r--src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterial.h7
-rw-r--r--src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterialsmodel.cpp179
-rw-r--r--src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterialsmodel.h47
-rw-r--r--src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.cpp5
-rw-r--r--src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.h2
-rw-r--r--src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.cpp630
-rw-r--r--src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.h101
-rw-r--r--src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp622
-rw-r--r--src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h33
-rw-r--r--src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp145
-rw-r--r--src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h36
-rw-r--r--src/plugins/qmldesigner/components/curveeditor/detail/keyframeitem.cpp2
-rw-r--r--src/plugins/qmldesigner/components/edit3d/edit3dcanvas.cpp48
-rw-r--r--src/plugins/qmldesigner/components/edit3d/edit3dcanvas.h3
-rw-r--r--src/plugins/qmldesigner/components/edit3d/edit3dview.cpp205
-rw-r--r--src/plugins/qmldesigner/components/edit3d/edit3dview.h13
-rw-r--r--src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp64
-rw-r--r--src/plugins/qmldesigner/components/edit3d/edit3dwidget.h1
-rw-r--r--src/plugins/qmldesigner/components/formeditor/dragtool.cpp23
-rw-r--r--src/plugins/qmldesigner/components/formeditor/dragtool.h1
-rw-r--r--src/plugins/qmldesigner/components/formeditor/formeditorannotationicon.cpp12
-rw-r--r--src/plugins/qmldesigner/components/formeditor/formeditorgraphicsview.cpp2
-rw-r--r--src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp22
-rw-r--r--src/plugins/qmldesigner/components/formeditor/formeditortoolbutton.cpp2
-rw-r--r--src/plugins/qmldesigner/components/formeditor/selectionindicator.cpp4
-rw-r--r--src/plugins/qmldesigner/components/integration/designdocument.cpp27
-rw-r--r--src/plugins/qmldesigner/components/integration/designdocument.h11
-rw-r--r--src/plugins/qmldesigner/components/integration/designdocumentview.cpp10
-rw-r--r--src/plugins/qmldesigner/components/itemlibrary/import3dcanvas.cpp82
-rw-r--r--src/plugins/qmldesigner/components/itemlibrary/import3dcanvas.h37
-rw-r--r--src/plugins/qmldesigner/components/itemlibrary/import3dconnectionmanager.cpp47
-rw-r--r--src/plugins/qmldesigner/components/itemlibrary/import3dconnectionmanager.h28
-rw-r--r--src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.cpp281
-rw-r--r--src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.h33
-rw-r--r--src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.ui76
-rw-r--r--src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp109
-rw-r--r--src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.h36
-rw-r--r--src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp80
-rw-r--r--src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp4
-rw-r--r--src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.cpp2
-rw-r--r--src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp11
-rw-r--r--src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h5
-rw-r--r--src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp5
-rw-r--r--src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp12
-rw-r--r--src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h5
-rw-r--r--src/plugins/qmldesigner/components/materialeditor/materialeditorqmlbackend.cpp19
-rw-r--r--src/plugins/qmldesigner/components/materialeditor/materialeditorqmlbackend.h6
-rw-r--r--src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp101
-rw-r--r--src/plugins/qmldesigner/components/materialeditor/materialeditorview.h2
-rw-r--r--src/plugins/qmldesigner/components/navigator/choosefrompropertylistdialog.cpp7
-rw-r--r--src/plugins/qmldesigner/components/navigator/iconcheckboxitemdelegate.cpp5
-rw-r--r--src/plugins/qmldesigner/components/navigator/navigatorsearchwidget.cpp4
-rw-r--r--src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp8
-rw-r--r--src/plugins/qmldesigner/components/navigator/previewtooltip.cpp3
-rw-r--r--src/plugins/qmldesigner/components/previewtooltip/previewimagetooltip.cpp3
-rw-r--r--src/plugins/qmldesigner/components/previewtooltip/previewtooltipbackend.cpp13
-rw-r--r--src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.cpp3
-rw-r--r--src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp33
-rw-r--r--src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h8
-rw-r--r--src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp22
-rw-r--r--src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.h2
-rw-r--r--src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp1
-rw-r--r--src/plugins/qmldesigner/components/propertyeditor/qmlmodelnodeproxy.cpp22
-rw-r--r--src/plugins/qmldesigner/components/texteditor/texteditorstatusbar.cpp3
-rw-r--r--src/plugins/qmldesigner/components/texteditor/texteditorview.cpp3
-rw-r--r--src/plugins/qmldesigner/components/texttool/textedititemwidget.cpp24
-rw-r--r--src/plugins/qmldesigner/components/texttool/textedititemwidget.h7
-rw-r--r--src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.cpp24
-rw-r--r--src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.h8
-rw-r--r--src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp4
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelineanimationform.cpp6
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelineform.cpp4
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinepropertyitem.cpp1
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinesettingsmodel.cpp2
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinewidget.cpp4
-rw-r--r--src/plugins/qmldesigner/designercore/generatedcomponentutils.cpp160
-rw-r--r--src/plugins/qmldesigner/designercore/generatedcomponentutils.h17
-rw-r--r--src/plugins/qmldesigner/designercore/include/externaldependenciesinterface.h1
-rw-r--r--src/plugins/qmldesigner/designercore/include/itemlibraryentry.h13
-rw-r--r--src/plugins/qmldesigner/designercore/include/model.h9
-rw-r--r--src/plugins/qmldesigner/designercore/include/nodehints.h9
-rw-r--r--src/plugins/qmldesigner/designercore/include/nodemetainfo.h9
-rw-r--r--src/plugins/qmldesigner/designercore/include/projectstorageids.h2
-rw-r--r--src/plugins/qmldesigner/designercore/include/rewriterview.h8
-rw-r--r--src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp35
-rw-r--r--src/plugins/qmldesigner/designercore/metainfo/itemlibraryentry.cpp23
-rw-r--r--src/plugins/qmldesigner/designercore/metainfo/metainforeader.cpp2
-rw-r--r--src/plugins/qmldesigner/designercore/metainfo/nodehints.cpp35
-rw-r--r--src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp1469
-rw-r--r--src/plugins/qmldesigner/designercore/metainfo/subcomponentmanager.cpp5
-rw-r--r--src/plugins/qmldesigner/designercore/model/model.cpp123
-rw-r--r--src/plugins/qmldesigner/designercore/model/model_p.h2
-rw-r--r--src/plugins/qmldesigner/designercore/model/propertycontainer.cpp2
-rw-r--r--src/plugins/qmldesigner/designercore/model/qmlvisualnode.cpp27
-rw-r--r--src/plugins/qmldesigner/designercore/model/rewriteactioncompressor.cpp3
-rw-r--r--src/plugins/qmldesigner/designercore/model/rewriterview.cpp98
-rw-r--r--src/plugins/qmldesigner/designercore/model/stylesheetmerger.cpp16
-rw-r--r--src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp37
-rw-r--r--src/plugins/qmldesigner/designercore/projectstorage/commontypecache.h247
-rw-r--r--src/plugins/qmldesigner/designercore/projectstorage/filesystem.cpp13
-rw-r--r--src/plugins/qmldesigner/designercore/projectstorage/filesystem.h1
-rw-r--r--src/plugins/qmldesigner/designercore/projectstorage/filesysteminterface.h1
-rw-r--r--src/plugins/qmldesigner/designercore/projectstorage/projectstorage.cpp752
-rw-r--r--src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h190
-rw-r--r--src/plugins/qmldesigner/designercore/projectstorage/projectstorageerrornotifier.cpp17
-rw-r--r--src/plugins/qmldesigner/designercore/projectstorage/projectstorageerrornotifier.h25
-rw-r--r--src/plugins/qmldesigner/designercore/projectstorage/projectstorageerrornotifierinterface.h27
-rw-r--r--src/plugins/qmldesigner/designercore/projectstorage/projectstorageexceptions.cpp18
-rw-r--r--src/plugins/qmldesigner/designercore/projectstorage/projectstorageexceptions.h12
-rw-r--r--src/plugins/qmldesigner/designercore/projectstorage/projectstorageinfotypes.h45
-rw-r--r--src/plugins/qmldesigner/designercore/projectstorage/projectstorageinterface.h18
-rw-r--r--src/plugins/qmldesigner/designercore/projectstorage/projectstoragetypes.h42
-rw-r--r--src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.cpp300
-rw-r--r--src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.h24
-rw-r--r--src/plugins/qmldesigner/designercore/projectstorage/qmldocumentparser.cpp29
-rw-r--r--src/plugins/qmldesigner/designercore/projectstorage/qmltypesparser.cpp33
-rw-r--r--src/plugins/qmldesigner/designercore/projectstorage/qmltypesparser.h2
-rw-r--r--src/plugins/qmldesigner/designercore/projectstorage/qmltypesparserinterface.h2
-rw-r--r--src/plugins/qmldesigner/designercore/projectstorage/sourcepath.h5
-rw-r--r--src/plugins/qmldesigner/designercore/projectstorage/storagecache.h2
-rw-r--r--src/plugins/qmldesigner/designercore/projectstorage/typeannotationreader.cpp18
-rw-r--r--src/plugins/qmldesigner/designercore/tracing/qmldesignertracing.cpp9
-rw-r--r--src/plugins/qmldesigner/designercore/tracing/qmldesignertracing.h16
-rw-r--r--src/plugins/qmldesigner/designercore/uniquename.cpp165
-rw-r--r--src/plugins/qmldesigner/designercore/uniquename.h17
-rw-r--r--src/plugins/qmldesigner/designmodecontext.cpp12
-rw-r--r--src/plugins/qmldesigner/designmodecontext.h9
-rw-r--r--src/plugins/qmldesigner/designmodewidget.h1
-rw-r--r--src/plugins/qmldesigner/documentwarningwidget.cpp4
-rw-r--r--src/plugins/qmldesigner/qmldesignerconstants.h20
-rw-r--r--src/plugins/qmldesigner/qmldesignerexternaldependencies.cpp5
-rw-r--r--src/plugins/qmldesigner/qmldesignerexternaldependencies.h1
-rw-r--r--src/plugins/qmldesigner/qmldesignerplugin.cpp18
-rw-r--r--src/plugins/qmldesigner/qmldesignerprojectmanager.cpp105
-rw-r--r--src/plugins/qmldesigner/qmldesignerprojectmanager.h5
-rw-r--r--src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewplugin.cpp2
-rw-r--r--src/plugins/qmldesigner/qtquickplugin/images/frame-animation-16px.pngbin0 -> 319 bytes
-rw-r--r--src/plugins/qmldesigner/qtquickplugin/images/frame-animation-24px.pngbin0 -> 403 bytes
-rw-r--r--src/plugins/qmldesigner/qtquickplugin/images/frame-animation-24px@2x.pngbin0 -> 496 bytes
-rw-r--r--src/plugins/qmldesigner/qtquickplugin/qtquickplugin.qrc3
-rw-r--r--src/plugins/qmldesigner/qtquickplugin/quick.metainfo20
-rw-r--r--src/plugins/qmldesigner/settingspage.cpp3
-rw-r--r--src/plugins/qmldesigner/shortcutmanager.cpp6
-rw-r--r--src/plugins/qmldesigner/shortcutmanager.h1
-rw-r--r--src/plugins/qmldesignerbase/QmlDesignerBase.json.in2
-rw-r--r--src/plugins/qmldesignerbase/studio/studiostyle.cpp30
-rw-r--r--src/plugins/qmldesignerbase/studio/studiostyle_p.cpp4
-rw-r--r--src/plugins/qmldesignerlite/QmlDesignerLite.json.in2
-rw-r--r--src/plugins/qmljseditor/QmlJSEditor.json.in5
-rw-r--r--src/plugins/qmljseditor/qmljscomponentfromobjectdef.cpp5
-rw-r--r--src/plugins/qmljseditor/qmljseditingsettingspage.cpp5
-rw-r--r--src/plugins/qmljseditor/qmljsquickfixes.cpp11
-rw-r--r--src/plugins/qmljseditor/qmljswrapinloader.cpp3
-rw-r--r--src/plugins/qmljseditor/qmloutlinemodel.cpp6
-rw-r--r--src/plugins/qmljstools/QmlJSTools.json.in2
-rw-r--r--src/plugins/qmljstools/qmljsfunctionfilter.cpp3
-rw-r--r--src/plugins/qmlpreview/QmlPreview.json.in5
-rw-r--r--src/plugins/qmlpreview/tests/qmlpreviewplugin_test.cpp2
-rw-r--r--src/plugins/qmlprofiler/QmlProfiler.json.in7
-rw-r--r--src/plugins/qmlprofiler/flamegraphview.cpp2
-rw-r--r--src/plugins/qmlprofiler/qmlprofilerbindingloopsrenderpass.cpp2
-rw-r--r--src/plugins/qmlprofiler/qmlprofilerstatisticsmodel.cpp12
-rw-r--r--src/plugins/qmlprofiler/tests/localqmlprofilerrunner_test.cpp4
-rw-r--r--src/plugins/qmlprojectmanager/.clang-format50
-rw-r--r--src/plugins/qmlprojectmanager/QmlProjectManager.json.in2
-rw-r--r--src/plugins/qmlprojectmanager/buildsystem/projectitem/converters.cpp7
-rw-r--r--src/plugins/qmlprojectmanager/buildsystem/projectitem/qmlprojectitem.cpp7
-rw-r--r--src/plugins/qmlprojectmanager/buildsystem/projectitem/qmlprojectitem.h2
-rw-r--r--src/plugins/qmlprojectmanager/buildsystem/qmlbuildsystem.cpp99
-rw-r--r--src/plugins/qmlprojectmanager/buildsystem/qmlbuildsystem.h13
-rw-r--r--src/plugins/qmlprojectmanager/cmakegen/cmakegenerator.cpp43
-rw-r--r--src/plugins/qmlprojectmanager/cmakegen/cmakegenerator.h4
-rw-r--r--src/plugins/qmlprojectmanager/cmakegen/templates/qmlcomponents.tpl2
-rw-r--r--src/plugins/qmlprojectmanager/qmlmainfileaspect.cpp2
-rw-r--r--src/plugins/qmlprojectmanager/qmlmainfileaspect.h2
-rw-r--r--src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp4
-rw-r--r--src/plugins/qmlprojectmanager/qmlprojectconstants.h1
-rw-r--r--src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp6
-rw-r--r--src/plugins/qnx/Qnx.json.in10
-rw-r--r--src/plugins/qnx/slog2inforunner.cpp2
-rw-r--r--src/plugins/qtapplicationmanager/QtApplicationManagerIntegration.json.in8
-rw-r--r--src/plugins/qtapplicationmanager/appmanagerplugin.cpp1
-rw-r--r--src/plugins/qtapplicationmanager/appmanagerruncontrol.cpp62
-rw-r--r--src/plugins/qtapplicationmanager/appmanagerruncontrol.h1
-rw-r--r--src/plugins/qtsupport/QtSupport.json.in2
-rw-r--r--src/plugins/qtsupport/externaleditors.cpp7
-rw-r--r--src/plugins/qtsupport/gettingstartedwelcomepage.cpp4
-rw-r--r--src/plugins/qtsupport/qtbuildaspects.cpp8
-rw-r--r--src/plugins/qtsupport/qtbuildaspects.h4
-rw-r--r--src/plugins/qtsupport/qtcreator_tutorials.xml12
-rw-r--r--src/plugins/qtsupport/qtkitaspect.cpp2
-rw-r--r--src/plugins/qtsupport/qtparser.cpp8
-rw-r--r--src/plugins/qtsupport/qttestparser.cpp4
-rw-r--r--src/plugins/qtsupport/qtversionmanager.cpp4
-rw-r--r--src/plugins/qtsupport/translationwizardpage.cpp2
-rw-r--r--src/plugins/remotelinux/RemoteLinux.json.in9
-rw-r--r--src/plugins/remotelinux/filesystemaccess_test.cpp2
-rw-r--r--src/plugins/remotelinux/linuxdevice.cpp47
-rw-r--r--src/plugins/remotelinux/linuxdevicetester.cpp3
-rw-r--r--src/plugins/remotelinux/makeinstallstep.cpp2
-rw-r--r--src/plugins/remotelinux/publickeydeploymentdialog.cpp2
-rw-r--r--src/plugins/remotelinux/sshdevicewizard.cpp1
-rw-r--r--src/plugins/remotelinux/sshkeycreationdialog.cpp8
-rw-r--r--src/plugins/resourceeditor/ResourceEditor.json.in7
-rw-r--r--src/plugins/resourceeditor/qrceditor/resourcefile.cpp2
-rw-r--r--src/plugins/rustls/CMakeLists.txt4
-rw-r--r--src/plugins/rustls/rustls.qbs8
-rw-r--r--src/plugins/rustls/rustls/init.lua200
-rw-r--r--src/plugins/rustls/rustls/rustls.lua24
-rw-r--r--src/plugins/saferenderer/SafeRenderer.json.in8
-rw-r--r--src/plugins/screenrecorder/ScreenRecorder.json.in5
-rw-r--r--src/plugins/screenrecorder/cropandtrim.cpp14
-rw-r--r--src/plugins/screenrecorder/export.cpp2
-rw-r--r--src/plugins/screenrecorder/ffmpegutils.cpp4
-rw-r--r--src/plugins/screenrecorder/record.cpp3
-rw-r--r--src/plugins/screenrecorder/screenrecorderplugin.cpp5
-rw-r--r--src/plugins/scxmleditor/ScxmlEditor.json.in7
-rw-r--r--src/plugins/scxmleditor/common/shapestoolbox.cpp2
-rw-r--r--src/plugins/scxmleditor/common/stateview.cpp2
-rw-r--r--src/plugins/scxmleditor/plugin_interface/scattributeitemdelegate.cpp6
-rw-r--r--src/plugins/scxmleditor/plugin_interface/scattributeitemmodel.cpp6
-rw-r--r--src/plugins/scxmleditor/plugin_interface/scxmltypes.h112
-rw-r--r--src/plugins/serialterminal/SerialTerminal.json.in2
-rw-r--r--src/plugins/silversearcher/SilverSearcher.json.in9
-rw-r--r--src/plugins/silversearcher/findinfilessilversearcher.cpp9
-rw-r--r--src/plugins/squish/Squish.json.in9
-rw-r--r--src/plugins/squish/squishoutputpane.cpp4
-rw-r--r--src/plugins/squish/squishperspective.cpp4
-rw-r--r--src/plugins/squish/squishserverprocess.cpp5
-rw-r--r--src/plugins/squish/squishsettings.cpp8
-rw-r--r--src/plugins/squish/squishtools.cpp31
-rw-r--r--src/plugins/squish/squishwizardpages.cpp2
-rw-r--r--src/plugins/squish/testresult.cpp18
-rw-r--r--src/plugins/studiowelcome/StudioWelcome.json.in2
-rw-r--r--src/plugins/studiowelcome/studiowelcomeplugin.cpp38
-rw-r--r--src/plugins/subversion/Subversion.json.in8
-rw-r--r--src/plugins/subversion/subversionclient.cpp1
-rw-r--r--src/plugins/subversion/subversionplugin.cpp2
-rw-r--r--src/plugins/subversion/subversionsettings.cpp2
-rw-r--r--src/plugins/tellajoke/tellajoke.qbs5
-rw-r--r--src/plugins/terminal/Terminal.json.in7
-rw-r--r--src/plugins/terminal/shellintegration.cpp6
-rw-r--r--src/plugins/terminal/shellmodel.cpp2
-rw-r--r--src/plugins/terminal/terminalpane.cpp2
-rw-r--r--src/plugins/terminal/terminalsettings.cpp42
-rw-r--r--src/plugins/terminal/terminalwidget.cpp2
-rw-r--r--src/plugins/texteditor/TextEditor.json.in2
-rw-r--r--src/plugins/texteditor/basefilefind.cpp55
-rw-r--r--src/plugins/texteditor/basefilefind.h10
-rw-r--r--src/plugins/texteditor/bookmarkmanager.cpp1
-rw-r--r--src/plugins/texteditor/completionsettingspage.cpp2
-rw-r--r--src/plugins/texteditor/displaysettingspage.cpp2
-rw-r--r--src/plugins/texteditor/findincurrentfile.cpp29
-rw-r--r--src/plugins/texteditor/findinfiles.cpp22
-rw-r--r--src/plugins/texteditor/findinfiles.h7
-rw-r--r--src/plugins/texteditor/findinopenfiles.cpp29
-rw-r--r--src/plugins/texteditor/fontsettings.cpp3
-rw-r--r--src/plugins/texteditor/fontsettingspage.cpp7
-rw-r--r--src/plugins/texteditor/refactoringchanges.cpp11
-rw-r--r--src/plugins/texteditor/refactoringchanges.h1
-rw-r--r--src/plugins/texteditor/textdocument.cpp5
-rw-r--r--src/plugins/texteditor/texteditor.cpp62
-rw-r--r--src/plugins/texteditor/texteditor.h12
-rw-r--r--src/plugins/texteditor/textmark.cpp2
-rw-r--r--src/plugins/todo/Todo.json.in7
-rw-r--r--src/plugins/todo/keyword.cpp2
-rw-r--r--src/plugins/todo/settings.cpp15
-rw-r--r--src/plugins/updateinfo/UpdateInfo.json.in5
-rw-r--r--src/plugins/valgrind/Valgrind.json.in9
-rw-r--r--src/plugins/valgrind/valgrindmemcheckparsertest.cpp8
-rw-r--r--src/plugins/valgrind/valgrindsettings.cpp2
-rw-r--r--src/plugins/valgrind/valgrindsettings.h2
-rw-r--r--src/plugins/valgrind/valgrindtestrunnertest.cpp5
-rw-r--r--src/plugins/valgrind/xmlprotocol/parser.cpp4
-rw-r--r--src/plugins/vcpkg/Vcpkg.json.in5
-rw-r--r--src/plugins/vcsbase/VcsBase.json.in8
-rw-r--r--src/plugins/vcsbase/commonvcssettings.cpp6
-rw-r--r--src/plugins/vcsbase/submiteditorwidget.cpp3
-rw-r--r--src/plugins/vcsbase/submitfilemodel.cpp2
-rw-r--r--src/plugins/vcsbase/vcsbaseclient.cpp2
-rw-r--r--src/plugins/vcsbase/vcsbasediffeditorcontroller.cpp4
-rw-r--r--src/plugins/vcsbase/vcsbaseeditor.cpp4
-rw-r--r--src/plugins/vcsbase/wizard/vcscommandpage.cpp18
-rw-r--r--src/plugins/vcsbase/wizard/vcsconfigurationpage.cpp2
-rw-r--r--src/plugins/webassembly/WebAssembly.json.in9
-rw-r--r--src/plugins/webassembly/webassemblyrunconfiguration.cpp2
-rw-r--r--src/plugins/webassembly/webassemblysettings.cpp1
-rw-r--r--src/plugins/welcome/Welcome.json.in2
-rw-r--r--src/plugins/welcome/welcomeplugin.cpp18
1056 files changed, 41302 insertions, 36478 deletions
diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt
index 605bdc3931..a0bfe5b1cf 100644
--- a/src/plugins/CMakeLists.txt
+++ b/src/plugins/CMakeLists.txt
@@ -48,7 +48,6 @@ add_subdirectory(autotoolsprojectmanager)
add_subdirectory(bazaar)
add_subdirectory(beautifier)
add_subdirectory(clearcase)
-add_subdirectory(cmakeprojectmanager)
add_subdirectory(cvs)
add_subdirectory(designer)
add_subdirectory(docker)
@@ -80,6 +79,7 @@ if (WITH_QMLDESIGNER)
endif()
add_subdirectory(python)
add_subdirectory(clangformat)
+add_subdirectory(cmakeprojectmanager)
# Level 7:
add_subdirectory(android)
@@ -99,6 +99,7 @@ add_subdirectory(perfprofiler)
add_subdirectory(qbsprojectmanager)
add_subdirectory(ctfvisualizer)
add_subdirectory(squish)
+add_subdirectory(appstatisticsmonitor)
# Level 8:
add_subdirectory(boot2qt)
@@ -120,5 +121,5 @@ add_subdirectory(mcusupport)
add_subdirectory(qtapplicationmanager)
add_subdirectory(luatests)
add_subdirectory(tellajoke)
-add_subdirectory(lualsp)
-add_subdirectory(luatemplates)
+add_subdirectory(luals)
+add_subdirectory(rustls)
diff --git a/src/plugins/android/Android.json.in b/src/plugins/android/Android.json.in
index 7a020c5061..f314302842 100644
--- a/src/plugins/android/Android.json.in
+++ b/src/plugins/android/Android.json.in
@@ -13,8 +13,14 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Device Support",
- "Description" : "Support for deployment to and execution on Android Devices.",
- "Url" : "http://necessitas.kde.org",
+ "Description" : "Develop applications for Android devices",
+ "LongDescription" : [
+ "Connect devices with USB to run, debug, and analyze applications built for them.",
+ "You also need:",
+ "- Qt for Android",
+ "- Android development tools"
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES},
"Mimetypes" : [
diff --git a/src/plugins/android/androidavdmanager.cpp b/src/plugins/android/androidavdmanager.cpp
index e537911fc6..3b5c6ccf91 100644
--- a/src/plugins/android/androidavdmanager.cpp
+++ b/src/plugins/android/androidavdmanager.cpp
@@ -2,209 +2,25 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "androidavdmanager.h"
+#include "androidconfigurations.h"
#include "androidtr.h"
-#include "avdmanageroutputparser.h"
#include <coreplugin/icore.h>
-#include <projectexplorer/projectexplorerconstants.h>
-
-#include <utils/algorithm.h>
-#include <utils/async.h>
#include <utils/qtcprocess.h>
-#include <utils/qtcassert.h>
#include <QLoggingCategory>
#include <QMainWindow>
#include <QMessageBox>
-#include <chrono>
-
using namespace Utils;
-using namespace std;
using namespace std::chrono_literals;
-namespace Android::Internal {
-
-const int avdCreateTimeoutMs = 30000;
+namespace Android::Internal::AndroidAvdManager {
static Q_LOGGING_CATEGORY(avdManagerLog, "qtc.android.avdManager", QtWarningMsg)
-/*!
- Runs the \c avdmanager tool specific to configuration \a config with arguments \a args. Returns
- \c true if the command is successfully executed. Output is copied into \a output. The function
- blocks the calling thread.
- */
-bool AndroidAvdManager::avdManagerCommand(const QStringList &args, QString *output)
-{
- CommandLine cmd(androidConfig().avdManagerToolPath(), args);
- Process proc;
- proc.setEnvironment(androidConfig().toolsEnvironment());
- qCDebug(avdManagerLog).noquote() << "Running AVD Manager command:" << cmd.toUserOutput();
- proc.setCommand(cmd);
- proc.runBlocking();
- if (proc.result() == ProcessResult::FinishedWithSuccess) {
- if (output)
- *output = proc.allOutput();
- return true;
- }
- return false;
-}
-
-static bool checkForTimeout(const chrono::steady_clock::time_point &start,
- int msecs = 3000)
-{
- bool timedOut = false;
- auto end = chrono::steady_clock::now();
- if (chrono::duration_cast<chrono::milliseconds>(end-start).count() > msecs)
- timedOut = true;
- return timedOut;
-}
-
-static CreateAvdInfo createAvdCommand(const CreateAvdInfo &info)
-{
- CreateAvdInfo result = info;
-
- if (!result.isValid()) {
- qCDebug(avdManagerLog) << "AVD Create failed. Invalid CreateAvdInfo" << result.name
- << result.systemImage->displayText() << result.systemImage->apiLevel();
- result.error = Tr::tr("Cannot create AVD. Invalid input.");
- return result;
- }
-
- CommandLine avdManager(androidConfig().avdManagerToolPath(), {"create", "avd", "-n", result.name});
- avdManager.addArgs({"-k", result.systemImage->sdkStylePath()});
-
- if (result.sdcardSize > 0)
- avdManager.addArgs({"-c", QString("%1M").arg(result.sdcardSize)});
-
- if (!result.deviceDefinition.isEmpty() && result.deviceDefinition != "Custom")
- avdManager.addArgs({"-d", QString("%1").arg(result.deviceDefinition)});
-
- if (result.overwrite)
- avdManager.addArg("-f");
-
- qCDebug(avdManagerLog).noquote() << "Running AVD Manager command:" << avdManager.toUserOutput();
- Process proc;
- proc.setProcessMode(ProcessMode::Writer);
- proc.setEnvironment(androidConfig().toolsEnvironment());
- proc.setCommand(avdManager);
- proc.start();
- if (!proc.waitForStarted()) {
- result.error = Tr::tr("Could not start process \"%1\".").arg(avdManager.toUserOutput());
- return result;
- }
- QTC_CHECK(proc.isRunning());
- proc.write("yes\n"); // yes to "Do you wish to create a custom hardware profile"
-
- auto start = chrono::steady_clock::now();
- QString errorOutput;
- QByteArray question;
- while (errorOutput.isEmpty()) {
- proc.waitForReadyRead(500ms);
- question += proc.readAllRawStandardOutput();
- if (question.endsWith(QByteArray("]:"))) {
- // truncate to last line
- int index = question.lastIndexOf(QByteArray("\n"));
- if (index != -1)
- question = question.mid(index);
- if (question.contains("hw.gpu.enabled"))
- proc.write("yes\n");
- else
- proc.write("\n");
- question.clear();
- }
- // The exit code is always 0, so we need to check stderr
- // For now assume that any output at all indicates a error
- errorOutput = QString::fromLocal8Bit(proc.readAllRawStandardError());
- if (!proc.isRunning())
- break;
-
- // For a sane input and command, process should finish before timeout.
- if (checkForTimeout(start, avdCreateTimeoutMs))
- result.error = Tr::tr("Cannot create AVD. Command timed out.");
- }
-
- result.error = errorOutput;
- return result;
-}
-
-AndroidAvdManager::AndroidAvdManager() = default;
-
-AndroidAvdManager::~AndroidAvdManager() = default;
-
-QFuture<CreateAvdInfo> AndroidAvdManager::createAvd(CreateAvdInfo info) const
-{
- return Utils::asyncRun(&createAvdCommand, info);
-}
-
-static void avdConfigEditManufacturerTag(const FilePath &avdPath, bool recoverMode = false)
-{
- if (!avdPath.exists())
- return;
-
- const FilePath configFilePath = avdPath / "config.ini";
- FileReader reader;
- if (!reader.fetch(configFilePath, QIODevice::ReadOnly | QIODevice::Text))
- return;
-
- FileSaver saver(configFilePath);
- QTextStream textStream(reader.data());
- while (!textStream.atEnd()) {
- QString line = textStream.readLine();
- if (line.contains("hw.device.manufacturer")) {
- if (recoverMode)
- line.replace("#", "");
- else
- line.prepend("#");
- }
- line.append("\n");
- saver.write(line.toUtf8());
- }
- saver.finalize();
-}
-
-static AndroidDeviceInfoList listVirtualDevices()
-{
- QString output;
- AndroidDeviceInfoList avdList;
- /*
- Currenly avdmanager tool fails to parse some AVDs because the correct
- device definitions at devices.xml does not have some of the newest devices.
- Particularly, failing because of tag "hw.device.manufacturer", thus removing
- it would make paring successful. However, it has to be returned afterwards,
- otherwise, Android Studio would give an error during parsing also. So this fix
- aim to keep support for Qt Creator and Android Studio.
- */
- FilePaths allAvdErrorPaths;
- FilePaths avdErrorPaths;
-
- do {
- if (!AndroidAvdManager::avdManagerCommand({"list", "avd"}, &output)) {
- qCDebug(avdManagerLog)
- << "Avd list command failed" << output << androidConfig().sdkToolsVersion();
- return {};
- }
-
- avdErrorPaths.clear();
- avdList = parseAvdList(output, &avdErrorPaths);
- allAvdErrorPaths << avdErrorPaths;
- for (const FilePath &avdPath : std::as_const(avdErrorPaths))
- avdConfigEditManufacturerTag(avdPath); // comment out manufacturer tag
- } while (!avdErrorPaths.isEmpty()); // try again
-
- for (const FilePath &avdPath : std::as_const(allAvdErrorPaths))
- avdConfigEditManufacturerTag(avdPath, true); // re-add manufacturer tag
-
- return avdList;
-}
-
-QFuture<AndroidDeviceInfoList> AndroidAvdManager::avdList() const
-{
- return Utils::asyncRun(listVirtualDevices);
-}
-
-QString AndroidAvdManager::startAvd(const QString &name) const
+QString startAvd(const QString &name)
{
if (!findAvd(name).isEmpty() || startAvdAsync(name))
return waitForAvd(name);
@@ -227,9 +43,9 @@ static bool is32BitUserSpace()
return false;
}
-bool AndroidAvdManager::startAvdAsync(const QString &avdName) const
+bool startAvdAsync(const QString &avdName)
{
- const FilePath emulator = androidConfig().emulatorToolPath();
+ const FilePath emulator = AndroidConfig::emulatorToolPath();
if (!emulator.exists()) {
QMetaObject::invokeMethod(Core::ICore::mainWindow(), [emulator] {
QMessageBox::critical(Core::ICore::dialogParent(),
@@ -259,11 +75,11 @@ bool AndroidAvdManager::startAvdAsync(const QString &avdName) const
});
// start the emulator
- CommandLine cmd(androidConfig().emulatorToolPath());
+ CommandLine cmd(emulator);
if (is32BitUserSpace())
cmd.addArg("-force-32bit");
- cmd.addArgs(androidConfig().emulatorArgs(), CommandLine::Raw);
+ cmd.addArgs(AndroidConfig::emulatorArgs(), CommandLine::Raw);
cmd.addArgs({"-avd", avdName});
qCDebug(avdManagerLog).noquote() << "Running command (startAvdAsync):" << cmd.toUserOutput();
avdProcess->setCommand(cmd);
@@ -271,20 +87,40 @@ bool AndroidAvdManager::startAvdAsync(const QString &avdName) const
return avdProcess->waitForStarted(QDeadlineTimer::Forever);
}
-QString AndroidAvdManager::findAvd(const QString &avdName) const
+QString findAvd(const QString &avdName)
{
- const QList<AndroidDeviceInfo> devices = androidConfig().connectedDevices();
- for (const AndroidDeviceInfo &device : devices) {
- if (device.type != ProjectExplorer::IDevice::Emulator)
+ const QStringList lines = AndroidConfig::devicesCommandOutput();
+ for (const QString &line : lines) {
+ // skip the daemon logs
+ if (line.startsWith("* daemon"))
continue;
- if (device.avdName == avdName)
- return device.serialNumber;
+
+ const QString serialNumber = line.left(line.indexOf('\t')).trimmed();
+ if (!serialNumber.startsWith("emulator"))
+ continue;
+
+ if (AndroidConfig::getAvdName(serialNumber) == avdName)
+ return serialNumber;
}
return {};
}
-QString AndroidAvdManager::waitForAvd(const QString &avdName,
- const std::optional<QFuture<void>> &future) const
+static bool waitForBooted(const QString &serialNumber, const std::optional<QFuture<void>> &future)
+{
+ // found a serial number, now wait until it's done booting...
+ for (int i = 0; i < 60; ++i) {
+ if (future && future->isCanceled())
+ return false;
+ if (isAvdBooted(serialNumber))
+ return true;
+ QThread::sleep(2);
+ if (!AndroidConfig::isConnected(serialNumber)) // device was disconnected
+ return false;
+ }
+ return false;
+}
+
+QString waitForAvd(const QString &avdName, const std::optional<QFuture<void>> &future)
{
// we cannot use adb -e wait-for-device, since that doesn't work if a emulator is already running
// 60 rounds of 2s sleeping, two minutes for the avd to start
@@ -300,36 +136,17 @@ QString AndroidAvdManager::waitForAvd(const QString &avdName,
return {};
}
-bool AndroidAvdManager::isAvdBooted(const QString &device) const
+bool isAvdBooted(const QString &device)
{
- QStringList arguments = AndroidDeviceInfo::adbSelector(device);
- arguments << "shell" << "getprop" << "init.svc.bootanim";
-
- const CommandLine command({androidConfig().adbToolPath(), arguments});
- qCDebug(avdManagerLog).noquote() << "Running command (isAvdBooted):" << command.toUserOutput();
+ const CommandLine cmd{AndroidConfig::adbToolPath(), {AndroidDeviceInfo::adbSelector(device),
+ "shell", "getprop", "init.svc.bootanim"}};
+ qCDebug(avdManagerLog).noquote() << "Running command (isAvdBooted):" << cmd.toUserOutput();
Process adbProc;
- adbProc.setCommand(command);
+ adbProc.setCommand(cmd);
adbProc.runBlocking();
if (adbProc.result() != ProcessResult::FinishedWithSuccess)
return false;
- QString value = adbProc.allOutput().trimmed();
- return value == "stopped";
-}
-
-bool AndroidAvdManager::waitForBooted(const QString &serialNumber,
- const std::optional<QFuture<void>> &future) const
-{
- // found a serial number, now wait until it's done booting...
- for (int i = 0; i < 60; ++i) {
- if (future && future->isCanceled())
- return false;
- if (isAvdBooted(serialNumber))
- return true;
- QThread::sleep(2);
- if (!androidConfig().isConnected(serialNumber)) // device was disconnected
- return false;
- }
- return false;
+ return adbProc.allOutput().trimmed() == "stopped";
}
-} // Android::Internal
+} // namespace Android::Internal::AndroidAvdManager
diff --git a/src/plugins/android/androidavdmanager.h b/src/plugins/android/androidavdmanager.h
index 4cf9a8d81d..5a1f188038 100644
--- a/src/plugins/android/androidavdmanager.h
+++ b/src/plugins/android/androidavdmanager.h
@@ -2,32 +2,16 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
-#include "androidconfigurations.h"
-
#include <QFuture>
#include <optional>
-namespace Android::Internal {
-
-class AndroidAvdManager
-{
-public:
- AndroidAvdManager();
- ~AndroidAvdManager();
-
- QFuture<CreateAvdInfo> createAvd(CreateAvdInfo info) const;
- QFuture<AndroidDeviceInfoList> avdList() const;
-
- QString startAvd(const QString &name) const;
- bool startAvdAsync(const QString &avdName) const;
- QString findAvd(const QString &avdName) const;
- QString waitForAvd(const QString &avdName, const std::optional<QFuture<void>> &future = {}) const;
- bool isAvdBooted(const QString &device) const;
- static bool avdManagerCommand(const QStringList &args, QString *output);
+namespace Android::Internal::AndroidAvdManager {
-private:
- bool waitForBooted(const QString &serialNumber, const std::optional<QFuture<void>> &future = {}) const;
-};
+QString startAvd(const QString &name);
+bool startAvdAsync(const QString &avdName);
+QString findAvd(const QString &avdName);
+QString waitForAvd(const QString &avdName, const std::optional<QFuture<void>> &future = {});
+bool isAvdBooted(const QString &device);
-} // Android::Internal
+} // namespace Android::Internal::AndroidAvdManager
diff --git a/src/plugins/android/androidbuildapkstep.cpp b/src/plugins/android/androidbuildapkstep.cpp
index 317b72722b..51f7318097 100644
--- a/src/plugins/android/androidbuildapkstep.cpp
+++ b/src/plugins/android/androidbuildapkstep.cpp
@@ -415,7 +415,7 @@ bool AndroidBuildApkWidget::isOpenSslLibsIncluded()
QString AndroidBuildApkWidget::openSslIncludeFileContent(const FilePath &projectPath)
{
- QString openSslPath = androidConfig().openSslLocation().toString();
+ QString openSslPath = AndroidConfig::openSslLocation().toString();
if (projectPath.endsWith(".pro"))
return "android: include(" + openSslPath + "/openssl.pri)";
if (projectPath.endsWith("CMakeLists.txt"))
@@ -541,7 +541,7 @@ bool AndroidBuildApkStep::init()
QStringList arguments = {"--input", m_inputFile.path(),
"--output", outputDir.path(),
"--android-platform", m_buildTargetSdk,
- "--jdk", androidConfig().openJDKLocation().path()};
+ "--jdk", AndroidConfig::openJDKLocation().path()};
if (verboseOutput())
arguments << "--verbose";
@@ -929,17 +929,16 @@ QVariant AndroidBuildApkStep::data(Utils::Id id) const
{
if (id == Constants::AndroidNdkPlatform) {
if (auto qtVersion = QtKitAspect::qtVersion(kit()))
- return androidConfig()
- .bestNdkPlatformMatch(AndroidManager::minimumSDK(target()), qtVersion);
+ return AndroidConfig::bestNdkPlatformMatch(AndroidManager::minimumSDK(target()), qtVersion);
return {};
}
if (id == Constants::NdkLocation) {
if (auto qtVersion = QtKitAspect::qtVersion(kit()))
- return QVariant::fromValue(androidConfig().ndkLocation(qtVersion));
+ return QVariant::fromValue(AndroidConfig::ndkLocation(qtVersion));
return {};
}
if (id == Constants::SdkLocation)
- return QVariant::fromValue(androidConfig().sdkLocation());
+ return QVariant::fromValue(AndroidConfig::sdkLocation());
if (id == Constants::AndroidMkSpecAbis)
return AndroidManager::applicationAbis(target());
@@ -1000,9 +999,9 @@ QAbstractItemModel *AndroidBuildApkStep::keystoreCertificates()
"-storepass", m_keystorePasswd, "-J-Duser.language=en"};
Process keytoolProc;
- keytoolProc.setCommand({androidConfig().keytoolPath(), params});
+ keytoolProc.setCommand({AndroidConfig::keytoolPath(), params});
using namespace std::chrono_literals;
- keytoolProc.runBlocking(30s, EventLoopMode::On);
+ keytoolProc.runBlocking(30s);
if (keytoolProc.result() > ProcessResult::FinishedWithError)
QMessageBox::critical(nullptr, Tr::tr("Error"), Tr::tr("Failed to run keytool."));
else
diff --git a/src/plugins/android/androidconfigurations.cpp b/src/plugins/android/androidconfigurations.cpp
index f9835e17b0..feadec6111 100644
--- a/src/plugins/android/androidconfigurations.cpp
+++ b/src/plugins/android/androidconfigurations.cpp
@@ -59,8 +59,8 @@
# include <QTest>
#endif // WITH_TESTS
-using namespace QtSupport;
using namespace ProjectExplorer;
+using namespace QtSupport;
using namespace Utils;
namespace {
@@ -141,43 +141,177 @@ static QString buildToolsPackageMarker()
return QLatin1String(Constants::buildToolsPackageName) + ";";
}
-//////////////////////////////////
-// AndroidConfig
-//////////////////////////////////
+static QString getDeviceProperty(const QString &device, const QString &property)
+{
+ // workaround for '????????????' serial numbers
+ Process adbProc;
+ adbProc.setCommand({AndroidConfig::adbToolPath(),
+ {AndroidDeviceInfo::adbSelector(device), "shell", "getprop", property}});
+ adbProc.runBlocking();
+ if (adbProc.result() == ProcessResult::FinishedWithSuccess)
+ return adbProc.allOutput();
+ return {};
+}
-QLatin1String AndroidConfig::toolchainPrefix(const Abi &abi)
+static QLatin1String toolsPrefix(const Abi &abi)
{
switch (abi.architecture()) {
case Abi::ArmArchitecture:
if (abi.wordWidth() == 64)
- return AArch64ToolchainPrefix;
- return ArmToolchainPrefix;
+ return AArch64ToolsPrefix;
+ return ArmToolsPrefix;
case Abi::X86Architecture:
if (abi.wordWidth() == 64)
- return X86_64ToolchainPrefix;
- return X86ToolchainPrefix;
+ return X86_64ToolsPrefix;
+ return X86ToolsPrefix;
default:
return Unknown;
}
}
-QLatin1String AndroidConfig::toolsPrefix(const Abi &abi)
+static QLatin1String toolchainPrefix(const Abi &abi)
{
switch (abi.architecture()) {
case Abi::ArmArchitecture:
if (abi.wordWidth() == 64)
- return AArch64ToolsPrefix;
- return ArmToolsPrefix;
+ return AArch64ToolchainPrefix;
+ return ArmToolchainPrefix;
case Abi::X86Architecture:
if (abi.wordWidth() == 64)
- return X86_64ToolsPrefix;
- return X86ToolsPrefix;
+ return X86_64ToolchainPrefix;
+ return X86ToolchainPrefix;
default:
return Unknown;
}
}
-QLatin1String AndroidConfig::displayName(const Abi &abi)
+static FilePath gdbPathFromNdk(const Abi &abi, const FilePath &ndkLocation)
+{
+ const FilePath path = ndkLocation.pathAppended(
+ QString("prebuilt/%1/bin/gdb%2").arg(AndroidConfig::toolchainHostFromNdk(ndkLocation),
+ QString(QTC_HOST_EXE_SUFFIX)));
+ if (path.exists())
+ return path;
+ // fallback for old NDKs (e.g. 10e)
+ return ndkLocation.pathAppended(QString("toolchains/%1-4.9/prebuilt/%2/bin/%3-gdb%4")
+ .arg(toolchainPrefix(abi),
+ AndroidConfig::toolchainHostFromNdk(ndkLocation),
+ toolsPrefix(abi),
+ QString(QTC_HOST_EXE_SUFFIX)));
+}
+
+static FilePath lldbPathFromNdk(const FilePath &ndkLocation)
+{
+ const FilePath path = ndkLocation.pathAppended(
+ QString("toolchains/llvm/prebuilt/%1/bin/lldb%2")
+ .arg(AndroidConfig::toolchainHostFromNdk(ndkLocation), QString(QTC_HOST_EXE_SUFFIX)));
+ return path.exists() ? path : FilePath();
+}
+
+namespace AndroidConfig {
+
+struct SdkForQtVersions
+{
+ QList<QVersionNumber> versions;
+ QStringList essentialPackages;
+
+ bool containsVersion(const QVersionNumber &qtVersion) const
+ {
+ return versions.contains(qtVersion)
+ || versions.contains(QVersionNumber(qtVersion.majorVersion(),
+ qtVersion.minorVersion()));
+ }
+};
+
+struct AndroidConfigData
+{
+ void load(const QtcSettings &settings);
+ void save(QtcSettings &settings) const;
+ void parseDependenciesJson();
+
+ FilePath m_sdkLocation;
+ FilePath m_temporarySdkToolsPath;
+ QStringList m_sdkManagerToolArgs;
+ FilePath m_openJDKLocation;
+ FilePath m_keystoreLocation;
+ FilePath m_openSslLocation;
+ QString m_emulatorArgs;
+ bool m_automaticKitCreation = true;
+ QUrl m_sdkToolsUrl;
+ QByteArray m_sdkToolsSha256;
+ QStringList m_commonEssentialPkgs;
+ SdkForQtVersions m_defaultSdkDepends;
+ QList<SdkForQtVersions> m_specificQtVersions;
+ QStringList m_customNdkList;
+ FilePath m_defaultNdk;
+ bool m_sdkFullyConfigured = false;
+ QHash<QString, QString> m_serialNumberToDeviceName; // cache
+};
+
+static AndroidConfigData &config()
+{
+ static AndroidConfigData theAndroidConfig;
+ return theAndroidConfig;
+}
+
+static FilePath ndkSubPath(const SdkForQtVersions &packages)
+{
+ const QString ndkPrefix = ndkPackageMarker();
+ for (const QString &package : packages.essentialPackages)
+ if (package.startsWith(ndkPrefix))
+ return FilePath::fromString(NdksSubDir) / package.sliced(ndkPrefix.length());
+
+ return {};
+}
+
+static FilePath ndkSubPathFromQtVersion(const QtVersion &version)
+{
+ if (auto androidQtVersion = dynamic_cast<const AndroidQtVersion *>(&version)) {
+ bool ok;
+ const AndroidQtVersion::BuiltWith bw = androidQtVersion->builtWith(&ok);
+ if (ok)
+ return FilePath::fromString(NdksSubDir) / bw.ndkVersion.toString();
+ }
+
+ for (const SdkForQtVersions &item : config().m_specificQtVersions) {
+ if (item.containsVersion(version.qtVersion()))
+ return ndkSubPath(item);
+ }
+ return ndkSubPath(config().m_defaultSdkDepends);
+}
+
+//////////////////////////////////
+// AndroidConfig
+//////////////////////////////////
+
+QString getAvdName(const QString &serialnumber)
+{
+ const int index = serialnumber.indexOf(QLatin1String("-"));
+ if (index == -1)
+ return {};
+ bool ok;
+ const int port = serialnumber.mid(index + 1).toInt(&ok);
+ if (!ok)
+ return {};
+
+ QTcpSocket tcpSocket;
+ tcpSocket.connectToHost(QHostAddress(QHostAddress::LocalHost), port);
+ if (!tcpSocket.waitForConnected(100)) // Don't wait more than 100ms for a local connection
+ return {};
+
+ tcpSocket.write("avd name\nexit\n");
+ tcpSocket.waitForDisconnected(500);
+
+ const QByteArrayList response = tcpSocket.readAll().split('\n');
+ // The input "avd name" might not be echoed as-is, but contain ASCII control sequences.
+ for (int i = response.size() - 1; i > 1; --i) {
+ if (response.at(i).startsWith("OK"))
+ return QString::fromLatin1(response.at(i - 1)).trimmed();
+ }
+ return {};
+}
+
+QLatin1String displayName(const Abi &abi)
{
switch (abi.architecture()) {
case Abi::ArmArchitecture:
@@ -193,11 +327,11 @@ QLatin1String AndroidConfig::displayName(const Abi &abi)
}
}
-void AndroidConfig::load(const QtcSettings &settings)
+void AndroidConfigData::load(const QtcSettings &settings)
{
// user settings
QVariant emulatorArgs = settings.value(EmulatorArgsKey, QString("-netdelay none -netspeed full"));
- if (emulatorArgs.typeId() == QVariant::StringList) // Changed in 8.0 from QStringList to QString.
+ if (emulatorArgs.typeId() == QMetaType::QStringList) // Changed in 8.0 from QStringList to QString.
emulatorArgs = ProcessArgs::joinArgs(emulatorArgs.toStringList());
m_emulatorArgs = emulatorArgs.toString();
m_sdkLocation = FilePath::fromUserInput(settings.value(SDKLocationKey).toString()).cleanPath();
@@ -224,7 +358,7 @@ void AndroidConfig::load(const QtcSettings &settings)
// persistent settings
}
m_customNdkList.removeAll("");
- if (!m_defaultNdk.isEmpty() && ndkVersion(m_defaultNdk).isNull()) {
+ if (!m_defaultNdk.isEmpty() && AndroidConfig::ndkVersion(m_defaultNdk).isNull()) {
if (avdConfigLog().isDebugEnabled())
qCDebug(avdConfigLog).noquote() << "Clearing invalid default NDK setting:"
<< m_defaultNdk.toUserOutput();
@@ -233,7 +367,7 @@ void AndroidConfig::load(const QtcSettings &settings)
parseDependenciesJson();
}
-void AndroidConfig::save(QtcSettings &settings) const
+void AndroidConfigData::save(QtcSettings &settings) const
{
QFileInfo fileInfo(sdkSettingsFileName());
if (fileInfo.exists())
@@ -251,7 +385,7 @@ void AndroidConfig::save(QtcSettings &settings) const
settings.setValue(SdkFullyConfiguredKey, m_sdkFullyConfigured);
}
-void AndroidConfig::parseDependenciesJson()
+void AndroidConfigData::parseDependenciesJson()
{
const FilePath sdkConfigUserFile = Core::ICore::userResourcePath(JsonFilePath);
const FilePath sdkConfigFile = Core::ICore::resourcePath(JsonFilePath);
@@ -368,7 +502,7 @@ static QList<int> availableNdkPlatformsV21Plus(const FilePath &ndkLocation, cons
if (abis.isEmpty())
return {};
- const QString abi = AndroidConfig::toolsPrefix(abis.first());
+ const QString abi = toolsPrefix(abis.first());
const FilePath libPath =
AndroidConfig::toolchainPathFromNdk(ndkLocation, hostOs) / "sysroot/usr/lib" / abi;
const FilePaths dirEntries = libPath.dirEntries(QDir::Dirs | QDir::NoDotAndDotDot);
@@ -389,125 +523,90 @@ static QList<int> availableNdkPlatformsImpl(const FilePath &ndkLocation, const A
return Utils::sorted(std::move(result), std::greater<>());
}
-QList<int> AndroidConfig::availableNdkPlatforms(const QtVersion *qtVersion) const
+static QList<int> availableNdkPlatforms(const QtVersion *qtVersion)
{
- return availableNdkPlatformsImpl(ndkLocation(qtVersion), qtVersion->qtAbis(),
+ return availableNdkPlatformsImpl(AndroidConfig::ndkLocation(qtVersion), qtVersion->qtAbis(),
HostOsInfo::hostOs());
}
-QStringList AndroidConfig::getCustomNdkList() const
-{
- return m_customNdkList;
-}
+QStringList getCustomNdkList() { return config().m_customNdkList; }
-void AndroidConfig::addCustomNdk(const QString &customNdk)
+void addCustomNdk(const QString &customNdk)
{
- if (!m_customNdkList.contains(customNdk))
- m_customNdkList.append(customNdk);
+ if (!config().m_customNdkList.contains(customNdk))
+ config().m_customNdkList.append(customNdk);
}
-void AndroidConfig::removeCustomNdk(const QString &customNdk)
+void removeCustomNdk(const QString &customNdk)
{
- m_customNdkList.removeAll(customNdk);
+ config().m_customNdkList.removeAll(customNdk);
}
-void AndroidConfig::setDefaultNdk(const Utils::FilePath &defaultNdk)
-{
- m_defaultNdk = defaultNdk;
-}
+void setDefaultNdk(const FilePath &defaultNdk) { config().m_defaultNdk = defaultNdk; }
-FilePath AndroidConfig::defaultNdk() const
-{
- return m_defaultNdk;
-}
+FilePath defaultNdk() { return config().m_defaultNdk; }
-FilePath AndroidConfig::openSslLocation() const
-{
- return m_openSslLocation;
-}
+FilePath openSslLocation() { return config().m_openSslLocation; }
-void AndroidConfig::setOpenSslLocation(const FilePath &openSslLocation)
+void setOpenSslLocation(const FilePath &openSslLocation)
{
- m_openSslLocation = openSslLocation;
+ config().m_openSslLocation = openSslLocation;
}
-QStringList AndroidConfig::apiLevelNamesFor(const SdkPlatformList &platforms)
+QStringList apiLevelNamesFor(const SdkPlatformList &platforms)
{
return Utils::transform(platforms, AndroidConfig::apiLevelNameFor);
}
-QString AndroidConfig::apiLevelNameFor(const SdkPlatform *platform)
+QString apiLevelNameFor(const SdkPlatform *platform)
{
if (platform && platform->apiLevel() > 0) {
QString sdkStylePath = platform->sdkStylePath();
return sdkStylePath.remove("platforms;");
}
-
return {};
}
-FilePath AndroidConfig::adbToolPath() const
+FilePath adbToolPath()
{
- return m_sdkLocation.pathAppended("platform-tools/adb").withExecutableSuffix();
+ return config().m_sdkLocation.pathAppended("platform-tools/adb").withExecutableSuffix();
}
-FilePath AndroidConfig::emulatorToolPath() const
+FilePath emulatorToolPath()
{
- const FilePath emulatorFile = m_sdkLocation.pathAppended("emulator/emulator")
- .withExecutableSuffix();
- if (emulatorFile.exists())
- return emulatorFile;
-
- return {};
+ const FilePath emulatorFile
+ = config().m_sdkLocation.pathAppended("emulator/emulator").withExecutableSuffix();
+ return emulatorFile.exists() ? emulatorFile : FilePath();
}
-FilePath AndroidConfig::sdkManagerToolPath() const
+FilePath sdkManagerToolPath()
{
- const FilePath sdkmanagerPath = m_sdkLocation.pathAppended(Constants::cmdlineToolsName)
+ const FilePath sdkmanagerPath = config()
+ .m_sdkLocation.pathAppended(Constants::cmdlineToolsName)
.pathAppended("latest/bin/sdkmanager" ANDROID_BAT_SUFFIX);
if (sdkmanagerPath.exists())
return sdkmanagerPath;
// If it's a first time install use the path of Constants::cmdlineToolsName temporary download
- const FilePath sdkmanagerTmpPath = m_temporarySdkToolsPath.pathAppended(
+ const FilePath sdkmanagerTmpPath = config().m_temporarySdkToolsPath.pathAppended(
"/bin/sdkmanager" ANDROID_BAT_SUFFIX);
- if (sdkmanagerTmpPath.exists())
- return sdkmanagerTmpPath;
-
- return {};
+ return sdkmanagerTmpPath.exists() ? sdkmanagerTmpPath : FilePath();
}
-FilePath AndroidConfig::avdManagerToolPath() const
+FilePath avdManagerToolPath()
{
- const FilePath sdkmanagerPath = m_sdkLocation.pathAppended(Constants::cmdlineToolsName)
+ const FilePath sdkmanagerPath = config()
+ .m_sdkLocation.pathAppended(Constants::cmdlineToolsName)
.pathAppended("/latest/bin/avdmanager" ANDROID_BAT_SUFFIX);
- if (sdkmanagerPath.exists())
- return sdkmanagerPath;
-
- return {};
-}
-
-void AndroidConfig::setTemporarySdkToolsPath(const Utils::FilePath &path)
-{
- m_temporarySdkToolsPath = path;
+ return sdkmanagerPath.exists() ? sdkmanagerPath : FilePath();
}
-FilePath AndroidConfig::sdkToolsVersionPath() const
+void setTemporarySdkToolsPath(const FilePath &path)
{
- const FilePath sdkVersionPaths = m_sdkLocation.pathAppended(Constants::cmdlineToolsName)
- .pathAppended("/latest/source.properties");
- if (sdkVersionPaths.exists())
- return sdkVersionPaths;
-
- // If it's a first time install use the path of Constants::cmdlineToolsName temporary download
- const FilePath tmpSdkPath = m_temporarySdkToolsPath.pathAppended("source.properties");
- if (tmpSdkPath.exists())
- return tmpSdkPath;
-
- return {};
+ config().m_temporarySdkToolsPath = path;
}
-FilePath AndroidConfig::toolchainPathFromNdk(const FilePath &ndkLocation, OsType hostOs)
+FilePath toolchainPathFromNdk(const FilePath &ndkLocation, OsType hostOs)
{
const FilePath tcPath = ndkLocation / "toolchains/";
FilePath toolchainPath;
@@ -544,12 +643,12 @@ FilePath AndroidConfig::toolchainPathFromNdk(const FilePath &ndkLocation, OsType
return {};
}
-FilePath AndroidConfig::toolchainPath(const QtVersion *qtVersion) const
+FilePath toolchainPath(const QtVersion *qtVersion)
{
return toolchainPathFromNdk(ndkLocation(qtVersion));
}
-FilePath AndroidConfig::clangPathFromNdk(const FilePath &ndkLocation)
+FilePath clangPathFromNdk(const FilePath &ndkLocation)
{
const FilePath path = toolchainPathFromNdk(ndkLocation);
if (path.isEmpty())
@@ -557,205 +656,88 @@ FilePath AndroidConfig::clangPathFromNdk(const FilePath &ndkLocation)
return path.pathAppended("bin/clang").withExecutableSuffix();
}
-FilePath AndroidConfig::gdbPath(const Abi &abi, const QtVersion *qtVersion) const
-{
- return gdbPathFromNdk(abi, ndkLocation(qtVersion));
-}
-
-FilePath AndroidConfig::gdbPathFromNdk(const Abi &abi, const FilePath &ndkLocation)
-{
- const FilePath path = ndkLocation.pathAppended(
- QString("prebuilt/%1/bin/gdb%2").arg(toolchainHostFromNdk(ndkLocation),
- QString(QTC_HOST_EXE_SUFFIX)));
- if (path.exists())
- return path;
- // fallback for old NDKs (e.g. 10e)
- return ndkLocation.pathAppended(QString("toolchains/%1-4.9/prebuilt/%2/bin/%3-gdb%4")
- .arg(toolchainPrefix(abi),
- toolchainHostFromNdk(ndkLocation),
- toolsPrefix(abi),
- QString(QTC_HOST_EXE_SUFFIX)));
-}
-
-FilePath AndroidConfig::lldbPathFromNdk(const FilePath &ndkLocation)
-{
- 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)
+FilePath makePathFromNdk(const FilePath &ndkLocation)
{
return ndkLocation.pathAppended(
QString("prebuilt/%1/bin/make%2").arg(toolchainHostFromNdk(ndkLocation),
QString(QTC_HOST_EXE_SUFFIX)));
}
-FilePath AndroidConfig::openJDKBinPath() const
+static FilePath openJDKBinPath()
{
- const FilePath path = m_openJDKLocation;
- if (!path.isEmpty())
- return path.pathAppended("bin");
- return path;
+ const FilePath path = config().m_openJDKLocation;
+ return path.isEmpty() ? path : path.pathAppended("bin");
}
-FilePath AndroidConfig::keytoolPath() const
+FilePath keytoolPath()
{
return openJDKBinPath().pathAppended(keytoolName).withExecutableSuffix();
}
-QList<AndroidDeviceInfo> AndroidConfig::connectedDevices(QString *error) const
+QStringList devicesCommandOutput()
{
- QList<AndroidDeviceInfo> devices;
- Process adbProc;
- CommandLine cmd{adbToolPath(), {"devices"}};
- adbProc.setCommand(cmd);
- using namespace std::chrono_literals;
- adbProc.runBlocking(30s);
- if (adbProc.result() != ProcessResult::FinishedWithSuccess) {
- if (error)
- *error = Tr::tr("Could not run: %1").arg(cmd.toUserOutput());
- return devices;
- }
- QStringList adbDevs = adbProc.allOutput().split('\n', Qt::SkipEmptyParts);
- if (adbDevs.empty())
- return devices;
-
- for (const QString &line : adbDevs) // remove the daemon logs
- if (line.startsWith("* daemon"))
- adbDevs.removeOne(line);
- adbDevs.removeFirst(); // remove "List of devices attached" header line
-
- // workaround for '????????????' serial numbers:
- // can use "adb -d" when only one usb device attached
- for (const QString &device : std::as_const(adbDevs)) {
- const QString serialNo = device.left(device.indexOf('\t')).trimmed();
- const QString deviceType = device.mid(device.indexOf('\t')).trimmed();
- AndroidDeviceInfo dev;
- dev.serialNumber = serialNo;
- dev.type = serialNo.startsWith(QLatin1String("emulator")) ? IDevice::Emulator
- : IDevice::Hardware;
- dev.sdk = getSDKVersion(dev.serialNumber);
- dev.cpuAbi = getAbis(dev.serialNumber);
- if (deviceType == QLatin1String("unauthorized"))
- dev.state = IDevice::DeviceConnected;
- else if (deviceType == QLatin1String("offline"))
- dev.state = IDevice::DeviceDisconnected;
- else
- dev.state = IDevice::DeviceReadyToUse;
-
- if (dev.type == IDevice::Emulator) {
- dev.avdName = getAvdName(dev.serialNumber);
- if (dev.avdName.isEmpty())
- dev.avdName = serialNo;
- }
-
- devices.push_back(dev);
- }
+ Process adbProcess;
+ adbProcess.setCommand({adbToolPath(), {"devices"}});
+ adbProcess.runBlocking();
+ if (adbProcess.result() != ProcessResult::FinishedWithSuccess)
+ return {};
- Utils::sort(devices);
- if (devices.isEmpty() && error)
- *error = Tr::tr("No devices found in output of: %1").arg(cmd.toUserOutput());
- return devices;
+ // mid(1) - remove "List of devices attached" header line.
+ // Example output: "List of devices attached\nemulator-5554\tdevice\n\n".
+ return adbProcess.allOutput().split('\n', Qt::SkipEmptyParts).mid(1);
}
-bool AndroidConfig::isConnected(const QString &serialNumber) const
+bool isConnected(const QString &serialNumber)
{
- const QList<AndroidDeviceInfo> devices = connectedDevices();
- for (const AndroidDeviceInfo &device : devices) {
- if (device.serialNumber == serialNumber)
+ const QStringList lines = devicesCommandOutput();
+ for (const QString &line : lines) {
+ // skip the daemon logs
+ if (!line.startsWith("* daemon") && line.left(line.indexOf('\t')).trimmed() == serialNumber)
return true;
}
return false;
}
-QString AndroidConfig::getDeviceProperty(const QString &device, const QString &property)
-{
- // workaround for '????????????' serial numbers
- CommandLine cmd(androidConfig().adbToolPath(), AndroidDeviceInfo::adbSelector(device));
- cmd.addArgs({"shell", "getprop", property});
+bool sdkFullyConfigured() { return config().m_sdkFullyConfigured; }
- Process adbProc;
- adbProc.setCommand(cmd);
- adbProc.runBlocking();
- if (adbProc.result() != ProcessResult::FinishedWithSuccess)
- return {};
-
- return adbProc.allOutput();
-}
-
-int AndroidConfig::getSDKVersion(const QString &device)
+void setSdkFullyConfigured(bool allEssentialsInstalled)
{
- QString tmp = getDeviceProperty(device, "ro.build.version.sdk");
- if (tmp.isEmpty())
- return -1;
- return tmp.trimmed().toInt();
+ config().m_sdkFullyConfigured = allEssentialsInstalled;
}
-QString AndroidConfig::getAvdName(const QString &serialnumber)
+int getSDKVersion(const QString &device)
{
- int index = serialnumber.indexOf(QLatin1String("-"));
- if (index == -1)
- return {};
- bool ok;
- int port = serialnumber.mid(index + 1).toInt(&ok);
- if (!ok)
- return {};
-
- const QByteArray avdName = "avd name\n";
-
- QTcpSocket tcpSocket;
- tcpSocket.connectToHost(QHostAddress(QHostAddress::LocalHost), port);
- if (!tcpSocket.waitForConnected(100)) // Don't wait more than 100ms for a local connection
- return {};
-
- tcpSocket.write(avdName + "exit\n");
- tcpSocket.waitForDisconnected(500);
-
- QByteArray name;
- const QByteArrayList response = tcpSocket.readAll().split('\n');
- // The input "avd name" might not be echoed as-is, but contain ASCII
- // control sequences.
- for (int i = response.size() - 1; i > 1; --i) {
- if (response.at(i).startsWith("OK")) {
- name = response.at(i - 1);
- break;
- }
- }
- return QString::fromLatin1(name).trimmed();
+ const QString tmp = getDeviceProperty(device, "ro.build.version.sdk");
+ return tmp.isEmpty() ? -1 : tmp.trimmed().toInt();
}
//!
-//! \brief AndroidConfigurations::getProductModel
+//! \brief AndroidConfig::getProductModel
//! \param device serial number
//! \return the produce model of the device or if that cannot be read the serial number
//!
-QString AndroidConfig::getProductModel(const QString &device) const
+QString getProductModel(const QString &device)
{
- if (m_serialNumberToDeviceName.contains(device))
- return m_serialNumberToDeviceName.value(device);
+ if (config().m_serialNumberToDeviceName.contains(device))
+ return config().m_serialNumberToDeviceName.value(device);
- QString model = getDeviceProperty(device, "ro.product.model").trimmed();
+ const QString model = getDeviceProperty(device, "ro.product.model").trimmed();
if (model.isEmpty())
return device;
- if (!device.startsWith(QLatin1String("????")))
- m_serialNumberToDeviceName.insert(device, model);
+ if (!device.startsWith("????"))
+ config().m_serialNumberToDeviceName.insert(device, model);
return model;
}
-QStringList AndroidConfig::getAbis(const QString &device)
+QStringList getAbis(const QString &device)
{
- const FilePath adbTool = androidConfig().adbToolPath();
+ const FilePath adbTool = AndroidConfig::adbToolPath();
QStringList result;
// First try via ro.product.cpu.abilist
- QStringList arguments = AndroidDeviceInfo::adbSelector(device);
- arguments << "shell" << "getprop" << "ro.product.cpu.abilist";
Process adbProc;
- adbProc.setCommand({adbTool, arguments});
+ adbProc.setCommand({adbTool,
+ {AndroidDeviceInfo::adbSelector(device), "shell", "getprop", "ro.product.cpu.abilist"}});
adbProc.runBlocking();
if (adbProc.result() != ProcessResult::FinishedWithSuccess)
return result;
@@ -769,15 +751,13 @@ QStringList AndroidConfig::getAbis(const QString &device)
// Fall back to ro.product.cpu.abi, ro.product.cpu.abi2 ...
for (int i = 1; i < 6; ++i) {
- QStringList arguments = AndroidDeviceInfo::adbSelector(device);
- arguments << QLatin1String("shell") << QLatin1String("getprop");
+ CommandLine cmd{adbTool, {AndroidDeviceInfo::adbSelector(device), "shell", "getprop"}};
if (i == 1)
- arguments << QLatin1String("ro.product.cpu.abi");
+ cmd.addArg("ro.product.cpu.abi");
else
- arguments << QString::fromLatin1("ro.product.cpu.abi%1").arg(i);
-
+ cmd.addArg(QString::fromLatin1("ro.product.cpu.abi%1").arg(i));
Process abiProc;
- abiProc.setCommand({adbTool, arguments});
+ abiProc.setCommand(cmd);
abiProc.runBlocking();
if (abiProc.result() != ProcessResult::FinishedWithSuccess)
return result;
@@ -790,17 +770,14 @@ QStringList AndroidConfig::getAbis(const QString &device)
return result;
}
-bool AndroidConfig::isValidNdk(const QString &ndkLocation) const
+bool isValidNdk(const QString &ndkLocation)
{
- auto ndkPath = Utils::FilePath::fromUserInput(ndkLocation);
+ const FilePath ndkPath = FilePath::fromUserInput(ndkLocation);
- if (!ndkPath.exists())
+ if (!ndkPath.exists() || !ndkPath.pathAppended("toolchains").exists())
return false;
- if (!ndkPath.pathAppended("toolchains").exists())
- return false;
-
- const QVersionNumber version = ndkVersion(ndkPath);
+ const QVersionNumber version = AndroidConfig::ndkVersion(ndkPath);
if (version.isNull())
return false;
@@ -808,11 +785,10 @@ bool AndroidConfig::isValidNdk(const QString &ndkLocation) const
if (version.majorVersion() <= 22
&& (!ndkPlatformsDir.exists() || ndkPlatformsDir.toString().contains(' ')))
return false;
-
return true;
}
-QString AndroidConfig::bestNdkPlatformMatch(int target, const QtVersion *qtVersion) const
+QString bestNdkPlatformMatch(int target, const QtVersion *qtVersion)
{
target = std::max(AndroidManager::defaultMinimumSDK(qtVersion), target);
const QList<int> platforms = availableNdkPlatforms(qtVersion);
@@ -823,19 +799,35 @@ QString AndroidConfig::bestNdkPlatformMatch(int target, const QtVersion *qtVersi
return QString("android-%1").arg(AndroidManager::defaultMinimumSDK(qtVersion));
}
-FilePath AndroidConfig::sdkLocation() const
+FilePath sdkLocation()
{
- return m_sdkLocation;
+ return config().m_sdkLocation;
}
-void AndroidConfig::setSdkLocation(const FilePath &sdkLocation)
+void setSdkLocation(const FilePath &sdkLocation)
{
- m_sdkLocation = sdkLocation;
+ config().m_sdkLocation = sdkLocation;
+}
+
+static FilePath sdkToolsVersionPath()
+{
+ const FilePath sdkVersionPaths = config()
+ .m_sdkLocation.pathAppended(Constants::cmdlineToolsName)
+ .pathAppended("/latest/source.properties");
+ if (sdkVersionPaths.exists())
+ return sdkVersionPaths;
+
+ // If it's a first time install use the path of Constants::cmdlineToolsName temporary download
+ const FilePath tmpSdkPath = config().m_temporarySdkToolsPath.pathAppended("source.properties");
+ if (tmpSdkPath.exists())
+ return tmpSdkPath;
+
+ return {};
}
-QVersionNumber AndroidConfig::sdkToolsVersion() const
+QVersionNumber sdkToolsVersion()
{
- if (!m_sdkLocation.exists())
+ if (!config().m_sdkLocation.exists())
return {};
const FilePath sdkToolsPropertiesPath = sdkToolsVersionPath();
@@ -843,40 +835,34 @@ QVersionNumber AndroidConfig::sdkToolsVersion() const
return QVersionNumber::fromString(settings.value(sdkToolsVersionKey).toString());
}
-QVersionNumber AndroidConfig::buildToolsVersion() const
+QVersionNumber buildToolsVersion()
{
//TODO: return version according to qt version
QVersionNumber maxVersion;
- QDir buildToolsDir(m_sdkLocation.pathAppended("build-tools").toString());
+ QDir buildToolsDir(config().m_sdkLocation.pathAppended("build-tools").toString());
const auto files = buildToolsDir.entryInfoList(QDir::Dirs|QDir::NoDotAndDotDot);
for (const QFileInfo &file: files)
maxVersion = qMax(maxVersion, QVersionNumber::fromString(file.fileName()));
return maxVersion;
}
-QStringList AndroidConfig::sdkManagerToolArgs() const
-{
- return m_sdkManagerToolArgs;
-}
+QStringList sdkManagerToolArgs() { return config().m_sdkManagerToolArgs; }
-void AndroidConfig::setSdkManagerToolArgs(const QStringList &args)
-{
- m_sdkManagerToolArgs = args;
-}
+void setSdkManagerToolArgs(const QStringList &args) { config().m_sdkManagerToolArgs = args; }
-FilePath AndroidConfig::ndkLocation(const QtVersion *qtVersion) const
+FilePath ndkLocation(const QtVersion *qtVersion)
{
- if (!m_defaultNdk.isEmpty())
- return m_defaultNdk; // A selected default NDK is good for any Qt version
+ if (!config().m_defaultNdk.isEmpty())
+ return config().m_defaultNdk; // A selected default NDK is good for any Qt version
return sdkLocation().resolvePath(ndkSubPathFromQtVersion(*qtVersion));
}
-QVersionNumber AndroidConfig::ndkVersion(const QtVersion *qtVersion) const
+QVersionNumber ndkVersion(const QtVersion *qtVersion)
{
return ndkVersion(ndkLocation(qtVersion));
}
-QVersionNumber AndroidConfig::ndkVersion(const FilePath &ndkPath)
+QVersionNumber ndkVersion(const FilePath &ndkPath)
{
QVersionNumber version;
if (!ndkPath.exists()) {
@@ -921,48 +907,13 @@ QVersionNumber AndroidConfig::ndkVersion(const FilePath &ndkPath)
return version;
}
-QStringList AndroidConfig::allEssentials() const
-{
- QtVersions installedVersions = QtVersionManager::versions(
- [](const QtVersion *v) {
- return v->targetDeviceTypes().contains(Android::Constants::ANDROID_DEVICE_TYPE);
- });
-
- QStringList allPackages(defaultEssentials());
- for (const QtVersion *version : installedVersions)
- allPackages.append(essentialsFromQtVersion(*version));
- allPackages.removeDuplicates();
+QUrl sdkToolsUrl() { return config().m_sdkToolsUrl; }
- return allPackages;
-}
+QByteArray getSdkToolsSha256() { return config().m_sdkToolsSha256; }
-static QStringList packagesWithoutNdks(const QStringList &packages)
+static QStringList defaultEssentials()
{
- return Utils::filtered(packages, [] (const QString &p) {
- return !p.startsWith(ndkPackageMarker()); });
-}
-
-bool AndroidConfig::allEssentialsInstalled(AndroidSdkManager *sdkManager)
-{
- QStringList essentialPkgs(allEssentials());
- const auto installedPkgs = sdkManager->installedSdkPackages();
- for (const AndroidSdkPackage *pkg : installedPkgs) {
- if (essentialPkgs.contains(pkg->sdkStylePath()))
- essentialPkgs.removeOne(pkg->sdkStylePath());
- if (essentialPkgs.isEmpty())
- break;
- }
- if (!m_defaultNdk.isEmpty())
- essentialPkgs = packagesWithoutNdks(essentialPkgs);
- return essentialPkgs.isEmpty() ? true : false;
-}
-
-bool AndroidConfig::sdkToolsOk() const
-{
- bool exists = sdkLocation().exists();
- bool writable = sdkLocation().isWritableDir();
- bool sdkToolsExist = !sdkToolsVersion().isNull();
- return exists && writable && sdkToolsExist;
+ return config().m_defaultSdkDepends.essentialPackages + config().m_commonEssentialPkgs;
}
static QStringList packagesExcludingBuiltWithDefaults(const QStringList &packages)
@@ -1004,7 +955,7 @@ static QString essentialBuiltWithBuildToolsPackage(int builtWithApiVersion)
return installedBuildTool;
}
-QStringList AndroidConfig::essentialsFromQtVersion(const QtVersion &version) const
+static QStringList essentialsFromQtVersion(const QtVersion &version)
{
if (auto androidQtVersion = dynamic_cast<const AndroidQtVersion *>(&version)) {
bool ok;
@@ -1017,72 +968,76 @@ QStringList AndroidConfig::essentialsFromQtVersion(const QtVersion &version) con
builtWithPackages.append(essentialBuiltWithBuildToolsPackage(bw.apiVersion));
return builtWithPackages + packagesExcludingBuiltWithDefaults(
- m_defaultSdkDepends.essentialPackages);
+ config().m_defaultSdkDepends.essentialPackages);
}
}
- QVersionNumber qtVersion = version.qtVersion();
- for (const SdkForQtVersions &item : m_specificQtVersions)
+ const QVersionNumber qtVersion = version.qtVersion();
+ for (const SdkForQtVersions &item : config().m_specificQtVersions) {
if (item.containsVersion(qtVersion))
return item.essentialPackages;
-
- return m_defaultSdkDepends.essentialPackages;
-}
-
-static FilePath ndkSubPath(const SdkForQtVersions &packages)
-{
- const QString ndkPrefix = ndkPackageMarker();
- for (const QString &package : packages.essentialPackages)
- if (package.startsWith(ndkPrefix))
- return FilePath::fromString(NdksSubDir) / package.sliced(ndkPrefix.length());
-
- return {};
+ }
+ return config().m_defaultSdkDepends.essentialPackages;
}
-FilePath AndroidConfig::ndkSubPathFromQtVersion(const QtVersion &version) const
+QStringList allEssentials()
{
- if (auto androidQtVersion = dynamic_cast<const AndroidQtVersion *>(&version)) {
- bool ok;
- const AndroidQtVersion::BuiltWith bw = androidQtVersion->builtWith(&ok);
- if (ok)
- return FilePath::fromString(NdksSubDir) / bw.ndkVersion.toString();
- }
+ QtVersions installedVersions = QtVersionManager::versions(
+ [](const QtVersion *v) {
+ return v->targetDeviceTypes().contains(Android::Constants::ANDROID_DEVICE_TYPE);
+ });
- for (const SdkForQtVersions &item : m_specificQtVersions)
- if (item.containsVersion(version.qtVersion()))
- return ndkSubPath(item);
+ QStringList allPackages(defaultEssentials());
+ for (const QtVersion *version : installedVersions)
+ allPackages.append(essentialsFromQtVersion(*version));
+ allPackages.removeDuplicates();
- return ndkSubPath(m_defaultSdkDepends);
+ return allPackages;
}
-QStringList AndroidConfig::defaultEssentials() const
+static QStringList packagesWithoutNdks(const QStringList &packages)
{
- return m_defaultSdkDepends.essentialPackages + m_commonEssentialPkgs;
+ return Utils::filtered(packages, [] (const QString &p) {
+ return !p.startsWith(ndkPackageMarker());
+ });
}
-bool SdkForQtVersions::containsVersion(const QVersionNumber &qtVersion) const
+bool allEssentialsInstalled(AndroidSdkManager *sdkManager)
{
- return versions.contains(qtVersion)
- || versions.contains(QVersionNumber(qtVersion.majorVersion(),
- qtVersion.minorVersion()));
+ QStringList essentialPkgs(allEssentials());
+ const auto installedPkgs = sdkManager->installedSdkPackages();
+ for (const AndroidSdkPackage *pkg : installedPkgs) {
+ if (essentialPkgs.contains(pkg->sdkStylePath()))
+ essentialPkgs.removeOne(pkg->sdkStylePath());
+ if (essentialPkgs.isEmpty())
+ break;
+ }
+ if (!config().m_defaultNdk.isEmpty())
+ essentialPkgs = packagesWithoutNdks(essentialPkgs);
+ return essentialPkgs.isEmpty() ? true : false;
}
-FilePath AndroidConfig::openJDKLocation() const
+bool sdkToolsOk()
{
- return m_openJDKLocation;
+ const bool exists = sdkLocation().exists();
+ const bool writable = sdkLocation().isWritableDir();
+ const bool sdkToolsExist = !sdkToolsVersion().isNull();
+ return exists && writable && sdkToolsExist;
}
-void AndroidConfig::setOpenJDKLocation(const FilePath &openJDKLocation)
+FilePath openJDKLocation() { return config().m_openJDKLocation; }
+
+void setOpenJDKLocation(const FilePath &openJDKLocation)
{
- m_openJDKLocation = openJDKLocation;
+ config().m_openJDKLocation = openJDKLocation;
}
-QString AndroidConfig::toolchainHost(const QtVersion *qtVersion) const
+QString toolchainHost(const QtVersion *qtVersion)
{
return toolchainHostFromNdk(ndkLocation(qtVersion));
}
-QString AndroidConfig::toolchainHostFromNdk(const FilePath &ndkPath)
+QString toolchainHostFromNdk(const FilePath &ndkPath)
{
// detect toolchain host
QString toolchainHost;
@@ -1112,27 +1067,15 @@ QString AndroidConfig::toolchainHostFromNdk(const FilePath &ndkPath)
return toolchainHost;
}
-QString AndroidConfig::emulatorArgs() const
-{
- return m_emulatorArgs;
-}
+QString emulatorArgs() { return config().m_emulatorArgs; }
-void AndroidConfig::setEmulatorArgs(const QString &args)
-{
- m_emulatorArgs = args;
-}
+void setEmulatorArgs(const QString &args) { config().m_emulatorArgs = args; }
-bool AndroidConfig::automaticKitCreation() const
-{
- return m_automaticKitCreation;
-}
+bool automaticKitCreation() { return config().m_automaticKitCreation; }
-void AndroidConfig::setAutomaticKitCreation(bool b)
-{
- m_automaticKitCreation = b;
-}
+void setAutomaticKitCreation(bool b) { config().m_automaticKitCreation = b; }
-FilePath AndroidConfig::defaultSdkPath()
+FilePath defaultSdkPath()
{
QString sdkFromEnvVar = QString::fromLocal8Bit(getenv("ANDROID_SDK_ROOT"));
if (!sdkFromEnvVar.isEmpty())
@@ -1153,6 +1096,101 @@ FilePath AndroidConfig::defaultSdkPath()
QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + "/Android/Sdk");
}
+Environment toolsEnvironment()
+{
+ Environment env = Environment::systemEnvironment();
+ FilePath jdkLocation = openJDKLocation();
+ if (!jdkLocation.isEmpty()) {
+ env.set(Constants::JAVA_HOME_ENV_VAR, jdkLocation.toUserOutput());
+ env.prependOrSetPath(jdkLocation.pathAppended("bin"));
+ }
+ return env;
+}
+
+static FilePath androidStudioPath()
+{
+#if defined(Q_OS_WIN)
+ const QLatin1String registryKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\Android Studio");
+ const QLatin1String valueName("Path");
+ const QSettings settings64(registryKey, QSettings::Registry64Format);
+ const QSettings settings32(registryKey, QSettings::Registry32Format);
+ return FilePath::fromUserInput(
+ settings64.value(valueName, settings32.value(valueName).toString()).toString());
+#endif
+ return {}; // TODO non-Windows
+}
+
+FilePath getJdkPath()
+{
+ FilePath jdkHome = FilePath::fromString(qtcEnvironmentVariable(Constants::JAVA_HOME_ENV_VAR));
+ if (jdkHome.exists())
+ return jdkHome;
+
+ if (HostOsInfo::isWindowsHost()) {
+ // Look for Android Studio's jdk first
+ const FilePath androidStudioSdkPath = androidStudioPath();
+ if (!androidStudioSdkPath.isEmpty()) {
+ const QStringList allVersions{"jbr", "jre"};
+ for (const QString &version : allVersions) {
+ const FilePath androidStudioSdkJbrPath = androidStudioSdkPath / version;
+ if (androidStudioSdkJbrPath.exists())
+ return androidStudioSdkJbrPath;
+ }
+ }
+
+ if (jdkHome.isEmpty()) {
+ QStringList allVersions;
+ QSettings settings("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\JDK\\",
+ QSettings::NativeFormat);
+ allVersions = settings.childGroups();
+#ifdef Q_OS_WIN
+ if (allVersions.isEmpty()) {
+ settings.setDefaultFormat(QSettings::Registry64Format);
+ allVersions = settings.childGroups();
+ }
+#endif // Q_OS_WIN
+
+ // Look for the highest existing JDK
+ allVersions.sort();
+ std::reverse(allVersions.begin(), allVersions.end()); // Order descending
+ for (const QString &version : std::as_const(allVersions)) {
+ settings.beginGroup(version);
+ jdkHome = FilePath::fromUserInput(settings.value("JavaHome").toString());
+ settings.endGroup();
+ if (jdkHome.exists())
+ break;
+ }
+ }
+ } else {
+ QStringList args;
+ if (HostOsInfo::isMacHost())
+ args << "-c"
+ << "/usr/libexec/java_home";
+ else
+ args << "-c"
+ << "readlink -f $(which java)";
+
+ Process findJdkPathProc;
+ findJdkPathProc.setCommand({"sh", args});
+ findJdkPathProc.start();
+ findJdkPathProc.waitForFinished();
+ QByteArray jdkPath = findJdkPathProc.rawStdOut().trimmed();
+
+ if (HostOsInfo::isMacHost()) {
+ jdkHome = FilePath::fromUtf8(jdkPath);
+ } else {
+ jdkPath.replace("bin/java", ""); // For OpenJDK 11
+ jdkPath.replace("jre", "");
+ jdkPath.replace("//", "/");
+ jdkHome = FilePath::fromUtf8(jdkPath);
+ }
+ }
+
+ return jdkHome;
+}
+
+} // namespace AndroidConfig
+
///////////////////////////////////
// AndroidConfigurations
///////////////////////////////////
@@ -1169,11 +1207,9 @@ AndroidConfigurations::AndroidConfigurations()
m_instance = this;
}
-void AndroidConfigurations::setConfig(const AndroidConfig &devConfigs)
+void AndroidConfigurations::applyConfig()
{
emit m_instance->aboutToUpdate();
- androidConfig() = devConfigs;
-
m_instance->save();
updateAndroidDevice();
registerNewToolchains();
@@ -1227,12 +1263,12 @@ void AndroidConfigurations::removeUnusedDebuggers()
QList<FilePath> uniqueNdks;
for (const QtVersion *qt : qtVersions) {
- FilePath ndkLocation = androidConfig().ndkLocation(qt);
+ FilePath ndkLocation = AndroidConfig::ndkLocation(qt);
if (!uniqueNdks.contains(ndkLocation))
uniqueNdks.append(ndkLocation);
}
- uniqueNdks.append(FileUtils::toFilePathList(androidConfig().getCustomNdkList()));
+ uniqueNdks.append(FileUtils::toFilePathList(AndroidConfig::getCustomNdkList()));
const QList<Debugger::DebuggerItem> allDebuggers = Debugger::DebuggerItemManager::debuggers();
for (const Debugger::DebuggerItem &debugger : allDebuggers) {
@@ -1297,14 +1333,14 @@ static QVariant findOrRegisterDebugger(Toolchain *tc,
bool customDebugger = false)
{
const FilePath ndk = static_cast<AndroidToolchain *>(tc)->ndkLocation();
- const FilePath lldbCommand = androidConfig().lldbPathFromNdk(ndk);
+ const FilePath lldbCommand = 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 = androidConfig().gdbPathFromNdk(tc->targetAbi(), ndk);
+ const FilePath gdbCommand = gdbPathFromNdk(tc->targetAbi(), ndk);
// check if the debugger is already registered, but ignoring the display name
const Debugger::DebuggerItem *existingGdb = existingDebugger(gdbCommand,
@@ -1324,7 +1360,7 @@ static QVariant findOrRegisterDebugger(Toolchain *tc,
debugger.setEngineType(Debugger::LldbEngineType);
debugger.setUnexpandedDisplayName(custom + mainName
.arg(getMultiOrSingleAbiString(allSupportedAbis()))
- .arg(androidConfig().ndkVersion(ndk).toString())
+ .arg(AndroidConfig::ndkVersion(ndk).toString())
+ ' ' + debugger.engineTypeName());
debugger.setAutoDetected(true);
debugger.reinitializeFromFile();
@@ -1343,10 +1379,10 @@ static QVariant findOrRegisterDebugger(Toolchain *tc,
debugger.setEngineType(Debugger::GdbEngineType);
// NDK 10 and older have multiple gdb versions per ABI, so check for that.
- const bool oldNdkVersion = androidConfig().ndkVersion(ndk) <= QVersionNumber{11};
+ const bool oldNdkVersion = AndroidConfig::ndkVersion(ndk) <= QVersionNumber{11};
debugger.setUnexpandedDisplayName(custom + mainName
.arg(getMultiOrSingleAbiString(oldNdkVersion ? abisList : allSupportedAbis()))
- .arg(androidConfig().ndkVersion(ndk).toString())
+ .arg(AndroidConfig::ndkVersion(ndk).toString())
+ ' ' + debugger.engineTypeName());
debugger.setAutoDetected(true);
debugger.reinitializeFromFile();
@@ -1357,9 +1393,9 @@ static QVariant findOrRegisterDebugger(Toolchain *tc,
void AndroidConfigurations::registerCustomToolchainsAndDebuggers()
{
const Toolchains existingAndroidToolchains = ToolchainManager::toolchains(
- Utils::equal(&Toolchain::typeId, Utils::Id(Constants::ANDROID_TOOLCHAIN_TYPEID)));
+ Utils::equal(&Toolchain::typeId, Id(Constants::ANDROID_TOOLCHAIN_TYPEID)));
- const FilePaths customNdks = FileUtils::toFilePathList(androidConfig().getCustomNdkList());
+ const FilePaths customNdks = FileUtils::toFilePathList(AndroidConfig::getCustomNdkList());
const Toolchains customToolchains
= autodetectToolchainsFromNdks(existingAndroidToolchains, customNdks, true);
@@ -1378,8 +1414,8 @@ void AndroidConfigurations::updateAutomaticKitList()
if (DeviceTypeKitAspect::deviceTypeId(k) == Constants::ANDROID_DEVICE_TYPE) {
if (k->value(Constants::ANDROID_KIT_NDK).isNull() || k->value(Constants::ANDROID_KIT_SDK).isNull()) {
if (QtVersion *qt = QtKitAspect::qtVersion(k)) {
- k->setValueSilently(Constants::ANDROID_KIT_NDK, androidConfig().ndkLocation(qt).toString());
- k->setValue(Constants::ANDROID_KIT_SDK, androidConfig().sdkLocation().toString());
+ k->setValueSilently(Constants::ANDROID_KIT_NDK, AndroidConfig::ndkLocation(qt).toString());
+ k->setValue(Constants::ANDROID_KIT_SDK, AndroidConfig::sdkLocation().toString());
}
}
}
@@ -1419,7 +1455,7 @@ void AndroidConfigurations::updateAutomaticKitList()
for (const QtVersion *qt : qtVersionsForArch.value(tc->targetAbi())) {
FilePath tcNdk = static_cast<const AndroidToolchain *>(tc)->ndkLocation();
- if (tcNdk != androidConfig().ndkLocation(qt))
+ if (tcNdk != AndroidConfig::ndkLocation(qt))
continue;
const Toolchains allLanguages
@@ -1461,8 +1497,8 @@ void AndroidConfigurations::updateAutomaticKitList()
k->setUnexpandedDisplayName(Tr::tr("Android %1 Clang %2")
.arg(versionStr)
.arg(getMultiOrSingleAbiString(abis)));
- k->setValueSilently(Constants::ANDROID_KIT_NDK, androidConfig().ndkLocation(qt).toString());
- k->setValueSilently(Constants::ANDROID_KIT_SDK, androidConfig().sdkLocation().toString());
+ k->setValueSilently(Constants::ANDROID_KIT_NDK, AndroidConfig::ndkLocation(qt).toString());
+ k->setValueSilently(Constants::ANDROID_KIT_SDK, AndroidConfig::sdkLocation().toString());
};
if (existingKit) {
@@ -1478,23 +1514,6 @@ void AndroidConfigurations::updateAutomaticKitList()
KitManager::deregisterKits(unhandledKits);
}
-Environment AndroidConfig::toolsEnvironment() const
-{
- Environment env = Environment::systemEnvironment();
- FilePath jdkLocation = openJDKLocation();
- if (!jdkLocation.isEmpty()) {
- env.set(Constants::JAVA_HOME_ENV_VAR, jdkLocation.toUserOutput());
- env.prependOrSetPath(jdkLocation.pathAppended("bin"));
- }
- return env;
-}
-
-AndroidConfig &androidConfig()
-{
- static AndroidConfig theCurrentConfig;
- return theCurrentConfig;
-}
-
AndroidSdkManager *AndroidConfigurations::sdkManager()
{
return m_instance->m_sdkManager.get();
@@ -1509,97 +1528,15 @@ void AndroidConfigurations::save()
{
QtcSettings *settings = Core::ICore::settings();
settings->beginGroup(SettingsGroup);
- androidConfig().save(*settings);
+ AndroidConfig::config().save(*settings);
settings->endGroup();
}
-static FilePath androidStudioPath()
-{
-#if defined(Q_OS_WIN)
- const QLatin1String registryKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\Android Studio");
- const QLatin1String valueName("Path");
- const QSettings settings64(registryKey, QSettings::Registry64Format);
- const QSettings settings32(registryKey, QSettings::Registry32Format);
- return FilePath::fromUserInput(
- settings64.value(valueName, settings32.value(valueName).toString()).toString());
-#endif
- return {}; // TODO non-Windows
-}
-
-FilePath AndroidConfig::getJdkPath()
-{
- FilePath jdkHome = FilePath::fromString(qtcEnvironmentVariable(Constants::JAVA_HOME_ENV_VAR));
- if (jdkHome.exists())
- return jdkHome;
-
- if (HostOsInfo::isWindowsHost()) {
- // Look for Android Studio's jdk first
- const FilePath androidStudioSdkPath = androidStudioPath();
- if (!androidStudioSdkPath.isEmpty()) {
- const QStringList allVersions{"jbr", "jre"};
- for (const QString &version : allVersions) {
- const FilePath androidStudioSdkJbrPath = androidStudioSdkPath / version;
- if (androidStudioSdkJbrPath.exists())
- return androidStudioSdkJbrPath;
- }
- }
-
- if (jdkHome.isEmpty()) {
- QStringList allVersions;
- QSettings settings("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\JDK\\",
- QSettings::NativeFormat);
- allVersions = settings.childGroups();
-#ifdef Q_OS_WIN
- if (allVersions.isEmpty()) {
- settings.setDefaultFormat(QSettings::Registry64Format);
- allVersions = settings.childGroups();
- }
-#endif // Q_OS_WIN
-
- // Look for the highest existing JDK
- allVersions.sort();
- std::reverse(allVersions.begin(), allVersions.end()); // Order descending
- for (const QString &version : std::as_const(allVersions)) {
- settings.beginGroup(version);
- jdkHome = FilePath::fromUserInput(settings.value("JavaHome").toString());
- settings.endGroup();
- if (jdkHome.exists())
- break;
- }
- }
- } else {
- QStringList args;
- if (HostOsInfo::isMacHost())
- args << "-c"
- << "/usr/libexec/java_home";
- else
- args << "-c"
- << "readlink -f $(which java)";
-
- Process findJdkPathProc;
- findJdkPathProc.setCommand({"sh", args});
- findJdkPathProc.start();
- findJdkPathProc.waitForFinished();
- QByteArray jdkPath = findJdkPathProc.rawStdOut().trimmed();
-
- if (HostOsInfo::isMacHost()) {
- jdkHome = FilePath::fromUtf8(jdkPath);
- } else {
- jdkPath.replace("bin/java", ""); // For OpenJDK 11
- jdkPath.replace("jre", "");
- jdkPath.replace("//", "/");
- jdkHome = FilePath::fromUtf8(jdkPath);
- }
- }
-
- return jdkHome;
-}
-
void AndroidConfigurations::load()
{
QtcSettings *settings = Core::ICore::settings();
settings->beginGroup(SettingsGroup);
- androidConfig().load(*settings);
+ AndroidConfig::config().load(*settings);
settings->endGroup();
}
@@ -1610,8 +1547,7 @@ void AndroidConfigurations::updateAndroidDevice()
IDevice::ConstPtr dev = devMgr->find(Constants::ANDROID_DEVICE_ID);
if (dev)
devMgr->removeDevice(dev->id());
-
- AndroidDeviceManager::instance()->setupDevicesWatcher();
+ AndroidDeviceManager::setupDevicesWatcher();
}
#ifdef WITH_TESTS
@@ -1677,7 +1613,7 @@ void AndroidConfigurationsTest::testAndroidConfigAvailableNdkPlatforms()
QFETCH(OsType, hostOs);
QFETCH(QList<int>, expectedPlatforms);
- const QList<int> foundPlatforms = availableNdkPlatformsImpl(ndkPath, abis, hostOs);
+ const QList<int> foundPlatforms = AndroidConfig::availableNdkPlatformsImpl(ndkPath, abis, hostOs);
QCOMPARE(foundPlatforms, expectedPlatforms);
}
diff --git a/src/plugins/android/androidconfigurations.h b/src/plugins/android/androidconfigurations.h
index ce4604de18..81477b408a 100644
--- a/src/plugins/android/androidconfigurations.h
+++ b/src/plugins/android/androidconfigurations.h
@@ -28,154 +28,96 @@ namespace Internal { class AndroidSdkManager; }
class CreateAvdInfo
{
public:
- bool isValid() const { return systemImage && systemImage->isValid() && !name.isEmpty(); }
- const SystemImage *systemImage = nullptr;
+ QString sdkStylePath;
+ int apiLevel = -1;
QString name;
QString abi;
QString deviceDefinition;
int sdcardSize = 0;
- QString error; // only used in the return value of createAVD
- bool overwrite = false;
- bool cancelled = false;
};
-struct SdkForQtVersions
-{
- QList<QVersionNumber> versions;
- QStringList essentialPackages;
-
-public:
- bool containsVersion(const QVersionNumber &qtVersion) const;
-};
+namespace AndroidConfig {
-class AndroidConfig
-{
-public:
- void load(const Utils::QtcSettings &settings);
- void save(Utils::QtcSettings &settings) const;
+QString getAvdName(const QString &serialnumber);
+QStringList apiLevelNamesFor(const SdkPlatformList &platforms);
+QString apiLevelNameFor(const SdkPlatform *platform);
- static QStringList apiLevelNamesFor(const SdkPlatformList &platforms);
- static QString apiLevelNameFor(const SdkPlatform *platform);
+Utils::FilePath sdkLocation();
+void setSdkLocation(const Utils::FilePath &sdkLocation);
+QVersionNumber sdkToolsVersion();
+QVersionNumber buildToolsVersion();
+QStringList sdkManagerToolArgs();
+void setSdkManagerToolArgs(const QStringList &args);
- Utils::FilePath sdkLocation() const;
- void setSdkLocation(const Utils::FilePath &sdkLocation);
- QVersionNumber sdkToolsVersion() const;
- Utils::FilePath sdkToolsVersionPath() const;
- QVersionNumber buildToolsVersion() const;
- QStringList sdkManagerToolArgs() const;
- void setSdkManagerToolArgs(const QStringList &args);
+Utils::FilePath ndkLocation(const QtSupport::QtVersion *qtVersion);
+QVersionNumber ndkVersion(const QtSupport::QtVersion *qtVersion);
+QVersionNumber ndkVersion(const Utils::FilePath &ndkPath);
- Utils::FilePath ndkLocation(const QtSupport::QtVersion *qtVersion) const;
- QVersionNumber ndkVersion(const QtSupport::QtVersion *qtVersion) const;
- static QVersionNumber ndkVersion(const Utils::FilePath &ndkPath);
+QUrl sdkToolsUrl();
+QByteArray getSdkToolsSha256();
- QUrl sdkToolsUrl() const { return m_sdkToolsUrl; }
- QByteArray getSdkToolsSha256() const { return m_sdkToolsSha256; }
- Utils::FilePath ndkSubPathFromQtVersion(const QtSupport::QtVersion &version) const; // relative!
+QStringList allEssentials();
+bool allEssentialsInstalled(Internal::AndroidSdkManager *sdkManager);
+bool sdkToolsOk();
- QStringList defaultEssentials() const;
- QStringList essentialsFromQtVersion(const QtSupport::QtVersion &version) const;
- QStringList allEssentials() const;
- bool allEssentialsInstalled(Internal::AndroidSdkManager *sdkManager);
- bool sdkToolsOk() const;
+Utils::FilePath openJDKLocation();
+void setOpenJDKLocation(const Utils::FilePath &openJDKLocation);
- Utils::FilePath openJDKLocation() const;
- void setOpenJDKLocation(const Utils::FilePath &openJDKLocation);
+QString toolchainHost(const QtSupport::QtVersion *qtVersion);
+QString toolchainHostFromNdk(const Utils::FilePath &ndkPath);
- Utils::FilePath keystoreLocation() const;
+QString emulatorArgs();
+void setEmulatorArgs(const QString &args);
- QString toolchainHost(const QtSupport::QtVersion *qtVersion) const;
- static QString toolchainHostFromNdk(const Utils::FilePath &ndkPath);
+bool automaticKitCreation();
+void setAutomaticKitCreation(bool b);
- QString emulatorArgs() const;
- void setEmulatorArgs(const QString &args);
+Utils::FilePath defaultSdkPath();
+Utils::FilePath adbToolPath();
+Utils::FilePath emulatorToolPath();
+Utils::FilePath sdkManagerToolPath();
+Utils::FilePath avdManagerToolPath();
- bool automaticKitCreation() const;
- void setAutomaticKitCreation(bool b);
+void setTemporarySdkToolsPath(const Utils::FilePath &path);
- static Utils::FilePath defaultSdkPath();
- Utils::FilePath adbToolPath() const;
- Utils::FilePath emulatorToolPath() const;
- Utils::FilePath sdkManagerToolPath() const;
- Utils::FilePath avdManagerToolPath() const;
+Utils::FilePath toolchainPath(const QtSupport::QtVersion *qtVersion);
+Utils::FilePath toolchainPathFromNdk(
+ const Utils::FilePath &ndkLocation, Utils::OsType hostOs = Utils::HostOsInfo::hostOs());
+Utils::FilePath clangPathFromNdk(const Utils::FilePath &ndkLocation);
- void setTemporarySdkToolsPath(const Utils::FilePath &path);
+Utils::FilePath makePathFromNdk(const Utils::FilePath &ndkLocation);
- Utils::FilePath toolchainPath(const QtSupport::QtVersion *qtVersion) const;
- static Utils::FilePath toolchainPathFromNdk(const Utils::FilePath &ndkLocation,
- Utils::OsType hostOs = Utils::HostOsInfo::hostOs());
- static Utils::FilePath clangPathFromNdk(const Utils::FilePath &ndkLocation);
+Utils::FilePath keytoolPath();
- Utils::FilePath gdbPath(const ProjectExplorer::Abi &abi, const QtSupport::QtVersion *qtVersion) const;
- static Utils::FilePath gdbPathFromNdk(const ProjectExplorer::Abi &abi,
- const Utils::FilePath &ndkLocation);
- static Utils::FilePath lldbPathFromNdk(const Utils::FilePath &ndkLocation);
- static Utils::FilePath makePathFromNdk(const Utils::FilePath &ndkLocation);
+QStringList devicesCommandOutput();
- Utils::FilePath keytoolPath() const;
+QString bestNdkPlatformMatch(int target, const QtSupport::QtVersion *qtVersion);
- QList<AndroidDeviceInfo> connectedDevices(QString *error = nullptr) const;
+QLatin1String displayName(const ProjectExplorer::Abi &abi);
- QString bestNdkPlatformMatch(int target, const QtSupport::QtVersion *qtVersion) const;
+QString getProductModel(const QString &device);
+bool isConnected(const QString &serialNumber);
- static QLatin1String toolchainPrefix(const ProjectExplorer::Abi &abi);
- static QLatin1String toolsPrefix(const ProjectExplorer::Abi &abi);
- static QLatin1String displayName(const ProjectExplorer::Abi &abi);
+bool sdkFullyConfigured();
+void setSdkFullyConfigured(bool allEssentialsInstalled);
- QString getProductModel(const QString &device) const;
- bool isConnected(const QString &serialNumber) const;
+bool isValidNdk(const QString &ndkLocation);
+QStringList getCustomNdkList();
+void addCustomNdk(const QString &customNdk);
+void removeCustomNdk(const QString &customNdk);
+void setDefaultNdk(const Utils::FilePath &defaultNdk);
+Utils::FilePath defaultNdk();
- bool sdkFullyConfigured() const { return m_sdkFullyConfigured; }
- void setSdkFullyConfigured(bool allEssentialsInstalled) { m_sdkFullyConfigured = allEssentialsInstalled; }
+Utils::FilePath openSslLocation();
+void setOpenSslLocation(const Utils::FilePath &openSslLocation);
- bool isValidNdk(const QString &ndkLocation) const;
- QStringList getCustomNdkList() const;
- void addCustomNdk(const QString &customNdk);
- void removeCustomNdk(const QString &customNdk);
- void setDefaultNdk(const Utils::FilePath &defaultNdk);
- Utils::FilePath defaultNdk() const;
+Utils::FilePath getJdkPath();
+QStringList getAbis(const QString &device);
+int getSDKVersion(const QString &device);
- Utils::FilePath openSslLocation() const;
- void setOpenSslLocation(const Utils::FilePath &openSslLocation);
-
- static Utils::FilePath getJdkPath();
- static QStringList getAbis(const QString &device);
- static int getSDKVersion(const QString &device);
-
- Utils::Environment toolsEnvironment() const;
-
-private:
- static QString getDeviceProperty(const QString &device, const QString &property);
-
- Utils::FilePath openJDKBinPath() const;
- static QString getAvdName(const QString &serialnumber);
-
- void parseDependenciesJson();
-
- QList<int> availableNdkPlatforms(const QtSupport::QtVersion *qtVersion) const;
-
- Utils::FilePath m_sdkLocation;
- Utils::FilePath m_temporarySdkToolsPath;
- QStringList m_sdkManagerToolArgs;
- Utils::FilePath m_openJDKLocation;
- Utils::FilePath m_keystoreLocation;
- Utils::FilePath m_openSslLocation;
- QString m_emulatorArgs;
- bool m_automaticKitCreation = true;
- QUrl m_sdkToolsUrl;
- QByteArray m_sdkToolsSha256;
- QStringList m_commonEssentialPkgs;
- SdkForQtVersions m_defaultSdkDepends;
- QList<SdkForQtVersions> m_specificQtVersions;
- QStringList m_customNdkList;
- Utils::FilePath m_defaultNdk;
- bool m_sdkFullyConfigured = false;
-
- //caches
- mutable QHash<QString, QString> m_serialNumberToDeviceName;
-};
+Utils::Environment toolsEnvironment();
-AndroidConfig &androidConfig();
+} // namespace AndroidConfig
class AndroidConfigurations : public QObject
{
@@ -183,7 +125,7 @@ class AndroidConfigurations : public QObject
public:
static Internal::AndroidSdkManager *sdkManager();
- static void setConfig(const AndroidConfig &config);
+ static void applyConfig();
static AndroidConfigurations *instance();
static void registerNewToolchains();
diff --git a/src/plugins/android/androidcreatekeystorecertificate.cpp b/src/plugins/android/androidcreatekeystorecertificate.cpp
index 77aed2a6ad..004ce94f7b 100644
--- a/src/plugins/android/androidcreatekeystorecertificate.cpp
+++ b/src/plugins/android/androidcreatekeystorecertificate.cpp
@@ -262,8 +262,8 @@ void AndroidCreateKeystoreCertificate::buttonBoxAccepted()
if (!m_stateNameLineEdit->text().isEmpty())
distinguishedNames += QLatin1String(", S=") + m_stateNameLineEdit->text().replace(',', QLatin1String("\\,"));
- const CommandLine command(androidConfig().keytoolPath(),
- { "-genkey", "-keyalg", "RSA",
+ const CommandLine command(AndroidConfig::keytoolPath(),
+ {"-genkey", "-keyalg", "RSA",
"-keystore", m_keystoreFilePath.toString(),
"-storepass", keystorePassword(),
"-alias", certificateAlias(),
@@ -275,7 +275,7 @@ void AndroidCreateKeystoreCertificate::buttonBoxAccepted()
Process genKeyCertProc;
genKeyCertProc.setCommand(command);
using namespace std::chrono_literals;
- genKeyCertProc.runBlocking(15s, EventLoopMode::On);
+ genKeyCertProc.runBlocking(15s);
if (genKeyCertProc.result() != ProcessResult::FinishedWithSuccess) {
QMessageBox::critical(this, Tr::tr("Error"),
diff --git a/src/plugins/android/androiddebugsupport.cpp b/src/plugins/android/androiddebugsupport.cpp
index cea404ef6f..cb3d58a068 100644
--- a/src/plugins/android/androiddebugsupport.cpp
+++ b/src/plugins/android/androiddebugsupport.cpp
@@ -110,8 +110,7 @@ void AndroidDebugSupport::start()
QtSupport::QtVersion *qtVersion = QtSupport::QtKitAspect::qtVersion(kit);
if (!HostOsInfo::isWindowsHost()
- && (qtVersion
- && androidConfig().ndkVersion(qtVersion) >= QVersionNumber(11, 0, 0))) {
+ && (qtVersion && AndroidConfig::ndkVersion(qtVersion) >= QVersionNumber(11, 0, 0))) {
qCDebug(androidDebugSupportLog) << "UseTargetAsync: " << true;
setUseTargetAsync(true);
}
@@ -165,7 +164,7 @@ void AndroidDebugSupport::start()
int sdkVersion = qMax(AndroidManager::minimumSDK(kit), minimumNdk);
if (qtVersion) {
- const FilePath ndkLocation = androidConfig().ndkLocation(qtVersion);
+ const FilePath ndkLocation = AndroidConfig::ndkLocation(qtVersion);
FilePath sysRoot = ndkLocation
/ "platforms"
/ QString("android-%1").arg(sdkVersion)
diff --git a/src/plugins/android/androiddeployqtstep.cpp b/src/plugins/android/androiddeployqtstep.cpp
index 459bdb6eea..9c5681d086 100644
--- a/src/plugins/android/androiddeployqtstep.cpp
+++ b/src/plugins/android/androiddeployqtstep.cpp
@@ -66,27 +66,45 @@ const QLatin1String InstallFailedUpdateIncompatible("INSTALL_FAILED_UPDATE_INCOM
const QLatin1String InstallFailedPermissionModelDowngrade("INSTALL_FAILED_PERMISSION_MODEL_DOWNGRADE");
const QLatin1String InstallFailedVersionDowngrade("INSTALL_FAILED_VERSION_DOWNGRADE");
+enum DeployErrorFlag
+{
+ NoError = 0,
+ InconsistentCertificates = 0x0001,
+ UpdateIncompatible = 0x0002,
+ PermissionModelDowngrade = 0x0004,
+ VersionDowngrade = 0x0008,
+ Failure = 0x0010
+};
+
+Q_DECLARE_FLAGS(DeployErrorFlags, DeployErrorFlag)
+
+static DeployErrorFlags parseDeployErrors(const QString &deployOutputLine)
+{
+ DeployErrorFlags errorCode = NoError;
+
+ if (deployOutputLine.contains(InstallFailedInconsistentCertificatesString))
+ errorCode |= InconsistentCertificates;
+ if (deployOutputLine.contains(InstallFailedUpdateIncompatible))
+ errorCode |= UpdateIncompatible;
+ if (deployOutputLine.contains(InstallFailedPermissionModelDowngrade))
+ errorCode |= PermissionModelDowngrade;
+ if (deployOutputLine.contains(InstallFailedVersionDowngrade))
+ errorCode |= VersionDowngrade;
+
+ return errorCode;
+}
+
// AndroidDeployQtStep
class AndroidDeployQtStep : public BuildStep
{
Q_OBJECT
- enum DeployErrorCode
- {
- NoError = 0,
- InconsistentCertificates = 0x0001,
- UpdateIncompatible = 0x0002,
- PermissionModelDowngrade = 0x0004,
- VersionDowngrade = 0x0008,
- Failure = 0x0010
- };
-
public:
AndroidDeployQtStep(BuildStepList *bc, Id id);
signals:
- void askForUninstall(DeployErrorCode errorCode);
+ void askForUninstall(DeployErrorFlags errorCode);
private:
void runCommand(const CommandLine &command);
@@ -94,26 +112,17 @@ private:
bool init() override;
Tasking::GroupItem runRecipe() final;
void gatherFilesToPull();
- DeployErrorCode runDeploy(QPromise<void> &promise);
- void slotAskForUninstall(DeployErrorCode errorCode);
+ DeployErrorFlags runDeploy(QPromise<void> &promise);
+ void slotAskForUninstall(DeployErrorFlags errorFlags);
void runImpl(QPromise<void> &promise);
QWidget *createConfigWidget() override;
- void processReadyReadStdOutput(DeployErrorCode &errorCode);
+ void processReadyReadStdOutput(DeployErrorFlags &errorCode);
void stdOutput(const QString &line);
- void processReadyReadStdError(DeployErrorCode &errorCode);
+ void processReadyReadStdError(DeployErrorFlags &errorCode);
void stdError(const QString &line);
- DeployErrorCode parseDeployErrors(const QString &deployOutputLine) const;
-
- friend void operator|=(DeployErrorCode &e1, const DeployErrorCode &e2) {
- e1 = static_cast<AndroidDeployQtStep::DeployErrorCode>((int)e1 | (int)e2);
- }
-
- friend DeployErrorCode operator|(const DeployErrorCode &e1, const DeployErrorCode &e2) {
- return static_cast<AndroidDeployQtStep::DeployErrorCode>((int)e1 | (int)e2);
- }
void reportWarningOrError(const QString &message, Task::TaskType type);
@@ -169,7 +178,7 @@ bool AndroidDeployQtStep::init()
return false;
}
- m_androiddeployqtArgs = CommandLine();
+ m_androiddeployqtArgs = {};
m_androidABIs = AndroidManager::applicationAbis(target());
if (m_androidABIs.isEmpty()) {
@@ -288,7 +297,7 @@ bool AndroidDeployQtStep::init()
m_apkPath = FilePath::fromString(node->data(Constants::AndroidApk).toString());
if (!m_apkPath.isEmpty()) {
m_manifestName = FilePath::fromString(node->data(Constants::AndroidManifest).toString());
- m_command = androidConfig().adbToolPath();
+ m_command = AndroidConfig::adbToolPath();
AndroidManager::setManifestPath(target(), m_manifestName);
} else {
QString jsonFile = AndroidQtVersion::androidDeploymentSettings(target()).toString();
@@ -326,22 +335,21 @@ bool AndroidDeployQtStep::init()
}
} else {
m_uninstallPreviousPackageRun = true;
- m_command = androidConfig().adbToolPath();
+ m_command = AndroidConfig::adbToolPath();
m_apkPath = AndroidManager::packagePath(target());
m_workingDirectory = bc ? AndroidManager::buildDirectory(target()): FilePath();
}
m_environment = bc ? bc->environment() : Environment();
- m_adbPath = androidConfig().adbToolPath();
+ m_adbPath = AndroidConfig::adbToolPath();
- AndroidAvdManager avdManager;
// Start the AVD if not running.
- if (!m_avdName.isEmpty() && avdManager.findAvd(m_avdName).isEmpty())
- avdManager.startAvdAsync(m_avdName);
+ if (!m_avdName.isEmpty() && AndroidAvdManager::findAvd(m_avdName).isEmpty())
+ AndroidAvdManager::startAvdAsync(m_avdName);
return true;
}
-AndroidDeployQtStep::DeployErrorCode AndroidDeployQtStep::runDeploy(QPromise<void> &promise)
+DeployErrorFlags AndroidDeployQtStep::runDeploy(QPromise<void> &promise)
{
CommandLine cmd(m_command);
if (m_useAndroiddeployqt && m_apkPath.isEmpty()) {
@@ -356,23 +364,24 @@ AndroidDeployQtStep::DeployErrorCode AndroidDeployQtStep::runDeploy(QPromise<voi
} else {
RunConfiguration *rc = target()->activeRunConfiguration();
- QTC_ASSERT(rc, return DeployErrorCode::Failure);
+ QTC_ASSERT(rc, return Failure);
QString packageName;
if (m_uninstallPreviousPackageRun) {
- packageName = AndroidManager::packageName(m_manifestName);
+ packageName = AndroidManager::packageName(target());
if (packageName.isEmpty()) {
- reportWarningOrError(Tr::tr("Cannot find the package name from the Android Manifest "
- "file \"%1\".").arg(m_manifestName.toUserOutput()),
- Task::Error);
+ reportWarningOrError(
+ Tr::tr("Cannot find the package name from AndroidManifest.xml nor "
+ "build.gradle files at \"%1\".")
+ .arg(AndroidManager::androidBuildDirectory(target()).toUserOutput()),
+ Task::Error);
return Failure;
}
const QString msg = Tr::tr("Uninstalling the previous package \"%1\".").arg(packageName);
qCDebug(deployStepLog) << msg;
emit addOutput(msg, OutputFormat::NormalMessage);
runCommand({m_adbPath,
- AndroidDeviceInfo::adbSelector(m_serialNumber)
- << "uninstall" << packageName});
+ {AndroidDeviceInfo::adbSelector(m_serialNumber), "uninstall", packageName}});
}
cmd.addArgs(AndroidDeviceInfo::adbSelector(m_serialNumber));
@@ -385,7 +394,7 @@ AndroidDeployQtStep::DeployErrorCode AndroidDeployQtStep::runDeploy(QPromise<voi
process.setEnvironment(m_environment);
process.setUseCtrlCStub(true);
- DeployErrorCode deployError = NoError;
+ DeployErrorFlags deployError = NoError;
process.setStdOutLineCallback([this, &deployError](const QString &line) {
deployError |= parseDeployErrors(line);
@@ -442,46 +451,32 @@ AndroidDeployQtStep::DeployErrorCode AndroidDeployQtStep::runDeploy(QPromise<voi
return deployError;
}
-void AndroidDeployQtStep::slotAskForUninstall(DeployErrorCode errorCode)
+void AndroidDeployQtStep::slotAskForUninstall(DeployErrorFlags errorFlags)
{
- Q_ASSERT(errorCode > 0);
-
- QString uninstallMsg = Tr::tr("Deployment failed with the following errors:\n\n");
- uint errorCodeFlags = errorCode;
- uint mask = 1;
- while (errorCodeFlags) {
- switch (errorCodeFlags & mask) {
- case DeployErrorCode::PermissionModelDowngrade:
- uninstallMsg += InstallFailedPermissionModelDowngrade+"\n";
- break;
- case InconsistentCertificates:
- uninstallMsg += InstallFailedInconsistentCertificatesString+"\n";
- break;
- case UpdateIncompatible:
- uninstallMsg += InstallFailedUpdateIncompatible+"\n";
- break;
- case VersionDowngrade:
- uninstallMsg += InstallFailedVersionDowngrade+"\n";
- break;
- default:
- break;
- }
- errorCodeFlags &= ~mask;
- mask <<= 1;
- }
-
- uninstallMsg.append(Tr::tr("\nUninstalling the installed package may solve the issue.\n"
- "Do you want to uninstall the existing package?"));
- int button = QMessageBox::critical(nullptr, Tr::tr("Install failed"), uninstallMsg,
- QMessageBox::Yes, QMessageBox::No);
- m_askForUninstall = button == QMessageBox::Yes;
+ Q_ASSERT(errorFlags > 0);
+
+ QString uninstallMsg = Tr::tr("Deployment failed with the following errors:") + "\n\n";
+ if (errorFlags & InconsistentCertificates)
+ uninstallMsg += InstallFailedInconsistentCertificatesString + '\n';
+ if (errorFlags & UpdateIncompatible)
+ uninstallMsg += InstallFailedUpdateIncompatible + '\n';
+ if (errorFlags & PermissionModelDowngrade)
+ uninstallMsg += InstallFailedPermissionModelDowngrade + '\n';
+ if (errorFlags & VersionDowngrade)
+ uninstallMsg += InstallFailedVersionDowngrade + '\n';
+ uninstallMsg += '\n';
+ uninstallMsg.append(Tr::tr("Uninstalling the installed package may solve the issue.") + '\n');
+ uninstallMsg.append(Tr::tr("Do you want to uninstall the existing package?"));
+
+ m_askForUninstall = QMessageBox::critical(nullptr, Tr::tr("Install failed"), uninstallMsg,
+ QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes;
}
// TODO: This implementation is not thread safe.
void AndroidDeployQtStep::runImpl(QPromise<void> &promise)
{
if (!m_avdName.isEmpty()) {
- const QString serialNumber = AndroidAvdManager().waitForAvd(m_avdName, promise.future());
+ const QString serialNumber = AndroidAvdManager::waitForAvd(m_avdName, promise.future());
qCDebug(deployStepLog) << "Deploying to AVD:" << m_avdName << serialNumber;
if (serialNumber.isEmpty()) {
reportWarningOrError(Tr::tr("The deployment AVD \"%1\" cannot be started.")
@@ -494,8 +489,8 @@ void AndroidDeployQtStep::runImpl(QPromise<void> &promise)
AndroidManager::setDeviceSerialNumber(target(), serialNumber);
}
- DeployErrorCode returnValue = runDeploy(promise);
- if (returnValue > DeployErrorCode::NoError && returnValue < DeployErrorCode::Failure) {
+ DeployErrorFlags returnValue = runDeploy(promise);
+ if (returnValue > NoError && returnValue < Failure) {
emit askForUninstall(returnValue);
if (m_askForUninstall) {
m_uninstallPreviousPackageRun = true;
@@ -519,9 +514,8 @@ void AndroidDeployQtStep::runImpl(QPromise<void> &promise)
reportWarningOrError(error, Task::Error);
}
- runCommand({m_adbPath,
- AndroidDeviceInfo::adbSelector(m_serialNumber)
- << "pull" << itr.key() << itr.value().nativePath()});
+ runCommand({m_adbPath, {AndroidDeviceInfo::adbSelector(m_serialNumber), "pull", itr.key(),
+ itr.value().nativePath()}});
if (!itr.value().exists()) {
const QString error = Tr::tr("Package deploy: Failed to pull \"%1\" to \"%2\".")
.arg(itr.key())
@@ -635,22 +629,6 @@ void AndroidDeployQtStep::stdError(const QString &line)
}
}
-AndroidDeployQtStep::DeployErrorCode AndroidDeployQtStep::parseDeployErrors(
- const QString &deployOutputLine) const
-{
- DeployErrorCode errorCode = NoError;
-
- if (deployOutputLine.contains(InstallFailedInconsistentCertificatesString))
- errorCode |= InconsistentCertificates;
- if (deployOutputLine.contains(InstallFailedUpdateIncompatible))
- errorCode |= UpdateIncompatible;
- if (deployOutputLine.contains(InstallFailedPermissionModelDowngrade))
- errorCode |= PermissionModelDowngrade;
- if (deployOutputLine.contains(InstallFailedVersionDowngrade))
- errorCode |= VersionDowngrade;
-
- return errorCode;
-}
void AndroidDeployQtStep::reportWarningOrError(const QString &message, Task::TaskType type)
{
diff --git a/src/plugins/android/androiddevice.cpp b/src/plugins/android/androiddevice.cpp
index 0242fdeded..7e6e715dc8 100644
--- a/src/plugins/android/androiddevice.cpp
+++ b/src/plugins/android/androiddevice.cpp
@@ -2,14 +2,16 @@
// Copyright (C) 2016 BogDan Vatra <bog_dan_ro@yahoo.com>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#include "androiddevice.h"
+
#include "androidavdmanager.h"
#include "androidconfigurations.h"
#include "androidconstants.h"
-#include "androiddevice.h"
#include "androidmanager.h"
#include "androidsignaloperation.h"
#include "androidtr.h"
#include "avddialog.h"
+#include "avdmanageroutputparser.h"
#include <coreplugin/icore.h>
@@ -21,6 +23,8 @@
#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
+#include <solutions/tasking/tasktree.h>
+
#include <utils/async.h>
#include <utils/qtcprocess.h>
#include <utils/qtcassert.h>
@@ -35,6 +39,7 @@
#include <QTimer>
using namespace ProjectExplorer;
+using namespace Tasking;
using namespace Utils;
namespace {
@@ -47,10 +52,160 @@ static constexpr char ipRegexStr[] = "(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}
static const QRegularExpression ipRegex = QRegularExpression(ipRegexStr);
static constexpr char wifiDevicePort[] = "5555";
+enum TagModification { CommentOut, Uncomment };
+static class AndroidDeviceManagerInstance *s_instance = nullptr;
+
+class AndroidDeviceManagerInstance : public QObject
+{
+public:
+ AndroidDeviceManagerInstance(QObject *parent);
+ ~AndroidDeviceManagerInstance()
+ {
+ QTC_ASSERT(s_instance == this, return);
+ s_instance = nullptr;
+ }
+
+ void setupDevicesWatcher();
+ void eraseAvd(const IDevice::Ptr &device, QWidget *parent);
+
+ Group m_avdListRecipe;
+ TaskTreeRunner m_avdListRunner;
+ std::unique_ptr<Process> m_removeAvdProcess;
+ QFileSystemWatcher m_avdFileSystemWatcher;
+ Guard m_avdPathGuard;
+ std::unique_ptr<Process> m_adbDeviceWatcherProcess;
+};
+
+static QString displayNameFromInfo(const AndroidDeviceInfo &info)
+{
+ return info.type == IDevice::Hardware ? AndroidConfig::getProductModel(info.serialNumber)
+ : info.avdName;
+}
+
+static IDevice::DeviceState getDeviceState(const QString &serial, IDevice::MachineType type)
+{
+ const QStringList args = AndroidDeviceInfo::adbSelector(serial) << "shell" << "echo 1";
+ const SdkToolResult result = AndroidManager::runAdbCommand(args);
+ if (result.success())
+ return IDevice::DeviceReadyToUse;
+ else if (type == IDevice::Emulator || result.stdErr().contains("unauthorized"))
+ return IDevice::DeviceConnected;
+ return IDevice::DeviceDisconnected;
+}
+
+static void updateDeviceState(const IDevice::ConstPtr &device)
+{
+ const AndroidDevice *dev = static_cast<const AndroidDevice *>(device.get());
+ const QString serial = dev->serialNumber();
+ DeviceManager *const devMgr = DeviceManager::instance();
+ const Id id = dev->id();
+ if (!serial.isEmpty())
+ devMgr->setDeviceState(id, getDeviceState(serial, dev->machineType()));
+ else if (dev->machineType() == IDevice::Emulator)
+ devMgr->setDeviceState(id, IDevice::DeviceConnected);
+}
+
+static void startAvd(const IDevice::Ptr &device, QWidget *parent)
+{
+ Q_UNUSED(parent)
+ const AndroidDevice *androidDev = static_cast<const AndroidDevice *>(device.get());
+ const QString name = androidDev->avdName();
+ qCDebug(androidDeviceLog, "Starting Android AVD id \"%s\".", qPrintable(name));
+ auto future = Utils::asyncRun([name, device] {
+ const QString serialNumber = AndroidAvdManager::startAvd(name);
+ // Mark the AVD as ReadyToUse once we know it's started
+ if (!serialNumber.isEmpty()) {
+ DeviceManager *const devMgr = DeviceManager::instance();
+ devMgr->setDeviceState(device->id(), IDevice::DeviceReadyToUse);
+ }
+ });
+ // TODO: use future!
+}
+
+static void setEmulatorArguments(QWidget *parent)
+{
+ const QString helpUrl =
+ "https://developer.android.com/studio/run/emulator-commandline#startup-options";
+
+ QInputDialog dialog(parent ? parent : Core::ICore::dialogParent());
+ dialog.setWindowTitle(Tr::tr("Emulator Command-line Startup Options"));
+ dialog.setLabelText(Tr::tr("Emulator command-line startup options "
+ "(<a href=\"%1\">Help Web Page</a>):")
+ .arg(helpUrl));
+ dialog.setTextValue(AndroidConfig::emulatorArgs());
+
+ if (auto label = dialog.findChild<QLabel *>()) {
+ label->setOpenExternalLinks(true);
+ label->setMinimumWidth(500);
+ }
+
+ if (dialog.exec() == QDialog::Accepted)
+ AndroidConfig::setEmulatorArgs(dialog.textValue());
+}
+
+static QString emulatorName(const QString &serialNumber)
+{
+ const QStringList args = AndroidDeviceInfo::adbSelector(serialNumber) << "emu" << "avd" << "name";
+ return AndroidManager::runAdbCommand(args).stdOut();
+}
+
+static QString getRunningAvdsSerialNumber(const QString &name)
+{
+ const QStringList lines = AndroidConfig::devicesCommandOutput();
+ for (const QString &line : lines) {
+ // skip the daemon logs
+ if (line.startsWith("* daemon"))
+ continue;
+
+ const QString serialNumber = line.left(line.indexOf('\t')).trimmed();
+ if (!serialNumber.startsWith("emulator"))
+ continue;
+
+ const QString stdOut = emulatorName(serialNumber);
+ if (stdOut.isEmpty())
+ continue; // Not an avd
+
+ if (stdOut.left(stdOut.indexOf('\n')) == name)
+ return serialNumber;
+ }
+ return {};
+}
+
+static FilePath avdFilePath()
+{
+ QString avdEnvVar = qtcEnvironmentVariable("ANDROID_AVD_HOME");
+ if (avdEnvVar.isEmpty()) {
+ avdEnvVar = qtcEnvironmentVariable("ANDROID_SDK_HOME");
+ if (avdEnvVar.isEmpty())
+ avdEnvVar = qtcEnvironmentVariable("HOME");
+ avdEnvVar.append("/.android/avd");
+ }
+ return FilePath::fromUserInput(avdEnvVar);
+}
+
+static IDevice::Ptr createDeviceFromInfo(const CreateAvdInfo &info)
+{
+ if (info.apiLevel < 0) {
+ qCWarning(androidDeviceLog) << "System image of the created AVD is nullptr";
+ return IDevice::Ptr();
+ }
+ AndroidDevice *dev = new AndroidDevice;
+ const Id deviceId = AndroidDevice::idFromAvdInfo(info);
+ dev->setupId(IDevice::AutoDetected, deviceId);
+ dev->setMachineType(IDevice::Emulator);
+ dev->settings()->displayName.setValue(info.name);
+ dev->setDeviceState(IDevice::DeviceConnected);
+ dev->setAvdPath(avdFilePath() / (info.name + ".avd"));
+ dev->setExtraData(Constants::AndroidAvdName, info.name);
+ dev->setExtraData(Constants::AndroidCpuAbi, {info.abi});
+ dev->setExtraData(Constants::AndroidSdk, info.apiLevel);
+ return IDevice::Ptr(dev);
+}
+
class AndroidDeviceWidget : public IDeviceWidget
{
public:
- AndroidDeviceWidget(const ProjectExplorer::IDevice::Ptr &device);
+ AndroidDeviceWidget(const IDevice::Ptr &device);
void updateDeviceFromUi() final {}
static QString dialogTitle();
@@ -60,6 +215,66 @@ public:
static bool questionDialog(const QString &question, QWidget *parent = nullptr);
};
+static void setupWifiForDevice(const IDevice::Ptr &device, QWidget *parent)
+{
+ if (device->deviceState() != IDevice::DeviceReadyToUse) {
+ AndroidDeviceWidget::infoDialog(
+ Tr::tr("The device has to be connected with ADB debugging "
+ "enabled to use this feature."), parent);
+ return;
+ }
+
+ const auto androidDev = static_cast<const AndroidDevice *>(device.get());
+ const QStringList adbSelector = AndroidDeviceInfo::adbSelector(androidDev->serialNumber());
+ // prepare port
+ QStringList args = adbSelector;
+ args.append({"tcpip", wifiDevicePort});
+ const SdkToolResult result = AndroidManager::runAdbCommand(args);
+ if (!result.success()) {
+ AndroidDeviceWidget::criticalDialog(
+ Tr::tr("Opening connection port %1 failed.").arg(wifiDevicePort),
+ parent);
+ return;
+ }
+
+ QTimer::singleShot(2000, parent, [adbSelector, parent] {
+ // Get device IP address
+ QStringList args = adbSelector;
+ args.append({"shell", "ip", "route"});
+ const SdkToolResult ipRes = AndroidManager::runAdbCommand(args);
+ if (!ipRes.success()) {
+ AndroidDeviceWidget::criticalDialog(
+ Tr::tr("Retrieving the device IP address failed."), parent);
+ return;
+ }
+
+ // Expected output from "ip route" is:
+ // 192.168.1.0/24 dev wlan0 proto kernel scope link src 192.168.1.190
+ // where the ip of interest is at the end of the line
+ const QStringList ipParts = ipRes.stdOut().split(" ");
+ QString ip;
+ if (!ipParts.isEmpty()) {
+ ip = ipParts.last();
+ }
+ if (!ipRegex.match(ipParts.last()).hasMatch()) {
+ AndroidDeviceWidget::criticalDialog(
+ Tr::tr("The retrieved IP address is invalid."), parent);
+ return;
+ }
+
+ // Connect to device
+ args = adbSelector;
+ args.append({"connect", QString("%1:%2").arg(ip).arg(wifiDevicePort)});
+ const SdkToolResult connectRes = AndroidManager::runAdbCommand(args);
+ if (!connectRes.success()) {
+ AndroidDeviceWidget::criticalDialog(
+ Tr::tr("Connecting to the device IP \"%1\" failed.").arg(ip),
+ parent);
+ return;
+ }
+ });
+}
+
AndroidDeviceWidget::AndroidDeviceWidget(const IDevice::Ptr &device)
: IDeviceWidget(device)
{
@@ -158,7 +373,7 @@ AndroidDevice::AndroidDevice()
addDeviceAction({Tr::tr("Refresh"), [](const IDevice::Ptr &device, QWidget *parent) {
Q_UNUSED(parent)
- AndroidDeviceManager::instance()->updateDeviceState(device);
+ updateDeviceState(device);
}});
}
@@ -188,26 +403,26 @@ void AndroidDevice::addActionsIfNotFound()
if (machineType() == Emulator) {
if (!hasStartAction) {
addDeviceAction({startAvdAction, [](const IDevice::Ptr &device, QWidget *parent) {
- AndroidDeviceManager::instance()->startAvd(device, parent);
+ startAvd(device, parent);
}});
}
if (!hasEraseAction) {
addDeviceAction({eraseAvdAction, [](const IDevice::Ptr &device, QWidget *parent) {
- AndroidDeviceManager::instance()->eraseAvd(device, parent);
+ s_instance->eraseAvd(device, parent);
}});
}
if (!hasAvdArgumentsAction) {
addDeviceAction({avdArgumentsAction, [](const IDevice::Ptr &device, QWidget *parent) {
Q_UNUSED(device)
- AndroidDeviceManager::instance()->setEmulatorArguments(parent);
+ setEmulatorArguments(parent);
}});
}
} else if (machineType() == Hardware && !ipRegex.match(id().toString()).hasMatch()) {
if (!hasSetupWifi) {
addDeviceAction({setupWifi, [](const IDevice::Ptr &device, QWidget *parent) {
- AndroidDeviceManager::instance()->setupWifiForDevice(device, parent);
+ setupWifiForDevice(device, parent);
}});
}
}
@@ -237,17 +452,9 @@ AndroidDeviceInfo AndroidDevice::androidDeviceInfoFromIDevice(const IDevice *dev
info.avdPath = FilePath::fromSettings(dev->extraData(Constants::AndroidAvdPath));
info.sdk = dev->extraData(Constants::AndroidSdk).toInt();
info.type = dev->machineType();
-
return info;
}
-QString AndroidDevice::displayNameFromInfo(const AndroidDeviceInfo &info)
-{
- return info.type == IDevice::Hardware
- ? androidConfig().getProductModel(info.serialNumber)
- : info.avdName;
-}
-
Id AndroidDevice::idFromDeviceInfo(const AndroidDeviceInfo &info)
{
const QString id = (info.type == IDevice::Hardware ? info.serialNumber : info.avdName);
@@ -256,7 +463,7 @@ Id AndroidDevice::idFromDeviceInfo(const AndroidDeviceInfo &info)
Id AndroidDevice::idFromAvdInfo(const CreateAvdInfo &info)
{
- return Id(Constants::ANDROID_DEVICE_ID).withSuffix(':' + info.name);
+ return Id(Constants::ANDROID_DEVICE_ID).withSuffix(':' + info.name);
}
QStringList AndroidDevice::supportedAbis() const
@@ -315,8 +522,7 @@ QString AndroidDevice::serialNumber() const
const QString serialNumber = extraData(Constants::AndroidSerialNumber).toString();
if (machineType() == Hardware)
return serialNumber;
-
- return AndroidDeviceManager::instance()->getRunningAvdsSerialNumber(avdName());
+ return getRunningAvdsSerialNumber(avdName());
}
QString AndroidDevice::avdName() const
@@ -410,259 +616,102 @@ void AndroidDevice::initAvdSettings()
m_avdSettings.reset(new QSettings(configPath.toUserOutput(), QSettings::IniFormat));
}
-void AndroidDeviceManager::updateAvdsList()
-{
- if (!m_avdsFutureWatcher.isRunning() && androidConfig().adbToolPath().exists())
- m_avdsFutureWatcher.setFuture(m_avdManager.avdList());
-}
-
-IDevice::DeviceState AndroidDeviceManager::getDeviceState(const QString &serial,
- IDevice::MachineType type) const
-{
- const QStringList args = AndroidDeviceInfo::adbSelector(serial) << "shell" << "echo 1";
- const SdkToolResult result = AndroidManager::runAdbCommand(args);
- if (result.success())
- return IDevice::DeviceReadyToUse;
- else if (type == IDevice::Emulator || result.stdErr().contains("unauthorized"))
- return IDevice::DeviceConnected;
-
- return IDevice::DeviceDisconnected;
-}
-
-void AndroidDeviceManager::updateDeviceState(const ProjectExplorer::IDevice::ConstPtr &device)
+static void handleDevicesListChange(const QString &serialNumber)
{
- const AndroidDevice *dev = static_cast<const AndroidDevice *>(device.get());
- const QString serial = dev->serialNumber();
DeviceManager *const devMgr = DeviceManager::instance();
- const Id id = dev->id();
- if (!serial.isEmpty())
- devMgr->setDeviceState(id, getDeviceState(serial, dev->machineType()));
- else if (dev->machineType() == IDevice::Emulator)
- devMgr->setDeviceState(id, IDevice::DeviceConnected);
-}
-
-void AndroidDeviceManager::startAvd(const ProjectExplorer::IDevice::Ptr &device, QWidget *parent)
-{
- Q_UNUSED(parent)
- const AndroidDevice *androidDev = static_cast<const AndroidDevice *>(device.get());
- const QString name = androidDev->avdName();
- qCDebug(androidDeviceLog, "Starting Android AVD id \"%s\".", qPrintable(name));
- auto future = Utils::asyncRun([this, name, device] {
- const QString serialNumber = m_avdManager.startAvd(name);
- // Mark the AVD as ReadyToUse once we know it's started
- if (!serialNumber.isEmpty()) {
- DeviceManager *const devMgr = DeviceManager::instance();
- devMgr->setDeviceState(device->id(), IDevice::DeviceReadyToUse);
- }
- });
- // TODO: use future!
-}
-
-void AndroidDeviceManager::eraseAvd(const IDevice::Ptr &device, QWidget *parent)
-{
- if (!device)
- return;
-
- if (device->machineType() == IDevice::Hardware)
+ const QStringList serialBits = serialNumber.split('\t');
+ if (serialBits.size() < 2)
return;
- const QString name = static_cast<const AndroidDevice *>(device.get())->avdName();
- const QString question
- = Tr::tr("Erase the Android AVD \"%1\"?\nThis cannot be undone.").arg(name);
- if (!AndroidDeviceWidget::questionDialog(question, parent))
- return;
+ // Sample output of adb track-devices, the first 4 digits are for state type
+ // and sometimes 4 zeros are reported as part for the serial number.
+ // 00546db0e8d7 authorizing
+ // 00546db0e8d7 device
+ // 0000001711201JEC207789 offline
+ // emulator-5554 device
+ QString dirtySerial = serialBits.first().trimmed();
+ if (dirtySerial.startsWith("0000"))
+ dirtySerial = dirtySerial.mid(4);
+ if (dirtySerial.startsWith("00"))
+ dirtySerial = dirtySerial.mid(4);
+ const bool isEmulator = dirtySerial.startsWith("emulator");
- qCDebug(androidDeviceLog) << QString("Erasing Android AVD \"%1\" from the system.").arg(name);
- m_removeAvdProcess.reset(new Process);
- const CommandLine command(androidConfig().avdManagerToolPath(), {"delete", "avd", "-n", name});
- qCDebug(androidDeviceLog).noquote() << "Running command (removeAvd):" << command.toUserOutput();
- m_removeAvdProcess->setEnvironment(androidConfig().toolsEnvironment());
- m_removeAvdProcess->setCommand(command);
- connect(m_removeAvdProcess.get(), &Process::done, this, [this, device] {
- const QString name = device->displayName();
- if (m_removeAvdProcess->result() == ProcessResult::FinishedWithSuccess) {
- qCDebug(androidDeviceLog, "Android AVD id \"%s\" removed from the system.",
- qPrintable(name));
- // Remove the device from QtC after it's been removed using avdmanager.
- DeviceManager::instance()->removeDevice(device->id());
- } else {
- AndroidDeviceWidget::criticalDialog(Tr::tr("An error occurred while removing the "
- "Android AVD \"%1\" using avdmanager tool.").arg(name));
- }
- m_removeAvdProcess.release()->deleteLater();
- });
- m_removeAvdProcess->start();
-}
+ const QString &serial = dirtySerial;
+ const QString stateStr = serialBits.at(1).trimmed();
-void AndroidDeviceManager::setupWifiForDevice(const IDevice::Ptr &device, QWidget *parent)
-{
- if (device->deviceState() != IDevice::DeviceReadyToUse) {
- AndroidDeviceWidget::infoDialog(
- Tr::tr("The device has to be connected with ADB debugging "
- "enabled to use this feature."), parent);
- return;
- }
+ IDevice::DeviceState state;
+ if (stateStr == "device")
+ state = IDevice::DeviceReadyToUse;
+ else if (stateStr == "offline")
+ state = IDevice::DeviceDisconnected;
+ else
+ state = IDevice::DeviceConnected;
- const auto androidDev = static_cast<const AndroidDevice *>(device.get());
- const QStringList adbSelector = AndroidDeviceInfo::adbSelector(androidDev->serialNumber());
- // prepare port
- QStringList args = adbSelector;
- args.append({"tcpip", wifiDevicePort});
- const SdkToolResult result = AndroidManager::runAdbCommand(args);
- if (!result.success()) {
- AndroidDeviceWidget::criticalDialog(
- Tr::tr("Opening connection port %1 failed.").arg(wifiDevicePort),
- parent);
- return;
- }
+ if (isEmulator) {
+ const QString avdName = emulatorName(serial);
+ const Id avdId = Id(Constants::ANDROID_DEVICE_ID).withSuffix(':' + avdName);
+ devMgr->setDeviceState(avdId, state);
+ } else {
+ const Id id = Id(Constants::ANDROID_DEVICE_ID).withSuffix(':' + serial);
+ QString displayName = AndroidConfig::getProductModel(serial);
+ // Check if the device is connected via WiFi. A sample serial of such devices can be
+ // like: "192.168.1.190:5555"
+ static const auto ipRegex = QRegularExpression(ipRegexStr + QStringLiteral(":(\\d{1,5})"));
+ if (ipRegex.match(serial).hasMatch())
+ displayName += QLatin1String(" (WiFi)");
- QTimer::singleShot(2000, parent, [adbSelector, parent] {
- // Get device IP address
- QStringList args = adbSelector;
- args.append({"shell", "ip", "route"});
- const SdkToolResult ipRes = AndroidManager::runAdbCommand(args);
- if (!ipRes.success()) {
- AndroidDeviceWidget::criticalDialog(
- Tr::tr("Retrieving the device IP address failed."), parent);
- return;
- }
+ if (IDevice::ConstPtr dev = devMgr->find(id)) {
+ // DeviceManager doens't seem to have a way to directly update the name, if the name
+ // of the device has changed, remove it and register it again with the new name.
+ if (dev->displayName() == displayName)
+ devMgr->setDeviceState(id, state);
+ else
+ devMgr->removeDevice(id);
+ } else {
+ AndroidDevice *newDev = new AndroidDevice();
+ newDev->setupId(IDevice::AutoDetected, id);
+ newDev->settings()->displayName.setValue(displayName);
+ newDev->setMachineType(IDevice::Hardware);
+ newDev->setDeviceState(state);
- // Expected output from "ip route" is:
- // 192.168.1.0/24 dev wlan0 proto kernel scope link src 192.168.1.190
- // where the ip of interest is at the end of the line
- const QStringList ipParts = ipRes.stdOut().split(" ");
- QString ip;
- if (!ipParts.isEmpty()) {
- ip = ipParts.last();
- }
- if (!ipRegex.match(ipParts.last()).hasMatch()) {
- AndroidDeviceWidget::criticalDialog(
- Tr::tr("The retrieved IP address is invalid."), parent);
- return;
- }
+ newDev->setExtraData(Constants::AndroidSerialNumber, serial);
+ newDev->setExtraData(Constants::AndroidCpuAbi, AndroidConfig::getAbis(serial));
+ newDev->setExtraData(Constants::AndroidSdk, AndroidConfig::getSDKVersion(serial));
- // Connect to device
- args = adbSelector;
- args.append({"connect", QString("%1:%2").arg(ip).arg(wifiDevicePort)});
- const SdkToolResult connectRes = AndroidManager::runAdbCommand(args);
- if (!connectRes.success()) {
- AndroidDeviceWidget::criticalDialog(
- Tr::tr("Connecting to the device IP \"%1\" failed.").arg(ip),
- parent);
- return;
+ qCDebug(androidDeviceLog, "Registering new Android device id \"%s\".",
+ newDev->id().toString().toUtf8().data());
+ devMgr->addDevice(IDevice::ConstPtr(newDev));
}
- });
-}
-
-QString AndroidDeviceManager::emulatorName(const QString &serialNumber) const
-{
- QStringList args = AndroidDeviceInfo::adbSelector(serialNumber);
- args.append({"emu", "avd", "name"});
- return AndroidManager::runAdbCommand(args).stdOut();
-}
-
-void AndroidDeviceManager::setEmulatorArguments(QWidget *parent)
-{
- const QString helpUrl =
- "https://developer.android.com/studio/run/emulator-commandline#startup-options";
-
- QInputDialog dialog(parent ? parent : Core::ICore::dialogParent());
- dialog.setWindowTitle(Tr::tr("Emulator Command-line Startup Options"));
- dialog.setLabelText(Tr::tr("Emulator command-line startup options "
- "(<a href=\"%1\">Help Web Page</a>):")
- .arg(helpUrl));
- dialog.setTextValue(androidConfig().emulatorArgs());
-
- if (auto label = dialog.findChild<QLabel*>()) {
- label->setOpenExternalLinks(true);
- label->setMinimumWidth(500);
}
-
- if (dialog.exec() != QDialog::Accepted)
- return;
-
- androidConfig().setEmulatorArgs(dialog.textValue());
}
-QString AndroidDeviceManager::getRunningAvdsSerialNumber(const QString &name) const
+static void modifyManufacturerTag(const FilePath &avdPath, TagModification modification)
{
- for (const AndroidDeviceInfo &dev : androidConfig().connectedDevices()) {
- if (!dev.serialNumber.startsWith("emulator"))
- continue;
- const QString stdOut = emulatorName(dev.serialNumber);
- if (stdOut.isEmpty())
- continue; // Not an avd
- const QStringList outputLines = stdOut.split('\n');
- if (outputLines.size() > 1 && outputLines.first() == name)
- return dev.serialNumber;
- }
-
- return {};
-}
-
-void AndroidDeviceManager::setupDevicesWatcher()
-{
- if (!androidConfig().adbToolPath().exists()) {
- qCDebug(androidDeviceLog) << "Cannot start ADB device watcher"
- << "because adb path does not exist.";
+ if (!avdPath.exists())
return;
- }
-
- if (!m_adbDeviceWatcherProcess)
- m_adbDeviceWatcherProcess.reset(new Process(this));
- if (m_adbDeviceWatcherProcess->isRunning()) {
- qCDebug(androidDeviceLog) << "ADB device watcher is already running.";
+ const FilePath configFilePath = avdPath / "config.ini";
+ FileReader reader;
+ if (!reader.fetch(configFilePath, QIODevice::ReadOnly | QIODevice::Text))
return;
- }
- connect(m_adbDeviceWatcherProcess.get(), &Process::done, this, [this] {
- if (m_adbDeviceWatcherProcess->error() != QProcess::UnknownError) {
- qCDebug(androidDeviceLog) << "ADB device watcher encountered an error:"
- << m_adbDeviceWatcherProcess->errorString();
- if (!m_adbDeviceWatcherProcess->isRunning()) {
- qCDebug(androidDeviceLog) << "Restarting the ADB device watcher now.";
- QTimer::singleShot(0, m_adbDeviceWatcherProcess.get(), &Process::start);
- }
+ FileSaver saver(configFilePath);
+ QTextStream textStream(reader.data());
+ while (!textStream.atEnd()) {
+ QString line = textStream.readLine();
+ if (line.contains("hw.device.manufacturer")) {
+ if (modification == Uncomment)
+ line.replace("#", "");
+ else
+ line.prepend("#");
}
- qCDebug(androidDeviceLog) << "ADB device watcher finished.";
- });
-
- m_adbDeviceWatcherProcess->setStdErrLineCallback([](const QString &error) {
- qCDebug(androidDeviceLog) << "ADB device watcher error" << error; });
- m_adbDeviceWatcherProcess->setStdOutLineCallback([this](const QString &output) {
- HandleDevicesListChange(output);
- });
-
- const CommandLine command = CommandLine(androidConfig().adbToolPath(), {"track-devices"});
- m_adbDeviceWatcherProcess->setCommand(command);
- m_adbDeviceWatcherProcess->setWorkingDirectory(command.executable().parentDir());
- m_adbDeviceWatcherProcess->setEnvironment(androidConfig().toolsEnvironment());
- m_adbDeviceWatcherProcess->start();
-
- // Setup AVD filesystem watcher to listen for changes when an avd is created/deleted,
- // or started/stopped
- QString avdEnvVar = qtcEnvironmentVariable("ANDROID_AVD_HOME");
- if (avdEnvVar.isEmpty()) {
- avdEnvVar = qtcEnvironmentVariable("ANDROID_SDK_HOME");
- if (avdEnvVar.isEmpty())
- avdEnvVar = qtcEnvironmentVariable("HOME");
- avdEnvVar.append("/.android/avd");
+ line.append("\n");
+ saver.write(line.toUtf8());
}
- const FilePath avdPath = FilePath::fromUserInput(avdEnvVar);
- m_avdFileSystemWatcher.addPath(avdPath.toString());
- connect(&m_avdsFutureWatcher, &QFutureWatcherBase::finished,
- this, &AndroidDeviceManager::HandleAvdsListChange);
- connect(&m_avdFileSystemWatcher, &QFileSystemWatcher::directoryChanged, this, [this] {
- // If the avd list upate command is running no need to call it again.
- if (!m_avdsFutureWatcher.isRunning())
- updateAvdsList();
- });
- // Call initial update
- updateAvdsList();
+ saver.finalize();
}
-void AndroidDeviceManager::HandleAvdsListChange()
+static void handleAvdListChange(const AndroidDeviceInfoList &avdList)
{
DeviceManager *const devMgr = DeviceManager::instance();
@@ -675,9 +724,9 @@ void AndroidDeviceManager::HandleAvdsListChange()
}
QList<Id> connectedDevs;
- for (const AndroidDeviceInfo &item : m_avdsFutureWatcher.result()) {
+ for (const AndroidDeviceInfo &item : avdList) {
const Id deviceId = AndroidDevice::idFromDeviceInfo(item);
- const QString displayName = AndroidDevice::displayNameFromInfo(item);
+ const QString displayName = displayNameFromInfo(item);
IDevice::ConstPtr dev = devMgr->find(deviceId);
if (dev) {
const auto androidDev = static_cast<const AndroidDevice *>(dev.get());
@@ -705,7 +754,7 @@ void AndroidDeviceManager::HandleAvdsListChange()
}
}
- AndroidDevice *newDev = new AndroidDevice();
+ AndroidDevice *newDev = new AndroidDevice;
newDev->setupId(IDevice::AutoDetected, deviceId);
newDev->settings()->displayName.setValue(displayName);
newDev->setMachineType(item.type);
@@ -727,106 +776,215 @@ void AndroidDeviceManager::HandleAvdsListChange()
// Set devices no longer connected to disconnected state.
for (const Id &id : existingAvds) {
if (!connectedDevs.contains(id)) {
- qCDebug(androidDeviceLog, "Removing AVD id \"%s\" because it no longer exists.",
- id.toString().toUtf8().data());
- devMgr->removeDevice(id);
+ qCDebug(androidDeviceLog, "Removing AVD id \"%s\" because it no longer exists.",
+ id.toString().toUtf8().data());
+ devMgr->removeDevice(id);
}
}
}
-void AndroidDeviceManager::HandleDevicesListChange(const QString &serialNumber)
+AndroidDeviceManagerInstance::AndroidDeviceManagerInstance(QObject *parent)
+ : QObject(parent)
+ , m_avdListRecipe{}
{
- DeviceManager *const devMgr = DeviceManager::instance();
- const QStringList serialBits = serialNumber.split('\t');
- if (serialBits.size() < 2)
- return;
-
- // Sample output of adb track-devices, the first 4 digits are for state type
- // and sometimes 4 zeros are reported as part for the serial number.
- // 00546db0e8d7 authorizing
- // 00546db0e8d7 device
- // 0000001711201JEC207789 offline
- // emulator-5554 device
- QString dirtySerial = serialBits.first().trimmed();
- if (dirtySerial.startsWith("0000"))
- dirtySerial = dirtySerial.mid(4);
- if (dirtySerial.startsWith("00"))
- dirtySerial = dirtySerial.mid(4);
- const bool isEmulator = dirtySerial.startsWith("emulator");
+ QTC_ASSERT(!s_instance, return);
+ s_instance = this;
- const QString &serial = dirtySerial;
- const QString stateStr = serialBits.at(1).trimmed();
+ const Storage<FilePaths> storage;
- IDevice::DeviceState state;
- if (stateStr == "device")
- state = IDevice::DeviceReadyToUse;
- else if (stateStr == "offline")
- state = IDevice::DeviceDisconnected;
- else
- state = IDevice::DeviceConnected;
+ const LoopUntil iterator([storage](int iteration) {
+ return iteration == 0 || storage->count() > 0;
+ });
- if (isEmulator) {
- const QString avdName = emulatorName(serial);
- const Id avdId = Id(Constants::ANDROID_DEVICE_ID).withSuffix(':' + avdName);
- devMgr->setDeviceState(avdId, state);
- } else {
- const Id id = Id(Constants::ANDROID_DEVICE_ID).withSuffix(':' + serial);
- QString displayName = androidConfig().getProductModel(serial);
- // Check if the device is connected via WiFi. A sample serial of such devices can be
- // like: "192.168.1.190:5555"
- static const auto ipRegex = QRegularExpression(ipRegexStr + QStringLiteral(":(\\d{1,5})"));
- if (ipRegex.match(serial).hasMatch())
- displayName += QLatin1String(" (WiFi)");
+ const auto onProcessSetup = [](Process &process) {
+ const CommandLine cmd(AndroidConfig::avdManagerToolPath(), {"list", "avd"});
+ qCDebug(androidDeviceLog).noquote() << "Running AVD Manager command:" << cmd.toUserOutput();
+ process.setEnvironment(AndroidConfig::toolsEnvironment());
+ process.setCommand(cmd);
+ };
+ const auto onProcessDone = [storage](const Process &process, DoneWith result) {
+ const QString output = process.allOutput();
+ if (result != DoneWith::Success) {
+ qCDebug(androidDeviceLog)
+ << "Avd list command failed" << output << AndroidConfig::sdkToolsVersion();
+ return DoneResult::Error;
+ }
- if (IDevice::ConstPtr dev = devMgr->find(id)) {
- // DeviceManager doens't seem to have a way to directly update the name, if the name
- // of the device has changed, remove it and register it again with the new name.
- if (dev->displayName() == displayName)
- devMgr->setDeviceState(id, state);
- else
- devMgr->removeDevice(id);
+ const auto parsedAvdList = parseAvdList(output);
+ if (parsedAvdList.errorPaths.isEmpty()) {
+ for (const FilePath &avdPath : *storage)
+ modifyManufacturerTag(avdPath, Uncomment);
+ storage->clear(); // Don't repeat anymore
+ handleAvdListChange(parsedAvdList.avdList);
} else {
- AndroidDevice *newDev = new AndroidDevice();
- newDev->setupId(IDevice::AutoDetected, id);
- newDev->settings()->displayName.setValue(displayName);
- newDev->setMachineType(IDevice::Hardware);
- newDev->setDeviceState(state);
+ for (const FilePath &avdPath : parsedAvdList.errorPaths)
+ modifyManufacturerTag(avdPath, CommentOut);
+ storage->append(parsedAvdList.errorPaths);
+ }
+ return DoneResult::Success; // Repeat
+ };
+
+ // Currenly avdmanager tool fails to parse some AVDs because the correct
+ // device definitions at devices.xml does not have some of the newest devices.
+ // Particularly, failing because of tag "hw.device.manufacturer", thus removing
+ // it would make paring successful. However, it has to be returned afterwards,
+ // otherwise, Android Studio would give an error during parsing also. So this fix
+ // aim to keep support for Qt Creator and Android Studio.
+
+ m_avdListRecipe = Group {
+ storage,
+ iterator,
+ ProcessTask(onProcessSetup, onProcessDone)
+ };
+}
- newDev->setExtraData(Constants::AndroidSerialNumber, serial);
- newDev->setExtraData(Constants::AndroidCpuAbi, androidConfig().getAbis(serial));
- newDev->setExtraData(Constants::AndroidSdk, androidConfig().getSDKVersion(serial));
+void AndroidDeviceManagerInstance::setupDevicesWatcher()
+{
+ if (!AndroidConfig::adbToolPath().exists()) {
+ qCDebug(androidDeviceLog) << "Cannot start ADB device watcher"
+ << "because adb path does not exist.";
+ return;
+ }
- qCDebug(androidDeviceLog, "Registering new Android device id \"%s\".",
- newDev->id().toString().toUtf8().data());
- devMgr->addDevice(IDevice::ConstPtr(newDev));
- }
+ if (!m_adbDeviceWatcherProcess)
+ m_adbDeviceWatcherProcess.reset(new Process(this));
+
+ if (m_adbDeviceWatcherProcess->isRunning()) {
+ qCDebug(androidDeviceLog) << "ADB device watcher is already running.";
+ return;
}
-}
-static AndroidDeviceManager *s_instance = nullptr;
+ connect(m_adbDeviceWatcherProcess.get(), &Process::done, this, [this] {
+ if (m_adbDeviceWatcherProcess->error() != QProcess::UnknownError) {
+ qCDebug(androidDeviceLog) << "ADB device watcher encountered an error:"
+ << m_adbDeviceWatcherProcess->errorString();
+ if (!m_adbDeviceWatcherProcess->isRunning()) {
+ qCDebug(androidDeviceLog) << "Restarting the ADB device watcher now.";
+ QTimer::singleShot(0, m_adbDeviceWatcherProcess.get(), &Process::start);
+ }
+ }
+ qCDebug(androidDeviceLog) << "ADB device watcher finished.";
+ });
+
+ m_adbDeviceWatcherProcess->setStdErrLineCallback([](const QString &error) {
+ qCDebug(androidDeviceLog) << "ADB device watcher error" << error; });
+ m_adbDeviceWatcherProcess->setStdOutLineCallback([](const QString &output) {
+ handleDevicesListChange(output);
+ });
+
+ const CommandLine command{AndroidConfig::adbToolPath(), {"track-devices"}};
+ m_adbDeviceWatcherProcess->setCommand(command);
+ m_adbDeviceWatcherProcess->setWorkingDirectory(command.executable().parentDir());
+ m_adbDeviceWatcherProcess->setEnvironment(AndroidConfig::toolsEnvironment());
+ m_adbDeviceWatcherProcess->start();
-AndroidDeviceManager *AndroidDeviceManager::instance()
+ // Setup AVD filesystem watcher to listen for changes when an avd is created/deleted,
+ // or started/stopped
+ m_avdFileSystemWatcher.addPath(avdFilePath().toString());
+ connect(&m_avdFileSystemWatcher, &QFileSystemWatcher::directoryChanged, this, [this] {
+ if (!m_avdPathGuard.isLocked())
+ AndroidDeviceManager::updateAvdList();
+ });
+ // Call initial update
+ AndroidDeviceManager::updateAvdList();
+}
+
+void AndroidDeviceManagerInstance::eraseAvd(const IDevice::Ptr &device, QWidget *parent)
{
- return s_instance;
+ if (!device)
+ return;
+
+ if (device->machineType() == IDevice::Hardware)
+ return;
+
+ const QString name = static_cast<const AndroidDevice *>(device.get())->avdName();
+ const QString question
+ = Tr::tr("Erase the Android AVD \"%1\"?\nThis cannot be undone.").arg(name);
+ if (!AndroidDeviceWidget::questionDialog(question, parent))
+ return;
+
+ qCDebug(androidDeviceLog) << QString("Erasing Android AVD \"%1\" from the system.").arg(name);
+ m_removeAvdProcess.reset(new Process);
+ const CommandLine command(AndroidConfig::avdManagerToolPath(), {"delete", "avd", "-n", name});
+ qCDebug(androidDeviceLog).noquote() << "Running command (removeAvd):" << command.toUserOutput();
+ m_removeAvdProcess->setEnvironment(AndroidConfig::toolsEnvironment());
+ m_removeAvdProcess->setCommand(command);
+ connect(m_removeAvdProcess.get(), &Process::done, this, [this, device] {
+ const QString name = device->displayName();
+ if (m_removeAvdProcess->result() == ProcessResult::FinishedWithSuccess) {
+ qCDebug(androidDeviceLog, "Android AVD id \"%s\" removed from the system.",
+ qPrintable(name));
+ // Remove the device from QtC after it's been removed using avdmanager.
+ DeviceManager::instance()->removeDevice(device->id());
+ } else {
+ AndroidDeviceWidget::criticalDialog(Tr::tr("An error occurred while removing the "
+ "Android AVD \"%1\" using avdmanager tool.").arg(name));
+ }
+ m_removeAvdProcess.release()->deleteLater();
+ });
+ m_removeAvdProcess->start();
}
-AndroidDeviceManager::AndroidDeviceManager(QObject *parent)
- : QObject(parent)
+namespace AndroidDeviceManager {
+
+void setupDevicesWatcher() { s_instance->setupDevicesWatcher(); }
+
+void updateAvdList()
{
- QTC_ASSERT(!s_instance, return);
- s_instance = this;
+ if (AndroidConfig::adbToolPath().exists())
+ s_instance->m_avdListRunner.start(s_instance->m_avdListRecipe);
}
-AndroidDeviceManager::~AndroidDeviceManager()
+expected_str<void> createAvd(const CreateAvdInfo &info, bool force)
{
- m_avdsFutureWatcher.waitForFinished();
- QTC_ASSERT(s_instance == this, return);
- s_instance = nullptr;
+ CommandLine cmd(AndroidConfig::avdManagerToolPath(), {"create", "avd", "-n", info.name});
+ cmd.addArgs({"-k", info.sdkStylePath});
+ if (info.sdcardSize > 0)
+ cmd.addArgs({"-c", QString("%1M").arg(info.sdcardSize)});
+
+ const QString deviceDef = info.deviceDefinition;
+ if (!deviceDef.isEmpty() && deviceDef != "Custom")
+ cmd.addArgs({"-d", deviceDef});
+
+ if (force)
+ cmd.addArg("-f");
+
+ Process process;
+ process.setProcessMode(ProcessMode::Writer);
+ process.setEnvironment(AndroidConfig::toolsEnvironment());
+ process.setCommand(cmd);
+ process.setWriteData("yes\n"); // yes to "Do you wish to create a custom hardware profile"
+
+ QByteArray buffer;
+ QObject::connect(&process, &Process::readyReadStandardOutput, &process, [&process, &buffer] {
+ // This interaction is needed only if there is no "-d" arg for the avdmanager command.
+ buffer += process.readAllRawStandardOutput();
+ if (buffer.endsWith(QByteArray("]:"))) {
+ // truncate to last line
+ const int index = buffer.lastIndexOf('\n');
+ if (index != -1)
+ buffer = buffer.mid(index);
+ if (buffer.contains("hw.gpu.enabled"))
+ process.write("yes\n");
+ else
+ process.write("\n");
+ buffer.clear();
+ }
+ });
+
+ GuardLocker locker(s_instance->m_avdPathGuard);
+ process.runBlocking();
+ if (process.result() != ProcessResult::FinishedWithSuccess)
+ return Utils::make_unexpected(process.exitMessage());
+ return {};
}
+
+} // namespace AndroidDeviceManager
+
// Factory
-class AndroidDeviceFactory final : public ProjectExplorer::IDeviceFactory
+class AndroidDeviceFactory final : public IDeviceFactory
{
public:
AndroidDeviceFactory()
@@ -837,7 +995,7 @@ public:
":/android/images/androiddevice.png");
setConstructionFunction(&AndroidDevice::create);
setCreator([] {
- if (!androidConfig().sdkToolsOk()) {
+ if (!AndroidConfig::sdkToolsOk()) {
AndroidDeviceWidget::infoDialog(Tr::tr("Android support is not yet configured."));
return IDevice::Ptr();
}
@@ -846,16 +1004,15 @@ public:
if (dialog.exec() != QDialog::Accepted)
return IDevice::Ptr();
- const IDevice::Ptr dev = dialog.device();
+ const IDevice::Ptr dev = createDeviceFromInfo(dialog.avdInfo());
if (const auto androidDev = static_cast<AndroidDevice *>(dev.get())) {
qCDebug(androidDeviceLog, "Created new Android AVD id \"%s\".",
qPrintable(androidDev->avdName()));
- } else {
- AndroidDeviceWidget::criticalDialog(
- Tr::tr("The device info returned from AvdDialog is invalid."));
+ return dev;
}
-
- return IDevice::Ptr(dev);
+ AndroidDeviceWidget::criticalDialog(
+ Tr::tr("The device info returned from AvdDialog is invalid."));
+ return IDevice::Ptr();
});
}
};
@@ -867,7 +1024,7 @@ void setupAndroidDevice()
void setupAndroidDeviceManager(QObject *guard)
{
- (void) new AndroidDeviceManager(guard);
+ (void) new AndroidDeviceManagerInstance(guard);
}
} // Android::Internal
diff --git a/src/plugins/android/androiddevice.h b/src/plugins/android/androiddevice.h
index 57b386dc4d..fbb7e0bccb 100644
--- a/src/plugins/android/androiddevice.h
+++ b/src/plugins/android/androiddevice.h
@@ -4,14 +4,16 @@
#pragma once
-#include "androidavdmanager.h"
#include "androidconfigurations.h"
#include "androiddeviceinfo.h"
#include <projectexplorer/devicesupport/idevice.h>
#include <projectexplorer/devicesupport/idevicefactory.h>
-#include <QFutureWatcher>
+#include <solutions/tasking/tasktreerunner.h>
+
+#include <utils/guard.h>
+
#include <QFileSystemWatcher>
#include <QSettings>
@@ -27,7 +29,6 @@ public:
static IDevice::Ptr create();
static AndroidDeviceInfo androidDeviceInfoFromIDevice(const IDevice *dev);
- static QString displayNameFromInfo(const AndroidDeviceInfo &info);
static Utils::Id idFromDeviceInfo(const AndroidDeviceInfo &info);
static Utils::Id idFromAvdInfo(const CreateAvdInfo &info);
@@ -69,40 +70,13 @@ private:
std::unique_ptr<QSettings> m_avdSettings;
};
-class AndroidDeviceManager : public QObject
-{
-public:
- static AndroidDeviceManager *instance();
- void setupDevicesWatcher();
- void updateAvdsList();
- IDevice::DeviceState getDeviceState(const QString &serial, IDevice::MachineType type) const;
- void updateDeviceState(const ProjectExplorer::IDevice::ConstPtr &device);
-
- void startAvd(const ProjectExplorer::IDevice::Ptr &device, QWidget *parent = nullptr);
- void eraseAvd(const ProjectExplorer::IDevice::Ptr &device, QWidget *parent = nullptr);
- void setupWifiForDevice(const ProjectExplorer::IDevice::Ptr &device, QWidget *parent = nullptr);
-
- void setEmulatorArguments(QWidget *parent = nullptr);
+namespace AndroidDeviceManager {
- QString getRunningAvdsSerialNumber(const QString &name) const;
+void setupDevicesWatcher();
+void updateAvdList();
+Utils::expected_str<void> createAvd(const CreateAvdInfo &info, bool force);
-private:
- explicit AndroidDeviceManager(QObject *parent);
- ~AndroidDeviceManager();
-
- void HandleDevicesListChange(const QString &serialNumber);
- void HandleAvdsListChange();
-
- QString emulatorName(const QString &serialNumber) const;
-
- QFutureWatcher<AndroidDeviceInfoList> m_avdsFutureWatcher;
- std::unique_ptr<Utils::Process> m_removeAvdProcess;
- QFileSystemWatcher m_avdFileSystemWatcher;
- std::unique_ptr<Utils::Process> m_adbDeviceWatcherProcess;
- AndroidAvdManager m_avdManager;
-
- friend void setupAndroidDeviceManager(QObject *guard);
-};
+} // namespace AndroidDeviceManager
void setupAndroidDevice();
void setupAndroidDeviceManager(QObject *guard);
diff --git a/src/plugins/android/androidmanager.cpp b/src/plugins/android/androidmanager.cpp
index 5128f761a0..9cca27242d 100644
--- a/src/plugins/android/androidmanager.cpp
+++ b/src/plugins/android/androidmanager.cpp
@@ -34,6 +34,7 @@
#include <QVersionNumber>
using namespace Android::Internal;
+using namespace Core;
using namespace ProjectExplorer;
using namespace Utils;
@@ -50,19 +51,32 @@ const char qtcSignature[] = "This file is generated by QtCreator to be read by "
static Q_LOGGING_CATEGORY(androidManagerLog, "qtc.android.androidManager", QtWarningMsg)
-class Library
+static std::optional<QDomElement> documentElement(const FilePath &fileName)
{
-public:
- int level = -1;
- QStringList dependencies;
- QString name;
-};
-
-using LibrariesMap = QMap<QString, Library>;
+ QFile file(fileName.toString());
+ if (!file.open(QIODevice::ReadOnly)) {
+ MessageManager::writeDisrupting(Tr::tr("Cannot open: %1").arg(fileName.toUserOutput()));
+ return {};
+ }
+ QDomDocument doc;
+ if (!doc.setContent(file.readAll())) {
+ MessageManager::writeDisrupting(Tr::tr("Cannot parse: %1").arg(fileName.toUserOutput()));
+ return {};
+ }
+ return doc.documentElement();
+}
-static bool openXmlFile(QDomDocument &doc, const FilePath &fileName);
-static bool openManifest(const Target *target, QDomDocument &doc);
-static int parseMinSdk(const QDomElement &manifestElem);
+static int parseMinSdk(const QDomElement &manifestElem)
+{
+ const QDomElement usesSdk = manifestElem.firstChildElement("uses-sdk");
+ if (!usesSdk.isNull() && usesSdk.hasAttribute("android:minSdkVersion")) {
+ bool ok;
+ int tmp = usesSdk.attribute("android:minSdkVersion").toInt(&ok);
+ if (ok)
+ return tmp;
+ }
+ return 0;
+}
static const ProjectNode *currentProjectNode(const Target *target)
{
@@ -71,30 +85,62 @@ static const ProjectNode *currentProjectNode(const Target *target)
QString packageName(const Target *target)
{
- QDomDocument doc;
- if (!openManifest(target, doc))
- return {};
- QDomElement manifestElem = doc.documentElement();
- return manifestElem.attribute(QLatin1String("package"));
-}
+ QString packageName;
+
+ // Check build.gradle
+ auto isComment = [](const QByteArray &trimmed) {
+ return trimmed.startsWith("//") || trimmed.startsWith('*') || trimmed.startsWith("/*");
+ };
+
+ const FilePath androidBuildDir = androidBuildDirectory(target);
+ const expected_str<QByteArray> gradleContents = androidBuildDir.pathAppended("build.gradle")
+ .fileContents();
+ if (gradleContents) {
+ const auto lines = gradleContents->split('\n');
+ for (const auto &line : lines) {
+ const QByteArray trimmed = line.trimmed();
+ if (isComment(trimmed) || !trimmed.contains("namespace"))
+ continue;
+
+ int idx = trimmed.indexOf('=');
+ if (idx == -1)
+ idx = trimmed.indexOf(' ');
+ if (idx > -1) {
+ packageName = QString::fromUtf8(trimmed.mid(idx + 1).trimmed());
+ if (packageName == "androidPackageName") {
+ // Check gradle.properties
+ const QSettings gradleProperties = QSettings(
+ androidBuildDir.pathAppended("gradle.properties").toFSPathString(),
+ QSettings::IniFormat);
+ packageName = gradleProperties.value("androidPackageName").toString();
+ } else {
+ // Remove quotes
+ if (packageName.size() > 2)
+ packageName = packageName.remove(0, 1).chopped(1);
+ }
-QString packageName(const FilePath &manifestFile)
-{
- QDomDocument doc;
- if (!openXmlFile(doc, manifestFile))
- return {};
- QDomElement manifestElem = doc.documentElement();
- return manifestElem.attribute(QLatin1String("package"));
+ break;
+ }
+ }
+ }
+
+ if (packageName.isEmpty()) {
+ // Check AndroidManifest.xml
+ const auto element = documentElement(AndroidManager::manifestPath(target));
+ if (element)
+ packageName = element->attribute("package");
+ }
+
+ return packageName;
}
QString activityName(const Target *target)
{
- QDomDocument doc;
- if (!openManifest(target, doc))
+ const auto element = documentElement(AndroidManager::manifestPath(target));
+ if (!element)
return {};
- QDomElement activityElem = doc.documentElement().firstChildElement(
- QLatin1String("application")).firstChildElement(QLatin1String("activity"));
- return activityElem.attribute(QLatin1String("android:name"));
+ return element->firstChildElement("application").firstChildElement("activity")
+ .attribute("android:name");
}
static FilePath manifestSourcePath(const Target *target)
@@ -118,10 +164,11 @@ static FilePath manifestSourcePath(const Target *target)
*/
int minimumSDK(const Target *target)
{
- QDomDocument doc;
- if (!openXmlFile(doc, manifestSourcePath(target)))
+ const auto element = documentElement(manifestSourcePath(target));
+ if (!element)
return minimumSDK(target->kit());
- const int minSdkVersion = parseMinSdk(doc.documentElement());
+
+ const int minSdkVersion = parseMinSdk(*element);
if (minSdkVersion == 0)
return AndroidManager::defaultMinimumSDK(QtSupport::QtKitAspect::qtVersion(target->kit()));
return minSdkVersion;
@@ -136,12 +183,12 @@ int minimumSDK(const Kit *kit)
int minSdkVersion = -1;
QtSupport::QtVersion *version = QtSupport::QtKitAspect::qtVersion(kit);
if (version && version->targetDeviceTypes().contains(Constants::ANDROID_DEVICE_TYPE)) {
- FilePath stockManifestFilePath = FilePath::fromUserInput(
+ const FilePath stockManifestFilePath = FilePath::fromUserInput(
version->prefix().toString() + "/src/android/templates/AndroidManifest.xml");
- QDomDocument doc;
- if (openXmlFile(doc, stockManifestFilePath)) {
- minSdkVersion = parseMinSdk(doc.documentElement());
- }
+
+ const auto element = documentElement(stockManifestFilePath);
+ if (element)
+ minSdkVersion = parseMinSdk(*element);
}
if (minSdkVersion == 0)
return AndroidManager::defaultMinimumSDK(version);
@@ -190,25 +237,23 @@ QJsonObject deploymentSettings(const Target *target)
QJsonObject settings;
settings["_description"] = qtcSignature;
settings["qt"] = qt->prefix().toString();
- settings["ndk"] = androidConfig().ndkLocation(qt).toString();
- settings["sdk"] = androidConfig().sdkLocation().toString();
+ settings["ndk"] = AndroidConfig::ndkLocation(qt).toString();
+ settings["sdk"] = AndroidConfig::sdkLocation().toString();
if (!qt->supportsMultipleQtAbis()) {
const QStringList abis = applicationAbis(target);
QTC_ASSERT(abis.size() == 1, return {});
- settings["stdcpp-path"] = (androidConfig().toolchainPath(qt)
+ settings["stdcpp-path"] = (AndroidConfig::toolchainPath(qt)
/ "sysroot/usr/lib"
/ archTriplet(abis.first())
/ "libc++_shared.so").toString();
} else {
- settings["stdcpp-path"] = androidConfig()
- .toolchainPath(qt)
- .pathAppended("sysroot/usr/lib")
- .toString();
+ settings["stdcpp-path"]
+ = AndroidConfig::toolchainPath(qt).pathAppended("sysroot/usr/lib").toString();
}
settings["toolchain-prefix"] = "llvm";
settings["tool-prefix"] = "llvm";
settings["useLLVM"] = true;
- settings["ndk-host"] = androidConfig().toolchainHost(qt);
+ settings["ndk-host"] = AndroidConfig::toolchainHost(qt);
return settings;
}
@@ -240,7 +285,7 @@ bool isQt5CmakeProject(const ProjectExplorer::Target *target)
{
const QtSupport::QtVersion *qt = QtSupport::QtKitAspect::qtVersion(target->kit());
const bool isQt5 = qt && qt->qtVersion() < QVersionNumber(6, 0, 0);
- const Core::Context cmakeCtx = Core::Context(CMakeProjectManager::Constants::CMAKE_PROJECT_ID);
+ const Context cmakeCtx(CMakeProjectManager::Constants::CMAKE_PROJECT_ID);
const bool isCmakeProject = (target->project()->projectContext() == cmakeCtx);
return isQt5 && isCmakeProject;
}
@@ -371,7 +416,7 @@ bool skipInstallationAndPackageSteps(const Target *target)
const Project *p = target->project();
- const Core::Context cmakeCtx = Core::Context(CMakeProjectManager::Constants::CMAKE_PROJECT_ID);
+ const Context cmakeCtx(CMakeProjectManager::Constants::CMAKE_PROJECT_ID);
const bool isCmakeProject = p->projectContext() == cmakeCtx;
if (isCmakeProject)
return false; // CMake reports ProductType::Other for Android Apps
@@ -538,43 +583,6 @@ QString androidNameForApiLevel(int x)
}
}
-static void raiseError(const QString &reason)
-{
- QMessageBox::critical(nullptr, Tr::tr("Error creating Android templates."), reason);
-}
-
-static bool openXmlFile(QDomDocument &doc, const FilePath &fileName)
-{
- QFile f(fileName.toString());
- if (!f.open(QIODevice::ReadOnly))
- return false;
-
- if (!doc.setContent(f.readAll())) {
- raiseError(Tr::tr("Cannot parse \"%1\".").arg(fileName.toUserOutput()));
- return false;
- }
- return true;
-}
-
-static bool openManifest(const Target *target, QDomDocument &doc)
-{
- return openXmlFile(doc, AndroidManager::manifestPath(target));
-}
-
-static int parseMinSdk(const QDomElement &manifestElem)
-{
- QDomElement usesSdk = manifestElem.firstChildElement(QLatin1String("uses-sdk"));
- if (usesSdk.isNull())
- return 0;
- if (usesSdk.hasAttribute(QLatin1String("android:minSdkVersion"))) {
- bool ok;
- int tmp = usesSdk.attribute(QLatin1String("android:minSdkVersion")).toInt(&ok);
- if (ok)
- return tmp;
- }
- return 0;
-}
-
void installQASIPackage(Target *target, const FilePath &packagePath)
{
const QStringList appAbis = AndroidManager::applicationAbis(target);
@@ -587,9 +595,9 @@ void installQASIPackage(Target *target, const FilePath &packagePath)
QString deviceSerialNumber = info.serialNumber;
if (info.type == IDevice::Emulator) {
- deviceSerialNumber = AndroidAvdManager().startAvd(info.avdName);
+ deviceSerialNumber = AndroidAvdManager::startAvd(info.avdName);
if (deviceSerialNumber.isEmpty())
- Core::MessageManager::writeDisrupting(Tr::tr("Starting Android virtual device failed."));
+ MessageManager::writeDisrupting(Tr::tr("Starting Android virtual device failed."));
}
QStringList arguments = AndroidDeviceInfo::adbSelector(deviceSerialNumber);
@@ -600,7 +608,7 @@ void installQASIPackage(Target *target, const FilePath &packagePath)
// TODO: Potential leak when the process is still running on Creator shutdown.
QObject::connect(process, &Process::done, process, &QObject::deleteLater);
} else {
- Core::MessageManager::writeDisrupting(
+ MessageManager::writeDisrupting(
Tr::tr("Android package installation failed.\n%1").arg(error));
}
}
@@ -609,12 +617,12 @@ bool checkKeystorePassword(const FilePath &keystorePath, const QString &keystore
{
if (keystorePasswd.isEmpty())
return false;
- const CommandLine cmd(androidConfig().keytoolPath(),
+ const CommandLine cmd(AndroidConfig::keytoolPath(),
{"-list", "-keystore", keystorePath.toUserOutput(),
"--storepass", keystorePasswd});
Process proc;
proc.setCommand(cmd);
- proc.runBlocking(10s, EventLoopMode::On);
+ proc.runBlocking(10s);
return proc.result() == ProcessResult::FinishedWithSuccess;
}
@@ -630,8 +638,8 @@ bool checkCertificatePassword(const FilePath &keystorePath, const QString &keyst
arguments << certificatePasswd;
Process proc;
- proc.setCommand({androidConfig().keytoolPath(), arguments});
- proc.runBlocking(10s, EventLoopMode::On);
+ proc.setCommand({AndroidConfig::keytoolPath(), arguments});
+ proc.runBlocking(10s);
return proc.result() == ProcessResult::FinishedWithSuccess;
}
@@ -639,19 +647,19 @@ bool checkCertificateExists(const FilePath &keystorePath, const QString &keystor
const QString &alias)
{
// assumes that the keystore password is correct
- QStringList arguments = { "-list", "-keystore", keystorePath.toUserOutput(),
- "--storepass", keystorePasswd, "-alias", alias };
+ const QStringList arguments = {"-list", "-keystore", keystorePath.toUserOutput(),
+ "--storepass", keystorePasswd, "-alias", alias};
Process proc;
- proc.setCommand({androidConfig().keytoolPath(), arguments});
- proc.runBlocking(10s, EventLoopMode::On);
+ proc.setCommand({AndroidConfig::keytoolPath(), arguments});
+ proc.runBlocking(10s);
return proc.result() == ProcessResult::FinishedWithSuccess;
}
Process *startAdbProcess(const QStringList &args, QString *err)
{
std::unique_ptr<Process> process(new Process);
- const FilePath adb = androidConfig().adbToolPath();
+ const FilePath adb = AndroidConfig::adbToolPath();
const CommandLine command{adb, args};
qCDebug(androidManagerLog).noquote() << "Running command (async):" << command.toUserOutput();
process->setCommand(command);
@@ -689,7 +697,7 @@ static SdkToolResult runCommand(const CommandLine &command, const QByteArray &wr
SdkToolResult runAdbCommand(const QStringList &args, const QByteArray &writeData, int timeoutS)
{
- return runCommand({androidConfig().adbToolPath(), args}, writeData, timeoutS);
+ return runCommand({AndroidConfig::adbToolPath(), args}, writeData, timeoutS);
}
} // namespace Android::AndroidManager
diff --git a/src/plugins/android/androidmanager.h b/src/plugins/android/androidmanager.h
index f3b9302d13..382768e898 100644
--- a/src/plugins/android/androidmanager.h
+++ b/src/plugins/android/androidmanager.h
@@ -39,7 +39,6 @@ namespace AndroidManager
constexpr auto firstQtWithAndroidDeployQt = {5, 4, 0};
QString packageName(const ProjectExplorer::Target *target);
-QString packageName(const Utils::FilePath &manifestFile);
QString activityName(const ProjectExplorer::Target *target);
QString deviceSerialNumber(const ProjectExplorer::Target *target);
diff --git a/src/plugins/android/androidmanifesteditorwidget.cpp b/src/plugins/android/androidmanifesteditorwidget.cpp
index 608f0b5262..049cd67404 100644
--- a/src/plugins/android/androidmanifesteditorwidget.cpp
+++ b/src/plugins/android/androidmanifesteditorwidget.cpp
@@ -592,7 +592,7 @@ void AndroidManifestEditorWidget::postSave()
const FilePath docPath = m_textEditorWidget->textDocument()->filePath();
if (Target *target = androidTarget(docPath)) {
if (BuildConfiguration *bc = target->activeBuildConfiguration()) {
- QString androidNdkPlatform = androidConfig().bestNdkPlatformMatch(
+ QString androidNdkPlatform = AndroidConfig::bestNdkPlatformMatch(
AndroidManager::minimumSDK(target),
QtSupport::QtKitAspect::qtVersion(
androidTarget(m_textEditorWidget->textDocument()->filePath())->kit()));
diff --git a/src/plugins/android/androidplugin.cpp b/src/plugins/android/androidplugin.cpp
index e1217188bb..2ab9e2f068 100644
--- a/src/plugins/android/androidplugin.cpp
+++ b/src/plugins/android/androidplugin.cpp
@@ -112,8 +112,8 @@ class AndroidPlugin final : public ExtensionSystem::IPlugin
setupJavaEditor();
setupAndroidManifestEditor();
- connect(KitManager::instance(), &KitManager::kitsLoaded,
- this, &AndroidPlugin::kitsRestored);
+ connect(KitManager::instance(), &KitManager::kitsLoaded, this, &AndroidPlugin::kitsRestored,
+ Qt::SingleShotConnection);
LanguageClient::LanguageClientSettings::registerClientType(
{Android::Constants::JLS_SETTINGS_ID,
@@ -135,7 +135,7 @@ class AndroidPlugin final : public ExtensionSystem::IPlugin
return v->targetDeviceTypes().contains(Android::Constants::ANDROID_DEVICE_TYPE);
}).isEmpty();
- if (!androidConfig().sdkFullyConfigured() && qtForAndroidInstalled)
+ if (!AndroidConfig::sdkFullyConfigured() && qtForAndroidInstalled)
askUserAboutAndroidSetup();
AndroidConfigurations::registerNewToolchains();
@@ -145,8 +145,6 @@ class AndroidPlugin final : public ExtensionSystem::IPlugin
AndroidConfigurations::registerNewToolchains();
AndroidConfigurations::updateAutomaticKitList();
});
- disconnect(KitManager::instance(), &KitManager::kitsLoaded,
- this, &AndroidPlugin::kitsRestored);
}
void askUserAboutAndroidSetup()
diff --git a/src/plugins/android/androidqmlpreviewworker.cpp b/src/plugins/android/androidqmlpreviewworker.cpp
index d37986e97f..5570e54096 100644
--- a/src/plugins/android/androidqmlpreviewworker.cpp
+++ b/src/plugins/android/androidqmlpreviewworker.cpp
@@ -14,30 +14,27 @@
#include <coreplugin/icore.h>
#include <projectexplorer/buildsystem.h>
-#include <projectexplorer/devicesupport/devicemanager.h>
#include <projectexplorer/environmentaspect.h>
-#include <projectexplorer/kit.h>
-#include <projectexplorer/project.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/runcontrol.h>
#include <projectexplorer/target.h>
+#include <solutions/tasking/tasktreerunner.h>
+
#include <qmlprojectmanager/qmlprojectconstants.h>
-#include <qtsupport/baseqtversion.h>
#include <qtsupport/qtkitaspect.h>
-#include <utils/async.h>
#include <utils/qtcprocess.h>
#include <QDateTime>
-#include <QDeadlineTimer>
-#include <QFutureWatcher>
-#include <QThread>
using namespace ProjectExplorer;
+using namespace Tasking;
using namespace Utils;
+using namespace std::chrono_literals;
+
namespace Android::Internal {
#define APP_ID "io.qt.qtdesignviewer"
@@ -45,7 +42,17 @@ namespace Android::Internal {
class ApkInfo
{
public:
- ApkInfo();
+ ApkInfo()
+ : abis{ProjectExplorer::Constants::ANDROID_ABI_X86,
+ ProjectExplorer::Constants::ANDROID_ABI_X86_64,
+ ProjectExplorer::Constants::ANDROID_ABI_ARM64_V8A,
+ ProjectExplorer::Constants::ANDROID_ABI_ARMEABI_V7A}
+ , appId(APP_ID)
+ , uploadDir("/data/local/tmp/" APP_ID "/")
+ // TODO Add possibility to run Qt5 built version of Qt Design Viewer
+ , activityId(APP_ID "/org.qtproject.qt.android.bindings.QtActivity")
+ , name("Qt Design Viewer")
+ {}
const QStringList abis;
const QString appId;
const QString uploadDir;
@@ -53,18 +60,6 @@ public:
const QString name;
};
-ApkInfo::ApkInfo() :
- abis({ProjectExplorer::Constants::ANDROID_ABI_X86,
- ProjectExplorer::Constants::ANDROID_ABI_X86_64,
- ProjectExplorer::Constants::ANDROID_ABI_ARM64_V8A,
- ProjectExplorer::Constants::ANDROID_ABI_ARMEABI_V7A}),
- appId(APP_ID),
- uploadDir("/data/local/tmp/" APP_ID "/"),
- // TODO Add possibility to run Qt5 built version of Qt Design Viewer
- activityId(APP_ID "/org.qtproject.qt.android.bindings.QtActivity"),
- name("Qt Design Viewer")
-{
-}
Q_GLOBAL_STATIC(ApkInfo, apkInfo)
@@ -82,7 +77,6 @@ class AndroidQmlPreviewWorker : public RunWorker
Q_OBJECT
public:
AndroidQmlPreviewWorker(RunControl *runControl);
- ~AndroidQmlPreviewWorker();
signals:
void previewPidChanged();
@@ -96,10 +90,9 @@ private:
bool preparePreviewArtefacts();
bool uploadPreviewArtefacts();
+ CommandLine adbCommand(const QStringList &arguments) const;
SdkToolResult runAdbCommand(const QStringList &arguments) const;
SdkToolResult runAdbShellCommand(const QStringList &arguments) const;
- int pidofPreview() const;
- bool isPreviewRunning(int lastKnownPid = -1) const;
void startPidWatcher();
void startLogcat();
@@ -108,15 +101,15 @@ private:
bool startPreviewApp();
bool stopPreviewApp();
- Utils::FilePath designViewerApkPath(const QString &abi) const;
- Utils::FilePath createQmlrcFile(const Utils::FilePath &workFolder, const QString &basename);
+ FilePath designViewerApkPath(const QString &abi) const;
+ FilePath createQmlrcFile(const FilePath &workFolder, const QString &basename);
RunControl *m_rc = nullptr;
QString m_serialNumber;
QStringList m_avdAbis;
int m_viewerPid = -1;
- QFutureWatcher<void> m_pidFutureWatcher;
- Utils::Process m_logcatProcess;
+ TaskTreeRunner m_pidRunner;
+ Process m_logcatProcess;
QString m_logcatStartTimeStamp;
UploadInfo m_uploadInfo;
};
@@ -133,6 +126,16 @@ FilePath AndroidQmlPreviewWorker::designViewerApkPath(const QString &abi) const
return {};
}
+CommandLine AndroidQmlPreviewWorker::adbCommand(const QStringList &arguments) const
+{
+ CommandLine cmd{AndroidConfig::adbToolPath()};
+ if (!m_serialNumber.isEmpty())
+ cmd.addArgs(AndroidDeviceInfo::adbSelector(m_serialNumber));
+ cmd.addArg("shell");
+ cmd.addArgs(arguments);
+ return cmd;
+}
+
SdkToolResult AndroidQmlPreviewWorker::runAdbCommand(const QStringList &arguments) const
{
QStringList args;
@@ -147,44 +150,49 @@ SdkToolResult AndroidQmlPreviewWorker::runAdbShellCommand(const QStringList &arg
return runAdbCommand(QStringList() << "shell" << arguments);
}
-int AndroidQmlPreviewWorker::pidofPreview() const
+void AndroidQmlPreviewWorker::startPidWatcher()
{
- const QStringList command{"pidof", apkInfo()->appId};
- const SdkToolResult res = runAdbShellCommand(command);
- return res.success() ? res.stdOut().toInt() : -1;
-}
+ const LoopUntil pidIterator([this](int) { return m_viewerPid <= 0; });
+ const LoopUntil alivePidIterator([this](int) { return m_viewerPid > 0; });
-bool AndroidQmlPreviewWorker::isPreviewRunning(int lastKnownPid) const
-{
- const int pid = pidofPreview();
- return (lastKnownPid > 1) ? lastKnownPid == pid : pid > 1;
-}
+ const auto onPidSetup = [this](Process &process) {
+ process.setCommand(adbCommand({"pidof", apkInfo()->appId}));
+ };
+ const auto onPidDone = [this](const Process &process) {
+ bool ok = false;
+ const int pid = process.cleanedStdOut().trimmed().toInt(&ok);
+ if (ok && pid > 0) {
+ m_viewerPid = pid;
+ // TODO: make a continuation task (logcat)
+ emit previewPidChanged();
+ }
+ };
-void AndroidQmlPreviewWorker::startPidWatcher()
-{
- m_pidFutureWatcher.setFuture(Utils::asyncRun([this] {
- // wait for started
- const int sleepTimeMs = 2000;
- QDeadlineTimer deadline(20000);
- while (!m_pidFutureWatcher.isCanceled() && !deadline.hasExpired()) {
- if (m_viewerPid == -1) {
- m_viewerPid = pidofPreview();
- if (m_viewerPid > 0) {
- emit previewPidChanged();
- break;
- }
- }
- QThread::msleep(sleepTimeMs);
+ const auto onAlivePidDone = [this](const Process &process) {
+ bool ok = false;
+ const int pid = process.cleanedStdOut().trimmed().toInt(&ok);
+ if (!ok || pid != m_viewerPid) {
+ m_viewerPid = -1;
+ stop();
}
+ };
- while (!m_pidFutureWatcher.isCanceled()) {
- if (!isPreviewRunning(m_viewerPid)) {
- stop();
- break;
- }
- QThread::msleep(sleepTimeMs);
+ const TimeoutTask timeout([](std::chrono::milliseconds &timeout) { timeout = 2s; });
+
+ const Group root {
+ Group {
+ pidIterator,
+ ProcessTask(onPidSetup, onPidDone, CallDoneIf::Success),
+ timeout
+ }.withTimeout(20s),
+ Group {
+ alivePidIterator,
+ ProcessTask(onPidSetup, onAlivePidDone),
+ timeout
}
- }));
+ };
+
+ m_pidRunner.start(root);
}
void AndroidQmlPreviewWorker::startLogcat()
@@ -192,7 +200,7 @@ void AndroidQmlPreviewWorker::startLogcat()
QString args = QString("logcat --pid=%1").arg(m_viewerPid);
if (!m_logcatStartTimeStamp.isEmpty())
args += QString(" -T '%1'").arg(m_logcatStartTimeStamp);
- CommandLine cmd(androidConfig().adbToolPath());
+ CommandLine cmd(AndroidConfig::adbToolPath());
cmd.setArguments(args);
m_logcatProcess.setCommand(cmd);
m_logcatProcess.setUseCtrlCStub(true);
@@ -220,7 +228,7 @@ AndroidQmlPreviewWorker::AndroidQmlPreviewWorker(RunControl *runControl)
m_rc(runControl)
{
connect(this, &RunWorker::started, this, &AndroidQmlPreviewWorker::startPidWatcher);
- connect(this, &RunWorker::stopped, &m_pidFutureWatcher, &QFutureWatcher<void>::cancel);
+ connect(this, &RunWorker::stopped, &m_pidRunner, &TaskTreeRunner::reset);
connect(this, &AndroidQmlPreviewWorker::previewPidChanged,
this, &AndroidQmlPreviewWorker::startLogcat);
@@ -230,12 +238,6 @@ AndroidQmlPreviewWorker::AndroidQmlPreviewWorker(RunControl *runControl)
});
}
-AndroidQmlPreviewWorker::~AndroidQmlPreviewWorker()
-{
- m_pidFutureWatcher.cancel();
- m_pidFutureWatcher.waitForFinished();
-}
-
void AndroidQmlPreviewWorker::start()
{
const SdkToolResult dateResult = runAdbCommand({"shell", "date", "+%s"});
@@ -254,7 +256,7 @@ void AndroidQmlPreviewWorker::start()
void AndroidQmlPreviewWorker::stop()
{
- if (!isPreviewRunning(m_viewerPid) || stopPreviewApp())
+ if (m_viewerPid <= 0 || stopPreviewApp())
appendMessage(Tr::tr("%1 has been stopped.").arg(apkInfo()->name), NormalMessageFormat);
m_viewerPid = -1;
reportStopped();
@@ -262,13 +264,12 @@ void AndroidQmlPreviewWorker::stop()
bool AndroidQmlPreviewWorker::ensureAvdIsRunning()
{
- AndroidAvdManager avdMananager;
QString devSN = AndroidManager::deviceSerialNumber(m_rc->target());
if (devSN.isEmpty())
devSN = m_serialNumber;
- if (!avdMananager.isAvdBooted(devSN)) {
+ if (!AndroidAvdManager::isAvdBooted(devSN)) {
const IDevice *dev = DeviceKitAspect::device(m_rc->target()->kit()).get();
if (!dev) {
appendMessage(Tr::tr("Selected device is invalid."), ErrorMessageFormat);
@@ -283,13 +284,13 @@ bool AndroidQmlPreviewWorker::ensureAvdIsRunning()
if (devInfoLocal.isValid()) {
if (dev->machineType() == IDevice::Emulator) {
appendMessage(Tr::tr("Launching AVD."), NormalMessageFormat);
- devInfoLocal.serialNumber = avdMananager.startAvd(devInfoLocal.avdName);
+ devInfoLocal.serialNumber = AndroidAvdManager::startAvd(devInfoLocal.avdName);
}
if (devInfoLocal.serialNumber.isEmpty()) {
appendMessage(Tr::tr("Could not start AVD."), ErrorMessageFormat);
} else {
m_serialNumber = devInfoLocal.serialNumber;
- m_avdAbis = androidConfig().getAbis(m_serialNumber);
+ m_avdAbis = AndroidConfig::getAbis(m_serialNumber);
}
return !devInfoLocal.serialNumber.isEmpty();
} else {
@@ -297,7 +298,7 @@ bool AndroidQmlPreviewWorker::ensureAvdIsRunning()
}
return false;
}
- m_avdAbis = androidConfig().getAbis(m_serialNumber);
+ m_avdAbis = AndroidConfig::getAbis(m_serialNumber);
return true;
}
diff --git a/src/plugins/android/androidqtversion.cpp b/src/plugins/android/androidqtversion.cpp
index eb9f180138..67232b0c9d 100644
--- a/src/plugins/android/androidqtversion.cpp
+++ b/src/plugins/android/androidqtversion.cpp
@@ -61,9 +61,9 @@ QString AndroidQtVersion::invalidReason() const
{
QString tmp = QtVersion::invalidReason();
if (tmp.isEmpty()) {
- if (androidConfig().ndkLocation(this).isEmpty())
+ if (AndroidConfig::ndkLocation(this).isEmpty())
return Tr::tr("NDK is not configured in Devices > Android.");
- if (androidConfig().sdkLocation().isEmpty())
+ if (AndroidConfig::sdkLocation().isEmpty())
return Tr::tr("SDK is not configured in Devices > Android.");
if (qtAbis().isEmpty())
return Tr::tr("Failed to detect the ABIs used by the Qt version. Check the settings in "
@@ -79,7 +79,7 @@ bool AndroidQtVersion::supportsMultipleQtAbis() const
Abis AndroidQtVersion::detectQtAbis() const
{
- const bool conf = androidConfig().sdkFullyConfigured();
+ const bool conf = AndroidConfig::sdkFullyConfigured();
return conf ? Utils::transform<Abis>(androidAbis(), &AndroidManager::androidAbi2Abi) : Abis();
}
@@ -87,18 +87,17 @@ void AndroidQtVersion::addToEnvironment(const Kit *k, Utils::Environment &env) c
{
QtVersion::addToEnvironment(k, env);
- const AndroidConfig &config = androidConfig();
// this env vars are used by qmake mkspecs to generate makefiles (check QTDIR/mkspecs/android-g++/qmake.conf for more info)
- env.set(QLatin1String("ANDROID_NDK_HOST"), config.toolchainHost(this));
- env.set(QLatin1String("ANDROID_NDK_ROOT"), config.ndkLocation(this).toUserOutput());
+ env.set(QLatin1String("ANDROID_NDK_HOST"), AndroidConfig::toolchainHost(this));
+ env.set(QLatin1String("ANDROID_NDK_ROOT"), AndroidConfig::ndkLocation(this).toUserOutput());
env.set(QLatin1String("ANDROID_NDK_PLATFORM"),
- config.bestNdkPlatformMatch(qMax(minimumNDK(), AndroidManager::minimumSDK(k)), this));
+ AndroidConfig::bestNdkPlatformMatch(qMax(minimumNDK(), AndroidManager::minimumSDK(k)), this));
}
void AndroidQtVersion::setupQmakeRunEnvironment(Utils::Environment &env) const
{
env.set(QLatin1String("ANDROID_NDK_ROOT"),
- androidConfig().ndkLocation(this).toUserOutput());
+ AndroidConfig::ndkLocation(this).toUserOutput());
}
QString AndroidQtVersion::description() const
diff --git a/src/plugins/android/androidrunner.cpp b/src/plugins/android/androidrunner.cpp
index 72b786fa73..260bef647f 100644
--- a/src/plugins/android/androidrunner.cpp
+++ b/src/plugins/android/androidrunner.cpp
@@ -167,28 +167,24 @@ void AndroidRunner::launchAVD()
AndroidManager::setDeviceSerialNumber(m_target, info.serialNumber);
emit androidDeviceInfoChanged(info);
if (info.isValid()) {
- AndroidAvdManager avdManager;
- if (!info.avdName.isEmpty() && avdManager.findAvd(info.avdName).isEmpty()) {
- bool launched = avdManager.startAvdAsync(info.avdName);
- m_launchedAVDName = launched ? info.avdName:"";
- } else {
+ if (!info.avdName.isEmpty() && AndroidAvdManager::findAvd(info.avdName).isEmpty())
+ m_launchedAVDName = AndroidAvdManager::startAvdAsync(info.avdName) ? info.avdName : "";
+ else
m_launchedAVDName.clear();
- }
}
}
void AndroidRunner::checkAVD()
{
- AndroidAvdManager avdManager;
- QString serialNumber = avdManager.findAvd(m_launchedAVDName);
+ const QString serialNumber = AndroidAvdManager::findAvd(m_launchedAVDName);
if (!serialNumber.isEmpty())
return; // try again on next timer hit
- if (avdManager.isAvdBooted(serialNumber)) {
+ if (AndroidAvdManager::isAvdBooted(serialNumber)) {
m_checkAVDTimer.stop();
AndroidManager::setDeviceSerialNumber(m_target, serialNumber);
emit asyncStart();
- } else if (!androidConfig().isConnected(serialNumber)) {
+ } else if (!AndroidConfig::isConnected(serialNumber)) {
// device was disconnected
m_checkAVDTimer.stop();
}
diff --git a/src/plugins/android/androidrunnerworker.cpp b/src/plugins/android/androidrunnerworker.cpp
index 34688de9a1..b5041917b0 100644
--- a/src/plugins/android/androidrunnerworker.cpp
+++ b/src/plugins/android/androidrunnerworker.cpp
@@ -20,20 +20,15 @@
#include <qtsupport/baseqtversion.h>
#include <qtsupport/qtkitaspect.h>
-#include <utils/async.h>
-#include <utils/fileutils.h>
#include <utils/hostosinfo.h>
#include <utils/qtcprocess.h>
-#include <utils/stringutils.h>
-#include <utils/temporaryfile.h>
#include <utils/url.h>
#include <QDate>
#include <QLoggingCategory>
-#include <QScopeGuard>
#include <QRegularExpression>
+#include <QScopeGuard>
#include <QTcpServer>
-#include <QThread>
#include <chrono>
@@ -57,17 +52,6 @@ static const QRegularExpression userIdPattern("u(\\d+)_a");
static const std::chrono::milliseconds s_jdbTimeout = 5s;
-static int APP_START_TIMEOUT = 45000;
-static bool isTimedOut(const chrono::high_resolution_clock::time_point &start,
- int msecs = APP_START_TIMEOUT)
-{
- bool timedOut = false;
- auto end = chrono::high_resolution_clock::now();
- if (chrono::duration_cast<chrono::milliseconds>(end-start).count() > msecs)
- timedOut = true;
- return timedOut;
-}
-
static qint64 extractPID(const QString &output, const QString &packageName)
{
qint64 pid = -1;
@@ -82,67 +66,6 @@ static qint64 extractPID(const QString &output, const QString &packageName)
return pid;
}
-static void findProcessPIDAndUser(QPromise<PidUserPair> &promise,
- const QStringList &selector,
- const QString &packageName,
- bool preNougat)
-{
- if (packageName.isEmpty())
- return;
-
- static const QString pidScript = "pidof -s '%1'";
- static const QString pidScriptPreNougat = QStringLiteral("for p in /proc/[0-9]*; "
- "do cat <$p/cmdline && echo :${p##*/}; done");
- QStringList args = {selector};
- FilePath adbPath = androidConfig().adbToolPath();
- args.append("shell");
- args.append(preNougat ? pidScriptPreNougat : pidScript.arg(packageName));
-
- qint64 processPID = -1;
- chrono::high_resolution_clock::time_point start = chrono::high_resolution_clock::now();
- do {
- QThread::msleep(200);
- Process proc;
- proc.setCommand({adbPath, args});
- proc.runBlocking();
- const QString out = proc.allOutput();
- if (preNougat) {
- processPID = extractPID(out, packageName);
- } else {
- if (!out.isEmpty())
- processPID = out.trimmed().toLongLong();
- }
- } while ((processPID == -1 || processPID == 0) && !isTimedOut(start) && !promise.isCanceled());
-
- qCDebug(androidRunWorkerLog) << "PID found:" << processPID << ", PreNougat:" << preNougat;
-
- qint64 processUser = 0;
- if (processPID > 0 && !promise.isCanceled()) {
- args = {selector};
- args.append({"shell", "ps", "-o", "user", "-p"});
- args.append(QString::number(processPID));
- Process proc;
- proc.setCommand({adbPath, args});
- proc.runBlocking();
- const QString out = proc.allOutput();
- if (!out.isEmpty()) {
- QRegularExpressionMatch match;
- qsizetype matchPos = out.indexOf(userIdPattern, 0, &match);
- if (matchPos >= 0 && match.capturedLength(1) > 0) {
- bool ok = false;
- processUser = match.captured(1).toInt(&ok);
- if (!ok)
- processUser = 0;
- }
- }
- }
-
- qCDebug(androidRunWorkerLog) << "USER found:" << processUser;
-
- if (!promise.isCanceled())
- promise.addResult(PidUserPair(processPID, processUser));
-}
-
static QString gdbServerArch(const QString &androidAbi)
{
if (androidAbi == ProjectExplorer::Constants::ANDROID_ABI_ARM64_V8A)
@@ -170,11 +93,9 @@ static FilePath debugServer(bool useLldb, const Target *target)
QtSupport::QtVersion *qtVersion = QtSupport::QtKitAspect::qtVersion(target->kit());
QString preferredAbi = AndroidManager::apkDevicePreferredAbi(target);
- const AndroidConfig &config = androidConfig();
-
if (useLldb) {
// Search suitable lldb-server binary.
- const FilePath prebuilt = config.ndkLocation(qtVersion) / "toolchains/llvm/prebuilt";
+ const FilePath prebuilt = AndroidConfig::ndkLocation(qtVersion) / "toolchains/llvm/prebuilt";
const QString abiNeedle = lldbServerArch2(preferredAbi);
// The new, built-in LLDB.
@@ -194,7 +115,7 @@ static FilePath debugServer(bool useLldb, const Target *target)
return lldbServer;
} else {
// Search suitable gdbserver binary.
- const FilePath path = config.ndkLocation(qtVersion)
+ const FilePath path = AndroidConfig::ndkLocation(qtVersion)
.pathAppended(QString("prebuilt/android-%1/gdbserver/gdbserver")
.arg(gdbServerArch(preferredAbi)));
if (path.exists())
@@ -251,14 +172,14 @@ AndroidRunnerWorker::AndroidRunnerWorker(RunWorker *runner, const QString &packa
if (const Store sd = runControl->settingsData(Constants::ANDROID_AM_START_ARGS);
!sd.values().isEmpty()) {
- QTC_CHECK(sd.first().type() == QVariant::String);
+ QTC_CHECK(sd.first().typeId() == QMetaType::QString);
const QString startArgs = sd.first().toString();
m_amStartExtraArgs = ProcessArgs::splitArgs(startArgs, OsTypeOtherUnix);
}
if (const Store sd = runControl->settingsData(Constants::ANDROID_PRESTARTSHELLCMDLIST);
!sd.values().isEmpty()) {
- QTC_CHECK(sd.first().type() == QVariant::String);
+ QTC_CHECK(sd.first().typeId() == QMetaType::QString);
const QStringList commands = sd.first().toString().split('\n', Qt::SkipEmptyParts);
for (const QString &shellCmd : commands)
m_beforeStartAdbCommands.append(QString("shell %1").arg(shellCmd));
@@ -266,7 +187,7 @@ AndroidRunnerWorker::AndroidRunnerWorker(RunWorker *runner, const QString &packa
if (const Store sd = runControl->settingsData(Constants::ANDROID_POSTFINISHSHELLCMDLIST);
!sd.values().isEmpty()) {
- QTC_CHECK(sd.first().type() == QVariant::String);
+ QTC_CHECK(sd.first().typeId() == QMetaType::QString);
const QStringList commands = sd.first().toString().split('\n', Qt::SkipEmptyParts);
for (const QString &shellCmd : commands)
m_afterFinishAdbCommands.append(QString("shell %1").arg(shellCmd));
@@ -288,9 +209,6 @@ AndroidRunnerWorker::~AndroidRunnerWorker()
{
if (m_processPID != -1)
forceStop();
-
- if (!m_pidFinder.isFinished())
- m_pidFinder.cancel();
}
bool AndroidRunnerWorker::runAdb(const QStringList &args, QString *stdOut,
@@ -367,12 +285,6 @@ bool AndroidRunnerWorker::packageFileExists(const QString &filePath)
return success && !output.trimmed().isEmpty();
}
-void AndroidRunnerWorker::adbKill(qint64 pid)
-{
- if (!runAdb({"shell", "run-as", m_packageName, "kill", "-9", QString::number(pid)}))
- runAdb({"shell", "kill", "-9", QString::number(pid)});
-}
-
QStringList AndroidRunnerWorker::selector() const
{
return AndroidDeviceInfo::adbSelector(m_deviceSerialNumber);
@@ -385,8 +297,11 @@ void AndroidRunnerWorker::forceStop()
// try killing it via kill -9
QString output;
runAdb({"shell", "pidof", m_packageName}, &output);
- if (m_processPID != -1 && output == QString::number(m_processPID))
- adbKill(m_processPID);
+ const QString pidString = QString::number(m_processPID);
+ if (m_processPID != -1 && output == pidString
+ && !runAdb({"shell", "run-as", m_packageName, "kill", "-9", pidString})) {
+ runAdb({"shell", "kill", "-9", pidString});
+ }
}
void AndroidRunnerWorker::logcatReadStandardError()
@@ -512,7 +427,7 @@ void Android::Internal::AndroidRunnerWorker::asyncStartLogcat()
}
const QStringList logcatArgs = selector() << "logcat" << timeArg;
- const FilePath adb = androidConfig().adbToolPath();
+ const FilePath adb = AndroidConfig::adbToolPath();
qCDebug(androidRunWorkerLog).noquote() << "Running logcat command (async):"
<< CommandLine(adb, logcatArgs).toUserOutput();
m_adbLogcatProcess->setCommand({adb, logcatArgs});
@@ -710,19 +625,69 @@ void AndroidRunnerWorker::asyncStart()
{
asyncStartHelper();
- m_pidFinder = Utils::onResultReady(Utils::asyncRun(findProcessPIDAndUser,
- selector(),
- m_packageName,
- m_isPreNougat),
- this,
- bind(&AndroidRunnerWorker::onProcessIdChanged, this, _1));
+ using namespace Tasking;
+
+ const Storage<PidUserPair> pidStorage;
+
+ const FilePath adbPath = AndroidConfig::adbToolPath();
+ const QStringList args = selector();
+ const QString pidScript = m_isPreNougat
+ ? QString("for p in /proc/[0-9]*; do cat <$p/cmdline && echo :${p##*/}; done")
+ : QString("pidof -s '%1'").arg(m_packageName);
+
+ const auto onPidSetup = [adbPath, args, pidScript](Process &process) {
+ process.setCommand({adbPath, {args, "shell", pidScript}});
+ };
+ const auto onPidDone = [pidStorage, packageName = m_packageName,
+ isPreNougat = m_isPreNougat](const Process &process) {
+ const QString out = process.allOutput();
+ if (isPreNougat)
+ pidStorage->first = extractPID(out, packageName);
+ else if (!out.isEmpty())
+ pidStorage->first = out.trimmed().toLongLong();
+ };
+
+ const auto onUserSetup = [pidStorage, adbPath, args](Process &process) {
+ process.setCommand({adbPath, {args, "shell", "ps", "-o", "user", "-p",
+ QString::number(pidStorage->first)}});
+ };
+ const auto onUserDone = [pidStorage](const Process &process) {
+ const QString out = process.allOutput();
+ if (out.isEmpty())
+ return DoneResult::Error;
+
+ QRegularExpressionMatch match;
+ qsizetype matchPos = out.indexOf(userIdPattern, 0, &match);
+ if (matchPos >= 0 && match.capturedLength(1) > 0) {
+ bool ok = false;
+ const qint64 processUser = match.captured(1).toInt(&ok);
+ if (ok) {
+ pidStorage->second = processUser;
+ return DoneResult::Success;
+ }
+ }
+ return DoneResult::Error;
+ };
+
+ const Group root {
+ pidStorage,
+ onGroupSetup([pidStorage] { *pidStorage = {-1, 0}; }),
+ Forever {
+ stopOnSuccess,
+ ProcessTask(onPidSetup, onPidDone, CallDoneIf::Success),
+ TimeoutTask([](std::chrono::milliseconds &timeout) { timeout = 200ms; },
+ [] { return DoneResult::Error; })
+ }.withTimeout(45s),
+ ProcessTask(onUserSetup, onUserDone, CallDoneIf::Success),
+ onGroupDone([pidStorage, this] { onProcessIdChanged(*pidStorage); })
+ };
+
+ m_pidRunner.start(root);
}
void AndroidRunnerWorker::asyncStop()
{
- if (!m_pidFinder.isFinished())
- m_pidFinder.cancel();
-
+ m_pidRunner.reset();
if (m_processPID != -1)
forceStop();
@@ -742,7 +707,7 @@ void AndroidRunnerWorker::handleJdbWaiting()
}
m_afterFinishAdbCommands.push_back(removeForward.join(' '));
- const FilePath jdbPath = androidConfig().openJDKLocation()
+ const FilePath jdbPath = AndroidConfig::openJDKLocation()
.pathAppended("bin/jdb").withExecutableSuffix();
QStringList jdbArgs("-connect");
@@ -823,8 +788,6 @@ void AndroidRunnerWorker::removeForwardPort(const QString &port)
void AndroidRunnerWorker::onProcessIdChanged(const PidUserPair &pidUser)
{
- // Don't write to m_psProc from a different thread
- QTC_ASSERT(QThread::currentThread() == thread(), return);
qCDebug(androidRunWorkerLog) << "Process ID changed from:" << m_processPID
<< "to:" << pidUser.first;
m_processPID = pidUser.first;
diff --git a/src/plugins/android/androidrunnerworker.h b/src/plugins/android/androidrunnerworker.h
index ac995fe162..bba8d35104 100644
--- a/src/plugins/android/androidrunnerworker.h
+++ b/src/plugins/android/androidrunnerworker.h
@@ -6,12 +6,11 @@
#include <qmldebug/qmldebugcommandlinearguments.h>
+#include <solutions/tasking/tasktreerunner.h>
+
#include <utils/environment.h>
#include <utils/port.h>
-#include <QFuture>
-#include <utility>
-
namespace Utils {
class FilePath;
class Process;
@@ -35,33 +34,33 @@ public:
AndroidRunnerWorker(ProjectExplorer::RunWorker *runner, const QString &packageName);
~AndroidRunnerWorker() override;
+ void setAndroidDeviceInfo(const AndroidDeviceInfo &info);
+ void asyncStart();
+ void asyncStop();
+ void setIsPreNougat(bool isPreNougat) { m_isPreNougat = isPreNougat; }
+ void setIntentName(const QString &intentName) { m_intentName = intentName; }
+
+signals:
+ void remoteProcessStarted(Utils::Port debugServerPort, const QUrl &qmlServer, qint64 pid);
+ void remoteProcessFinished(const QString &errString = QString());
+
+ void remoteOutput(const QString &output);
+ void remoteErrorOutput(const QString &output);
+
+private:
bool runAdb(const QStringList &args, QString *stdOut = nullptr, QString *stdErr = nullptr,
const QByteArray &writeData = {});
- void adbKill(qint64 pid);
QStringList selector() const;
void forceStop();
void logcatReadStandardError();
void logcatReadStandardOutput();
void logcatProcess(const QByteArray &text, QByteArray &buffer, bool onlyError);
- void setAndroidDeviceInfo(const AndroidDeviceInfo &info);
- void setIsPreNougat(bool isPreNougat) { m_isPreNougat = isPreNougat; }
- void setIntentName(const QString &intentName) { m_intentName = intentName; }
- void asyncStart();
- void asyncStop();
void handleJdbWaiting();
void handleJdbSettled();
void removeForwardPort(const QString &port);
-signals:
- void remoteProcessStarted(Utils::Port debugServerPort, const QUrl &qmlServer, qint64 pid);
- void remoteProcessFinished(const QString &errString = QString());
-
- void remoteOutput(const QString &output);
- void remoteErrorOutput(const QString &output);
-
-private:
void asyncStartHelper();
void startNativeDebugging();
bool startDebuggerServer(const QString &packageDir, const QString &debugServerFile, QString *errorStr = nullptr);
@@ -90,7 +89,7 @@ private:
std::unique_ptr<Utils::Process> m_psIsAlive;
QByteArray m_stdoutBuffer;
QByteArray m_stderrBuffer;
- QFuture<PidUserPair> m_pidFinder;
+ Tasking::TaskTreeRunner m_pidRunner;
bool m_useCppDebugger = false;
bool m_useLldb = false; // FIXME: Un-implemented currently.
QmlDebug::QmlDebugServicesPreset m_qmlDebugServices;
diff --git a/src/plugins/android/androidsdkdownloader.cpp b/src/plugins/android/androidsdkdownloader.cpp
index f3238b8c66..8f0993363e 100644
--- a/src/plugins/android/androidsdkdownloader.cpp
+++ b/src/plugins/android/androidsdkdownloader.cpp
@@ -8,14 +8,11 @@
#include <coreplugin/icore.h>
-#include <extensionsystem/pluginmanager.h>
-
#include <solutions/tasking/barrier.h>
#include <solutions/tasking/networkquery.h>
#include <utils/async.h>
#include <utils/filepath.h>
-#include <utils/futuresynchronizer.h>
#include <utils/networkaccessmanager.h>
#include <utils/unarchiver.h>
@@ -109,7 +106,7 @@ GroupItem downloadSdkRecipe()
Storage<StorageStruct> storage;
const auto onSetup = [] {
- if (androidConfig().sdkToolsUrl().isEmpty()) {
+ if (AndroidConfig::sdkToolsUrl().isEmpty()) {
logError(Tr::tr("The SDK Tools download URL is empty."));
return SetupResult::StopWithError;
}
@@ -117,7 +114,7 @@ GroupItem downloadSdkRecipe()
};
const auto onQuerySetup = [storage](NetworkQuery &query) {
- query.setRequest(QNetworkRequest(androidConfig().sdkToolsUrl()));
+ query.setRequest(QNetworkRequest(AndroidConfig::sdkToolsUrl()));
query.setNetworkAccessManager(NetworkAccessManager::instance());
NetworkQuery *queryPtr = &query;
QProgressDialog *progressDialog = storage->progressDialog.get();
@@ -169,8 +166,7 @@ GroupItem downloadSdkRecipe()
if (!storage->sdkFileName)
return SetupResult::StopWithError;
async.setConcurrentCallData(validateFileIntegrity, *storage->sdkFileName,
- androidConfig().getSdkToolsSha256());
- async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
+ AndroidConfig::getSdkToolsSha256());
storage->progressDialog->setRange(0, 0);
storage->progressDialog->setLabelText(Tr::tr("Verifying package integrity..."));
return SetupResult::Continue;
@@ -201,7 +197,7 @@ GroupItem downloadSdkRecipe()
logError(Tr::tr("Unarchiving error."));
return;
}
- androidConfig().setTemporarySdkToolsPath(
+ AndroidConfig::setTemporarySdkToolsPath(
storage->sdkFileName->parentDir().pathAppended(Constants::cmdlineToolsName));
};
const auto onCancelSetup = [storage] { return std::make_pair(storage->progressDialog.get(),
diff --git a/src/plugins/android/androidsdkmanager.cpp b/src/plugins/android/androidsdkmanager.cpp
index d62cc9e50e..76b4b5db72 100644
--- a/src/plugins/android/androidsdkmanager.cpp
+++ b/src/plugins/android/androidsdkmanager.cpp
@@ -113,9 +113,9 @@ private:
OutputFormatter *m_formatter = nullptr;
};
-static QString sdkRootArg(const AndroidConfig &config)
+static QString sdkRootArg()
{
- return "--sdk_root=" + config.sdkLocation().toString();
+ return "--sdk_root=" + AndroidConfig::sdkLocation().toString();
}
const QRegularExpression &assertionRegExp()
@@ -172,9 +172,9 @@ static GroupItem licensesRecipe(const Storage<DialogStorage> &dialogStorage)
"respective licenses are not accepted.") + "\n\n",
LogMessageFormat);
process.setProcessMode(ProcessMode::Writer);
- process.setEnvironment(androidConfig().toolsEnvironment());
- process.setCommand(CommandLine(androidConfig().sdkManagerToolPath(),
- {"--licenses", sdkRootArg(androidConfig())}));
+ process.setEnvironment(AndroidConfig::toolsEnvironment());
+ process.setCommand(CommandLine(AndroidConfig::sdkManagerToolPath(),
+ {"--licenses", sdkRootArg()}));
process.setUseCtrlCStub(true);
Process *processPtr = &process;
@@ -227,9 +227,9 @@ static GroupItem licensesRecipe(const Storage<DialogStorage> &dialogStorage)
static void setupSdkProcess(const QStringList &args, Process *process,
QuestionProgressDialog *dialog, int current, int total)
{
- process->setEnvironment(androidConfig().toolsEnvironment());
- process->setCommand({androidConfig().sdkManagerToolPath(),
- args + androidConfig().sdkManagerToolArgs()});
+ process->setEnvironment(AndroidConfig::toolsEnvironment());
+ process->setCommand({AndroidConfig::sdkManagerToolPath(),
+ args + AndroidConfig::sdkManagerToolArgs()});
QObject::connect(process, &Process::readyReadStandardOutput, dialog,
[process, dialog, current, total] {
QTextCodec *codec = QTextCodec::codecForLocale();
@@ -269,7 +269,7 @@ static GroupItem installationRecipe(const Storage<DialogStorage> &dialogStorage,
const int total = change.count();
const LoopList uninstallIterator(change.toUninstall);
const auto onUninstallSetup = [dialogStorage, uninstallIterator, total](Process &process) {
- const QStringList args = {"--uninstall", *uninstallIterator, sdkRootArg(androidConfig())};
+ const QStringList args = {"--uninstall", *uninstallIterator, sdkRootArg()};
QuestionProgressDialog *dialog = dialogStorage->m_dialog.get();
setupSdkProcess(args, &process, dialog, uninstallIterator.iteration(), total);
dialog->appendMessage(Tr::tr("Uninstalling %1...").arg(*uninstallIterator) + '\n',
@@ -280,7 +280,7 @@ static GroupItem installationRecipe(const Storage<DialogStorage> &dialogStorage,
const LoopList installIterator(change.toInstall);
const int offset = change.toUninstall.count();
const auto onInstallSetup = [dialogStorage, installIterator, offset, total](Process &process) {
- const QStringList args = {*installIterator, sdkRootArg(androidConfig())};
+ const QStringList args = {*installIterator, sdkRootArg()};
QuestionProgressDialog *dialog = dialogStorage->m_dialog.get();
setupSdkProcess(args, &process, dialog, offset + installIterator.iteration(), total);
dialog->appendMessage(Tr::tr("Installing %1...").arg(*installIterator) + '\n',
@@ -310,7 +310,7 @@ static GroupItem installationRecipe(const Storage<DialogStorage> &dialogStorage,
static GroupItem updateRecipe(const Storage<DialogStorage> &dialogStorage)
{
const auto onUpdateSetup = [dialogStorage](Process &process) {
- const QStringList args = {"--update", sdkRootArg(androidConfig())};
+ const QStringList args = {"--update", sdkRootArg()};
QuestionProgressDialog *dialog = dialogStorage->m_dialog.get();
setupSdkProcess(args, &process, dialog, 0, 1);
dialog->appendMessage(Tr::tr("Updating installed packages....") + '\n', NormalMessageFormat);
@@ -369,7 +369,7 @@ const AndroidSdkPackageList &AndroidSdkManager::allSdkPackages()
QStringList AndroidSdkManager::notFoundEssentialSdkPackages()
{
- QStringList essentials = androidConfig().allEssentials();
+ QStringList essentials = AndroidConfig::allEssentials();
const AndroidSdkPackageList &packages = allSdkPackages();
for (AndroidSdkPackage *package : packages) {
essentials.removeOne(package->sdkStylePath());
@@ -381,7 +381,7 @@ QStringList AndroidSdkManager::notFoundEssentialSdkPackages()
QStringList AndroidSdkManager::missingEssentialSdkPackages()
{
- const QStringList essentials = androidConfig().allEssentials();
+ const QStringList essentials = AndroidConfig::allEssentials();
const AndroidSdkPackageList &packages = allSdkPackages();
QStringList missingPackages;
for (AndroidSdkPackage *package : packages) {
@@ -461,7 +461,7 @@ BuildToolsList AndroidSdkManager::filteredBuildTools(int minApiLevel,
void AndroidSdkManager::refreshPackages()
{
- if (androidConfig().sdkManagerToolPath() != m_d->lastSdkManagerPath)
+ if (AndroidConfig::sdkManagerToolPath() != m_d->lastSdkManagerPath)
reloadPackages();
}
@@ -479,14 +479,14 @@ bool AndroidSdkManager::packageListingSuccessful() const
Runs the \c sdkmanger tool with arguments \a args. Returns \c true if the command is
successfully executed. Output is copied into \a output. The function blocks the calling thread.
*/
-static bool sdkManagerCommand(const AndroidConfig &config, const QStringList &args, QString *output)
+static bool sdkManagerCommand(const QStringList &args, QString *output)
{
QStringList newArgs = args;
- newArgs.append(sdkRootArg(config));
+ newArgs.append(sdkRootArg());
Process proc;
- proc.setEnvironment(config.toolsEnvironment());
+ proc.setEnvironment(AndroidConfig::toolsEnvironment());
proc.setTimeOutMessageBoxEnabled(true);
- proc.setCommand({config.sdkManagerToolPath(), newArgs});
+ proc.setCommand({AndroidConfig::sdkManagerToolPath(), newArgs});
qCDebug(sdkManagerLog).noquote() << "Running SDK Manager command (sync):"
<< proc.commandLine().toUserOutput();
proc.runBlocking(60s, EventLoopMode::On);
@@ -516,10 +516,10 @@ void AndroidSdkManagerPrivate::reloadSdkPackages()
qDeleteAll(m_allPackages);
m_allPackages.clear();
- lastSdkManagerPath = androidConfig().sdkManagerToolPath();
+ lastSdkManagerPath = AndroidConfig::sdkManagerToolPath();
m_packageListingSuccessful = false;
- if (androidConfig().sdkToolsVersion().isNull()) {
+ if (AndroidConfig::sdkToolsVersion().isNull()) {
// Configuration has invalid sdk path or corrupt installation.
emit m_sdkManager.packageReloadFinished();
return;
@@ -527,8 +527,8 @@ void AndroidSdkManagerPrivate::reloadSdkPackages()
QString packageListing;
QStringList args({"--list", "--verbose"});
- args << androidConfig().sdkManagerToolArgs();
- m_packageListingSuccessful = sdkManagerCommand(androidConfig(), args, &packageListing);
+ args << AndroidConfig::sdkManagerToolArgs();
+ m_packageListingSuccessful = sdkManagerCommand(args, &packageListing);
if (m_packageListingSuccessful) {
SdkManagerOutputParser parser(m_allPackages);
parser.parsePackageListing(packageListing);
diff --git a/src/plugins/android/androidsdkmanagerdialog.cpp b/src/plugins/android/androidsdkmanagerdialog.cpp
index a2297bdac3..8ae1cfd77b 100644
--- a/src/plugins/android/androidsdkmanagerdialog.cpp
+++ b/src/plugins/android/androidsdkmanagerdialog.cpp
@@ -40,9 +40,9 @@ public:
m_argumentDetailsEdit = new QPlainTextEdit;
m_argumentDetailsEdit->setReadOnly(true);
- m_process.setEnvironment(androidConfig().toolsEnvironment());
- m_process.setCommand({androidConfig().sdkManagerToolPath(),
- {"--help", "--sdk_root=" + androidConfig().sdkLocation().toString()}});
+ m_process.setEnvironment(AndroidConfig::toolsEnvironment());
+ m_process.setCommand({AndroidConfig::sdkManagerToolPath(),
+ {"--help", "--sdk_root=" + AndroidConfig::sdkLocation().toString()}});
connect(&m_process, &Process::done, this, [this] {
const QString output = m_process.allOutput();
QString argumentDetails;
@@ -64,7 +64,7 @@ public:
connect(dialogButtons, &QDialogButtonBox::rejected, this, &OptionsDialog::reject);
m_argumentsEdit = new QLineEdit;
- m_argumentsEdit->setText(androidConfig().sdkManagerToolArgs().join(" "));
+ m_argumentsEdit->setText(AndroidConfig::sdkManagerToolArgs().join(" "));
using namespace Layouting;
@@ -235,8 +235,8 @@ AndroidSdkManagerDialog::AndroidSdkManagerDialog(AndroidSdkManager *sdkManager,
OptionsDialog dlg(m_sdkManager, this);
if (dlg.exec() == QDialog::Accepted) {
QStringList arguments = dlg.sdkManagerArguments();
- if (arguments != androidConfig().sdkManagerToolArgs()) {
- androidConfig().setSdkManagerToolArgs(arguments);
+ if (arguments != AndroidConfig::sdkManagerToolArgs()) {
+ AndroidConfig::setSdkManagerToolArgs(arguments);
m_sdkManager->reloadPackages();
}
}
@@ -244,19 +244,19 @@ AndroidSdkManagerDialog::AndroidSdkManagerDialog(AndroidSdkManager *sdkManager,
connect(obsoleteCheckBox, &QCheckBox::stateChanged, this, [this](int state) {
const QString obsoleteArg = "--include_obsolete";
- QStringList args = androidConfig().sdkManagerToolArgs();
+ QStringList args = AndroidConfig::sdkManagerToolArgs();
if (state == Qt::Checked && !args.contains(obsoleteArg)) {
args.append(obsoleteArg);
- androidConfig().setSdkManagerToolArgs(args);
+ AndroidConfig::setSdkManagerToolArgs(args);
} else if (state == Qt::Unchecked && args.contains(obsoleteArg)) {
args.removeAll(obsoleteArg);
- androidConfig().setSdkManagerToolArgs(args);
+ AndroidConfig::setSdkManagerToolArgs(args);
}
m_sdkManager->reloadPackages();
});
connect(channelCheckbox, &QComboBox::currentIndexChanged, this, [this](int index) {
- QStringList args = androidConfig().sdkManagerToolArgs();
+ QStringList args = AndroidConfig::sdkManagerToolArgs();
QString existingArg;
for (int i = 0; i < 4; ++i) {
const QString arg = "--channel=" + QString::number(i);
@@ -268,17 +268,17 @@ AndroidSdkManagerDialog::AndroidSdkManagerDialog(AndroidSdkManager *sdkManager,
if (index == 0 && !existingArg.isEmpty()) {
args.removeAll(existingArg);
- androidConfig().setSdkManagerToolArgs(args);
+ AndroidConfig::setSdkManagerToolArgs(args);
} else if (index > 0) {
// Add 1 to account for Stable (second item) being channel 0
const QString channelArg = "--channel=" + QString::number(index - 1);
if (existingArg != channelArg) {
if (!existingArg.isEmpty()) {
args.removeAll(existingArg);
- androidConfig().setSdkManagerToolArgs(args);
+ AndroidConfig::setSdkManagerToolArgs(args);
}
args.append(channelArg);
- androidConfig().setSdkManagerToolArgs(args);
+ AndroidConfig::setSdkManagerToolArgs(args);
}
}
m_sdkManager->reloadPackages();
diff --git a/src/plugins/android/androidsettingswidget.cpp b/src/plugins/android/androidsettingswidget.cpp
index 785d0c5ac9..cdfdd5c029 100644
--- a/src/plugins/android/androidsettingswidget.cpp
+++ b/src/plugins/android/androidsettingswidget.cpp
@@ -314,7 +314,7 @@ AndroidSettingsWidget::AndroidSettingsWidget()
"and extracted to the selected path.\n"
"After the SDK Tools are properly set up, you are prompted to install any essential\n"
"packages required for Qt to build for Android.")
- .arg(androidConfig().sdkToolsUrl().toString()));
+ .arg(AndroidConfig::sdkToolsUrl().toString()));
auto sdkManagerToolButton = new QPushButton(Tr::tr("SDK Manager"));
@@ -380,22 +380,22 @@ AndroidSettingsWidget::AndroidSettingsWidget()
connect(m_openJdkLocationPathChooser, &PathChooser::rawPathChanged,
this, &AndroidSettingsWidget::validateJdk);
- if (androidConfig().openJDKLocation().isEmpty())
- androidConfig().setOpenJDKLocation(AndroidConfig::getJdkPath());
- m_openJdkLocationPathChooser->setFilePath(androidConfig().openJDKLocation());
+ if (AndroidConfig::openJDKLocation().isEmpty())
+ AndroidConfig::setOpenJDKLocation(AndroidConfig::getJdkPath());
+ m_openJdkLocationPathChooser->setFilePath(AndroidConfig::openJDKLocation());
m_openJdkLocationPathChooser->setPromptDialogTitle(Tr::tr("Select JDK Path"));
- if (androidConfig().sdkLocation().isEmpty())
- androidConfig().setSdkLocation(AndroidConfig::defaultSdkPath());
- m_sdkLocationPathChooser->setFilePath(androidConfig().sdkLocation());
+ if (AndroidConfig::sdkLocation().isEmpty())
+ AndroidConfig::setSdkLocation(AndroidConfig::defaultSdkPath());
+ m_sdkLocationPathChooser->setFilePath(AndroidConfig::sdkLocation());
m_sdkLocationPathChooser->setPromptDialogTitle(Tr::tr("Select Android SDK Folder"));
m_openSslPathChooser->setPromptDialogTitle(Tr::tr("Select OpenSSL Include Project File"));
- if (androidConfig().openSslLocation().isEmpty())
- androidConfig().setOpenSslLocation(androidConfig().sdkLocation() / ("android_openssl"));
- m_openSslPathChooser->setFilePath(androidConfig().openSslLocation());
+ if (AndroidConfig::openSslLocation().isEmpty())
+ AndroidConfig::setOpenSslLocation(AndroidConfig::sdkLocation() / ("android_openssl"));
+ m_openSslPathChooser->setFilePath(AndroidConfig::openSslLocation());
- m_createKitCheckBox->setChecked(androidConfig().automaticKitCreation());
+ m_createKitCheckBox->setChecked(AndroidConfig::automaticKitCreation());
downloadNdkToolButton->setIcon(downloadIcon);
@@ -435,7 +435,7 @@ AndroidSettingsWidget::AndroidSettingsWidget()
}
},
Group {
- title(Tr::tr("Android OpenSSL settings (Optional)")),
+ title(Tr::tr("Android OpenSSL Settings (Optional)")),
Grid {
Tr::tr("OpenSSL binaries location:"),
m_openSslPathChooser,
@@ -454,21 +454,21 @@ AndroidSettingsWidget::AndroidSettingsWidget()
connect(m_ndkListWidget, &QListWidget::currentTextChanged,
this, [this, removeCustomNdkButton](const QString &ndk) {
updateUI();
- removeCustomNdkButton->setEnabled(androidConfig().getCustomNdkList().contains(ndk));
+ removeCustomNdkButton->setEnabled(AndroidConfig::getCustomNdkList().contains(ndk));
});
connect(addCustomNdkButton, &QPushButton::clicked, this,
&AndroidSettingsWidget::addCustomNdkItem);
connect(removeCustomNdkButton, &QPushButton::clicked, this, [this] {
if (isDefaultNdkSelected())
- androidConfig().setDefaultNdk({});
- androidConfig().removeCustomNdk(m_ndkListWidget->currentItem()->text());
+ AndroidConfig::setDefaultNdk({});
+ AndroidConfig::removeCustomNdk(m_ndkListWidget->currentItem()->text());
m_ndkListWidget->takeItem(m_ndkListWidget->currentRow());
});
connect(m_makeDefaultNdkButton, &QPushButton::clicked, this, [this] {
const FilePath defaultNdk = isDefaultNdkSelected()
? FilePath()
: FilePath::fromUserInput(m_ndkListWidget->currentItem()->text());
- androidConfig().setDefaultNdk(defaultNdk);
+ AndroidConfig::setDefaultNdk(defaultNdk);
updateUI();
});
@@ -503,7 +503,7 @@ AndroidSettingsWidget::AndroidSettingsWidget()
if (result != Tasking::DoneWith::Success)
return;
// Make sure the sdk path is created before installing packages
- const FilePath sdkPath = androidConfig().sdkLocation();
+ const FilePath sdkPath = AndroidConfig::sdkLocation();
if (!sdkPath.createDir()) {
QMessageBox::warning(this, Android::Internal::dialogTitle(),
Tr::tr("Failed to create the SDK Tools path %1.")
@@ -518,7 +518,7 @@ AndroidSettingsWidget::AndroidSettingsWidget()
}, Qt::SingleShotConnection);
});
- setOnApply([] { AndroidConfigurations::setConfig(androidConfig()); });
+ setOnApply([] { AndroidConfigurations::applyConfig(); });
}
void AndroidSettingsWidget::showEvent(QShowEvent *event)
@@ -543,12 +543,12 @@ void AndroidSettingsWidget::updateNdkList()
ndk->installedLocation().toUserOutput()));
}
- const auto customNdks = androidConfig().getCustomNdkList();
+ const auto customNdks = AndroidConfig::getCustomNdkList();
for (const QString &ndk : customNdks) {
- if (androidConfig().isValidNdk(ndk)) {
+ if (AndroidConfig::isValidNdk(ndk)) {
m_ndkListWidget->addItem(new QListWidgetItem(Icons::UNLOCKED.icon(), ndk));
} else {
- androidConfig().removeCustomNdk(ndk);
+ AndroidConfig::removeCustomNdk(ndk);
}
}
@@ -563,8 +563,8 @@ void AndroidSettingsWidget::addCustomNdkItem()
.constFirst();
const QString ndkPath = QFileDialog::getExistingDirectory(this, Tr::tr("Select an NDK"), homePath);
- if (androidConfig().isValidNdk(ndkPath)) {
- androidConfig().addCustomNdk(ndkPath);
+ if (AndroidConfig::isValidNdk(ndkPath)) {
+ AndroidConfig::addCustomNdk(ndkPath);
if (m_ndkListWidget->findItems(ndkPath, Qt::MatchExactly).size() == 0) {
m_ndkListWidget->addItem(new QListWidgetItem(Icons::UNLOCKED.icon(), ndkPath));
}
@@ -582,9 +582,9 @@ void AndroidSettingsWidget::addCustomNdkItem()
bool AndroidSettingsWidget::isDefaultNdkSelected() const
{
- if (!androidConfig().defaultNdk().isEmpty()) {
+ if (!AndroidConfig::defaultNdk().isEmpty()) {
if (const QListWidgetItem *item = m_ndkListWidget->currentItem()) {
- return FilePath::fromUserInput(item->text()) == androidConfig().defaultNdk();
+ return FilePath::fromUserInput(item->text()) == AndroidConfig::defaultNdk();
}
}
return false;
@@ -592,8 +592,8 @@ bool AndroidSettingsWidget::isDefaultNdkSelected() const
void AndroidSettingsWidget::validateJdk()
{
- androidConfig().setOpenJDKLocation(m_openJdkLocationPathChooser->filePath());
- expected_str<void> test = testJavaC(androidConfig().openJDKLocation());
+ AndroidConfig::setOpenJDKLocation(m_openJdkLocationPathChooser->filePath());
+ expected_str<void> test = testJavaC(AndroidConfig::openJDKLocation());
m_androidSummary->setPointValid(JavaPathExistsAndWritableRow, test);
@@ -605,14 +605,14 @@ void AndroidSettingsWidget::validateJdk()
void AndroidSettingsWidget::validateOpenSsl()
{
- androidConfig().setOpenSslLocation(m_openSslPathChooser->filePath());
+ AndroidConfig::setOpenSslLocation(m_openSslPathChooser->filePath());
- m_openSslSummary->setPointValid(OpenSslPathExistsRow, androidConfig().openSslLocation().exists());
+ m_openSslSummary->setPointValid(OpenSslPathExistsRow, AndroidConfig::openSslLocation().exists());
- const bool priFileExists = androidConfig().openSslLocation().pathAppended("openssl.pri").exists();
+ const bool priFileExists = AndroidConfig::openSslLocation().pathAppended("openssl.pri").exists();
m_openSslSummary->setPointValid(OpenSslPriPathExists, priFileExists);
const bool cmakeListsExists
- = androidConfig().openSslLocation().pathAppended("CMakeLists.txt").exists();
+ = AndroidConfig::openSslLocation().pathAppended("CMakeLists.txt").exists();
m_openSslSummary->setPointValid(OpenSslCmakeListsPathExists, cmakeListsExists);
updateUI();
@@ -621,8 +621,8 @@ void AndroidSettingsWidget::validateOpenSsl()
void AndroidSettingsWidget::onSdkPathChanged()
{
const FilePath sdkPath = m_sdkLocationPathChooser->filePath().cleanPath();
- androidConfig().setSdkLocation(sdkPath);
- FilePath currentOpenSslPath = androidConfig().openSslLocation();
+ AndroidConfig::setSdkLocation(sdkPath);
+ FilePath currentOpenSslPath = AndroidConfig::openSslLocation();
if (currentOpenSslPath.isEmpty() || !currentOpenSslPath.exists())
currentOpenSslPath = sdkPath.pathAppended("android_openssl");
m_openSslPathChooser->setFilePath(currentOpenSslPath);
@@ -633,24 +633,24 @@ void AndroidSettingsWidget::onSdkPathChanged()
void AndroidSettingsWidget::validateSdk()
{
const FilePath sdkPath = m_sdkLocationPathChooser->filePath().cleanPath();
- androidConfig().setSdkLocation(sdkPath);
+ AndroidConfig::setSdkLocation(sdkPath);
- const FilePath path = androidConfig().sdkLocation();
+ const FilePath path = AndroidConfig::sdkLocation();
m_androidSummary->setPointValid(SdkPathExistsAndWritableRow,
path.exists() && path.isWritableDir());
m_androidSummary->setPointValid(SdkToolsInstalledRow,
- !androidConfig().sdkToolsVersion().isNull());
+ !AndroidConfig::sdkToolsVersion().isNull());
m_androidSummary->setPointValid(PlatformToolsInstalledRow,
- androidConfig().adbToolPath().exists());
+ AndroidConfig::adbToolPath().exists());
m_androidSummary->setPointValid(BuildToolsInstalledRow,
- !androidConfig().buildToolsVersion().isNull());
+ !AndroidConfig::buildToolsVersion().isNull());
m_androidSummary->setPointValid(SdkManagerSuccessfulRow, m_sdkManager.packageListingSuccessful());
// installedSdkPlatforms should not trigger a package reload as validate SDK is only called
// after AndroidSdkManager::packageReloadFinished.
m_androidSummary->setPointValid(PlatformSdkInstalledRow,
!m_sdkManager.installedSdkPlatforms().isEmpty());
m_androidSummary->setPointValid(AllEssentialsInstalledRow,
- androidConfig().allEssentialsInstalled(&m_sdkManager));
+ AndroidConfig::allEssentialsInstalled(&m_sdkManager));
const bool sdkToolsOk = m_androidSummary->rowsOk({SdkPathExistsAndWritableRow,
SdkToolsInstalledRow,
@@ -659,7 +659,7 @@ void AndroidSettingsWidget::validateSdk()
BuildToolsInstalledRow,
PlatformSdkInstalledRow,
AllEssentialsInstalledRow});
- androidConfig().setSdkFullyConfigured(sdkToolsOk && componentsOk);
+ AndroidConfig::setSdkFullyConfigured(sdkToolsOk && componentsOk);
if (sdkToolsOk && !componentsOk) {
const QStringList notFoundEssentials = m_sdkManager.notFoundEssentialSdkPackages();
if (!notFoundEssentials.isEmpty()) {
@@ -733,7 +733,8 @@ void AndroidSettingsWidget::downloadOpenSslRepo(const bool silent)
const QString openSslRepo("https://github.com/KDAB/android_openssl.git");
Process *gitCloner = new Process(this);
- CommandLine gitCloneCommand("git", {"clone", "--depth=1", openSslRepo, openSslPath.toString()});
+ const CommandLine gitCloneCommand("git", {"clone", "--depth=1", openSslRepo,
+ openSslPath.toString()});
gitCloner->setCommand(gitCloneCommand);
qCDebug(androidsettingswidget) << "Cloning OpenSSL repo: " << gitCloneCommand.toUserOutput();
@@ -782,7 +783,7 @@ void AndroidSettingsWidget::downloadOpenSslRepo(const bool silent)
void AndroidSettingsWidget::createKitToggled()
{
- androidConfig().setAutomaticKitCreation(m_createKitCheckBox->isChecked());
+ AndroidConfig::setAutomaticKitCreation(m_createKitCheckBox->isChecked());
}
void AndroidSettingsWidget::updateUI()
@@ -793,8 +794,8 @@ void AndroidSettingsWidget::updateUI()
const QListWidgetItem *currentItem = m_ndkListWidget->currentItem();
const FilePath currentNdk = FilePath::fromUserInput(currentItem ? currentItem->text() : "");
const QString infoText = Tr::tr("(SDK Version: %1, NDK Version: %2)")
- .arg(androidConfig().sdkToolsVersion().toString())
- .arg(currentNdk.isEmpty() ? "" : androidConfig().ndkVersion(currentNdk).toString());
+ .arg(AndroidConfig::sdkToolsVersion().toString())
+ .arg(currentNdk.isEmpty() ? "" : AndroidConfig::ndkVersion(currentNdk).toString());
m_androidSummary->setInfoText(androidSetupOk ? infoText : "");
m_androidSummary->setSetupOk(androidSetupOk);
@@ -808,7 +809,7 @@ void AndroidSettingsWidget::updateUI()
for (int row = 0; row < m_ndkListWidget->count(); ++row) {
QListWidgetItem *item = m_ndkListWidget->item(row);
const bool isDefaultNdk =
- FilePath::fromUserInput(item->text()) == androidConfig().defaultNdk();
+ FilePath::fromUserInput(item->text()) == AndroidConfig::defaultNdk();
item->setFont(isDefaultNdk ? markedFont : font);
}
}
@@ -820,7 +821,7 @@ void AndroidSettingsWidget::updateUI()
void AndroidSettingsWidget::downloadSdk()
{
- if (androidConfig().sdkToolsOk()) {
+ if (AndroidConfig::sdkToolsOk()) {
QMessageBox::warning(this, Android::Internal::dialogTitle(),
Tr::tr("The selected path already has a valid SDK Tools package."));
validateSdk();
diff --git a/src/plugins/android/androidsignaloperation.cpp b/src/plugins/android/androidsignaloperation.cpp
index 5016b2573e..9e92e860b4 100644
--- a/src/plugins/android/androidsignaloperation.cpp
+++ b/src/plugins/android/androidsignaloperation.cpp
@@ -13,7 +13,7 @@ namespace Android {
namespace Internal {
AndroidSignalOperation::AndroidSignalOperation()
- : m_adbPath(androidConfig().adbToolPath())
+ : m_adbPath(AndroidConfig::adbToolPath())
, m_timeout(new QTimer(this))
{
m_timeout->setInterval(5000);
diff --git a/src/plugins/android/androidtoolchain.cpp b/src/plugins/android/androidtoolchain.cpp
index a83a99a4d8..e430249bdb 100644
--- a/src/plugins/android/androidtoolchain.cpp
+++ b/src/plugins/android/androidtoolchain.cpp
@@ -77,7 +77,7 @@ bool AndroidToolchain::isValid() const
}
const bool isChildofNdk = compilerCommand().isChildOf(m_ndkLocation);
- const bool isChildofSdk = compilerCommand().isChildOf(androidConfig().sdkLocation());
+ const bool isChildofSdk = compilerCommand().isChildOf(AndroidConfig::sdkLocation());
return GccToolchain::isValid() && typeId() == Constants::ANDROID_TOOLCHAIN_TYPEID
&& targetAbi().isValid() && (isChildofNdk || isChildofSdk)
@@ -86,9 +86,8 @@ bool AndroidToolchain::isValid() const
void AndroidToolchain::addToEnvironment(Environment &env) const
{
- const AndroidConfig &config = androidConfig();
- env.set(QLatin1String("ANDROID_NDK_HOST"), config.toolchainHostFromNdk(m_ndkLocation));
- const FilePath javaHome = config.openJDKLocation();
+ env.set(QLatin1String("ANDROID_NDK_HOST"), AndroidConfig::toolchainHostFromNdk(m_ndkLocation));
+ const FilePath javaHome = AndroidConfig::openJDKLocation();
if (javaHome.exists()) {
env.set(Constants::JAVA_HOME_ENV_VAR, javaHome.toUserOutput());
const FilePath javaBin = javaHome.pathAppended("bin");
@@ -97,8 +96,8 @@ void AndroidToolchain::addToEnvironment(Environment &env) const
if (!currentJavaFilePath.isChildOf(javaBin))
env.prependOrSetPath(javaBin);
}
- env.set(QLatin1String("ANDROID_HOME"), config.sdkLocation().toUserOutput());
- env.set(QLatin1String("ANDROID_SDK_ROOT"), config.sdkLocation().toUserOutput());
+ env.set(QLatin1String("ANDROID_HOME"), AndroidConfig::sdkLocation().toUserOutput());
+ env.set(QLatin1String("ANDROID_SDK_ROOT"), AndroidConfig::sdkLocation().toUserOutput());
}
void AndroidToolchain::fromMap(const Store &data)
@@ -118,7 +117,7 @@ QStringList AndroidToolchain::suggestedMkspecList() const
FilePath AndroidToolchain::makeCommand(const Environment &env) const
{
Q_UNUSED(env)
- FilePath makePath = androidConfig().makePathFromNdk(m_ndkLocation);
+ FilePath makePath = AndroidConfig::makePathFromNdk(m_ndkLocation);
return makePath.exists() ? makePath : FilePath("make");
}
@@ -147,7 +146,7 @@ static FilePaths uniqueNdksForCurrentQtVersions()
FilePaths uniqueNdks;
for (const QtSupport::QtVersion *version : androidQtVersions) {
- FilePath ndk = androidConfig().ndkLocation(version);
+ FilePath ndk = AndroidConfig::ndkLocation(version);
if (!uniqueNdks.contains(ndk))
uniqueNdks.append(ndk);
}
@@ -161,15 +160,13 @@ ToolchainList autodetectToolchainsFromNdks(
const bool isCustom)
{
QList<Toolchain *> result;
- const AndroidConfig config = androidConfig();
-
const Id LanguageIds[] {
ProjectExplorer::Constants::CXX_LANGUAGE_ID,
ProjectExplorer::Constants::C_LANGUAGE_ID
};
for (const FilePath &ndkLocation : ndkLocations) {
- FilePath clangPath = config.clangPathFromNdk(ndkLocation);
+ const FilePath clangPath = AndroidConfig::clangPathFromNdk(ndkLocation);
if (!clangPath.exists()) {
qCDebug(androidTCLog) << "Clang toolchains detection fails. Can not find Clang"
<< clangPath;
@@ -193,11 +190,11 @@ ToolchainList autodetectToolchainsFromNdks(
const QString target = targetItr.key();
Toolchain *tc = findToolchain(compilerCommand, lang, target, alreadyKnown);
- QLatin1String customStr = isCustom ? QLatin1String("Custom ") : QLatin1String();
+ const QString customStr = isCustom ? "Custom " : QString();
const QString displayName(customStr + QString("Android Clang (%1, %2, NDK %3)")
- .arg(ToolchainManager::displayNameOfLanguageId(lang),
- AndroidConfig::displayName(abi),
- config.ndkVersion(ndkLocation).toString()));
+ .arg(ToolchainManager::displayNameOfLanguageId(lang),
+ AndroidConfig::displayName(abi),
+ AndroidConfig::ndkVersion(ndkLocation).toString()));
if (tc) {
// make sure to update the toolchain with current name format
if (tc->displayName() != displayName)
diff --git a/src/plugins/android/avddialog.cpp b/src/plugins/android/avddialog.cpp
index 81a5442716..4ae894a192 100644
--- a/src/plugins/android/avddialog.cpp
+++ b/src/plugins/android/avddialog.cpp
@@ -3,31 +3,34 @@
#include "avddialog.h"
#include "androidtr.h"
-#include "androidavdmanager.h"
-#include "androidconstants.h"
+#include "androidconfigurations.h"
#include "androiddevice.h"
#include "androidsdkmanager.h"
+#include <coreplugin/icore.h>
+
#include <projectexplorer/projectexplorerconstants.h>
#include <utils/algorithm.h>
#include <utils/infolabel.h>
#include <utils/layoutbuilder.h>
#include <utils/qtcassert.h>
+#include <utils/qtcprocess.h>
#include <utils/tooltip/tooltip.h>
#include <utils/utilsicons.h>
#include <QCheckBox>
#include <QComboBox>
#include <QDialogButtonBox>
-#include <QFutureWatcher>
#include <QKeyEvent>
#include <QLineEdit>
#include <QLoggingCategory>
+#include <QMessageBox>
#include <QPushButton>
#include <QSpinBox>
#include <QToolTip>
+using namespace ProjectExplorer;
using namespace Utils;
namespace Android::Internal {
@@ -35,8 +38,8 @@ namespace Android::Internal {
static Q_LOGGING_CATEGORY(avdDialogLog, "qtc.android.avdDialog", QtWarningMsg)
AvdDialog::AvdDialog(QWidget *parent)
- : QDialog(parent),
- m_allowedNameChars(QLatin1String("[a-z|A-Z|0-9|._-]*"))
+ : QDialog(parent)
+ , m_allowedNameChars(QLatin1String("[a-z|A-Z|0-9|._-]*"))
{
resize(800, 0);
setWindowTitle(Tr::tr("Create new AVD"));
@@ -106,6 +109,7 @@ AvdDialog::AvdDialog(QWidget *parent)
m_deviceTypeToStringMap.insert(AvdDialog::Automotive, "Automotive");
m_deviceTypeToStringMap.insert(AvdDialog::TV, "TV");
m_deviceTypeToStringMap.insert(AvdDialog::Wear, "Wear");
+ m_deviceTypeToStringMap.insert(AvdDialog::Desktop, "Desktop");
parseDeviceDefinitionsList();
for (const QString &type : m_deviceTypeToStringMap)
@@ -118,32 +122,25 @@ int AvdDialog::exec()
{
const int execResult = QDialog::exec();
if (execResult == QDialog::Accepted) {
- CreateAvdInfo result;
- result.systemImage = systemImage();
- result.name = name();
- result.abi = abi();
- result.deviceDefinition = deviceDefinition();
- result.sdcardSize = sdcardSize();
- result.overwrite = m_overwriteCheckBox->isChecked();
-
- const AndroidAvdManager avdManager;
- QFutureWatcher<CreateAvdInfo> createAvdFutureWatcher;
-
- QEventLoop loop;
- QObject::connect(&createAvdFutureWatcher, &QFutureWatcher<CreateAvdInfo>::finished,
- &loop, &QEventLoop::quit);
- QObject::connect(&createAvdFutureWatcher, &QFutureWatcher<CreateAvdInfo>::canceled,
- &loop, &QEventLoop::quit);
- createAvdFutureWatcher.setFuture(avdManager.createAvd(result));
- loop.exec(QEventLoop::ExcludeUserInputEvents);
-
- const QFuture<CreateAvdInfo> future = createAvdFutureWatcher.future();
- if (future.isResultReadyAt(0)) {
- m_createdAvdInfo = future.result();
- AndroidDeviceManager::instance()->updateAvdsList();
+ const SystemImage *si = systemImage();
+ if (!si || !si->isValid() || name().isEmpty()) {
+ QMessageBox::warning(Core::ICore::dialogParent(),
+ Tr::tr("Create new AVD"), Tr::tr("Cannot create AVD. Invalid input."));
+ return QDialog::Rejected;
}
- }
+ const CreateAvdInfo avdInfo{si->sdkStylePath(), si->apiLevel(), name(), abi(),
+ deviceDefinition(), sdcardSize()};
+ const auto result = AndroidDeviceManager::createAvd(avdInfo,
+ m_overwriteCheckBox->isChecked());
+ if (!result) {
+ QMessageBox::warning(Core::ICore::dialogParent(), Tr::tr("Create new AVD"),
+ result.error());
+ return QDialog::Rejected;
+ }
+ m_createdAvdInfo = avdInfo;
+ AndroidDeviceManager::updateAvdList();
+ }
return execResult;
}
@@ -152,23 +149,9 @@ bool AvdDialog::isValid() const
return !name().isEmpty() && systemImage() && systemImage()->isValid() && !abi().isEmpty();
}
-ProjectExplorer::IDevice::Ptr AvdDialog::device() const
+CreateAvdInfo AvdDialog::avdInfo() const
{
- if (!m_createdAvdInfo.systemImage) {
- qCWarning(avdDialogLog) << "System image of the created AVD is nullptr";
- return IDevice::Ptr();
- }
- AndroidDevice *dev = new AndroidDevice();
- const Utils::Id deviceId = AndroidDevice::idFromAvdInfo(m_createdAvdInfo);
- using namespace ProjectExplorer;
- dev->setupId(IDevice::AutoDetected, deviceId);
- dev->setMachineType(IDevice::Emulator);
- dev->settings()->displayName.setValue(m_createdAvdInfo.name);
- dev->setDeviceState(IDevice::DeviceConnected);
- dev->setExtraData(Constants::AndroidAvdName, m_createdAvdInfo.name);
- dev->setExtraData(Constants::AndroidCpuAbi, {m_createdAvdInfo.abi});
- dev->setExtraData(Constants::AndroidSdk, m_createdAvdInfo.systemImage->apiLevel());
- return IDevice::Ptr(dev);
+ return m_createdAvdInfo;
}
AvdDialog::DeviceType AvdDialog::tagToDeviceType(const QString &type_tag)
@@ -179,20 +162,60 @@ AvdDialog::DeviceType AvdDialog::tagToDeviceType(const QString &type_tag)
return AvdDialog::TV;
else if (type_tag.contains("android-automotive"))
return AvdDialog::Automotive;
- else
- return AvdDialog::PhoneOrTablet;
+ else if (type_tag.contains("android-desktop"))
+ return AvdDialog::Desktop;
+ return AvdDialog::PhoneOrTablet;
+}
+
+static bool avdManagerCommand(const QStringList &args, QString *output)
+{
+ CommandLine cmd(AndroidConfig::avdManagerToolPath(), args);
+ Process proc;
+ proc.setEnvironment(AndroidConfig::toolsEnvironment());
+ qCDebug(avdDialogLog).noquote() << "Running AVD Manager command:" << cmd.toUserOutput();
+ proc.setCommand(cmd);
+ proc.runBlocking();
+ if (proc.result() == ProcessResult::FinishedWithSuccess) {
+ if (output)
+ *output = proc.allOutput();
+ return true;
+ }
+ return false;
}
void AvdDialog::parseDeviceDefinitionsList()
{
QString output;
- if (!AndroidAvdManager::avdManagerCommand({"list", "device"}, &output)) {
+ if (!avdManagerCommand({"list", "device"}, &output)) {
qCDebug(avdDialogLog) << "Avd list command failed" << output
- << androidConfig().sdkToolsVersion();
+ << AndroidConfig::sdkToolsVersion();
return;
}
+ /* Example output:
+Available devices definitions:
+id: 0 or "automotive_1024p_landscape"
+ Name: Automotive (1024p landscape)
+ OEM : Google
+ Tag : android-automotive-playstore
+---------
+id: 1 or "automotive_1080p_landscape"
+ Name: Automotive (1080p landscape)
+ OEM : Google
+ Tag : android-automotive
+---------
+id: 2 or "Galaxy Nexus"
+ Name: Galaxy Nexus
+ OEM : Google
+---------
+id: 3 or "desktop_large"
+ Name: Large Desktop
+ OEM : Google
+ Tag : android-desktop
+...
+ */
+
QStringList avdDeviceInfo;
const auto lines = output.split('\n');
diff --git a/src/plugins/android/avddialog.h b/src/plugins/android/avddialog.h
index 33247807d9..2e92da15a0 100644
--- a/src/plugins/android/avddialog.h
+++ b/src/plugins/android/avddialog.h
@@ -20,7 +20,6 @@ QT_END_NAMESPACE
namespace Utils { class InfoLabel; }
namespace Android {
-class AndroidConfig;
class SdkPlatform;
namespace Internal {
@@ -33,9 +32,9 @@ public:
explicit AvdDialog(QWidget *parent = nullptr);
int exec() override;
- enum DeviceType { Phone, Tablet, Automotive, TV, Wear, PhoneOrTablet };
+ enum DeviceType { Phone, Tablet, Automotive, TV, Wear, Desktop, PhoneOrTablet };
- ProjectExplorer::IDevice::Ptr device() const;
+ CreateAvdInfo avdInfo() const;
const SystemImage *systemImage() const;
QString name() const;
diff --git a/src/plugins/android/avdmanageroutputparser.cpp b/src/plugins/android/avdmanageroutputparser.cpp
index 2727680d08..7a91b99f19 100644
--- a/src/plugins/android/avdmanageroutputparser.cpp
+++ b/src/plugins/android/avdmanageroutputparser.cpp
@@ -86,10 +86,10 @@ static std::optional<AndroidDeviceInfo> parseAvd(const QStringList &deviceInfo)
return {};
}
-AndroidDeviceInfoList parseAvdList(const QString &output, Utils::FilePaths *avdErrorPaths)
+ParsedAvdList parseAvdList(const QString &output)
{
- QTC_CHECK(avdErrorPaths);
AndroidDeviceInfoList avdList;
+ Utils::FilePaths errorPaths;
QStringList avdInfo;
using ErrorPath = Utils::FilePath;
using AvdResult = std::variant<std::monostate, AndroidDeviceInfo, ErrorPath>;
@@ -120,14 +120,14 @@ AndroidDeviceInfoList parseAvdList(const QString &output, Utils::FilePaths *avdE
if (auto info = std::get_if<AndroidDeviceInfo>(&result))
avdList << *info;
else if (auto errorPath = std::get_if<ErrorPath>(&result))
- *avdErrorPaths << *errorPath;
+ errorPaths << *errorPath;
avdInfo.clear();
} else {
avdInfo << line;
}
}
- return Utils::sorted(std::move(avdList));
+ return {Utils::sorted(std::move(avdList)), errorPaths};
}
int platformNameToApiLevel(const QString &platformName)
diff --git a/src/plugins/android/avdmanageroutputparser.h b/src/plugins/android/avdmanageroutputparser.h
index b3a74f8fc8..81cbe64eaa 100644
--- a/src/plugins/android/avdmanageroutputparser.h
+++ b/src/plugins/android/avdmanageroutputparser.h
@@ -10,7 +10,13 @@ namespace Internal {
const char avdManufacturerError[] = "no longer exists as a device";
-AndroidDeviceInfoList parseAvdList(const QString &output, Utils::FilePaths *avdErrorPaths);
+struct ParsedAvdList
+{
+ AndroidDeviceInfoList avdList;
+ Utils::FilePaths errorPaths;
+};
+
+ParsedAvdList parseAvdList(const QString &output);
int platformNameToApiLevel(const QString &platformName);
QString convertNameToExtension(const QString &name);
diff --git a/src/plugins/android/javalanguageserver.cpp b/src/plugins/android/javalanguageserver.cpp
index 671786756e..90d536dcae 100644
--- a/src/plugins/android/javalanguageserver.cpp
+++ b/src/plugins/android/javalanguageserver.cpp
@@ -305,7 +305,7 @@ void JLSClient::updateProjectFiles()
const QStringList classPaths = node->data(Constants::AndroidClassPaths).toStringList();
- const FilePath &sdkLocation = androidConfig().sdkLocation();
+ const FilePath &sdkLocation = AndroidConfig::sdkLocation();
const QString &targetSDK = AndroidManager::buildTargetSDK(m_currentTarget);
const FilePath androidJar = sdkLocation / QString("platforms/%2/android.jar")
.arg(targetSDK);
diff --git a/src/plugins/android/javaparser.cpp b/src/plugins/android/javaparser.cpp
index 72859bfdab..65ddfba9ad 100644
--- a/src/plugins/android/javaparser.cpp
+++ b/src/plugins/android/javaparser.cpp
@@ -62,7 +62,7 @@ OutputLineParser::Result JavaParser::handleLine(const QString &line, OutputForma
absoluteFilePath(file),
lineno);
LinkSpecs linkSpecs;
- addLinkSpecForAbsoluteFilePath(linkSpecs, task.file, task.line, match, 2);
+ addLinkSpecForAbsoluteFilePath(linkSpecs, task.file, task.line, task.column, match, 2);
scheduleTask(task, 1);
return {Status::Done, linkSpecs};
}
diff --git a/src/plugins/luatemplates/LuaTemplates.json.in b/src/plugins/appstatisticsmonitor/AppStatisticsMonitor.json.in
index 400630aa93..920b3ec31b 100644
--- a/src/plugins/luatemplates/LuaTemplates.json.in
+++ b/src/plugins/appstatisticsmonitor/AppStatisticsMonitor.json.in
@@ -1,9 +1,8 @@
{
- "Name" : "LuaTemplates",
+ "Name" : "AppStatisticsMonitor",
"Version" : "${IDE_VERSION}",
- "DisabledByDefault" : true,
- "SoftLoadable" : true,
"CompatVersion" : "${IDE_VERSION_COMPAT}",
+ "Experimental" : true,
"Vendor" : "The Qt Company Ltd",
"Copyright" : "(C) ${IDE_COPYRIGHT_YEAR} The Qt Company Ltd",
"License" : [ "Commercial Usage",
@@ -14,8 +13,10 @@
"",
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
- "Category" : "Scripting",
- "Description" : "Allows writing template wizards using lua",
- "Url" : "http://www.qt.io",
+ "Description" : "Visualize CPU and Memory consumption for running applications",
+ "LongDescription" : [
+ "Select an application to monitor its performance in real-time."
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/appstatisticsmonitor/CMakeLists.txt b/src/plugins/appstatisticsmonitor/CMakeLists.txt
new file mode 100644
index 0000000000..292eb2a3eb
--- /dev/null
+++ b/src/plugins/appstatisticsmonitor/CMakeLists.txt
@@ -0,0 +1,14 @@
+find_package(Qt6 COMPONENTS Charts QUIET)
+
+add_qtc_plugin(AppStatisticsMonitor
+ CONDITION TARGET Qt6::Charts
+ DEPENDS Qt6::Charts
+ SKIP_TRANSLATION
+ PLUGIN_DEPENDS Core ProjectExplorer
+ SOURCES
+ appstatisticsmonitorplugin.cpp
+ chart.cpp chart.h
+ manager.cpp manager.h
+ idataprovider.h idataprovider.cpp
+)
+
diff --git a/src/plugins/appstatisticsmonitor/appstatisticsmonitor.qbs b/src/plugins/appstatisticsmonitor/appstatisticsmonitor.qbs
new file mode 100644
index 0000000000..177dd9d0a2
--- /dev/null
+++ b/src/plugins/appstatisticsmonitor/appstatisticsmonitor.qbs
@@ -0,0 +1,20 @@
+QtcPlugin {
+ name: "AppStatisticsMonitor"
+
+ Depends { name: "Core" }
+ Depends { name: "ProjectExplorer" }
+ Depends { name: "Qt.charts"; required: false }
+
+ condition: Qt.charts.present
+
+ files: [
+ "appstatisticsmonitorplugin.cpp",
+ "appstatisticsmonitortr.h",
+ "chart.h",
+ "chart.cpp",
+ "idataprovider.h",
+ "idataprovider.cpp",
+ "manager.h",
+ "manager.cpp",
+ ]
+}
diff --git a/src/plugins/appstatisticsmonitor/appstatisticsmonitorplugin.cpp b/src/plugins/appstatisticsmonitor/appstatisticsmonitorplugin.cpp
new file mode 100644
index 0000000000..e91822e110
--- /dev/null
+++ b/src/plugins/appstatisticsmonitor/appstatisticsmonitorplugin.cpp
@@ -0,0 +1,32 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#include "manager.h"
+
+#include <extensionsystem/iplugin.h>
+
+#include <QString>
+
+namespace AppStatisticsMonitor::Internal {
+
+class AppStatisticsMonitorPlugin final : public ExtensionSystem::IPlugin
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "AppStatisticsMonitor.json")
+
+private:
+ void initialize() final;
+ std::unique_ptr<AppStatisticsMonitorManager> m_appstatisticsmonitorManager;
+ std::unique_ptr<AppStatisticsMonitorViewFactory> m_appstatisticsmonitorViewFactory;
+};
+
+void AppStatisticsMonitorPlugin::initialize()
+{
+ m_appstatisticsmonitorManager = std::make_unique<AppStatisticsMonitorManager>();
+ m_appstatisticsmonitorViewFactory = std::make_unique<AppStatisticsMonitorViewFactory>(
+ m_appstatisticsmonitorManager.get());
+}
+
+} // namespace AppStatisticsMonitor::Internal
+
+#include "appstatisticsmonitorplugin.moc"
diff --git a/src/plugins/appstatisticsmonitor/appstatisticsmonitortr.h b/src/plugins/appstatisticsmonitor/appstatisticsmonitortr.h
new file mode 100644
index 0000000000..fad1692e07
--- /dev/null
+++ b/src/plugins/appstatisticsmonitor/appstatisticsmonitortr.h
@@ -0,0 +1,15 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <QCoreApplication>
+
+namespace AppStatisticsMonitor {
+
+struct Tr
+{
+ Q_DECLARE_TR_FUNCTIONS(AppStatisticsMonitor)
+};
+
+} // namespace AppStatisticsMonitor
diff --git a/src/plugins/appstatisticsmonitor/chart.cpp b/src/plugins/appstatisticsmonitor/chart.cpp
new file mode 100644
index 0000000000..260282b39a
--- /dev/null
+++ b/src/plugins/appstatisticsmonitor/chart.cpp
@@ -0,0 +1,266 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#include "chart.h"
+
+#include <utils/theme/theme.h>
+
+#include <QAbstractAxis>
+#include <QChartView>
+#include <QDebug>
+#include <QPaintEvent>
+#include <QPainter>
+#include <QPen>
+#include <QPointF>
+#include <QRandomGenerator>
+#include <QSplineSeries>
+#include <QString>
+#include <QValueAxis>
+
+using namespace Utils;
+
+namespace AppStatisticsMonitor::Internal {
+
+static const int padding = 40;
+static const int numPadding = 10;
+static const QRectF dataRangeDefault = QRectF(0, 0, 5, 1);
+
+AppStatisticsMonitorChart::AppStatisticsMonitorChart(
+ const QString &name, QGraphicsItem *parent, Qt::WindowFlags wFlags)
+ : QChart(QChart::ChartTypeCartesian, parent, wFlags)
+ , m_series(new QLineSeries(this))
+ , m_axisX(new QValueAxis())
+ , m_axisY(new QValueAxis())
+ , m_point(0, 0)
+ , m_chartView(new QChartView(this))
+ , m_name(name)
+{
+ m_chartView->setMinimumHeight(200);
+ m_chartView->setMinimumWidth(400);
+ const QBrush brushTitle(creatorColor(Theme::Token_Text_Muted));
+ const QBrush brush(creatorColor(Theme::Token_Background_Default));
+ const QPen penBack(creatorColor(Theme::Token_Text_Muted));
+ const QPen penAxis(creatorColor(Theme::Token_Text_Muted));
+
+ setTitleBrush(brushTitle);
+ setBackgroundBrush(brush);
+ setBackgroundPen(penBack);
+ m_axisX->setLinePen(penAxis);
+ m_axisY->setLinePen(penAxis);
+ m_axisX->setLabelsColor(creatorColor(Theme::Token_Text_Muted));
+ m_axisY->setLabelsColor(creatorColor(Theme::Token_Text_Muted));
+ QPen pen(creatorColor(Theme::Token_Accent_Default));
+ pen.setWidth(2);
+ m_series->setPen(pen);
+
+ setTitle(m_name + " " + QString::number(m_point.y(), 'g', 4) + "%");
+ m_chartView->setRenderHint(QPainter::Antialiasing);
+ m_chartView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+ addSeries(m_series);
+
+ addAxis(m_axisX, Qt::AlignBottom);
+ addAxis(m_axisY, Qt::AlignLeft);
+ m_series->attachAxis(m_axisX);
+ m_series->attachAxis(m_axisY);
+ m_axisX->applyNiceNumbers();
+ m_axisY->applyNiceNumbers();
+ legend()->hide();
+
+ clear();
+}
+
+QChartView *AppStatisticsMonitorChart::chartView()
+{
+ return m_chartView;
+}
+
+void AppStatisticsMonitorChart::addNewPoint(const QPointF &point)
+{
+ m_point = point;
+ if (m_axisY->max() < m_point.y())
+ m_axisY->setRange(0, qRound(m_point.y()) + 1);
+ m_axisX->setRange(0, qRound(m_point.x()) + 1);
+
+ setTitle(m_name + " " + QString::number(m_point.y(), 'g', 4) + "%");
+ m_series->append(m_point);
+}
+
+void AppStatisticsMonitorChart::loadNewProcessData(const QList<double> &data)
+{
+ clear();
+ QList<QPointF> points{{0, 0}};
+ int i = 0;
+ double max_y = 0;
+
+ for (double e : qAsConst(data)) {
+ points.push_back({double(++i), e});
+ max_y = qMax(max_y, e);
+ }
+
+ m_axisY->setRange(0, qRound(max_y) + 1);
+ m_axisX->setRange(0, data.size() + 1);
+
+ m_series->clear();
+ m_series->append(points);
+}
+
+void AppStatisticsMonitorChart::clear()
+{
+ m_axisX->setRange(0, 5);
+ m_axisY->setRange(0, 1);
+ m_series->clear();
+ m_series->append(0, 0);
+}
+
+double AppStatisticsMonitorChart::lastPointX() const
+{
+ return m_point.x();
+}
+
+//---------------------- Chart -----------------------
+
+Chart::Chart(const QString &name, QWidget *parent)
+ : QWidget(parent)
+ , m_name(name)
+{
+ setMinimumHeight(200);
+ setMinimumWidth(400);
+}
+
+void Chart::addNewPoint(const QPointF &point)
+{
+ m_points.append(point);
+ update();
+}
+
+void Chart::loadNewProcessData(const QList<double> &data)
+{
+ clear();
+ for (long i = 0; i < data.size(); ++i) {
+ m_points.append(QPointF(i + 1, data[i]));
+ }
+ update();
+}
+
+double Chart::lastPointX() const
+{
+ if (m_points.isEmpty())
+ return 0;
+ return m_points.last().x();
+}
+
+void Chart::clear()
+{
+ m_points.clear();
+ addNewPoint({0, 0});
+ update();
+}
+
+void Chart::paintEvent(QPaintEvent *event)
+{
+ Q_UNUSED(event);
+ QPainter painter(this);
+
+ painter.fillRect(rect(), creatorColor(Theme::Token_Background_Default));
+
+ // add the name of the chart in the middle of the widget width and on the top
+ painter.drawText(
+ rect(),
+ Qt::AlignHCenter | Qt::AlignTop,
+ m_name + QString::number(m_points.last().y(), 'g', 4) + "%");
+
+ const QRectF dataRange = calculateDataRange();
+ updateScalingFactors(dataRange);
+
+ for (double x = dataRange.left(); x <= dataRange.right(); x += m_xGridStep) {
+ double xPos = padding + (x - dataRange.left()) * m_xScale;
+ if (xPos < padding || xPos > width() - padding)
+ continue;
+ painter.setPen(creatorColor(Theme::Token_Foreground_Default));
+ painter.drawLine(xPos, padding, xPos, height() - padding);
+
+ painter.setPen(creatorColor(Theme::Token_Text_Muted));
+ painter.drawText(xPos, height() - numPadding, QString::number(x));
+ }
+
+ for (double y = dataRange.top(); y <= dataRange.bottom(); y += m_yGridStep) {
+ double yPos = height() - padding - (y - (int) dataRange.top()) * m_yScale;
+ if (yPos < padding || yPos > height() - padding)
+ continue;
+
+ painter.setPen(creatorColor(Theme::Token_Foreground_Default));
+ painter.drawLine(padding, yPos, width() - padding, yPos);
+
+ painter.setPen(creatorColor(Theme::Token_Text_Muted));
+ painter.drawText(numPadding, yPos, QString::number(y));
+ }
+
+ painter.setPen(creatorColor(Theme::Token_Foreground_Default));
+ painter.drawLine(padding, height() - padding, width() - padding, height() - padding); // X axis
+ painter.drawLine(padding, height() - padding, padding, padding); // Y axis
+
+ QPen pen(creatorColor(Theme::Token_Accent_Default));
+ pen.setWidth(2);
+ painter.setPen(pen);
+ painter.setRenderHint(QPainter::Antialiasing);
+ for (int i = 1; i < m_points.size(); ++i) {
+ QPointF startPoint(
+ padding + (m_points[i - 1].x() - dataRange.left()) * m_xScale,
+ height() - padding - (m_points[i - 1].y() - dataRange.top()) * m_yScale);
+ QPointF endPoint(
+ padding + (m_points[i].x() - dataRange.left()) * m_xScale,
+ height() - padding - (m_points[i].y() - dataRange.top()) * m_yScale);
+ painter.drawLine(startPoint, endPoint);
+ }
+}
+
+void Chart::addPoint()
+{
+ for (int i = 0; i < 10; ++i) {
+ double x = m_points.size();
+ double y = sin(x) + 10 * cos(x / 10) + 10;
+ m_points.append(QPointF(x, y));
+ }
+ update();
+}
+
+QRectF Chart::calculateDataRange() const
+{
+ QRectF dataRange = QRectF(0, 0, 0, 0);
+
+ if (m_points.isEmpty())
+ return dataRange;
+
+ for (const QPointF &point : m_points) {
+ dataRange.setLeft(qMin(dataRange.left(), point.x()));
+ dataRange.setRight(qMax(dataRange.right(), point.x()));
+
+ dataRange.setBottom(qMin(dataRange.bottom(), point.y()));
+ dataRange.setTop(qMax(dataRange.top(), point.y()));
+ }
+ dataRange.setRight(round(dataRange.right()) + 1);
+ dataRange.setTop(round(dataRange.top()) + 1);
+
+ dataRange = dataRange.united(dataRangeDefault);
+ return dataRange;
+}
+
+void Chart::updateScalingFactors(const QRectF &dataRange)
+{
+ const double xRange = dataRange.right() - dataRange.left();
+ double yRange = dataRange.bottom() - dataRange.top();
+ yRange = yRange == 0 ? dataRange.top() : yRange;
+
+ m_xGridStep = qRound(xRange / 10);
+ m_xGridStep = m_xGridStep == 0 ? 1 : m_xGridStep;
+
+ m_yGridStep = yRange / 5;
+ m_yGridStep = qRound(m_yGridStep * 10.0) / 10.0;
+ if (yRange > 10)
+ m_yGridStep = qRound(m_yGridStep);
+ m_yGridStep = qMax(m_yGridStep, 0.1);
+
+ m_xScale = (width() - 2 * padding) / xRange;
+ m_yScale = (height() - 2 * padding) / yRange;
+}
+} // namespace AppStatisticsMonitor::Internal
diff --git a/src/plugins/appstatisticsmonitor/chart.h b/src/plugins/appstatisticsmonitor/chart.h
new file mode 100644
index 0000000000..ed478061ce
--- /dev/null
+++ b/src/plugins/appstatisticsmonitor/chart.h
@@ -0,0 +1,70 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+#pragma once
+
+#include <QChart>
+#include <QList>
+#include <QPaintEvent>
+#include <QPointF>
+#include <QRectF>
+#include <QWidget>
+
+QT_BEGIN_NAMESPACE
+class QChartView;
+class QLineSeries;
+class QValueAxis;
+QT_END_NAMESPACE
+
+namespace AppStatisticsMonitor::Internal {
+
+class AppStatisticsMonitorChart : public QChart
+{
+public:
+ AppStatisticsMonitorChart(
+ const QString &name, QGraphicsItem *parent = nullptr, Qt::WindowFlags wFlags = {});
+
+ void addNewPoint(const QPointF &point);
+ void loadNewProcessData(const QList<double> &data);
+ double lastPointX() const;
+ void clear();
+ QChartView *chartView();
+
+private:
+ QLineSeries *m_series;
+ QStringList m_titles;
+ QValueAxis *m_axisX;
+ QValueAxis *m_axisY;
+ QPointF m_point;
+
+ QChartView *m_chartView;
+ QString m_name;
+};
+
+class Chart : public QWidget
+{
+public:
+ Chart(const QString &name, QWidget *parent = nullptr);
+
+ void addNewPoint(const QPointF &point);
+ void loadNewProcessData(const QList<double> &data);
+ double lastPointX() const;
+
+ void clear();
+
+private:
+ void paintEvent(QPaintEvent *event) override;
+ void addPoint();
+ QRectF calculateDataRange() const;
+ void updateScalingFactors(const QRectF &dataRange);
+
+private:
+ QList<QPointF> m_points;
+ QString m_name;
+
+ double m_xScale = 1;
+ double m_yScale = 1;
+ double m_xGridStep = 1;
+ double m_yGridStep = 1;
+};
+
+} // namespace AppStatisticsMonitor::Internal
diff --git a/src/plugins/appstatisticsmonitor/idataprovider.cpp b/src/plugins/appstatisticsmonitor/idataprovider.cpp
new file mode 100644
index 0000000000..b2026e67cd
--- /dev/null
+++ b/src/plugins/appstatisticsmonitor/idataprovider.cpp
@@ -0,0 +1,237 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#include "idataprovider.h"
+
+#include <utils/expected.h>
+#include <utils/fileutils.h>
+#include <utils/hostosinfo.h>
+
+#include <QByteArray>
+#include <QRegularExpression>
+#include <QString>
+
+#ifdef Q_OS_LINUX
+#include <unistd.h>
+#endif
+
+using namespace Utils;
+
+namespace AppStatisticsMonitor::Internal {
+
+IDataProvider::IDataProvider(qint64 pid, QObject *parent)
+ : QObject(parent)
+ , m_pid(pid)
+{
+ m_timer.setInterval(1000);
+ connect(&m_timer, &QTimer::timeout, this, [this] { handleTimeout(); });
+ m_timer.start();
+}
+
+void IDataProvider::handleTimeout()
+{
+ m_memoryConsumption.append(getMemoryConsumption());
+ m_cpuConsumption.append(getCpuConsumption());
+ emit newDataAvailable();
+}
+
+QList<double> IDataProvider::memoryConsumptionHistory() const
+{
+ return m_memoryConsumption;
+}
+
+QList<double> IDataProvider::cpuConsumptionHistory() const
+{
+ return m_cpuConsumption;
+}
+
+double IDataProvider::memoryConsumptionLast() const
+{
+ return m_memoryConsumption.isEmpty() ? 0 : m_memoryConsumption.last();
+}
+
+double IDataProvider::cpuConsumptionLast() const
+{
+ return m_cpuConsumption.isEmpty() ? 0 : m_cpuConsumption.last();
+}
+
+// ------------------------- LinuxDataProvider --------------------------------
+#ifdef Q_OS_LINUX
+class LinuxDataProvider : public IDataProvider
+{
+public:
+ LinuxDataProvider(qint64 pid, QObject *parent = nullptr)
+ : IDataProvider(pid, parent)
+ {}
+
+ double getMemoryConsumption()
+ {
+ const FilePath statusMemory = FilePath::fromString(
+ QStringLiteral("/proc/%1/status").arg(m_pid));
+ const expected_str<QByteArray> statusMemoryContent = statusMemory.fileContents();
+
+ if (!statusMemoryContent)
+ return 0;
+
+ int vmPeak = 0;
+ const static QRegularExpression numberRX(QLatin1String("[^0-9]+"));
+ for (const QByteArray &element : statusMemoryContent.value().split('\n')) {
+ if (element.startsWith("VmHWM")) {
+ const QString p = QString::fromUtf8(element);
+ vmPeak = p.split(numberRX, Qt::SkipEmptyParts)[0].toLong();
+ }
+ }
+
+ const FilePath meminfoFile("/proc/meminfo");
+ const expected_str<QByteArray> meminfoContent = meminfoFile.fileContents();
+ if (!meminfoContent)
+ return 0;
+
+ const auto meminfo = meminfoContent.value().split('\n');
+ if (meminfo.isEmpty())
+ return 0;
+
+ const auto parts = QString::fromUtf8(meminfo.front()).split(numberRX, Qt::SkipEmptyParts);
+ if (parts.isEmpty())
+ return 0;
+
+ return double(vmPeak) / parts[0].toDouble() * 100;
+ }
+
+ // Provides the CPU usage from the last request
+ double getCpuConsumption()
+ {
+ const FilePath status = FilePath::fromString(QStringLiteral("/proc/%1/stat").arg(m_pid));
+ const FilePath uptimeFile = FilePath::fromString(QStringLiteral("/proc/uptime"));
+ const double clkTck = static_cast<double>(sysconf(_SC_CLK_TCK));
+ const expected_str<QByteArray> statusFileContent = status.fileContents();
+ const expected_str<QByteArray> uptimeFileContent = uptimeFile.fileContents();
+
+ if (!statusFileContent.has_value() || !uptimeFileContent.has_value() || clkTck == 0)
+ return 0;
+
+ const QList<QByteArray> processStatus = statusFileContent.value().split(' ');
+ if (processStatus.isEmpty() || processStatus.size() < 22)
+ return 0;
+
+ const double uptime = uptimeFileContent.value().split(' ')[0].toDouble();
+
+ const double utime = processStatus[13].toDouble() / clkTck;
+ const double stime = processStatus[14].toDouble() / clkTck;
+ const double cutime = processStatus[15].toDouble() / clkTck;
+ const double cstime = processStatus[16].toDouble() / clkTck;
+ const double starttime = processStatus[21].toDouble() / clkTck;
+
+ // Calculate CPU usage for last request
+ const double currentTotalTime = utime + stime + cutime + cstime;
+
+ const double elapsed = uptime - starttime;
+ const double clicks = (currentTotalTime - m_lastTotalTime) * clkTck;
+ const double timeClicks = (elapsed - m_lastRequestStartTime) * clkTck;
+
+ m_lastTotalTime = currentTotalTime;
+ m_lastRequestStartTime = elapsed;
+
+ return timeClicks > 0 ? 100 * (clicks / timeClicks) : 0;
+ }
+
+ // Provides the usage all over the process lifetime
+ // Can be used in the future for the process lifetime statistics
+
+ // double LinuxDataProvider::getCpuConsumption()
+ // {
+ // const FilePath status = FilePath::fromString(
+ // QStringLiteral("/proc/%1/stat").arg(m_pid));
+ // const FilePath uptimeFile = FilePath::fromString(QStringLiteral("/proc/uptime"));
+ // const double clkTck = static_cast<double>(sysconf(_SC_CLK_TCK));
+
+ // if (!status.fileContents().has_value() || !uptimeFile.fileContents().has_value() || clkTck == 0)
+ // return 0;
+
+ // const QVector<QByteArray> processStatus = status.fileContents().value().split(' ').toVector();
+ // const double uptime = uptimeFile.fileContents().value().split(' ')[0].toDouble();
+
+ // const double utime = processStatus[13].toDouble() / clkTck;
+ // const double stime = processStatus[14].toDouble() / clkTck;
+ // const double cutime = processStatus[15].toDouble() / clkTck;
+ // const double cstime = processStatus[16].toDouble() / clkTck;
+ // const double starttime = processStatus[21].toDouble() / clkTck;
+
+ // const double elapsed = uptime - starttime;
+ // const double usage_sec = utime + stime + cutime + cstime;
+ // const double usage = 100 * usage_sec / elapsed;
+
+ // return usage;
+ // }
+};
+
+#endif
+
+// ------------------------- WindowsDataProvider --------------------------------
+
+#ifdef Q_OS_WIN
+class WindowsDataProvider : public IDataProvider
+{
+public:
+ WindowsDataProvider(qint64 pid, QObject *parent = nullptr)
+ : IDataProvider(pid, parent)
+ {}
+
+ double getMemoryConsumption() { return 0; }
+
+ double getCpuConsumption() { return 0; }
+
+#if 0
+ double getMemoryConsumptionWindows(qint64 pid)
+ {
+ HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
+ if (hProcess == NULL) {
+ std::cerr << "Failed to open process. Error code: " << GetLastError() << std::endl;
+ return 1;
+ }
+
+ PROCESS_MEMORY_COUNTERS_EX pmc;
+ if (!GetProcessMemoryInfo(hProcess, (PROCESS_MEMORY_COUNTERS*)&pmc, sizeof(pmc))) {
+ std::cerr << "Failed to retrieve process memory information. Error code: " << GetLastError() << std::endl;
+ CloseHandle(hProcess);
+ return 1;
+ }
+
+ std::cout << "Process ID: " << pid << std::endl;
+ std::cout << "Memory consumption: " << pmc.PrivateUsage << " bytes" << std::endl;
+
+ CloseHandle(hProcess);
+ return pmc.PrivateUsage;
+ }
+#endif
+};
+#endif
+
+// ------------------------- MacDataProvider --------------------------------
+
+#ifdef Q_OS_MACOS
+class MacDataProvider : public IDataProvider
+{
+public:
+ MacDataProvider(qint64 pid, QObject *parent = nullptr)
+ : IDataProvider(pid, parent)
+ {}
+
+ double getMemoryConsumption() { return 0; }
+
+ double getCpuConsumption() { return 0; }
+};
+#endif
+
+IDataProvider *createDataProvider(qint64 pid)
+{
+#ifdef Q_OS_WIN
+ return new WindowsDataProvider(pid);
+#elif defined(Q_OS_MACOS)
+ return new MacDataProvider(pid);
+#else // Q_OS_LINUX
+ return new LinuxDataProvider(pid);
+#endif
+}
+
+} // namespace AppStatisticsMonitor::Internal
diff --git a/src/plugins/appstatisticsmonitor/idataprovider.h b/src/plugins/appstatisticsmonitor/idataprovider.h
new file mode 100644
index 0000000000..42e60ebb8b
--- /dev/null
+++ b/src/plugins/appstatisticsmonitor/idataprovider.h
@@ -0,0 +1,44 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+#pragma once
+
+#include <QList>
+#include <QObject>
+#include <QTimer>
+
+namespace AppStatisticsMonitor::Internal {
+
+class IDataProvider : public QObject
+{
+ Q_OBJECT
+public:
+ IDataProvider(qint64 pid, QObject *parent = nullptr);
+
+ QList<double> memoryConsumptionHistory() const;
+ QList<double> cpuConsumptionHistory() const;
+
+ double memoryConsumptionLast() const;
+ double cpuConsumptionLast() const;
+
+protected:
+ virtual double getMemoryConsumption() = 0;
+ virtual double getCpuConsumption() = 0;
+
+ QList<double> m_memoryConsumption;
+ QList<double> m_cpuConsumption;
+ qint64 m_pid;
+ double m_lastTotalTime;
+ double m_lastRequestStartTime;
+
+signals:
+ void newDataAvailable();
+
+private:
+ void handleTimeout();
+
+ QTimer m_timer;
+};
+
+IDataProvider *createDataProvider(qint64 pid);
+
+} // AppStatisticsMonitor::Internal
diff --git a/src/plugins/appstatisticsmonitor/manager.cpp b/src/plugins/appstatisticsmonitor/manager.cpp
new file mode 100644
index 0000000000..a0a08fd36a
--- /dev/null
+++ b/src/plugins/appstatisticsmonitor/manager.cpp
@@ -0,0 +1,220 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#include "manager.h"
+
+#include "chart.h"
+#include "idataprovider.h"
+
+#include <coreplugin/session.h>
+
+#include <projectexplorer/projectexplorer.h>
+#include <projectexplorer/runcontrol.h>
+
+#include <QChartView>
+#include <QFormLayout>
+#include <QGraphicsItem>
+#include <QHash>
+
+using namespace ProjectExplorer;
+using namespace Utils;
+
+namespace AppStatisticsMonitor::Internal {
+
+class AppStatisticsMonitorView : public QWidget
+{
+public:
+ explicit AppStatisticsMonitorView(
+ AppStatisticsMonitorManager *appStatisticManager);
+
+ ~AppStatisticsMonitorView() override;
+
+private:
+ QComboBox *m_comboBox;
+
+ std::unique_ptr<AppStatisticsMonitorChart> m_chartMem;
+ std::unique_ptr<AppStatisticsMonitorChart> m_chartCpu;
+
+ AppStatisticsMonitorManager *m_manager;
+};
+
+AppStatisticsMonitorManager::AppStatisticsMonitorManager()
+{
+ connect(
+ ProjectExplorer::ProjectExplorerPlugin::instance(),
+ &ProjectExplorer::ProjectExplorerPlugin::runControlStarted,
+ this,
+ [this](RunControl *runControl) {
+ qint64 pid = runControl->applicationProcessHandle().pid();
+
+ m_pidNameMap[pid] = runControl->displayName();
+ m_rcPidMap[runControl] = pid;
+
+ m_currentDataProvider = createDataProvider(pid);
+ m_pidDataProviders.insert(pid, m_currentDataProvider);
+
+ emit appStarted(runControl->displayName(), pid);
+ });
+
+ connect(
+ ProjectExplorer::ProjectExplorerPlugin::instance(),
+ &ProjectExplorer::ProjectExplorerPlugin::runControlStoped,
+ this,
+ [this](RunControl *runControl) {
+ const auto pidIt = m_rcPidMap.constFind(runControl);
+ if (pidIt == m_rcPidMap.constEnd())
+ return;
+
+ const qint64 pid = pidIt.value();
+ m_rcPidMap.erase(pidIt);
+
+ m_pidNameMap.remove(pid);
+ delete m_pidDataProviders[pid];
+ m_pidDataProviders.remove(pid);
+ if (m_pidDataProviders.isEmpty())
+ setCurrentDataProvider(-1);
+ else
+ setCurrentDataProvider(m_pidDataProviders.keys().last());
+
+ emit appStoped(pid);
+ });
+}
+
+QString AppStatisticsMonitorManager::nameForPid(qint64 pid) const
+{
+ const auto pidIt = m_pidNameMap.constFind(pid);
+ if (pidIt == m_pidNameMap.constEnd())
+ return {};
+
+ return pidIt.value();
+}
+
+IDataProvider *AppStatisticsMonitorManager::currentDataProvider() const
+{
+ return m_currentDataProvider;
+}
+
+void AppStatisticsMonitorManager::setCurrentDataProvider(qint64 pid)
+{
+ m_currentDataProvider = nullptr;
+ const auto pidIt = m_pidDataProviders.constFind(pid);
+ if (pidIt == m_pidDataProviders.constEnd())
+ return;
+
+ m_currentDataProvider = pidIt.value();
+ connect(
+ m_currentDataProvider,
+ &IDataProvider::newDataAvailable,
+ this,
+ &AppStatisticsMonitorManager::newDataAvailable);
+}
+
+QHash<qint64, QString> AppStatisticsMonitorManager::pidNameMap() const
+{
+ return m_pidNameMap;
+}
+
+// AppStatisticsMonitorView
+AppStatisticsMonitorView::AppStatisticsMonitorView(AppStatisticsMonitorManager *appManager)
+ : m_manager(appManager)
+{
+ auto layout = new QVBoxLayout;
+ auto form = new QFormLayout;
+ setLayout(layout);
+
+ m_comboBox = new QComboBox(this);
+ form->addRow(m_comboBox);
+
+ m_chartMem = std::make_unique<AppStatisticsMonitorChart>(tr("Memory consumption"));
+ m_chartCpu = std::make_unique<AppStatisticsMonitorChart>(tr("CPU consumption"));
+
+ form->addRow(m_chartMem->chartView());
+ form->addRow(m_chartCpu->chartView());
+
+ layout->addLayout(form);
+
+ for (auto pidName : m_manager->pidNameMap().asKeyValueRange()) {
+ qint64 pid = pidName.first;
+ m_comboBox->addItem(pidName.second + " : " + QString::number(pid), pid);
+ }
+ m_comboBox->setCurrentIndex(m_comboBox->count() - 1);
+
+ m_chartCpu->clear();
+ m_chartMem->clear();
+
+ auto updateCharts = [this](int index) {
+ m_manager->setCurrentDataProvider(m_comboBox->itemData(index).toLongLong());
+ if (m_manager->currentDataProvider() != nullptr) {
+ const QList<double> &memConsumptionHistory
+ = m_manager->currentDataProvider()->memoryConsumptionHistory();
+ const QList<double> &cpuConsumptionHistory
+ = m_manager->currentDataProvider()->cpuConsumptionHistory();
+
+ m_chartMem->loadNewProcessData(memConsumptionHistory);
+ m_chartCpu->loadNewProcessData(cpuConsumptionHistory);
+ }
+ };
+
+ if (m_comboBox->count() != 0)
+ updateCharts(m_comboBox->currentIndex());
+
+ connect(m_comboBox, &QComboBox::currentIndexChanged, this, [updateCharts](int index) {
+ updateCharts(index);
+ });
+
+ connect(
+ m_manager,
+ &AppStatisticsMonitorManager::appStarted,
+ this,
+ [this](const QString &name, qint64 pid) {
+ if (pid != m_comboBox->currentData()) {
+ m_comboBox->addItem(name + " : " + QString::number(pid), pid);
+
+ m_chartMem->clear();
+ m_chartCpu->clear();
+
+ m_comboBox->setCurrentIndex(m_comboBox->count() - 1);
+ }
+ });
+
+ connect(m_manager, &AppStatisticsMonitorManager::appStoped, this, [this](qint64 pid) {
+ m_chartMem->addNewPoint({m_chartMem->lastPointX() + 1, 0});
+ m_chartCpu->addNewPoint({m_chartCpu->lastPointX() + 1, 0});
+
+ const int indx = m_comboBox->findData(pid);
+ if (indx != -1)
+ m_comboBox->removeItem(indx);
+ });
+
+ connect(m_manager, &AppStatisticsMonitorManager::newDataAvailable, this, [this] {
+ const IDataProvider *currentDataProvider = m_manager->currentDataProvider();
+ if (currentDataProvider != nullptr) {
+ m_chartMem->addNewPoint(
+ {(double) currentDataProvider->memoryConsumptionHistory().size(),
+ currentDataProvider->memoryConsumptionLast()});
+ m_chartCpu->addNewPoint(
+ {(double) currentDataProvider->cpuConsumptionHistory().size(),
+ currentDataProvider->cpuConsumptionLast()});
+ }
+ });
+}
+
+AppStatisticsMonitorView::~AppStatisticsMonitorView() = default;
+
+// AppStatisticsMonitorViewFactory
+AppStatisticsMonitorViewFactory::AppStatisticsMonitorViewFactory(
+ AppStatisticsMonitorManager *appStatisticManager)
+ : m_manager(appStatisticManager)
+{
+ setDisplayName(("AppStatisticsMonitor"));
+ setPriority(300);
+ setId("AppStatisticsMonitor");
+ setActivationSequence(QKeySequence("Alt+S"));
+}
+
+Core::NavigationView AppStatisticsMonitorViewFactory::createWidget()
+{
+ return {new AppStatisticsMonitorView(m_manager), {}};
+}
+
+} // namespace AppStatisticsMonitor::Internal
diff --git a/src/plugins/appstatisticsmonitor/manager.h b/src/plugins/appstatisticsmonitor/manager.h
new file mode 100644
index 0000000000..beba9cec72
--- /dev/null
+++ b/src/plugins/appstatisticsmonitor/manager.h
@@ -0,0 +1,60 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "chart.h"
+
+#include "idataprovider.h"
+
+#include <coreplugin/inavigationwidgetfactory.h>
+
+#include <QComboBox>
+#include <QList>
+#include <QMap>
+
+namespace Core { class IContext; }
+namespace ProjectExplorer { class RunControl; }
+
+namespace AppStatisticsMonitor::Internal {
+
+class AppStatisticsMonitorManager : public QObject
+{
+ Q_OBJECT
+public:
+ AppStatisticsMonitorManager();
+
+ QString nameForPid(qint64 pid) const;
+ QHash<qint64, QString> pidNameMap() const;
+
+ double memoryConsumption(qint64 pid) const;
+ double cpuConsumption(qint64 pid) const;
+
+ IDataProvider *currentDataProvider() const;
+ void setCurrentDataProvider(qint64 pid);
+
+signals:
+ void newDataAvailable();
+ void appStarted(const QString &name, qint64 pid);
+ void appStoped(qint64 pid);
+
+private:
+ QHash<qint64, QString> m_pidNameMap;
+ QHash<ProjectExplorer::RunControl *, int> m_rcPidMap;
+
+ QMap<qint64, IDataProvider *> m_pidDataProviders;
+ IDataProvider *m_currentDataProvider;
+};
+
+class AppStatisticsMonitorViewFactory : public Core::INavigationWidgetFactory
+{
+public:
+ AppStatisticsMonitorViewFactory(AppStatisticsMonitorManager *appStatisticManager);
+
+private:
+ Core::NavigationView createWidget() override;
+
+ AppStatisticsMonitorManager *m_manager;
+};
+
+} // namespace AppStatisticsMonitor::Internal
diff --git a/src/plugins/autotest/AutoTest.json.in b/src/plugins/autotest/AutoTest.json.in
index 230a9ee0f6..041d62a0f4 100644
--- a/src/plugins/autotest/AutoTest.json.in
+++ b/src/plugins/autotest/AutoTest.json.in
@@ -12,7 +12,11 @@
"",
"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."
],
-"Description" : "Auto Test plugin.",
-"Url" : "http://www.qt.io",
+"Description" : "Create code based tests and build system based tests",
+"LongDescription" : [
+ "Code based testing offers special handling for particular testing frameworks that strongly ties to the underlying code models or specialized parsers.",
+ "Build system based testing is independent from any testing frameworks. It retrieves information directly from the underlying build system and uses it or even the build system as such to execute the respective tests."
+],
+"Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/autotest/ctest/ctesttool.cpp b/src/plugins/autotest/ctest/ctesttool.cpp
index 8b82953e2c..69e75e6a01 100644
--- a/src/plugins/autotest/ctest/ctesttool.cpp
+++ b/src/plugins/autotest/ctest/ctesttool.cpp
@@ -34,6 +34,7 @@ CTestTool::CTestTool()
setId("AutoTest.Framework.CTest");
setDisplayName(Tr::tr("CTest"));
+ // clang-format off
setLayouter([this] {
return Row { Form {
outputOnFail, br,
@@ -41,20 +42,21 @@ CTestTool::CTestTool()
stopOnFailure, br,
outputMode, br,
Group {
- title(Tr::tr("Repeat tests")),
- repeat.groupChecker(),
+ title(Tr::tr("Repeat Tests")),
+ groupChecker(repeat.groupChecker()),
Row { repetitionMode, repetitionCount},
}, br,
Group {
- title(Tr::tr("Run in parallel")),
- parallel.groupChecker(),
+ title(Tr::tr("Run in Parallel")),
+ groupChecker(parallel.groupChecker()),
Column {
Row { jobs }, br,
- Row { testLoad, threshold}
+ Row { testLoad, threshold }
}
}
}, st };
});
+ // clang-format on
outputOnFail.setSettingsKey("OutputOnFail");
outputOnFail.setLabelText(Tr::tr("Output on failure"));
diff --git a/src/plugins/autotest/ctest/ctesttreeitem.cpp b/src/plugins/autotest/ctest/ctesttreeitem.cpp
index a9f68d6895..2216a64cb2 100644
--- a/src/plugins/autotest/ctest/ctesttreeitem.cpp
+++ b/src/plugins/autotest/ctest/ctesttreeitem.cpp
@@ -93,7 +93,7 @@ QList<ITestConfiguration *> CTestTreeItem::testConfigurationsFor(const QStringLi
<< QString::number(testSettings().timeout() / 1000);
}
options << theCTestTool().activeSettingsAsOptions();
- CommandLine command = buildSystem->commandLineForTests(selected, options);
+ const CommandLine command = buildSystem->commandLineForTests(selected, options);
if (command.executable().isEmpty())
return {};
diff --git a/src/plugins/autotest/projectsettingswidget.cpp b/src/plugins/autotest/projectsettingswidget.cpp
index 01b6c06d00..b6347f52a7 100644
--- a/src/plugins/autotest/projectsettingswidget.cpp
+++ b/src/plugins/autotest/projectsettingswidget.cpp
@@ -74,6 +74,7 @@ ProjectTestSettingsWidget::ProjectTestSettingsWidget(Project *project)
QPushButton *removeFilter = new QPushButton(Tr::tr("Remove"), this);
removeFilter->setEnabled(false);
+ // clang-format off
using namespace Layouting;
Column {
Widget {
@@ -81,7 +82,7 @@ ProjectTestSettingsWidget::ProjectTestSettingsWidget(Project *project)
Column {
Row {
Group {
- title(Tr::tr("Active frameworks:")),
+ title(Tr::tr("Active Test Frameworks")),
Column { m_activeFrameworks },
},
st,
@@ -91,13 +92,13 @@ ProjectTestSettingsWidget::ProjectTestSettingsWidget(Project *project)
m_runAfterBuild,
st,
},
- noMargin(),
+ noMargin,
},
},
Row { // explicitly outside of the global settings
Group {
- title(Tr::tr("Limit files to path patterns")),
- m_applyFilter.groupChecker(),
+ title(Tr::tr("Limit Files to Path Patterns")),
+ groupChecker(m_applyFilter.groupChecker()),
Column {
filterLabel,
Row {
@@ -107,8 +108,9 @@ ProjectTestSettingsWidget::ProjectTestSettingsWidget(Project *project)
},
},
},
- noMargin(),
+ noMargin,
}.attachTo(this);
+ // clang-format on
generalWidget->setDisabled(m_projectSettings->useGlobalSettings());
diff --git a/src/plugins/autotest/testnavigationwidget.cpp b/src/plugins/autotest/testnavigationwidget.cpp
index 435b510f85..a8e1d98e05 100644
--- a/src/plugins/autotest/testnavigationwidget.cpp
+++ b/src/plugins/autotest/testnavigationwidget.cpp
@@ -88,8 +88,8 @@ TestNavigationWidget::TestNavigationWidget()
m_view->setItemDelegate(new TestTreeItemDelegate(this));
QPalette pal;
- pal.setColor(QPalette::Window, creatorTheme()->color(Theme::InfoBarBackground));
- pal.setColor(QPalette::WindowText, creatorTheme()->color(Theme::InfoBarText));
+ pal.setColor(QPalette::Window, creatorColor(Theme::InfoBarBackground));
+ pal.setColor(QPalette::WindowText, creatorColor(Theme::InfoBarText));
m_missingFrameworksWidget = new QFrame;
m_missingFrameworksWidget->setPalette(pal);
m_missingFrameworksWidget->setAutoFillBackground(true);
diff --git a/src/plugins/autotest/testresult.cpp b/src/plugins/autotest/testresult.cpp
index 75c3d45f6f..e1a1c6bc8a 100644
--- a/src/plugins/autotest/testresult.cpp
+++ b/src/plugins/autotest/testresult.cpp
@@ -135,33 +135,32 @@ QColor TestResult::colorForType(const ResultType type)
if (type >= ResultType::INTERNAL_MESSAGES_BEGIN && type <= ResultType::INTERNAL_MESSAGES_END)
return QColor("transparent");
- const Theme *theme = creatorTheme();
switch (type) {
case ResultType::Pass:
- return theme->color(Theme::OutputPanes_TestPassTextColor);
+ return creatorColor(Theme::OutputPanes_TestPassTextColor);
case ResultType::Fail:
- return theme->color(Theme::OutputPanes_TestFailTextColor);
+ return creatorColor(Theme::OutputPanes_TestFailTextColor);
case ResultType::ExpectedFail:
- return theme->color(Theme::OutputPanes_TestXFailTextColor);
+ return creatorColor(Theme::OutputPanes_TestXFailTextColor);
case ResultType::UnexpectedPass:
- return theme->color(Theme::OutputPanes_TestXPassTextColor);
+ return creatorColor(Theme::OutputPanes_TestXPassTextColor);
case ResultType::Skip:
- return theme->color(Theme::OutputPanes_TestSkipTextColor);
+ return creatorColor(Theme::OutputPanes_TestSkipTextColor);
case ResultType::MessageDebug:
case ResultType::MessageInfo:
- return theme->color(Theme::OutputPanes_TestDebugTextColor);
+ return creatorColor(Theme::OutputPanes_TestDebugTextColor);
case ResultType::MessageWarn:
- return theme->color(Theme::OutputPanes_TestWarnTextColor);
+ return creatorColor(Theme::OutputPanes_TestWarnTextColor);
case ResultType::MessageFatal:
case ResultType::MessageSystem:
case ResultType::MessageError:
- return theme->color(Theme::OutputPanes_TestFatalTextColor);
+ return creatorColor(Theme::OutputPanes_TestFatalTextColor);
case ResultType::BlacklistedPass:
case ResultType::BlacklistedFail:
case ResultType::BlacklistedXPass:
case ResultType::BlacklistedXFail:
default:
- return theme->color(Theme::OutputPanes_StdOutTextColor);
+ return creatorColor(Theme::OutputPanes_StdOutTextColor);
}
}
diff --git a/src/plugins/autotest/testresultdelegate.cpp b/src/plugins/autotest/testresultdelegate.cpp
index 14fe1f646f..8c9f11b2c7 100644
--- a/src/plugins/autotest/testresultdelegate.cpp
+++ b/src/plugins/autotest/testresultdelegate.cpp
@@ -53,13 +53,12 @@ void TestResultDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op
const TestResult testResult = resultFilterModel->testResult(index);
QTC_ASSERT(testResult.isValid(), painter->restore(); return);
- const QWidget *widget = dynamic_cast<const QWidget*>(painter->device());
- QWindow *window = widget ? widget->window()->windowHandle() : nullptr;
-
QIcon icon = index.data(Qt::DecorationRole).value<QIcon>();
- if (!icon.isNull())
+ if (!icon.isNull()) {
painter->drawPixmap(positions.left(), positions.top(),
- icon.pixmap(window, QSize(positions.iconSize(), positions.iconSize())));
+ icon.pixmap(QSize(positions.iconSize(), positions.iconSize()),
+ painter->device()->devicePixelRatio()));
+ }
TestResultItem *item = resultFilterModel->itemForIndex(index);
QTC_ASSERT(item, painter->restore(); return);
diff --git a/src/plugins/autotest/testresultspane.cpp b/src/plugins/autotest/testresultspane.cpp
index aacc1808bf..894107d5d4 100644
--- a/src/plugins/autotest/testresultspane.cpp
+++ b/src/plugins/autotest/testresultspane.cpp
@@ -86,8 +86,8 @@ TestResultsPane::TestResultsPane(QObject *parent) :
visualOutputWidget->setLayout(outputLayout);
QPalette pal;
- pal.setColor(QPalette::Window, creatorTheme()->color(Theme::InfoBarBackground));
- pal.setColor(QPalette::WindowText, creatorTheme()->color(Theme::InfoBarText));
+ pal.setColor(QPalette::Window, creatorColor(Theme::InfoBarBackground));
+ pal.setColor(QPalette::WindowText, creatorColor(Theme::InfoBarText));
m_summaryWidget = new QFrame;
m_summaryWidget->setPalette(pal);
m_summaryWidget->setAutoFillBackground(true);
diff --git a/src/plugins/autotest/testrunner.cpp b/src/plugins/autotest/testrunner.cpp
index fe3fe22fdd..6c8f19c1bb 100644
--- a/src/plugins/autotest/testrunner.cpp
+++ b/src/plugins/autotest/testrunner.cpp
@@ -383,7 +383,7 @@ void TestRunner::runTestsHelper()
connect(testStorage->m_outputReader.get(), &TestOutputReader::newOutputLineAvailable,
TestResultsPane::instance(), &TestResultsPane::addOutputLine);
- CommandLine command{config->testExecutable(), {}};
+ CommandLine command{config->testExecutable()};
if (config->testBase()->type() == ITestBase::Framework) {
TestConfiguration *current = static_cast<TestConfiguration *>(config);
QStringList omitted;
diff --git a/src/plugins/autotest/testsettingspage.cpp b/src/plugins/autotest/testsettingspage.cpp
index f74716b8e2..1b2c7cbd6e 100644
--- a/src/plugins/autotest/testsettingspage.cpp
+++ b/src/plugins/autotest/testsettingspage.cpp
@@ -75,7 +75,7 @@ TestSettingsWidget::TestSettingsWidget()
PushButton resetChoicesButton {
text(Tr::tr("Reset Cached Choices")),
- tooltip(Tr::tr("Clear all cached choices of run configurations for "
+ Layouting::toolTip(Tr::tr("Clear all cached choices of run configurations for "
"tests where the executable could not be deduced.")),
onClicked(&clearChoiceCache, this)
};
diff --git a/src/plugins/autotoolsprojectmanager/AutotoolsProjectManager.json.in b/src/plugins/autotoolsprojectmanager/AutotoolsProjectManager.json.in
index 399a70dd3a..008d370d73 100644
--- a/src/plugins/autotoolsprojectmanager/AutotoolsProjectManager.json.in
+++ b/src/plugins/autotoolsprojectmanager/AutotoolsProjectManager.json.in
@@ -14,7 +14,12 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Build Systems",
- "Description" : "Autotools project integration.",
- "Url" : "http://www.qt.io",
+ "Description" : "Open Autotools-based projects in Qt Creator",
+ "LongDescription" : [
+ "You also need:",
+ "- Autotools",
+ "- An Autotools-based project"
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/axivion/Axivion.json.in b/src/plugins/axivion/Axivion.json.in
index 50958b9e9d..c83b471b8a 100644
--- a/src/plugins/axivion/Axivion.json.in
+++ b/src/plugins/axivion/Axivion.json.in
@@ -15,7 +15,10 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Code Analyzer",
- "Description" : "Integration of the axivion dashboard.",
+ "Description" : "Access an Axivion dashboard server",
+ "LongDescription" : [
+ "Protect software from erosion with static code analysis, architecture analysis, and code-smells-detection."
+ ],
"Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/axivion/axivionoutputpane.cpp b/src/plugins/axivion/axivionoutputpane.cpp
index fd35ed61ff..80dd5103c1 100644
--- a/src/plugins/axivion/axivionoutputpane.cpp
+++ b/src/plugins/axivion/axivionoutputpane.cpp
@@ -717,7 +717,7 @@ void IssuesWidget::showNoDataOverlay()
iconRect.moveCenter(that->rect().center());
icon.paint(&p, iconRect);
p.save();
- p.setPen(Utils::creatorTheme()->color(Theme::TextColorDisabled));
+ p.setPen(Utils::creatorColor(Theme::TextColorDisabled));
p.drawText(iconRect.bottomRight() + QPoint{10, p.fontMetrics().height() / 2 - 16},
Tr::tr("No Data"));
p.restore();
@@ -750,7 +750,7 @@ public:
m_outputWidget->addWidget(issuesWidget);
QPalette pal = m_outputWidget->palette();
- pal.setColor(QPalette::Window, creatorTheme()->color(Theme::Color::BackgroundColorNormal));
+ pal.setColor(QPalette::Window, creatorColor(Theme::Color::BackgroundColorNormal));
m_outputWidget->setPalette(pal);
m_showDashboard = new QToolButton(m_outputWidget);
diff --git a/src/plugins/axivion/axivionplugin.cpp b/src/plugins/axivion/axivionplugin.cpp
index 062da02feb..e7bb3d176e 100644
--- a/src/plugins/axivion/axivionplugin.cpp
+++ b/src/plugins/axivion/axivionplugin.cpp
@@ -19,7 +19,6 @@
#include <coreplugin/navigationwidget.h>
#include <extensionsystem/iplugin.h>
-#include <extensionsystem/pluginmanager.h>
#include <projectexplorer/buildsystem.h>
#include <projectexplorer/project.h>
@@ -106,6 +105,9 @@ QString anyToSimpleString(const Dto::Any &any)
value = anyMap.find("name");
if (value != anyMap.end())
return anyToSimpleString(value->second);
+ value = anyMap.find("tag");
+ if (value != anyMap.end())
+ return anyToSimpleString(value->second);
}
return {};
}
@@ -126,9 +128,9 @@ static QString escapeKey(const QString &string)
return escaped.replace('\\', "\\\\").replace('@', "\\@");
}
-static QString credentialKey()
+static QString credentialKey(const AxivionServer &server)
{
- return escapeKey(settings().server.username) + '@' + escapeKey(settings().server.dashboard);
+ return escapeKey(server.username) + '@' + escapeKey(server.dashboard);
}
template <typename DtoType>
@@ -222,6 +224,9 @@ signals:
void issueDetailsChanged(const QString &issueDetailsHtml);
public:
+ // active id used for any network communication, defaults to settings' default
+ // set to projects settings' dashboard id on open project
+ Id m_dashboardServerId;
// TODO: Should be set to Unknown on server address change in settings.
ServerAccess m_serverAccess = ServerAccess::Unknown;
// TODO: Should be cleared on username change in settings.
@@ -279,10 +284,10 @@ std::optional<Dto::ProjectInfoDto> projectInfo()
// FIXME: extend to give some details?
// FIXME: move when curl is no more in use?
-bool handleCertificateIssue()
+bool handleCertificateIssue(const Utils::Id &serverId)
{
QTC_ASSERT(dd, return false);
- const QString serverHost = QUrl(settings().server.dashboard).host();
+ const QString serverHost = QUrl(settings().serverForId(serverId).dashboard).host();
if (QMessageBox::question(ICore::dialogParent(), Tr::tr("Certificate Error"),
Tr::tr("Server certificate for %1 cannot be authenticated.\n"
"Do you want to disable SSL verification for this server?\n"
@@ -291,7 +296,7 @@ bool handleCertificateIssue()
!= QMessageBox::Yes) {
return false;
}
- settings().server.validateCert = false;
+ settings().disableCertificateValidation(serverId);
settings().apply();
return true;
@@ -307,6 +312,7 @@ AxivionPluginPrivate::AxivionPluginPrivate()
void AxivionPluginPrivate::handleSslErrors(QNetworkReply *reply, const QList<QSslError> &errors)
{
+ QTC_ASSERT(dd, return);
#if QT_CONFIG(ssl)
const QList<QSslError::SslError> accepted{
QSslError::CertificateNotYetValid, QSslError::CertificateExpired,
@@ -315,7 +321,8 @@ void AxivionPluginPrivate::handleSslErrors(QNetworkReply *reply, const QList<QSs
};
if (Utils::allOf(errors,
[&accepted](const QSslError &e) { return accepted.contains(e.error()); })) {
- if (!settings().server.validateCert || handleCertificateIssue())
+ const bool shouldValidate = settings().serverForId(dd->m_dashboardServerId).validateCert;
+ if (!shouldValidate || handleCertificateIssue(dd->m_dashboardServerId))
reply->ignoreSslErrors(errors);
}
#else // ssl
@@ -349,6 +356,7 @@ void AxivionPluginPrivate::onStartupProjectChanged(Project *project)
handleOpenedDocs();
});
const AxivionProjectSettings *projSettings = AxivionProjectSettings::projectSettings(m_project);
+ switchActiveDashboardId(projSettings->dashboardId());
fetchProjectInfo(projSettings->dashboardProjectName());
}
@@ -493,7 +501,6 @@ static Group dtoRecipe(const Storage<DtoStorageType<DtoType>> &dtoStorage)
const auto deserialize = [](QPromise<expected_str<DtoType>> &promise, const QByteArray &input) {
promise.addResult(DtoType::deserializeExpected(input));
};
- task.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
task.setConcurrentCallData(deserialize, **storage);
return SetupResult::Continue;
};
@@ -544,16 +551,17 @@ static void handleCredentialError(const CredentialQuery &credential)
static Group authorizationRecipe()
{
+ const Id serverId = dd->m_dashboardServerId;
const Storage<QUrl> serverUrlStorage;
const Storage<GetDtoStorage<Dto::DashboardInfoDto>> unauthorizedDashboardStorage;
const auto onUnauthorizedGroupSetup = [serverUrlStorage, unauthorizedDashboardStorage] {
unauthorizedDashboardStorage->url = *serverUrlStorage;
return isServerAccessEstablished() ? SetupResult::StopWithSuccess : SetupResult::Continue;
};
- const auto onUnauthorizedDashboard = [unauthorizedDashboardStorage] {
+ const auto onUnauthorizedDashboard = [unauthorizedDashboardStorage, serverId] {
if (unauthorizedDashboardStorage->dtoData) {
const Dto::DashboardInfoDto &dashboardInfo = *unauthorizedDashboardStorage->dtoData;
- const QString &username = settings().server.username;
+ const QString &username = settings().serverForId(serverId).username;
if (username.isEmpty()
|| (dashboardInfo.username && *dashboardInfo.username == username)) {
dd->m_serverAccess = ServerAccess::NoAuthorization;
@@ -570,10 +578,10 @@ static Group authorizationRecipe()
const auto onCredentialLoopCondition = [](int) {
return dd->m_serverAccess == ServerAccess::WithAuthorization && !dd->m_apiToken;
};
- const auto onGetCredentialSetup = [](CredentialQuery &credential) {
+ const auto onGetCredentialSetup = [serverId](CredentialQuery &credential) {
credential.setOperation(CredentialOperation::Get);
credential.setService(s_axivionKeychainService);
- credential.setKey(credentialKey());
+ credential.setKey(credentialKey(settings().serverForId(serverId)));
};
const auto onGetCredentialDone = [](const CredentialQuery &credential, DoneWith result) {
if (result == DoneWith::Success)
@@ -587,19 +595,21 @@ static Group authorizationRecipe()
const Storage<QString> passwordStorage;
const Storage<GetDtoStorage<Dto::DashboardInfoDto>> dashboardStorage;
- const auto onPasswordGroupSetup = [serverUrlStorage, passwordStorage, dashboardStorage] {
+ const auto onPasswordGroupSetup
+ = [serverId, serverUrlStorage, passwordStorage, dashboardStorage] {
if (dd->m_apiToken)
return SetupResult::StopWithSuccess;
bool ok = false;
+ const AxivionServer server = settings().serverForId(serverId);
const QString text(Tr::tr("Enter the password for:\nDashboard: %1\nUser: %2")
- .arg(settings().server.dashboard, settings().server.username));
+ .arg(server.dashboard, server.username));
*passwordStorage = QInputDialog::getText(ICore::mainWindow(),
Tr::tr("Axivion Server Password"), text, QLineEdit::Password, {}, &ok);
if (!ok)
return SetupResult::StopWithError;
- const QString credential = settings().server.username + ':' + *passwordStorage;
+ const QString credential = server.username + ':' + *passwordStorage;
dashboardStorage->credential = "Basic " + credential.toUtf8().toBase64();
dashboardStorage->url = *serverUrlStorage;
return SetupResult::Continue;
@@ -626,14 +636,14 @@ static Group authorizationRecipe()
return SetupResult::Continue;
};
- const auto onSetCredentialSetup = [apiTokenStorage](CredentialQuery &credential) {
+ const auto onSetCredentialSetup = [apiTokenStorage, serverId](CredentialQuery &credential) {
if (!apiTokenStorage->dtoData || !apiTokenStorage->dtoData->token)
return SetupResult::StopWithSuccess;
dd->m_apiToken = apiTokenStorage->dtoData->token->toUtf8();
credential.setOperation(CredentialOperation::Set);
credential.setService(s_axivionKeychainService);
- credential.setKey(credentialKey());
+ credential.setKey(credentialKey(settings().serverForId(serverId)));
credential.setData(*dd->m_apiToken);
return SetupResult::Continue;
};
@@ -653,7 +663,7 @@ static Group authorizationRecipe()
dashboardStorage->url = *serverUrlStorage;
return SetupResult::Continue;
};
- const auto onDeleteCredentialSetup = [dashboardStorage](CredentialQuery &credential) {
+ const auto onDeleteCredentialSetup = [dashboardStorage, serverId](CredentialQuery &credential) {
if (dashboardStorage->dtoData) {
dd->m_dashboardInfo = toDashboardInfo(*dashboardStorage);
return SetupResult::StopWithSuccess;
@@ -663,13 +673,15 @@ static Group authorizationRecipe()
.arg(Tr::tr("The stored ApiToken is not valid anymore, removing it.")));
credential.setOperation(CredentialOperation::Delete);
credential.setService(s_axivionKeychainService);
- credential.setKey(credentialKey());
+ credential.setKey(credentialKey(settings().serverForId(serverId)));
return SetupResult::Continue;
};
return {
serverUrlStorage,
- onGroupSetup([serverUrlStorage] { *serverUrlStorage = QUrl(settings().server.dashboard); }),
+ onGroupSetup([serverUrlStorage, serverId] {
+ *serverUrlStorage = QUrl(settings().serverForId(serverId).dashboard);
+ }),
Group {
unauthorizedDashboardStorage,
onGroupSetup(onUnauthorizedGroupSetup),
@@ -1047,6 +1059,15 @@ void fetchIssueInfo(const QString &id)
dd->fetchIssueInfo(id);
}
+void switchActiveDashboardId(const Id &toDashboardId)
+{
+ QTC_ASSERT(dd, return);
+ dd->m_dashboardServerId = toDashboardId;
+ dd->m_serverAccess = ServerAccess::Unknown;
+ dd->m_apiToken.reset();
+ dd->m_dashboardInfo.reset();
+}
+
const std::optional<DashboardInfo> currentDashboardInfo()
{
QTC_ASSERT(dd, return std::nullopt);
diff --git a/src/plugins/axivion/axivionplugin.h b/src/plugins/axivion/axivionplugin.h
index 1a69c4ff37..1b25f7a781 100644
--- a/src/plugins/axivion/axivionplugin.h
+++ b/src/plugins/axivion/axivionplugin.h
@@ -6,6 +6,7 @@
#include "dashboard/dto.h"
#include <utils/expected.h>
+#include <utils/id.h>
#include <QHash>
#include <QUrl>
@@ -83,6 +84,7 @@ QIcon iconForIssue(const std::optional<Dto::IssueKind> &issueKind);
QString anyToSimpleString(const Dto::Any &any);
void fetchIssueInfo(const QString &id);
+void switchActiveDashboardId(const Utils::Id &toDashboardId);
const std::optional<DashboardInfo> currentDashboardInfo();
Utils::FilePath findFileForIssuePath(const Utils::FilePath &issuePath);
diff --git a/src/plugins/axivion/axivionprojectsettings.cpp b/src/plugins/axivion/axivionprojectsettings.cpp
index 023c251ea4..83969a6218 100644
--- a/src/plugins/axivion/axivionprojectsettings.cpp
+++ b/src/plugins/axivion/axivionprojectsettings.cpp
@@ -15,10 +15,12 @@
#include <solutions/tasking/tasktreerunner.h>
+#include <utils/algorithm.h>
#include <utils/infolabel.h>
#include <utils/layoutbuilder.h>
#include <utils/qtcassert.h>
+#include <QComboBox>
#include <QPushButton>
#include <QTreeWidget>
#include <QVBoxLayout>
@@ -30,6 +32,7 @@ using namespace Utils;
namespace Axivion::Internal {
const char PSK_PROJECTNAME[] = "Axivion.ProjectName";
+const char PSK_DASHBOARDID[] = "Axivion.DashboardId";
// AxivionProjectSettingsHandler
@@ -82,11 +85,15 @@ void AxivionProjectSettings::destroyProjectSettings()
void AxivionProjectSettings::load()
{
m_dashboardProjectName = m_project->namedSettings(PSK_PROJECTNAME).toString();
+ m_dashboardId = Id::fromSetting(m_project->namedSettings(PSK_DASHBOARDID));
+ if (!m_dashboardId.isValid())
+ m_dashboardId = settings().defaultDashboardId();
}
void AxivionProjectSettings::save()
{
m_project->setNamedSettings(PSK_PROJECTNAME, m_dashboardProjectName);
+ m_project->setNamedSettings(PSK_DASHBOARDID, m_dashboardId.toSetting());
}
// AxivionProjectSettingsWidget
@@ -99,13 +106,16 @@ public:
private:
void fetchProjects();
void onSettingsChanged();
+ void onServerChanged();
void linkProject();
void unlinkProject();
void updateUi();
void updateEnabledStates();
+ void updateServers();
AxivionProjectSettings *m_projectSettings = nullptr;
QLabel *m_linkedProject = nullptr;
+ QComboBox *m_dashboardServers = nullptr;
QTreeWidget *m_dashboardProjects = nullptr;
QPushButton *m_fetchProjects = nullptr;
QPushButton *m_link = nullptr;
@@ -123,6 +133,10 @@ AxivionProjectSettingsWidget::AxivionProjectSettingsWidget(Project *project)
m_linkedProject = new QLabel(this);
+ m_dashboardServers = new QComboBox(this);
+ m_dashboardServers->setSizeAdjustPolicy(QComboBox::AdjustToContents);
+ updateServers();
+
m_dashboardProjects = new QTreeWidget(this);
m_dashboardProjects->setHeaderHidden(true);
m_dashboardProjects->setRootIsDecorated(false);
@@ -141,6 +155,7 @@ AxivionProjectSettingsWidget::AxivionProjectSettingsWidget(Project *project)
using namespace Layouting;
Column {
noMargin,
+ m_dashboardServers,
m_linkedProject,
Tr::tr("Dashboard projects:"),
Core::ItemViewFind::createSearchableWrapper(m_dashboardProjects),
@@ -148,6 +163,8 @@ AxivionProjectSettingsWidget::AxivionProjectSettingsWidget(Project *project)
Row { m_fetchProjects, m_link, m_unlink, st }
}.attachTo(this);
+ connect(m_dashboardServers, &QComboBox::currentIndexChanged,
+ this, &AxivionProjectSettingsWidget::onServerChanged);
connect(m_dashboardProjects, &QTreeWidget::itemSelectionChanged,
this, &AxivionProjectSettingsWidget::updateEnabledStates);
connect(m_fetchProjects, &QPushButton::clicked,
@@ -187,6 +204,23 @@ void AxivionProjectSettingsWidget::onSettingsChanged()
{
m_dashboardProjects->clear();
m_infoLabel->setVisible(false);
+ // check if serverId vanished - reset to default
+ const Id serverId = settings().defaultDashboardId();
+ if (m_projectSettings->dashboardId() != serverId) {
+ m_projectSettings->setDashboardId(serverId);
+ switchActiveDashboardId(serverId);
+ }
+ updateServers();
+ updateUi();
+}
+
+void AxivionProjectSettingsWidget::onServerChanged()
+{
+ m_dashboardProjects->clear();
+ m_infoLabel->setVisible(false);
+ const Id id = m_dashboardServers->currentData().value<AxivionServer>().id;
+ m_projectSettings->setDashboardId(id);
+ switchActiveDashboardId(id);
updateUi();
}
@@ -222,11 +256,13 @@ void AxivionProjectSettingsWidget::updateUi()
void AxivionProjectSettingsWidget::updateEnabledStates()
{
- const bool hasDashboardSettings = !settings().server.dashboard.isEmpty();
+ const bool hasDashboardSettings = !settings().serverForId(m_projectSettings->dashboardId())
+ .dashboard.isEmpty();
const bool linked = !m_projectSettings->dashboardProjectName().isEmpty();
const bool linkable = m_dashboardProjects->topLevelItemCount()
&& !m_dashboardProjects->selectedItems().isEmpty();
+ m_dashboardServers->setEnabled(!linked);
m_fetchProjects->setEnabled(hasDashboardSettings);
m_link->setEnabled(!linked && linkable);
m_unlink->setEnabled(linked);
@@ -238,6 +274,20 @@ void AxivionProjectSettingsWidget::updateEnabledStates()
}
}
+void AxivionProjectSettingsWidget::updateServers()
+{
+ const QList<AxivionServer> available = settings().allAvailableServers();
+ m_dashboardServers->clear();
+ for (const AxivionServer &server : available)
+ m_dashboardServers->addItem(server.displayString(), QVariant::fromValue(server));
+ const Id id = m_projectSettings->dashboardId();
+ const int index = Utils::indexOf(available, [&id](const AxivionServer &s) {
+ return s.id == id;
+ });
+ if (index != -1)
+ m_dashboardServers->setCurrentIndex(index);
+}
+
class AxivionProjectPanelFactory : public ProjectPanelFactory
{
public:
diff --git a/src/plugins/axivion/axivionprojectsettings.h b/src/plugins/axivion/axivionprojectsettings.h
index 7cbdfbbf47..7639213e87 100644
--- a/src/plugins/axivion/axivionprojectsettings.h
+++ b/src/plugins/axivion/axivionprojectsettings.h
@@ -3,6 +3,8 @@
#pragma once
+#include <utils/id.h>
+
#include <QObject>
namespace ProjectExplorer { class Project; }
@@ -16,6 +18,8 @@ public:
void setDashboardProjectName(const QString &name) { m_dashboardProjectName = name; }
QString dashboardProjectName() const { return m_dashboardProjectName; }
+ void setDashboardId(const Utils::Id &dashboardId) { m_dashboardId = dashboardId; }
+ Utils::Id dashboardId() const { return m_dashboardId; }
static AxivionProjectSettings *projectSettings(ProjectExplorer::Project *project);
static void destroyProjectSettings();
@@ -27,6 +31,8 @@ private:
ProjectExplorer::Project *m_project = nullptr;
QString m_dashboardProjectName;
+ // id of the dashboard in use for this project, or default from settings on initialization
+ Utils::Id m_dashboardId;
};
} // Axivion::Internal
diff --git a/src/plugins/axivion/axivionsettings.cpp b/src/plugins/axivion/axivionsettings.cpp
index 2af22e7cb9..aaed7b93da 100644
--- a/src/plugins/axivion/axivionsettings.cpp
+++ b/src/plugins/axivion/axivionsettings.cpp
@@ -8,14 +8,18 @@
#include <coreplugin/dialogs/ioptionspage.h>
#include <coreplugin/icore.h>
+#include <utils/algorithm.h>
#include <utils/id.h>
#include <utils/layoutbuilder.h>
#include <utils/stringutils.h>
+#include <QComboBox>
#include <QDialog>
#include <QDialogButtonBox>
+#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
+#include <QMessageBox>
#include <QPushButton>
#include <QRegularExpression>
#include <QUuid>
@@ -72,16 +76,19 @@ static FilePath tokensFilePath()
.pathAppended("qtcreator/axivion.json");
}
-static void writeTokenFile(const FilePath &filePath, const AxivionServer &server)
+static void writeTokenFile(const FilePath &filePath, const QList<AxivionServer> &servers)
{
QJsonDocument doc;
- doc.setObject(server.toJson());
+ QJsonArray serverArray;
+ for (const AxivionServer &server : servers)
+ serverArray.append(server.toJson());
+ doc.setArray(serverArray);
// FIXME error handling?
filePath.writeFileContents(doc.toJson());
filePath.setPermissions(QFile::ReadUser | QFile::WriteUser);
}
-static AxivionServer readTokenFile(const FilePath &filePath)
+static QList<AxivionServer> readTokenFile(const FilePath &filePath)
{
if (!filePath.exists())
return {};
@@ -89,9 +96,19 @@ static AxivionServer readTokenFile(const FilePath &filePath)
if (!contents)
return {};
const QJsonDocument doc = QJsonDocument::fromJson(*contents);
- if (!doc.isObject())
+ if (doc.isObject()) // old approach
+ return { AxivionServer::fromJson(doc.object()) };
+ if (!doc.isArray())
return {};
- return AxivionServer::fromJson(doc.object());
+
+ QList<AxivionServer> result;
+ const QJsonArray serverArray = doc.array();
+ for (const auto &serverValue : serverArray) {
+ if (!serverValue.isObject())
+ continue;
+ result.append(AxivionServer::fromJson(serverValue.toObject()));
+ }
+ return result;
}
// AxivionSetting
@@ -110,17 +127,62 @@ AxivionSettings::AxivionSettings()
highlightMarks.setLabelText(Tr::tr("Highlight marks"));
highlightMarks.setToolTip(Tr::tr("Marks issues on the scroll bar."));
highlightMarks.setDefaultValue(false);
+ m_defaultServerId.setSettingsKey("DefaultDashboardId");
AspectContainer::readSettings();
- server = readTokenFile(tokensFilePath());
+ m_allServers = readTokenFile(tokensFilePath());
+
+ if (m_allServers.size() == 1 && m_defaultServerId().isEmpty()) // handle settings transition
+ m_defaultServerId.setValue(m_allServers.first().id.toString());
}
void AxivionSettings::toSettings() const
{
- writeTokenFile(tokensFilePath(), server);
+ writeTokenFile(tokensFilePath(), m_allServers);
AspectContainer::writeSettings();
}
+Id AxivionSettings::defaultDashboardId() const
+{
+ return Id::fromString(m_defaultServerId());
+}
+
+const AxivionServer AxivionSettings::defaultServer() const
+{
+ return serverForId(defaultDashboardId());
+}
+
+const AxivionServer AxivionSettings::serverForId(const Utils::Id &id) const
+{
+ return Utils::findOrDefault(m_allServers, [&id](const AxivionServer &server) {
+ return id == server.id;
+ });
+}
+
+void AxivionSettings::disableCertificateValidation(const Utils::Id &id)
+{
+ const int index = Utils::indexOf(m_allServers, [&id](const AxivionServer &server) {
+ return id == server.id;
+ });
+ if (index == -1)
+ return;
+
+ m_allServers[index].validateCert = false;
+}
+
+void AxivionSettings::updateDashboardServers(const QList<AxivionServer> &other)
+{
+ if (m_allServers == other)
+ return;
+
+ const Id oldDefault = defaultDashboardId();
+ if (!Utils::anyOf(other, [&oldDefault](const AxivionServer &s) { return s.id == oldDefault; }))
+ m_defaultServerId.setValue(other.isEmpty() ? QString{} : other.first().id.toString());
+
+ m_allServers = other;
+ emit changed(); // should we be more detailed? (id)
+}
+
// AxivionSettingsPage
// may allow some invalid, but does some minimal check for legality
@@ -149,8 +211,7 @@ static bool isUrlValid(const QString &in)
class DashboardSettingsWidget : public QWidget
{
public:
- enum Mode { Display, Edit };
- explicit DashboardSettingsWidget(Mode m, QWidget *parent, QPushButton *ok = nullptr);
+ explicit DashboardSettingsWidget(QWidget *parent, QPushButton *ok = nullptr);
AxivionServer dashboardServer() const;
void setDashboardServer(const AxivionServer &server);
@@ -158,26 +219,23 @@ public:
bool isValid() const;
private:
- Mode m_mode = Display;
Id m_id;
StringAspect m_dashboardUrl;
StringAspect m_username;
BoolAspect m_valid;
};
-DashboardSettingsWidget::DashboardSettingsWidget(Mode mode, QWidget *parent, QPushButton *ok)
+DashboardSettingsWidget::DashboardSettingsWidget(QWidget *parent, QPushButton *ok)
: QWidget(parent)
- , m_mode(mode)
{
- auto labelStyle = mode == Display ? StringAspect::LabelDisplay : StringAspect::LineEditDisplay;
m_dashboardUrl.setLabelText(Tr::tr("Dashboard URL:"));
- m_dashboardUrl.setDisplayStyle(labelStyle);
+ m_dashboardUrl.setDisplayStyle(StringAspect::LineEditDisplay);
m_dashboardUrl.setValidationFunction([](FancyLineEdit *edit, QString *) {
return isUrlValid(edit->text());
});
m_username.setLabelText(Tr::tr("Username:"));
- m_username.setDisplayStyle(labelStyle);
+ m_username.setDisplayStyle(StringAspect::LineEditDisplay);
m_username.setPlaceHolderText(Tr::tr("User name"));
using namespace Layouting;
@@ -188,15 +246,13 @@ DashboardSettingsWidget::DashboardSettingsWidget(Mode mode, QWidget *parent, QPu
noMargin
}.attachTo(this);
- if (mode == Edit) {
- QTC_ASSERT(ok, return);
- auto checkValidity = [this, ok] {
- m_valid.setValue(isValid());
- ok->setEnabled(m_valid());
- };
- connect(&m_dashboardUrl, &BaseAspect::changed, this, checkValidity);
- connect(&m_username, &BaseAspect::changed, this, checkValidity);
- }
+ QTC_ASSERT(ok, return);
+ auto checkValidity = [this, ok] {
+ m_valid.setValue(isValid());
+ ok->setEnabled(m_valid());
+ };
+ connect(&m_dashboardUrl, &BaseAspect::changed, this, checkValidity);
+ connect(&m_username, &BaseAspect::changed, this, checkValidity);
}
AxivionServer DashboardSettingsWidget::dashboardServer() const
@@ -205,7 +261,7 @@ AxivionServer DashboardSettingsWidget::dashboardServer() const
if (m_id.isValid())
result.id = m_id;
else
- result.id = m_mode == Edit ? Id::fromName(QUuid::createUuid().toByteArray()) : m_id;
+ result.id = Id::fromName(QUuid::createUuid().toByteArray());
result.dashboard = fixUrl(m_dashboardUrl());
result.username = m_username();
return result;
@@ -231,65 +287,122 @@ public:
void apply() override;
private:
- void showEditServerDialog();
+ void showServerDialog(bool add);
+ void removeCurrentServerConfig();
+ void updateDashboardServers();
+ void updateEnabledStates();
- DashboardSettingsWidget *m_dashboardDisplay = nullptr;
+ QComboBox *m_dashboardServers = nullptr;
QPushButton *m_edit = nullptr;
+ QPushButton *m_remove = nullptr;
};
AxivionSettingsWidget::AxivionSettingsWidget()
{
using namespace Layouting;
- m_dashboardDisplay = new DashboardSettingsWidget(DashboardSettingsWidget::Display, this);
- m_dashboardDisplay->setDashboardServer(settings().server);
+ m_dashboardServers = new QComboBox(this);
+ m_dashboardServers->setSizeAdjustPolicy(QComboBox::AdjustToContents);
+ updateDashboardServers();
+
+ auto addButton = new QPushButton(Tr::tr("Add..."), this);
m_edit = new QPushButton(Tr::tr("Edit..."), this);
+ m_remove = new QPushButton(Tr::tr("Remove"), this);
Column {
Row {
Form {
- m_dashboardDisplay, br
+ Tr::tr("Default dashboard server"), m_dashboardServers, br
}, st,
- Column { m_edit },
+ Column { addButton, m_edit, st, m_remove },
},
Space(10), br,
Row { settings().highlightMarks }, st
}.attachTo(this);
- connect(m_edit, &QPushButton::clicked, this, &AxivionSettingsWidget::showEditServerDialog);
+ connect(addButton, &QPushButton::clicked, this, [this] {
+ // add an empty item unconditionally
+ m_dashboardServers->addItem(Tr::tr("unset"), QVariant::fromValue(AxivionServer()));
+ m_dashboardServers->setCurrentIndex(m_dashboardServers->count() - 1);
+ showServerDialog(true);
+ });
+ connect(m_edit, &QPushButton::clicked, this, [this] { showServerDialog(false); });
+ connect(m_remove, &QPushButton::clicked,
+ this, &AxivionSettingsWidget::removeCurrentServerConfig);
+ updateEnabledStates();
}
void AxivionSettingsWidget::apply()
{
- settings().server = m_dashboardDisplay->dashboardServer();
- emit settings().changed(); // ugly but needed
+ QList<AxivionServer> servers;
+ for (int i = 0, end = m_dashboardServers->count(); i < end; ++i)
+ servers.append(m_dashboardServers->itemData(i).value<AxivionServer>());
+ settings().updateDashboardServers(servers);
settings().toSettings();
}
-void AxivionSettingsWidget::showEditServerDialog()
+void AxivionSettingsWidget::updateDashboardServers()
{
- const AxivionServer old = m_dashboardDisplay->dashboardServer();
+ m_dashboardServers->clear();
+ for (const AxivionServer &server : settings().allAvailableServers())
+ m_dashboardServers->addItem(server.displayString(), QVariant::fromValue(server));
+}
+
+void AxivionSettingsWidget::updateEnabledStates()
+{
+ const bool enabled = m_dashboardServers->count();
+ m_edit->setEnabled(enabled);
+ m_remove->setEnabled(enabled);
+}
+
+void AxivionSettingsWidget::removeCurrentServerConfig()
+{
+ const QString config = m_dashboardServers->currentData().value<AxivionServer>().displayString();
+ if (QMessageBox::question(ICore::dialogParent(), Tr::tr("Remove Server Configuration"),
+ Tr::tr("Do you really want to remove the server configuration "
+ "\"%1\"?").arg(config))
+ != QMessageBox::Yes) {
+ return;
+ }
+ m_dashboardServers->removeItem(m_dashboardServers->currentIndex());
+ updateEnabledStates();
+}
+
+void AxivionSettingsWidget::showServerDialog(bool add)
+{
+ const AxivionServer old = m_dashboardServers->currentData().value<AxivionServer>();
QDialog d;
- d.setWindowTitle(Tr::tr("Edit Dashboard Configuration"));
+ d.setWindowTitle(add ? Tr::tr("Add Dashboard Configuration")
+ : Tr::tr("Edit Dashboard Configuration"));
QVBoxLayout *layout = new QVBoxLayout;
auto buttons = new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Ok, this);
auto ok = buttons->button(QDialogButtonBox::Ok);
- auto dashboardWidget = new DashboardSettingsWidget(DashboardSettingsWidget::Edit, this, ok);
+ auto dashboardWidget = new DashboardSettingsWidget(this, ok);
dashboardWidget->setDashboardServer(old);
layout->addWidget(dashboardWidget);
- ok->setEnabled(m_dashboardDisplay->isValid());
+ ok->setEnabled(dashboardWidget->isValid());
connect(buttons->button(QDialogButtonBox::Cancel), &QPushButton::clicked, &d, &QDialog::reject);
connect(ok, &QPushButton::clicked, &d, &QDialog::accept);
layout->addWidget(buttons);
d.setLayout(layout);
d.resize(500, 200);
- if (d.exec() != QDialog::Accepted)
+ if (d.exec() != QDialog::Accepted) {
+ if (add) { // if we canceled an add, remove the canceled item
+ m_dashboardServers->removeItem(m_dashboardServers->currentIndex());
+ updateEnabledStates();
+ }
return;
+ }
if (dashboardWidget->isValid()) {
const AxivionServer server = dashboardWidget->dashboardServer();
- if (server != old)
- m_dashboardDisplay->setDashboardServer(server);
+ if (server != old) {
+ m_dashboardServers->setItemData(m_dashboardServers->currentIndex(),
+ QVariant::fromValue(server));
+ m_dashboardServers->setItemData(m_dashboardServers->currentIndex(),
+ server.displayString(), Qt::DisplayRole);
+ }
}
+ updateEnabledStates();
}
// AxivionSettingsPage
diff --git a/src/plugins/axivion/axivionsettings.h b/src/plugins/axivion/axivionsettings.h
index 257b4056c5..4f10a19788 100644
--- a/src/plugins/axivion/axivionsettings.h
+++ b/src/plugins/axivion/axivionsettings.h
@@ -17,12 +17,15 @@ namespace Axivion::Internal {
class AxivionServer
{
public:
+ QString displayString() const { return username + " @ " + dashboard; }
bool operator==(const AxivionServer &other) const;
bool operator!=(const AxivionServer &other) const;
QJsonObject toJson() const;
static AxivionServer fromJson(const QJsonObject &json);
+ // id starts empty == invalid; set to generated uuid on first apply of valid dashboard config,
+ // never changes afterwards
Utils::Id id;
QString dashboard;
QString username;
@@ -37,8 +40,17 @@ public:
void toSettings() const;
- AxivionServer server; // shall we have more than one?
+ Utils::Id defaultDashboardId() const;
+ const AxivionServer defaultServer() const;
+ const AxivionServer serverForId(const Utils::Id &id) const;
+ void disableCertificateValidation(const Utils::Id &id);
+ const QList<AxivionServer> allAvailableServers() const { return m_allServers; };
+ void updateDashboardServers(const QList<AxivionServer> &other);
+
Utils::BoolAspect highlightMarks{this};
+private:
+ Utils::StringAspect m_defaultServerId{this};
+ QList<AxivionServer> m_allServers;
};
AxivionSettings &settings();
diff --git a/src/plugins/axivion/dynamiclistmodel.cpp b/src/plugins/axivion/dynamiclistmodel.cpp
index 80419a3223..ecd8b361b2 100644
--- a/src/plugins/axivion/dynamiclistmodel.cpp
+++ b/src/plugins/axivion/dynamiclistmodel.cpp
@@ -73,7 +73,7 @@ QVariant DynamicListModel::data(const QModelIndex &index, int role) const
if (role == Qt::DisplayRole && index.column() == 0)
return Tr::tr("Fetching..."); // TODO improve/customize?
if (role == Qt::ForegroundRole && index.column() == 0)
- return Utils::creatorTheme()->color(Utils::Theme::TextColorDisabled);
+ return Utils::creatorColor(Utils::Theme::TextColorDisabled);
return {};
}
diff --git a/src/plugins/baremetal/BareMetal.json.in b/src/plugins/baremetal/BareMetal.json.in
index 13bd9ba4ac..805beb7cff 100644
--- a/src/plugins/baremetal/BareMetal.json.in
+++ b/src/plugins/baremetal/BareMetal.json.in
@@ -14,6 +14,12 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Device Support",
- "Description" : "This plugin adds a target for bare metal development.",
+ "Description" : "Develop applications for bare metal devices",
+ "LongDescription" : [
+ "Adds a target for bare metal development.",
+ "Connect devices with USB or WLAN to run, debug, and analyze applications built for them.",
+ "You also need:",
+ "- A toolchain for bare metal development"
+ ],
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/baremetal/debugservers/gdb/eblinkgdbserverprovider.cpp b/src/plugins/baremetal/debugservers/gdb/eblinkgdbserverprovider.cpp
index 7ce65d689e..c31ed27dea 100644
--- a/src/plugins/baremetal/debugservers/gdb/eblinkgdbserverprovider.cpp
+++ b/src/plugins/baremetal/debugservers/gdb/eblinkgdbserverprovider.cpp
@@ -151,7 +151,7 @@ QString EBlinkGdbServerProvider::channelString() const
CommandLine EBlinkGdbServerProvider::command() const
{
- CommandLine cmd{m_executableFile, {}};
+ CommandLine cmd{m_executableFile};
QStringList interFaceTypeStrings = {"swd", "jtag"};
// Obligatorily -I
diff --git a/src/plugins/baremetal/debugservers/gdb/gdbserverprovider.cpp b/src/plugins/baremetal/debugservers/gdb/gdbserverprovider.cpp
index 885e20fac4..773a2ac577 100644
--- a/src/plugins/baremetal/debugservers/gdb/gdbserverprovider.cpp
+++ b/src/plugins/baremetal/debugservers/gdb/gdbserverprovider.cpp
@@ -138,11 +138,8 @@ bool GdbServerProvider::isValid() const
bool GdbServerProvider::aboutToRun(DebuggerRunTool *runTool, QString &errorMessage) const
{
QTC_ASSERT(runTool, return false);
- const RunControl *runControl = runTool->runControl();
- const auto exeAspect = runControl->aspectData<ExecutableAspect>();
- QTC_ASSERT(exeAspect, return false);
-
- const FilePath bin = FilePath::fromString(exeAspect->executable.path());
+ const ProcessRunData runnable = runTool->runControl()->runnable();
+ const FilePath bin = FilePath::fromString(runnable.command.executable().path());
if (bin.isEmpty()) {
errorMessage = Tr::tr("Cannot debug: Local executable is not set.");
return false;
@@ -155,8 +152,7 @@ bool GdbServerProvider::aboutToRun(DebuggerRunTool *runTool, QString &errorMessa
ProcessRunData inferior;
inferior.command.setExecutable(bin);
- if (const auto argAspect = runControl->aspectData<ArgumentsAspect>())
- inferior.command.setArguments(argAspect->arguments);
+ inferior.command.setArguments(runnable.command.arguments());
runTool->setInferior(inferior);
runTool->setSymbolFile(bin);
runTool->setStartMode(AttachToRemoteServer);
diff --git a/src/plugins/baremetal/debugservers/gdb/stlinkutilgdbserverprovider.cpp b/src/plugins/baremetal/debugservers/gdb/stlinkutilgdbserverprovider.cpp
index b94012c9dc..96a94dc609 100644
--- a/src/plugins/baremetal/debugservers/gdb/stlinkutilgdbserverprovider.cpp
+++ b/src/plugins/baremetal/debugservers/gdb/stlinkutilgdbserverprovider.cpp
@@ -131,7 +131,7 @@ QString StLinkUtilGdbServerProvider::channelString() const
CommandLine StLinkUtilGdbServerProvider::command() const
{
- CommandLine cmd{m_executableFile, {}};
+ CommandLine cmd{m_executableFile};
if (m_extendedMode)
cmd.addArg("--multi");
diff --git a/src/plugins/baremetal/debugservers/uvsc/uvscserverprovider.cpp b/src/plugins/baremetal/debugservers/uvsc/uvscserverprovider.cpp
index ed84508322..4dac9b654c 100644
--- a/src/plugins/baremetal/debugservers/uvsc/uvscserverprovider.cpp
+++ b/src/plugins/baremetal/debugservers/uvsc/uvscserverprovider.cpp
@@ -168,11 +168,7 @@ QString UvscServerProvider::channelString() const
bool UvscServerProvider::aboutToRun(DebuggerRunTool *runTool, QString &errorMessage) const
{
QTC_ASSERT(runTool, return false);
- const RunControl *runControl = runTool->runControl();
- const auto exeAspect = runControl->aspectData<ExecutableAspect>();
- QTC_ASSERT(exeAspect, return false);
-
- const FilePath bin = exeAspect->executable;
+ const FilePath bin = runTool->runControl()->runnable().command.executable();
if (bin.isEmpty()) {
errorMessage = Tr::tr("Cannot debug: Local executable is not set.");
return false;
@@ -210,10 +206,8 @@ ProjectExplorer::RunWorker *UvscServerProvider::targetRunner(RunControl *runCont
{
// Get uVision executable path.
const ProcessRunData uv = DebuggerKitAspect::runnable(runControl->kit());
- CommandLine server(uv.command.executable());
- server.addArg("-j0");
- server.addArg(QStringLiteral("-s%1").arg(m_channel.port()));
-
+ const CommandLine server{uv.command.executable(),
+ {"-j0", QStringLiteral("-s%1").arg(m_channel.port())}};
ProcessRunData r;
r.command = server;
return new UvscServerProviderRunner(runControl, r);
diff --git a/src/plugins/baremetal/iarewparser.cpp b/src/plugins/baremetal/iarewparser.cpp
index 23a1d10a1d..286cb0e168 100644
--- a/src/plugins/baremetal/iarewparser.cpp
+++ b/src/plugins/baremetal/iarewparser.cpp
@@ -117,8 +117,8 @@ OutputLineParser::Result IarParser::parseWarningOrErrorOrFatalErrorDetailsMessag
m_expectSnippet = false;
m_expectFilePath = false;
LinkSpecs linkSpecs;
- addLinkSpecForAbsoluteFilePath(linkSpecs, m_lastTask.file, m_lastTask.line, match,
- FilePathIndex);
+ addLinkSpecForAbsoluteFilePath(
+ linkSpecs, m_lastTask.file, m_lastTask.line, m_lastTask.column, match, FilePathIndex);
return {Status::InProgress, linkSpecs};
}
diff --git a/src/plugins/baremetal/keilparser.cpp b/src/plugins/baremetal/keilparser.cpp
index 4ae0c6aa64..64bf64e46f 100644
--- a/src/plugins/baremetal/keilparser.cpp
+++ b/src/plugins/baremetal/keilparser.cpp
@@ -62,8 +62,8 @@ OutputLineParser::Result KeilParser::parseArmWarningOrErrorDetailsMessage(const
const QString descr = match.captured(DescriptionIndex);
newTask(CompileTask(type, descr, absoluteFilePath(fileName), lineno));
LinkSpecs linkSpecs;
- addLinkSpecForAbsoluteFilePath(linkSpecs, m_lastTask.file, m_lastTask.line, match,
- FilePathIndex);
+ addLinkSpecForAbsoluteFilePath(
+ linkSpecs, m_lastTask.file, m_lastTask.line, m_lastTask.column, match, FilePathIndex);
return {Status::InProgress, linkSpecs};
}
@@ -98,8 +98,8 @@ OutputLineParser::Result KeilParser::parseMcs51WarningOrErrorDetailsMessage1(con
match.captured(MessageTextIndex));
newTask(CompileTask(type, descr, absoluteFilePath(fileName), lineno));
LinkSpecs linkSpecs;
- addLinkSpecForAbsoluteFilePath(linkSpecs, m_lastTask.file, m_lastTask.line, match,
- FilePathIndex);
+ addLinkSpecForAbsoluteFilePath(
+ linkSpecs, m_lastTask.file, m_lastTask.line, m_lastTask.column, match, FilePathIndex);
return {Status::InProgress, linkSpecs};
}
@@ -119,8 +119,8 @@ OutputLineParser::Result KeilParser::parseMcs51WarningOrErrorDetailsMessage2(con
match.captured(MessageTextIndex));
newTask(CompileTask(type, descr, absoluteFilePath(fileName), lineno));
LinkSpecs linkSpecs;
- addLinkSpecForAbsoluteFilePath(linkSpecs, m_lastTask.file, m_lastTask.line, match,
- FilePathIndex);
+ addLinkSpecForAbsoluteFilePath(
+ linkSpecs, m_lastTask.file, m_lastTask.line, m_lastTask.column, match, FilePathIndex);
return {Status::InProgress, linkSpecs};
}
diff --git a/src/plugins/baremetal/keiltoolchain.cpp b/src/plugins/baremetal/keiltoolchain.cpp
index 0ca7495c94..bc260130b2 100644
--- a/src/plugins/baremetal/keiltoolchain.cpp
+++ b/src/plugins/baremetal/keiltoolchain.cpp
@@ -246,11 +246,7 @@ static Macros dumpArmPredefinedMacros(const FilePath &compiler, const QStringLis
{
Process cpp;
cpp.setEnvironment(env);
-
- QStringList args = extraArgs;
- args.push_back("-E");
- args.push_back("--list-macros");
- cpp.setCommand({compiler, args});
+ cpp.setCommand({compiler, {extraArgs, "-E", "--list-macros"}});
cpp.runBlocking();
if (cpp.result() != ProcessResult::FinishedWithSuccess) {
diff --git a/src/plugins/baremetal/sdccparser.cpp b/src/plugins/baremetal/sdccparser.cpp
index aa97173f85..517f051390 100644
--- a/src/plugins/baremetal/sdccparser.cpp
+++ b/src/plugins/baremetal/sdccparser.cpp
@@ -73,8 +73,8 @@ OutputLineParser::Result SdccParser::handleLine(const QString &line, OutputForma
const QString descr = match.captured(MessageTextIndex);
newTask(CompileTask(type, descr, absoluteFilePath(fileName), lineno));
LinkSpecs linkSpecs;
- addLinkSpecForAbsoluteFilePath(linkSpecs, m_lastTask.file, m_lastTask.line, match,
- FilePathIndex);
+ addLinkSpecForAbsoluteFilePath(
+ linkSpecs, m_lastTask.file, m_lastTask.line, m_lastTask.column, match, FilePathIndex);
return {Status::InProgress, linkSpecs};
}
@@ -90,8 +90,8 @@ OutputLineParser::Result SdccParser::handleLine(const QString &line, OutputForma
const QString descr = match.captured(MessageTextIndex);
newTask(CompileTask(type, descr, absoluteFilePath(fileName), lineno));
LinkSpecs linkSpecs;
- addLinkSpecForAbsoluteFilePath(linkSpecs, m_lastTask.file, m_lastTask.line, match,
- FilePathIndex);
+ addLinkSpecForAbsoluteFilePath(
+ linkSpecs, m_lastTask.file, m_lastTask.line, m_lastTask.column, match, FilePathIndex);
return {Status::InProgress, linkSpecs};
}
diff --git a/src/plugins/bazaar/Bazaar.json.in b/src/plugins/bazaar/Bazaar.json.in
index 82bb55661f..41d2ff9909 100644
--- a/src/plugins/bazaar/Bazaar.json.in
+++ b/src/plugins/bazaar/Bazaar.json.in
@@ -13,7 +13,11 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Version Control",
- "Description" : "Bazaar integration.",
- "Url" : "http://www.qt.io",
+ "Description" : "Access the Bazaar version control system",
+ "LongDescription" : [
+ "You also need:",
+ "- Bazaar"
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/bazaar/bazaarplugin.cpp b/src/plugins/bazaar/bazaarplugin.cpp
index 590cfd2327..31d9c24390 100644
--- a/src/plugins/bazaar/bazaarplugin.cpp
+++ b/src/plugins/bazaar/bazaarplugin.cpp
@@ -884,7 +884,7 @@ bool BazaarPluginPrivate::managesFile(const FilePath &workingDirectory, const QS
bool BazaarPluginPrivate::isConfigured() const
{
- const FilePath binary = settings().binaryPath();
+ const FilePath binary = settings().binaryPath.effectiveBinary();
return !binary.isEmpty() && binary.isExecutableFile();
}
@@ -947,24 +947,21 @@ VcsCommand *BazaarPluginPrivate::createInitialCheckoutCommand(const QString &url
const QString &localName,
const QStringList &extraArgs)
{
- QStringList args;
- args << m_client.vcsCommandString(BazaarClient::CloneCommand)
- << extraArgs << url << localName;
-
Environment env = m_client.processEnvironment(baseDirectory);
env.set("BZR_PROGRESS_BAR", "text");
auto command = VcsBaseClient::createVcsCommand(this, baseDirectory, env);
- command->addJob({m_client.vcsBinary(baseDirectory), args}, -1);
+ command->addJob({m_client.vcsBinary(baseDirectory),
+ {m_client.vcsCommandString(BazaarClient::CloneCommand), extraArgs, url, localName}}, -1);
return command;
}
void BazaarPluginPrivate::changed(const QVariant &v)
{
- switch (v.type()) {
- case QVariant::String:
+ switch (v.typeId()) {
+ case QMetaType::QString:
emit repositoryChanged(FilePath::fromVariant(v));
break;
- case QVariant::StringList:
+ case QMetaType::QStringList:
emit filesChanged(v.toStringList());
break;
default:
diff --git a/src/plugins/beautifier/Beautifier.json.in b/src/plugins/beautifier/Beautifier.json.in
index 79de488b61..e00653b6f5 100644
--- a/src/plugins/beautifier/Beautifier.json.in
+++ b/src/plugins/beautifier/Beautifier.json.in
@@ -14,7 +14,13 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "C++",
- "Description" : "Format source files with the help of beautifiers like AStyle, uncrustify or clang-format.",
- "Url" : "http://www.qt.io",
+ "Description" : "Apply indentation and style to source code files",
+ "LongDescription" : [
+ "You also need one of the following tools:",
+ "- Artistic Style",
+ "- ClangFormat",
+ "- Uncrustify"
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/beautifier/clangformat/clangformat.cpp b/src/plugins/beautifier/clangformat/clangformat.cpp
index 22bcea77f7..d964fe30b1 100644
--- a/src/plugins/beautifier/clangformat/clangformat.cpp
+++ b/src/plugins/beautifier/clangformat/clangformat.cpp
@@ -270,7 +270,8 @@ public:
title(Tr::tr("Options")),
bindTo(&options),
Form {
- s.usePredefinedStyle.adoptButton(predefinedStyleButton), predefinedBlob, br,
+ s.usePredefinedStyle.adoptButton(predefinedStyleButton),
+ predefinedBlob, br,
customizedStyleButton, configurations,
},
},
diff --git a/src/plugins/beautifier/generalsettings.cpp b/src/plugins/beautifier/generalsettings.cpp
index be80de3938..413e80698f 100644
--- a/src/plugins/beautifier/generalsettings.cpp
+++ b/src/plugins/beautifier/generalsettings.cpp
@@ -50,7 +50,7 @@ GeneralSettings::GeneralSettings()
return Column {
Group {
title(Tr::tr("Automatic Formatting on File Save")),
- autoFormatOnSave.groupChecker(),
+ groupChecker(autoFormatOnSave.groupChecker()),
Form {
autoFormatTools, br,
autoFormatMime, br,
diff --git a/src/plugins/bineditor/BinEditor.json.in b/src/plugins/bineditor/BinEditor.json.in
index 5d05094b0f..8a3bad5e88 100644
--- a/src/plugins/bineditor/BinEditor.json.in
+++ b/src/plugins/bineditor/BinEditor.json.in
@@ -13,7 +13,8 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Qt Creator",
- "Description" : "Binary editor component.",
- "Url" : "http://www.qt.io",
+ "Description" : "Edit binary files",
+ "LongDescription" : [],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/boot2qt/Boot2Qt.json.in b/src/plugins/boot2qt/Boot2Qt.json.in
index 6ab0b2a7d6..9bc2be52d8 100644
--- a/src/plugins/boot2qt/Boot2Qt.json.in
+++ b/src/plugins/boot2qt/Boot2Qt.json.in
@@ -15,7 +15,13 @@
],
"Category" : "Device Support",
- "Description" : "Support for the Boot2Qt Device access using the Qt Debug Bridge.",
- "Url" : "http://www.qt.io",
+ "Description" : "Develop applications for Boot to Qt devices",
+ "LongDescription" : [
+ "Connect devices with USB or WLAN to run, debug, and analyze applications built for them.",
+ "You also need:",
+ "- Boot to Qt",
+ "- Build tools and other dependencies related to the development host"
+],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/boot2qt/qdbdevice.cpp b/src/plugins/boot2qt/qdbdevice.cpp
index 1ede480417..d9cbd3ed2a 100644
--- a/src/plugins/boot2qt/qdbdevice.cpp
+++ b/src/plugins/boot2qt/qdbdevice.cpp
@@ -111,7 +111,7 @@ QdbDevice::QdbDevice()
setType(Constants::QdbLinuxOsType);
addDeviceAction({Tr::tr("Reboot Device"), [](const IDevice::Ptr &device, QWidget *) {
- (void) new DeviceApplicationObserver(device, {device->filePath("reboot"), {}});
+ (void) new DeviceApplicationObserver(device, CommandLine{device->filePath("reboot")});
}});
addDeviceAction({Tr::tr("Restore Default App"), [](const IDevice::Ptr &device, QWidget *) {
diff --git a/src/plugins/boot2qt/qdbplugin.cpp b/src/plugins/boot2qt/qdbplugin.cpp
index c0a5bb25ca..d3943e94b1 100644
--- a/src/plugins/boot2qt/qdbplugin.cpp
+++ b/src/plugins/boot2qt/qdbplugin.cpp
@@ -48,7 +48,7 @@ static void startFlashingWizard()
if (HostOsInfo::isWindowsHost()) {
if (Process::startDetached({"explorer.exe", {filePath.toUserOutput()}}))
return;
- } else if (Process::startDetached({filePath, {}})) {
+ } else if (Process::startDetached(CommandLine{filePath})) {
return;
}
const QString message = Tr::tr("Flash wizard \"%1\" failed to start.");
diff --git a/src/plugins/clangcodemodel/ClangCodeModel.json.in b/src/plugins/clangcodemodel/ClangCodeModel.json.in
index 4702372b83..6a1dcf7729 100644
--- a/src/plugins/clangcodemodel/ClangCodeModel.json.in
+++ b/src/plugins/clangcodemodel/ClangCodeModel.json.in
@@ -13,7 +13,10 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "C++",
- "Description" : "Clang Code Model plugin.",
- "Url" : "http://www.qt.io",
+ "Description" : "Use Clang code model instead of the built-in code model",
+ "LongDescription" : [
+ "The code model is the part of an IDE that understands the programming language you are using to write your application. With it, Qt Creator can help you code faster and avoid errors."
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/clangcodemodel/clangcodemodelplugin.cpp b/src/plugins/clangcodemodel/clangcodemodelplugin.cpp
index 28d00d84a2..62ef434a2e 100644
--- a/src/plugins/clangcodemodel/clangcodemodelplugin.cpp
+++ b/src/plugins/clangcodemodel/clangcodemodelplugin.cpp
@@ -68,6 +68,7 @@ private:
ClangCodeModelPlugin::~ClangCodeModelPlugin()
{
+ m_generatorWatcher.cancel();
m_generatorWatcher.waitForFinished();
}
@@ -140,13 +141,18 @@ void ClangCodeModelPlugin::createCompilationDBAction()
connect(&m_generatorWatcher, &QFutureWatcher<GenerateCompilationDbResult>::finished,
this, [this] {
- const GenerateCompilationDbResult result = m_generatorWatcher.result();
QString message;
- if (result.error.isEmpty()) {
- message = Tr::tr("Clang compilation database generated at \"%1\".")
- .arg(QDir::toNativeSeparators(result.filePath));
+ if (m_generatorWatcher.future().resultCount()) {
+ const GenerateCompilationDbResult result = m_generatorWatcher.result();
+ if (result) {
+ message = Tr::tr("Clang compilation database generated at \"%1\".")
+ .arg(result->toUserOutput());
+ } else {
+ message
+ = Tr::tr("Generating Clang compilation database failed: %1").arg(result.error());
+ }
} else {
- message = Tr::tr("Generating Clang compilation database failed: %1").arg(result.error);
+ message = Tr::tr("Generating Clang compilation database canceled.");
}
MessageManager::writeFlashing(message);
m_generateCompilationDBAction->setEnabled(true);
diff --git a/src/plugins/clangcodemodel/clangdclient.cpp b/src/plugins/clangcodemodel/clangdclient.cpp
index d3ede30b10..19cd4bd5a9 100644
--- a/src/plugins/clangcodemodel/clangdclient.cpp
+++ b/src/plugins/clangcodemodel/clangdclient.cpp
@@ -46,6 +46,7 @@
#include <projectexplorer/devicesupport/idevice.h>
#include <projectexplorer/kitaspects.h>
#include <projectexplorer/project.h>
+#include <projectexplorer/projectnodes.h>
#include <projectexplorer/projecttree.h>
#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
@@ -143,7 +144,7 @@ private:
case Qt::ForegroundRole:
if ((detail().endsWith("class") || detail().endsWith("struct"))
&& range().end() == selectionRange().end()) {
- return creatorTheme()->color(Theme::TextColorDisabled);
+ return creatorColor(Theme::TextColorDisabled);
}
break;
}
@@ -208,12 +209,11 @@ static BaseClientInterface *clientInterface(Project *project, const Utils::FileP
"--clang-tidy=0"}};
if (settings.workerThreadLimit() != 0)
cmd.addArg("-j=" + QString::number(settings.workerThreadLimit()));
- if (indexingEnabled && Utils::clangdVersion(clangdExePath) >= QVersionNumber(15)) {
+ if (indexingEnabled) {
cmd.addArg("--background-index-priority="
+ ClangdSettings::priorityToString(indexingPriority));
}
- if (Utils::clangdVersion(clangdExePath) >= QVersionNumber(16))
- cmd.addArg("--rename-file-limit=0");
+ cmd.addArg("--rename-file-limit=0");
if (!jsonDbDir.isEmpty())
cmd.addArg("--compile-commands-dir=" + clangdExePath.withNewMappedPath(jsonDbDir).path());
if (clangdLogServer().isDebugEnabled())
@@ -349,7 +349,7 @@ public:
ClangdClient * const q;
const CppEditor::ClangdSettings::Data settings;
- ClangdFollowSymbol *followSymbol = nullptr;
+ QList<ClangdFollowSymbol *> followSymbolOps;
ClangdSwitchDeclDef *switchDeclDef = nullptr;
ClangdFindLocalReferences *findLocalRefs = nullptr;
std::optional<QVersionNumber> versionNumber;
@@ -501,8 +501,8 @@ ClangdClient::ClangdClient(Project *project, const Utils::FilePath &jsonDbDir, c
ClangdClient::~ClangdClient()
{
- if (d->followSymbol)
- d->followSymbol->clear();
+ for (ClangdFollowSymbol * const followSymbol : std::as_const(d->followSymbolOps))
+ followSymbol->clear();
delete d;
}
@@ -557,9 +557,7 @@ void ClangdClient::findUsages(const CppEditor::CursorInEditor &cursor,
if (searchTerm.isEmpty())
return;
- if (replacement && versionNumber() >= QVersionNumber(16)
- && Utils::qtcEnvironmentVariable("QTC_CLANGD_RENAMING") != "0") {
-
+ if (replacement && Utils::qtcEnvironmentVariable("QTC_CLANGD_RENAMING") != "0") {
// If we have up-to-date highlighting data, we can prevent giving clangd
// macros or namespaces to rename, which it can't cope with.
// TODO: Fix this upstream for macros; see https://github.com/clangd/clangd/issues/729.
@@ -767,6 +765,15 @@ QList<Text::Range> ClangdClient::additionalDocumentHighlights(
qobject_cast<CppEditor::CppEditorWidget *>(editorWidget), cursor);
}
+bool ClangdClient::shouldSendDidSave(const TextEditor::TextDocument *doc) const
+{
+ for (const Project * const p : ProjectManager::projects()) {
+ if (const Node * const n = p->nodeForFilePath(doc->filePath()))
+ return n->asFileNode() && n->asFileNode()->fileType() == FileType::Header;
+ }
+ return CppEditor::ProjectFile::isHeader(doc->filePath());
+}
+
RefactoringFilePtr ClangdClient::createRefactoringFile(const FilePath &filePath) const
{
return CppEditor::CppRefactoringChanges(CppEditor::CppModelManager::snapshot()).file(filePath);
@@ -983,7 +990,7 @@ MessageId ClangdClient::requestSymbolInfo(const Utils::FilePath &filePath, const
#ifdef WITH_TESTS
ClangdFollowSymbol *ClangdClient::currentFollowSymbolOperation()
{
- return d->followSymbol;
+ return d->followSymbolOps.isEmpty() ? nullptr : d->followSymbolOps.first();
}
#endif
@@ -998,8 +1005,21 @@ void ClangdClient::followSymbol(TextDocument *document,
{
QTC_ASSERT(documentOpen(document), openDocument(document));
- if (d->followSymbol)
- d->followSymbol->cancel();
+ const ClangdFollowSymbol::Origin origin
+ = CppEditor::CppCodeModelSettings::isInteractiveFollowSymbol()
+ ? ClangdFollowSymbol::Origin::User
+ : ClangdFollowSymbol::Origin::Code;
+ if (origin == ClangdFollowSymbol::Origin::User) {
+ for (auto it = d->followSymbolOps.begin(); it != d->followSymbolOps.end(); ) {
+ ClangdFollowSymbol * const followSymbol = *it;
+ if (followSymbol->isInteractive()) {
+ it = d->followSymbolOps.erase(it);
+ followSymbol->cancel();
+ } else {
+ ++it;
+ }
+ }
+ }
const QTextCursor adjustedCursor = d->adjustedCursor(cursor, document);
if (followTo == FollowTo::SymbolDef && !resolveTarget) {
@@ -1013,14 +1033,13 @@ void ClangdClient::followSymbol(TextDocument *document,
qCDebug(clangdLog) << "follow symbol requested" << document->filePath()
<< adjustedCursor.blockNumber() << adjustedCursor.positionInBlock();
- auto clangdFollowSymbol = new ClangdFollowSymbol(this, adjustedCursor, editorWidget, document,
- callback, followTo, openInSplit);
+ auto clangdFollowSymbol = new ClangdFollowSymbol(this, origin, adjustedCursor, editorWidget,
+ document, callback, followTo, openInSplit);
connect(clangdFollowSymbol, &ClangdFollowSymbol::done, this, [this, clangdFollowSymbol] {
clangdFollowSymbol->deleteLater();
- if (clangdFollowSymbol == d->followSymbol)
- d->followSymbol = nullptr;
+ d->followSymbolOps.removeOne(clangdFollowSymbol);
});
- d->followSymbol = clangdFollowSymbol;
+ d->followSymbolOps << clangdFollowSymbol;
}
void ClangdClient::switchDeclDef(TextDocument *document, const QTextCursor &cursor,
@@ -1513,7 +1532,7 @@ void ClangdClient::Private::handleSemanticTokens(TextDocument *doc,
return;
}
force = force || isTesting;
- const auto data = highlightingData.find(doc);
+ auto data = highlightingData.find(doc);
if (data != highlightingData.end()) {
if (!force && data->previousTokens.first == tokens
&& data->previousTokens.second == version) {
@@ -1523,61 +1542,50 @@ void ClangdClient::Private::handleSemanticTokens(TextDocument *doc,
data->previousTokens.first = tokens;
data->previousTokens.second = version;
} else {
- highlightingData.insert(doc, {{tokens, version}, {}});
+ data = highlightingData.insert(doc, {{tokens, version}, {}});
}
for (const ExpandedSemanticToken &t : tokens)
qCDebug(clangdLogHighlight()) << '\t' << t.line << t.column << t.length << t.type
<< t.modifiers;
- const auto astHandler = [this, tokens, doc, version](const ClangdAstNode &ast, const MessageId &) {
- FinalizingSubtaskTimer t(highlightingTimer);
- if (!q->documentOpen(doc))
- return;
- if (version != q->documentVersion(doc->filePath())) {
- qCInfo(clangdLogHighlight) << "AST not up to date; aborting highlighting procedure"
- << version << q->documentVersion(doc->filePath());
- return;
- }
- if (clangdLogAst().isDebugEnabled())
- ast.print();
-
- const auto runner = [tokens, filePath = doc->filePath(),
- text = doc->document()->toPlainText(), ast,
- doc = QPointer(doc), rev = doc->document()->revision(),
- clangdVersion = q->versionNumber(),
- this] {
- try {
- return Utils::asyncRun(doSemanticHighlighting, filePath, tokens, text, ast, doc,
- rev, clangdVersion, highlightingTimer);
- } catch (const std::exception &e) {
- qWarning() << "caught" << e.what() << "in main highlighting thread";
- return QFuture<HighlightingResult>();
- }
- };
+ FinalizingSubtaskTimer ft(highlightingTimer);
+ if (!q->documentOpen(doc))
+ return;
+ if (version != q->documentVersion(doc->filePath())) {
+ qCInfo(clangdLogHighlight) << "AST not up to date; aborting highlighting procedure"
+ << version << q->documentVersion(doc->filePath());
+ return;
+ }
- if (isTesting) {
- const auto watcher = new QFutureWatcher<HighlightingResult>(q);
- connect(watcher, &QFutureWatcher<HighlightingResult>::finished,
- q, [this, watcher, fp = doc->filePath()] {
- emit q->highlightingResultsReady(watcher->future().results(), fp);
- watcher->deleteLater();
- });
- watcher->setFuture(runner());
- return;
+ const auto runner = [tokens, filePath = doc->filePath(),
+ text = doc->document()->toPlainText(),
+ rev = doc->document()->revision(), this] {
+ try {
+ return Utils::asyncRun(doSemanticHighlighting, filePath, tokens, text,
+ rev, highlightingTimer);
+ } catch (const std::exception &e) {
+ qWarning() << "caught" << e.what() << "in main highlighting thread";
+ return QFuture<HighlightingResult>();
}
-
- auto &data = highlightingData[doc];
- if (!data.highlighter)
- data.highlighter = new CppEditor::SemanticHighlighter(doc);
- else
- data.highlighter->updateFormatMapFromFontSettings();
- data.highlighter->setHighlightingRunner(runner);
- data.highlighter->run();
};
- if (q->versionNumber().majorVersion() >= 17)
- astHandler({}, {});
+
+ if (isTesting) {
+ const auto watcher = new QFutureWatcher<HighlightingResult>(q);
+ connect(watcher, &QFutureWatcher<HighlightingResult>::finished,
+ q, [this, watcher, fp = doc->filePath()] {
+ emit q->highlightingResultsReady(watcher->future().results(), fp);
+ watcher->deleteLater();
+ });
+ watcher->setFuture(runner());
+ return;
+ }
+
+ if (!data->highlighter)
+ data->highlighter = new CppEditor::SemanticHighlighter(doc);
else
- getAndHandleAst(doc, astHandler, AstCallbackMode::SyncIfPossible);
+ data->highlighter->updateFormatMapFromFontSettings();
+ data->highlighter->setHighlightingRunner(runner);
+ data->highlighter->run();
}
std::optional<QList<CodeAction> > ClangdDiagnostic::codeActions() const
diff --git a/src/plugins/clangcodemodel/clangdclient.h b/src/plugins/clangcodemodel/clangdclient.h
index c0f687ea1f..5a12c8f56d 100644
--- a/src/plugins/clangcodemodel/clangdclient.h
+++ b/src/plugins/clangcodemodel/clangdclient.h
@@ -148,7 +148,7 @@ private:
bool fileBelongsToProject(const Utils::FilePath &filePath) const override;
QList<Utils::Text::Range> additionalDocumentHighlights(
TextEditor::TextEditorWidget *editorWidget, const QTextCursor &cursor) override;
-
+ bool shouldSendDidSave(const TextEditor::TextDocument *doc) const override;
class Private;
class VirtualFunctionAssistProcessor;
diff --git a/src/plugins/clangcodemodel/clangdfollowsymbol.cpp b/src/plugins/clangcodemodel/clangdfollowsymbol.cpp
index 0da02cba33..29fa15fe8b 100644
--- a/src/plugins/clangcodemodel/clangdfollowsymbol.cpp
+++ b/src/plugins/clangcodemodel/clangdfollowsymbol.cpp
@@ -73,10 +73,10 @@ private:
class ClangdFollowSymbol::Private
{
public:
- Private(ClangdFollowSymbol *q, ClangdClient *client, const QTextCursor &cursor,
+ Private(ClangdFollowSymbol *q, ClangdClient *client, Origin origin, const QTextCursor &cursor,
CppEditorWidget *editorWidget, const FilePath &filePath, const LinkHandler &callback,
bool openInSplit)
- : q(q), client(client), cursor(cursor), editorWidget(editorWidget),
+ : q(q), client(client), origin(origin), cursor(cursor), editorWidget(editorWidget),
uri(client->hostPathToServerUri(filePath)), callback(callback),
virtualFuncAssistProvider(q),
docRevision(editorWidget ? editorWidget->textDocument()->document()->revision() : -1),
@@ -94,6 +94,7 @@ public:
ClangdFollowSymbol * const q;
ClangdClient * const client;
+ const Origin origin;
const QTextCursor cursor;
const QPointer<CppEditor::CppEditorWidget> editorWidget;
const DocumentUri uri;
@@ -117,11 +118,11 @@ public:
bool done = false;
};
-ClangdFollowSymbol::ClangdFollowSymbol(ClangdClient *client, const QTextCursor &cursor,
- CppEditorWidget *editorWidget, TextDocument *document, const LinkHandler &callback,
- FollowTo followTo, bool openInSplit)
+ClangdFollowSymbol::ClangdFollowSymbol(ClangdClient *client, Origin origin,
+ const QTextCursor &cursor, CppEditorWidget *editorWidget, TextDocument *document,
+ const LinkHandler &callback, FollowTo followTo, bool openInSplit)
: QObject(client),
- d(new Private(this, client, cursor, editorWidget, document->filePath(), callback,
+ d(new Private(this, client, origin, cursor, editorWidget, document->filePath(), callback,
openInSplit))
{
// Abort if the user does something else with the document in the meantime.
@@ -193,6 +194,11 @@ void ClangdFollowSymbol::clear()
d->pendingGotoDefRequests.clear();
}
+bool ClangdFollowSymbol::isInteractive() const
+{
+ return d->origin == Origin::User;
+}
+
void ClangdFollowSymbol::emitDone(const Link &link)
{
if (d->done)
@@ -459,8 +465,6 @@ void ClangdFollowSymbol::Private::handleGotoImplementationResult(
// Make a symbol info request for each link to get the class names.
// Also get the AST for the base declaration, so we can find out whether it's
// pure virtual and mark it accordingly.
- // In addition, we need to follow all override links, because for these, clangd
- // gives us the declaration instead of the definition (until clangd 16).
for (const Link &link : std::as_const(allLinks)) {
if (!client->documentForFilePath(link.targetFilePath) && addOpenFile(link.targetFilePath))
client->openExtraFile(link.targetFilePath);
@@ -487,42 +491,6 @@ void ClangdFollowSymbol::Private::handleGotoImplementationResult(
if (link == defLink)
continue;
-
- if (client->versionNumber().majorVersion() >= 17)
- continue;
-
- const TextDocumentIdentifier doc(client->hostPathToServerUri(link.targetFilePath));
- const TextDocumentPositionParams params(doc, pos);
- GotoDefinitionRequest defReq(params);
- defReq.setResponseCallback(
- [this, link, transformLink, sentinel = QPointer(q), reqId = defReq.id()](
- const GotoDefinitionRequest::Response &response) {
- qCDebug(clangdLog) << "handling additional go to definition reply for"
- << link.targetFilePath << link.targetLine;
- if (!sentinel)
- return;
- Link newLink;
- if (std::optional<GotoResult> _result = response.result()) {
- const GotoResult result = _result.value();
- if (const auto ploc = std::get_if<Location>(&result)) {
- newLink = transformLink(*ploc);
- } else if (const auto plloc = std::get_if<QList<Location>>(&result)) {
- if (!plloc->isEmpty())
- newLink = transformLink(plloc->value(0));
- }
- }
- qCDebug(clangdLog) << "def link is" << newLink.targetFilePath << newLink.targetLine;
- declDefMap.insert(link, newLink);
- pendingGotoDefRequests.removeOne(reqId);
- if (pendingSymbolInfoRequests.isEmpty() && pendingGotoDefRequests.isEmpty()
- && defLinkNode.isValid()) {
- handleDocumentInfoResults();
- }
- });
- pendingGotoDefRequests << defReq.id();
- qCDebug(clangdLog) << "sending additional go to definition request"
- << link.targetFilePath << link.targetLine;
- client->sendMessage(defReq, ClangdClient::SendDocUpdates::Ignore);
}
const FilePath defLinkFilePath = defLink.targetFilePath;
diff --git a/src/plugins/clangcodemodel/clangdfollowsymbol.h b/src/plugins/clangcodemodel/clangdfollowsymbol.h
index 33cef520d8..539ae461bc 100644
--- a/src/plugins/clangcodemodel/clangdfollowsymbol.h
+++ b/src/plugins/clangcodemodel/clangdfollowsymbol.h
@@ -23,7 +23,9 @@ class ClangdFollowSymbol : public QObject
{
Q_OBJECT
public:
- ClangdFollowSymbol(ClangdClient *client, const QTextCursor &cursor,
+ enum class Origin { User, Code };
+
+ ClangdFollowSymbol(ClangdClient *client, Origin origin, const QTextCursor &cursor,
CppEditor::CppEditorWidget *editorWidget,
TextEditor::TextDocument *document, const Utils::LinkHandler &callback,
FollowTo followTo, bool openInSplit);
@@ -31,6 +33,8 @@ public:
void cancel();
void clear();
+ bool isInteractive() const;
+
signals:
void done();
diff --git a/src/plugins/clangcodemodel/clangdlocatorfilters.cpp b/src/plugins/clangcodemodel/clangdlocatorfilters.cpp
index 469dfeec95..5bf5915044 100644
--- a/src/plugins/clangcodemodel/clangdlocatorfilters.cpp
+++ b/src/plugins/clangcodemodel/clangdlocatorfilters.cpp
@@ -10,8 +10,6 @@
#include <cppeditor/cppeditortr.h>
#include <cppeditor/cpplocatorfilter.h>
-#include <extensionsystem/pluginmanager.h>
-
#include <languageclient/currentdocumentsymbolsrequest.h>
#include <languageclient/locatorfilter.h>
@@ -186,7 +184,6 @@ static LocatorMatcherTask currentDocumentMatcher()
};
const auto onFilterSetup = [=](Async<void> &async) {
- async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
async.setConcurrentCallData(filterCurrentResults, *storage, *resultStorage,
TextDocument::currentTextDocument()->plainText());
};
diff --git a/src/plugins/clangcodemodel/clangdquickfixes.h b/src/plugins/clangcodemodel/clangdquickfixes.h
index cdd2269266..088fb5e7be 100644
--- a/src/plugins/clangcodemodel/clangdquickfixes.h
+++ b/src/plugins/clangcodemodel/clangdquickfixes.h
@@ -3,7 +3,7 @@
#pragma once
-#include <cppeditor/cppquickfix.h>
+#include <cppeditor/quickfixes/cppquickfix.h>
#include <languageclient/languageclientquickfix.h>
namespace ClangCodeModel {
diff --git a/src/plugins/clangcodemodel/clangdsemantichighlighting.cpp b/src/plugins/clangcodemodel/clangdsemantichighlighting.cpp
index c5bc3d7235..2b52ee4d14 100644
--- a/src/plugins/clangcodemodel/clangdsemantichighlighting.cpp
+++ b/src/plugins/clangcodemodel/clangdsemantichighlighting.cpp
@@ -28,117 +28,19 @@ using namespace TextEditor;
namespace ClangCodeModel::Internal {
Q_LOGGING_CATEGORY(clangdLogHighlight, "qtc.clangcodemodel.clangd.highlight", QtWarningMsg);
-// clangd reports also the #ifs, #elses and #endifs around the disabled code as disabled,
-// and not even in a consistent manner. We don't want this, so we have to clean up here.
-// But note that we require this behavior, as otherwise we would not be able to grey out
-// e.g. empty lines after an #ifdef, due to the lack of symbols.
-static QList<BlockRange> cleanupDisabledCode(HighlightingResults &results, const QTextDocument *doc,
- const QString &docContent)
-{
- QList<BlockRange> ifdefedOutRanges;
- int rangeStartPos = -1;
- for (auto it = results.begin(); it != results.end();) {
- const bool wasIfdefedOut = rangeStartPos != -1;
- const bool isIfDefedOut = it->textStyles.mainStyle == C_DISABLED_CODE;
- if (!isIfDefedOut) {
- if (wasIfdefedOut) {
- const QTextBlock block = doc->findBlockByNumber(it->line - 1);
- ifdefedOutRanges << BlockRange(rangeStartPos, block.position());
- rangeStartPos = -1;
- }
- ++it;
- continue;
- }
-
- if (!wasIfdefedOut)
- rangeStartPos = doc->findBlockByNumber(it->line - 1).position();
-
- // Does the current line contain a potential "ifdefed-out switcher"?
- // If not, no state change is possible and we continue with the next line.
- const auto isPreprocessorControlStatement = [&] {
- const int pos = Utils::Text::positionInText(doc, it->line, it->column);
- const QStringView content = subViewLen(docContent, pos, it->length).trimmed();
- if (content.isEmpty() || content.first() != '#')
- return false;
- int offset = 1;
- while (offset < content.size() && content.at(offset).isSpace())
- ++offset;
- if (offset == content.size())
- return false;
- const QStringView ppDirective = content.mid(offset);
- return ppDirective.startsWith(QLatin1String("if"))
- || ppDirective.startsWith(QLatin1String("elif"))
- || ppDirective.startsWith(QLatin1String("else"))
- || ppDirective.startsWith(QLatin1String("endif"));
- };
- if (!isPreprocessorControlStatement()) {
- ++it;
- continue;
- }
-
- if (!wasIfdefedOut) {
- // The #if or #else that starts disabled code should not be disabled.
- const QTextBlock nextBlock = doc->findBlockByNumber(it->line);
- rangeStartPos = nextBlock.isValid() ? nextBlock.position() : -1;
- it = results.erase(it);
- continue;
- }
-
- if (wasIfdefedOut && (it + 1 == results.end()
- || (it + 1)->textStyles.mainStyle != C_DISABLED_CODE
- || (it + 1)->line != it->line + 1)) {
- // The #else or #endif that ends disabled code should not be disabled.
- const QTextBlock block = doc->findBlockByNumber(it->line - 1);
- ifdefedOutRanges << BlockRange(rangeStartPos, block.position());
- rangeStartPos = -1;
- it = results.erase(it);
- continue;
- }
- ++it;
- }
-
- if (rangeStartPos != -1)
- ifdefedOutRanges << BlockRange(rangeStartPos, doc->characterCount());
-
- qCDebug(clangdLogHighlight) << "found" << ifdefedOutRanges.size() << "ifdefed-out ranges";
- if (clangdLogHighlight().isDebugEnabled()) {
- for (const BlockRange &r : std::as_const(ifdefedOutRanges))
- qCDebug(clangdLogHighlight) << r.first() << r.last();
- }
-
- return ifdefedOutRanges;
-}
-
class ExtraHighlightingResultsCollector
{
public:
- ExtraHighlightingResultsCollector(QPromise<HighlightingResult> &promise,
- HighlightingResults &results,
- const Utils::FilePath &filePath, const ClangdAstNode &ast,
- const QTextDocument *doc, const QString &docContent,
- const QVersionNumber &clangdVersion);
+ ExtraHighlightingResultsCollector(HighlightingResults &results,
+ const Utils::FilePath &filePath,
+ const QTextDocument *doc, const QString &docContent);
void collect();
private:
- static bool lessThan(const HighlightingResult &r1, const HighlightingResult &r2);
- static int onlyIndexOf(const QStringView &text, const QStringView &subString, int from = 0);
- int posForNodeStart(const ClangdAstNode &node) const;
- int posForNodeEnd(const ClangdAstNode &node) const;
- void insertResult(const HighlightingResult &result);
- void insertResult(const ClangdAstNode &node, TextStyle style);
- void insertAngleBracketInfo(int searchStart1, int searchEnd1, int searchStart2, int searchEnd2);
- void setResultPosFromRange(HighlightingResult &result, const Range &range);
- void collectFromNode(const ClangdAstNode &node);
- void visitNode(const ClangdAstNode&node);
-
- QPromise<HighlightingResult> &m_promise;
HighlightingResults &m_results;
const Utils::FilePath m_filePath;
- const ClangdAstNode &m_ast;
const QTextDocument * const m_doc;
const QString &m_docContent;
- const int m_clangdVersion;
- ClangdAstNode::FileStatus m_currentFileStatus = ClangdAstNode::FileStatus::Unknown;
};
void doSemanticHighlighting(
@@ -146,10 +48,7 @@ void doSemanticHighlighting(
const Utils::FilePath &filePath,
const QList<ExpandedSemanticToken> &tokens,
const QString &docContents,
- const ClangdAstNode &ast,
- const QPointer<TextDocument> &textDocument,
int docRevision,
- const QVersionNumber &clangdVersion,
const TaskTimer &taskTimer)
{
ThreadedSubtaskTimer t("highlighting", taskTimer);
@@ -157,111 +56,6 @@ void doSemanticHighlighting(
return;
const QTextDocument doc(docContents);
- const auto tokenRange = [&doc](const ExpandedSemanticToken &token) {
- const Position startPos(token.line - 1, token.column - 1);
- const Position endPos = startPos.withOffset(token.length, &doc);
- return Range(startPos, endPos);
- };
- const int clangdMajorVersion = clangdVersion.majorVersion();
- const auto isOutputParameter = [&ast, &tokenRange, clangdMajorVersion]
- (const ExpandedSemanticToken &token) {
- if (token.modifiers.contains(QLatin1String("usedAsMutableReference")))
- return true;
- if (token.modifiers.contains(QLatin1String("usedAsMutablePointer")))
- return true;
- if (clangdMajorVersion >= 16)
- return false;
- if (token.type != "variable" && token.type != "property" && token.type != "parameter")
- return false;
- const Range range = tokenRange(token);
- const ClangdAstPath path = getAstPath(ast, range);
- if (path.size() < 2)
- return false;
- if (token.type == "property"
- && (path.rbegin()->kind() == "MemberInitializer"
- || path.rbegin()->kind() == "CXXConstruct")) {
- return false;
- }
- if (path.rbegin()->hasConstType())
- return false;
- for (auto it = path.rbegin() + 1; it != path.rend(); ++it) {
- if (it->kind() == "CXXConstruct" || it->kind() == "MemberInitializer")
- return true;
-
- if (it->kind() == "Call") {
- // The first child is e.g. a called lambda or an object on which
- // the call happens, and should not be highlighted as an output argument.
- // If the call is not fully resolved (as in templates), we don't
- // know whether the argument is passed as const or not.
- if (it->arcanaContains("dependent type"))
- return false;
- const QList<ClangdAstNode> children = it->children().value_or(QList<ClangdAstNode>());
- return children.isEmpty()
- || (children.first().range() != (it - 1)->range()
- && children.first().kind() != "UnresolvedLookup");
- }
-
- // The token should get marked for e.g. lambdas, but not for assignment operators,
- // where the user sees that it's being written.
- if (it->kind() == "CXXOperatorCall") {
- const QList<ClangdAstNode> children = it->children().value_or(QList<ClangdAstNode>());
-
- // Child 1 is the call itself, Child 2 is the named entity on which the call happens
- // (a lambda or a class instance), after that follow the actual call arguments.
- if (children.size() < 2)
- return false;
-
- // The call itself is never modifiable.
- if (children.first().range() == range)
- return false;
-
- // The callable is never displayed as an output parameter.
- // TODO: A good argument can be made to display objects on which a non-const
- // operator or function is called as output parameters.
- if (children.at(1).range().contains(range))
- return false;
-
- QList<ClangdAstNode> firstChildTree{children.first()};
- while (!firstChildTree.isEmpty()) {
- const ClangdAstNode n = firstChildTree.takeFirst();
- const QString detail = n.detail().value_or(QString());
- if (detail.startsWith("operator")) {
- return !detail.contains('=')
- && !detail.contains("++") && !detail.contains("--")
- && !detail.contains("<<") && !detail.contains(">>")
- && !detail.contains("*");
- }
- firstChildTree << n.children().value_or(QList<ClangdAstNode>());
- }
- return true;
- }
-
- if (it->kind() == "Lambda")
- return false;
- if (it->kind() == "BinaryOperator")
- return false;
- if (it->hasConstType())
- return false;
-
- if (it->kind() == "CXXMemberCall") {
- if (it == path.rbegin())
- return false;
- const QList<ClangdAstNode> children = it->children().value_or(QList<ClangdAstNode>());
- QTC_ASSERT(!children.isEmpty(), return false);
-
- // The called object is never displayed as an output parameter.
- // TODO: A good argument can be made to display objects on which a non-const
- // operator or function is called as output parameters.
- return (it - 1)->range() != children.first().range();
- }
-
- if (it->kind() == "Member" && it->arcanaContains("(")
- && !it->arcanaContains("bound member function type")) {
- return false;
- }
- }
- return false;
- };
const std::function<HighlightingResult(const ExpandedSemanticToken &)> toResult
= [&](const ExpandedSemanticToken &token) {
@@ -278,40 +72,12 @@ void doSemanticHighlighting(
} else if (token.type == "function" || token.type == "method") {
styles.mainStyle = token.modifiers.contains(QLatin1String("virtual"))
? C_VIRTUAL_METHOD : C_FUNCTION;
- if (token.modifiers.contains("definition")) {
+ if (token.modifiers.contains("definition"))
styles.mixinStyles.push_back(C_FUNCTION_DEFINITION);
- } else if (clangdMajorVersion < 16 && ast.isValid()) {
- const ClangdAstPath path = getAstPath(ast, tokenRange(token));
- if (path.length() > 1) {
- const ClangdAstNode declNode = path.at(path.length() - 2);
- if ((declNode.kind() == "Function" || declNode.kind() == "CXXMethod")
- && declNode.hasChildWithRole("statement")) {
- styles.mixinStyles.push_back(C_FUNCTION_DEFINITION);
- }
- }
- }
} else if (token.type == "class") {
styles.mainStyle = C_TYPE;
- if (token.modifiers.contains("constructorOrDestructor")) {
+ if (token.modifiers.contains("constructorOrDestructor"))
styles.mainStyle = C_FUNCTION;
- } else if (clangdMajorVersion < 16 && ast.isValid()) {
- const ClangdAstPath path = getAstPath(ast, tokenRange(token));
- if (!path.isEmpty()) {
- if (path.last().kind() == "CXXConstructor") {
- if (!path.last().arcanaContains("implicit"))
- styles.mainStyle = C_FUNCTION;
- } else if (path.last().kind() == "Record" && path.length() > 1) {
- const ClangdAstNode node = path.at(path.length() - 2);
- if (node.kind() == "CXXDestructor" && !node.arcanaContains("implicit")) {
- styles.mainStyle = C_FUNCTION;
-
- // https://github.com/clangd/clangd/issues/872
- if (node.role() == "declaration")
- styles.mixinStyles.push_back(C_DECLARATION);
- }
- }
- }
- }
} else if (token.type == "comment") { // "comment" means code disabled via the preprocessor
styles.mainStyle = C_DISABLED_CODE;
} else if (token.type == "namespace") {
@@ -384,8 +150,10 @@ void doSemanticHighlighting(
styles.mainStyle = C_STATIC_MEMBER;
}
}
- if (isOutputParameter(token))
+ if (token.modifiers.contains(QLatin1String("usedAsMutableReference"))
+ || token.modifiers.contains(QLatin1String("usedAsMutablePointer"))) {
styles.mixinStyles.push_back(C_OUTPUT_ARGUMENT);
+ }
return HighlightingResult(token.line, token.column, token.length, styles);
};
@@ -398,24 +166,13 @@ void doSemanticHighlighting(
}
};
auto results = QtConcurrent::blockingMapped<HighlightingResults>(tokens, safeToResult);
- const bool handleInactiveCode = clangdMajorVersion < 17;
- QList<BlockRange> ifdefedOutBlocks;
- if (handleInactiveCode)
- ifdefedOutBlocks = cleanupDisabledCode(results, &doc, docContents);
- ExtraHighlightingResultsCollector(promise, results, filePath, ast, &doc, docContents,
- clangdVersion).collect();
+ ExtraHighlightingResultsCollector(results, filePath, &doc, docContents).collect();
Utils::erase(results, [](const HighlightingResult &res) {
// QTCREATORBUG-28639
return res.textStyles.mainStyle == C_TEXT && res.textStyles.mixinStyles.empty();
});
if (!promise.isCanceled()) {
qCInfo(clangdLogHighlight) << "reporting" << results.size() << "highlighting results";
- if (handleInactiveCode) {
- QMetaObject::invokeMethod(textDocument, [textDocument, ifdefedOutBlocks, docRevision] {
- if (textDocument && textDocument->document()->revision() == docRevision)
- textDocument->setIfdefedOutBlocks(ifdefedOutBlocks);
- }, Qt::QueuedConnection);
- }
QList<Range> virtualRanges;
for (const HighlightingResult &r : results) {
qCDebug(clangdLogHighlight)
@@ -440,11 +197,11 @@ void doSemanticHighlighting(
}
ExtraHighlightingResultsCollector::ExtraHighlightingResultsCollector(
- QPromise<HighlightingResult> &promise, HighlightingResults &results,
- const Utils::FilePath &filePath, const ClangdAstNode &ast, const QTextDocument *doc,
- const QString &docContent, const QVersionNumber &clangdVersion)
- : m_promise(promise), m_results(results), m_filePath(filePath), m_ast(ast), m_doc(doc),
- m_docContent(docContent), m_clangdVersion(clangdVersion.majorVersion())
+ HighlightingResults &results,
+ const Utils::FilePath &filePath, const QTextDocument *doc,
+ const QString &docContent)
+ : m_results(results), m_filePath(filePath), m_doc(doc),
+ m_docContent(docContent)
{
}
@@ -469,497 +226,6 @@ void ExtraHighlightingResultsCollector::collect()
for (const HighlightingResult &newRes : propHighlighter.highlight())
m_results.insert(++i, newRes);
}
-
- if (!m_ast.isValid())
- return;
- QTC_ASSERT(m_clangdVersion < 17, return);
- visitNode(m_ast);
-}
-
-bool ExtraHighlightingResultsCollector::lessThan(const HighlightingResult &r1,
- const HighlightingResult &r2)
-{
- return r1.line < r2.line || (r1.line == r2.line && r1.column < r2.column)
- || (r1.line == r2.line && r1.column == r2.column && r1.length < r2.length);
-}
-
-int ExtraHighlightingResultsCollector::onlyIndexOf(const QStringView &text,
- const QStringView &subString, int from)
-{
- const int firstIndex = text.indexOf(subString, from);
- if (firstIndex == -1)
- return -1;
- const int nextIndex = text.indexOf(subString, firstIndex + 1);
-
- // The second condion deals with the off-by-one error in TemplateSpecialization nodes;
- // see collectFromNode().
- return nextIndex == -1 || nextIndex == firstIndex + 1 ? firstIndex : -1;
-}
-
-// Unfortunately, the exact position of a specific token is usually not
-// recorded in the AST, so if we need that, we have to search for it textually.
-// In corner cases, this might get sabotaged by e.g. comments, in which case we give up.
-int ExtraHighlightingResultsCollector::posForNodeStart(const ClangdAstNode &node) const
-{
- return Utils::Text::positionInText(m_doc, node.range().start().line() + 1,
- node.range().start().character() + 1);
-}
-
-int ExtraHighlightingResultsCollector::posForNodeEnd(const ClangdAstNode &node) const
-{
- return Utils::Text::positionInText(m_doc, node.range().end().line() + 1,
- node.range().end().character() + 1);
-}
-
-void ExtraHighlightingResultsCollector::insertResult(const HighlightingResult &result)
-{
- if (!result.isValid()) // Some nodes don't have a range.
- 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_MACRO) {
- return;
- }
-
- // Bogus ranges; e.g. QTCREATORBUG-27601
- if (it != m_results.end()) {
- const int nextStartPos = Utils::Text::positionInText(m_doc, it->line, it->column);
- const int resultEndPos = Utils::Text::positionInText(m_doc, result.line, result.column)
- + result.length;
- if (resultEndPos > nextStartPos)
- return;
- }
-
- qCDebug(clangdLogHighlight) << "adding additional highlighting result"
- << result.line << result.column << result.length;
- m_results.insert(it, result);
- return;
- }
-}
-
-void ExtraHighlightingResultsCollector::insertResult(const ClangdAstNode &node, TextStyle style)
-{
- HighlightingResult result;
- result.useTextSyles = true;
- result.textStyles.mainStyle = style;
- setResultPosFromRange(result, node.range());
- insertResult(result);
- return;
-}
-
-// For matching the "<" and ">" brackets of template declarations, specializations
-// and instantiations.
-void ExtraHighlightingResultsCollector::insertAngleBracketInfo(int searchStart1, int searchEnd1,
- int searchStart2, int searchEnd2)
-{
- const int openingAngleBracketPos = onlyIndexOf(
- subViewEnd(m_docContent, searchStart1, searchEnd1),
- QStringView(QStringLiteral("<")));
- if (openingAngleBracketPos == -1)
- return;
- const int absOpeningAngleBracketPos = searchStart1 + openingAngleBracketPos;
- if (absOpeningAngleBracketPos > searchStart2)
- searchStart2 = absOpeningAngleBracketPos + 1;
- if (searchStart2 >= searchEnd2)
- return;
- const int closingAngleBracketPos = onlyIndexOf(
- subViewEnd(m_docContent, searchStart2, searchEnd2),
- QStringView(QStringLiteral(">")));
- if (closingAngleBracketPos == -1)
- return;
-
- const int absClosingAngleBracketPos = searchStart2 + closingAngleBracketPos;
- if (absOpeningAngleBracketPos > absClosingAngleBracketPos)
- return;
-
- HighlightingResult result;
- result.useTextSyles = true;
- result.textStyles.mainStyle = C_PUNCTUATION;
- Utils::Text::convertPosition(m_doc, absOpeningAngleBracketPos, &result.line, &result.column);
- ++result.column;
- result.length = 1;
- result.kind = CppEditor::SemanticHighlighter::AngleBracketOpen;
- insertResult(result);
- Utils::Text::convertPosition(m_doc, absClosingAngleBracketPos, &result.line, &result.column);
- ++result.column;
- result.kind = CppEditor::SemanticHighlighter::AngleBracketClose;
- insertResult(result);
-}
-
-void ExtraHighlightingResultsCollector::setResultPosFromRange(HighlightingResult &result,
- const Range &range)
-{
- if (!range.isValid())
- return;
- const Position startPos = range.start();
- const Position endPos = range.end();
- result.line = startPos.line() + 1;
- result.column = startPos.character() + 1;
- result.length = endPos.toPositionInDocument(m_doc) - startPos.toPositionInDocument(m_doc);
-}
-
-void ExtraHighlightingResultsCollector::collectFromNode(const ClangdAstNode &node)
-{
- if (node.kind().endsWith("Literal"))
- return;
- if (node.role() == "type" && node.kind() == "Builtin")
- return;
-
- if (m_clangdVersion < 16 && node.role() == "attribute"
- && (node.kind() == "Override" || node.kind() == "Final")) {
- insertResult(node, C_KEYWORD);
- return;
- }
-
- const bool isExpression = node.role() == "expression";
- if (m_clangdVersion < 16 && isExpression && node.kind() == "Predefined") {
- insertResult(node, C_LOCAL);
- return;
- }
-
- const bool isDeclaration = node.role() == "declaration";
- const int nodeStartPos = posForNodeStart(node);
- const int nodeEndPos = posForNodeEnd(node);
- const QList<ClangdAstNode> children = node.children().value_or(QList<ClangdAstNode>());
-
- // Match question mark and colon in ternary operators.
- if (m_clangdVersion < 16 && isExpression && node.kind() == "ConditionalOperator") {
- if (children.size() != 3)
- return;
-
- // The question mark is between sub-expressions 1 and 2, the colon is between
- // sub-expressions 2 and 3.
- const int searchStartPosQuestionMark = posForNodeEnd(children.first());
- const int searchEndPosQuestionMark = posForNodeStart(children.at(1));
- QStringView content = subViewEnd(m_docContent, searchStartPosQuestionMark,
- searchEndPosQuestionMark);
- const int questionMarkPos = onlyIndexOf(content, QStringView(QStringLiteral("?")));
- if (questionMarkPos == -1)
- return;
- const int searchStartPosColon = posForNodeEnd(children.at(1));
- const int searchEndPosColon = posForNodeStart(children.at(2));
- content = subViewEnd(m_docContent, searchStartPosColon, searchEndPosColon);
- const int colonPos = onlyIndexOf(content, QStringView(QStringLiteral(":")));
- if (colonPos == -1)
- return;
-
- const int absQuestionMarkPos = searchStartPosQuestionMark + questionMarkPos;
- const int absColonPos = searchStartPosColon + colonPos;
- if (absQuestionMarkPos > absColonPos)
- return;
-
- HighlightingResult result;
- result.useTextSyles = true;
- result.textStyles.mainStyle = C_PUNCTUATION;
- result.textStyles.mixinStyles.push_back(C_OPERATOR);
- Utils::Text::convertPosition(m_doc, absQuestionMarkPos, &result.line, &result.column);
- ++result.column;
- result.length = 1;
- result.kind = CppEditor::SemanticHighlighter::TernaryIf;
- insertResult(result);
- Utils::Text::convertPosition(m_doc, absColonPos, &result.line, &result.column);
- ++result.column;
- result.kind = CppEditor::SemanticHighlighter::TernaryElse;
- insertResult(result);
- return;
- }
-
- if (isDeclaration && (node.kind() == "FunctionTemplate" || node.kind() == "ClassTemplate")) {
- // The child nodes are the template parameters and and the function or class.
- // The opening angle bracket is before the first child node, the closing angle
- // bracket is before the function child node and after the last param node.
- const QString classOrFunctionKind = QLatin1String(node.kind() == "FunctionTemplate"
- ? "Function" : "CXXRecord");
- const auto functionOrClassIt = std::find_if(children.begin(), children.end(),
- [&classOrFunctionKind](const ClangdAstNode &n) {
- return n.role() == "declaration" && n.kind() == classOrFunctionKind;
- });
- if (functionOrClassIt == children.end() || functionOrClassIt == children.begin())
- return;
- const int firstTemplateParamStartPos = posForNodeStart(children.first());
- const int lastTemplateParamEndPos = posForNodeEnd(*(functionOrClassIt - 1));
- const int functionOrClassStartPos = posForNodeStart(*functionOrClassIt);
- insertAngleBracketInfo(nodeStartPos, firstTemplateParamStartPos,
- lastTemplateParamEndPos, functionOrClassStartPos);
- return;
- }
-
- const auto isTemplateParamDecl = [](const ClangdAstNode &node) {
- return node.isTemplateParameterDeclaration();
- };
- if (isDeclaration && node.kind() == "TypeAliasTemplate") {
- // Children are one node of type TypeAlias and the template parameters.
- // The opening angle bracket is before the first parameter and the closing
- // angle bracket is after the last parameter.
- // The TypeAlias node seems to appear first in the AST, even though lexically
- // is comes after the parameters. We don't rely on the order here.
- // Note that there is a second pair of angle brackets. That one is part of
- // a TemplateSpecialization, which is handled further below.
- const auto firstTemplateParam = std::find_if(children.begin(), children.end(),
- isTemplateParamDecl);
- if (firstTemplateParam == children.end())
- return;
- const auto lastTemplateParam = std::find_if(children.rbegin(), children.rend(),
- isTemplateParamDecl);
- QTC_ASSERT(lastTemplateParam != children.rend(), return);
- const auto typeAlias = std::find_if(children.begin(), children.end(),
- [](const ClangdAstNode &n) { return n.kind() == "TypeAlias"; });
- if (typeAlias == children.end())
- return;
-
- const int firstTemplateParamStartPos = posForNodeStart(*firstTemplateParam);
- const int lastTemplateParamEndPos = posForNodeEnd(*lastTemplateParam);
- const int searchEndPos = posForNodeStart(*typeAlias);
- insertAngleBracketInfo(nodeStartPos, firstTemplateParamStartPos,
- lastTemplateParamEndPos, searchEndPos);
- return;
- }
-
- if (isDeclaration && node.kind() == "ClassTemplateSpecialization") {
- // There is one child of kind TemplateSpecialization. The first pair
- // of angle brackets comes before that.
- if (children.size() == 1) {
- const int childNodePos = posForNodeStart(children.first());
- insertAngleBracketInfo(nodeStartPos, childNodePos, nodeStartPos, childNodePos);
- }
- return;
- }
-
- if (isDeclaration && node.kind() == "TemplateTemplateParm") {
- // The child nodes are template arguments and template parameters.
- // Arguments seem to appear before parameters in the AST, even though they
- // come after them in the source code. We don't rely on the order here.
- const auto firstTemplateParam = std::find_if(children.begin(), children.end(),
- isTemplateParamDecl);
- if (firstTemplateParam == children.end())
- return;
- const auto lastTemplateParam = std::find_if(children.rbegin(), children.rend(),
- isTemplateParamDecl);
- QTC_ASSERT(lastTemplateParam != children.rend(), return);
- const auto templateArg = std::find_if(children.begin(), children.end(),
- [](const ClangdAstNode &n) { return n.role() == "template argument"; });
-
- const int firstTemplateParamStartPos = posForNodeStart(*firstTemplateParam);
- const int lastTemplateParamEndPos = posForNodeEnd(*lastTemplateParam);
- const int searchEndPos = templateArg == children.end()
- ? nodeEndPos : posForNodeStart(*templateArg);
- insertAngleBracketInfo(nodeStartPos, firstTemplateParamStartPos,
- lastTemplateParamEndPos, searchEndPos);
- return;
- }
-
- // {static,dynamic,reinterpret}_cast<>().
- if (isExpression && node.kind().startsWith("CXX") && node.kind().endsWith("Cast")) {
- // First child is type, second child is expression.
- // The opening angle bracket is before the first child, the closing angle bracket
- // is between the two children.
- if (children.size() == 2) {
- insertAngleBracketInfo(nodeStartPos, posForNodeStart(children.first()),
- posForNodeEnd(children.first()),
- posForNodeStart(children.last()));
- }
- return;
- }
-
- if (node.kind() == "TemplateSpecialization") {
- // First comes the template type, then the template arguments.
- // The opening angle bracket is before the first template argument,
- // the closing angle bracket is after the last template argument.
- // The first child node has no range, so we start searching at the parent node.
- if (children.size() >= 2) {
- int searchStart2 = posForNodeEnd(children.last());
- int searchEnd2 = nodeEndPos;
-
- // There is a weird off-by-one error on the clang side: If there is a
- // nested template instantiation *and* there is no space between
- // the closing angle brackets, then the inner TemplateSpecialization node's range
- // will extend one character too far, covering the outer's closing angle bracket.
- // This is what we are correcting for here.
- // This issue is tracked at https://github.com/clangd/clangd/issues/871.
- if (searchStart2 == searchEnd2)
- --searchStart2;
- insertAngleBracketInfo(nodeStartPos, posForNodeStart(children.at(1)),
- searchStart2, searchEnd2);
- }
- return;
- }
-
- if (!isExpression && !isDeclaration)
- return;
-
- if (m_clangdVersion >= 16)
- return;
-
- // Operators, overloaded ones in particular.
- static const QString operatorPrefix = "operator";
- QString detail = node.detail().value_or(QString());
- const bool isCallToNew = node.kind() == "CXXNew";
- const bool isCallToDelete = node.kind() == "CXXDelete";
- const auto isProperOperator = [&] {
- if (isCallToNew || isCallToDelete)
- return true;
- if (!detail.startsWith(operatorPrefix))
- return false;
- if (detail == operatorPrefix)
- return false;
- const QChar nextChar = detail.at(operatorPrefix.length());
- return !nextChar.isLetterOrNumber() && nextChar != '_';
- };
- if (!isProperOperator())
- return;
-
- if (!isCallToNew && !isCallToDelete)
- detail.remove(0, operatorPrefix.length());
-
- if (node.kind() == "CXXConversion")
- return;
-
- HighlightingResult result;
- result.useTextSyles = true;
- const bool isOverloaded = isDeclaration || ((!isCallToNew && !isCallToDelete)
- || node.arcanaContains("CXXMethod"));
- result.textStyles.mainStyle = isCallToNew || isCallToDelete || detail.at(0).isSpace()
- ? C_KEYWORD : C_PUNCTUATION;
- result.textStyles.mixinStyles.push_back(C_OPERATOR);
- if (isOverloaded)
- result.textStyles.mixinStyles.push_back(C_OVERLOADED_OPERATOR);
- if (isDeclaration)
- result.textStyles.mixinStyles.push_back(C_DECLARATION);
-
- const QStringView nodeText = subViewEnd(m_docContent, nodeStartPos, nodeEndPos);
-
- if (isCallToNew || isCallToDelete) {
- result.line = node.range().start().line() + 1;
- result.column = node.range().start().character() + 1;
- result.length = isCallToNew ? 3 : 6;
- insertResult(result);
- if (node.arcanaContains("array")) {
- const int openingBracketOffset = nodeText.indexOf('[');
- if (openingBracketOffset == -1)
- return;
- const int closingBracketOffset = nodeText.lastIndexOf(']');
- if (closingBracketOffset == -1 || closingBracketOffset < openingBracketOffset)
- return;
-
- result.textStyles.mainStyle = C_PUNCTUATION;
- result.length = 1;
- Utils::Text::convertPosition(m_doc,
- nodeStartPos + openingBracketOffset,
- &result.line, &result.column);
- ++result.column;
- insertResult(result);
- Utils::Text::convertPosition(m_doc,
- nodeStartPos + closingBracketOffset,
- &result.line, &result.column);
- ++result.column;
- insertResult(result);
- }
- return;
- }
-
- if (isExpression && (detail == QLatin1String("()") || detail == QLatin1String("[]"))) {
- result.line = node.range().start().line() + 1;
- result.column = node.range().start().character() + 1;
- result.length = 1;
- insertResult(result);
- result.line = node.range().end().line() + 1;
- result.column = node.range().end().character();
- insertResult(result);
- return;
- }
-
- const int opStringLen = detail.at(0).isSpace() ? detail.length() - 1 : detail.length();
-
- // The simple case: Call to operator+, +=, * etc.
- if (nodeEndPos - nodeStartPos == opStringLen) {
- setResultPosFromRange(result, node.range());
- insertResult(result);
- return;
- }
-
- const int prefixOffset = nodeText.indexOf(operatorPrefix);
- if (prefixOffset == -1)
- return;
-
- const bool isArray = detail == "[]";
- const bool isCall = detail == "()";
- const bool isArrayNew = detail == " new[]";
- const bool isArrayDelete = detail == " delete[]";
- const QStringView searchTerm = isArray || isCall
- ? QStringView(detail).chopped(1) : isArrayNew || isArrayDelete
- ? QStringView(detail).chopped(2) : detail;
- const int opStringOffset = nodeText.indexOf(searchTerm, prefixOffset
- + operatorPrefix.length());
- if (opStringOffset == -1 || nodeText.indexOf(operatorPrefix, opStringOffset) != -1)
- return;
-
- const int opStringOffsetInDoc = nodeStartPos + opStringOffset
- + detail.length() - opStringLen;
- Utils::Text::convertPosition(m_doc, opStringOffsetInDoc, &result.line, &result.column);
- ++result.column;
- result.length = opStringLen;
- if (isArray || isCall)
- result.length = 1;
- else if (isArrayNew || isArrayDelete)
- result.length -= 2;
- if (!isArray && !isCall)
- insertResult(result);
- if (!isArray && !isCall && !isArrayNew && !isArrayDelete)
- return;
-
- result.textStyles.mainStyle = C_PUNCTUATION;
- result.length = 1;
- const int openingParenOffset = nodeText.indexOf(
- isCall ? '(' : '[', prefixOffset + operatorPrefix.length());
- if (openingParenOffset == -1)
- return;
- const int closingParenOffset = nodeText.indexOf(isCall ? ')' : ']', openingParenOffset + 1);
- if (closingParenOffset == -1 || closingParenOffset < openingParenOffset)
- return;
- Utils::Text::convertPosition(m_doc, nodeStartPos + openingParenOffset,
- &result.line, &result.column);
- ++result.column;
- insertResult(result);
- Utils::Text::convertPosition(m_doc, nodeStartPos + closingParenOffset,
- &result.line, &result.column);
- ++result.column;
- insertResult(result);
-}
-
-void ExtraHighlightingResultsCollector::visitNode(const ClangdAstNode &node)
-{
- if (m_promise.isCanceled())
- return;
- const ClangdAstNode::FileStatus prevFileStatus = m_currentFileStatus;
- m_currentFileStatus = node.fileStatus(m_filePath);
- if (m_currentFileStatus == ClangdAstNode::FileStatus::Unknown
- && prevFileStatus != ClangdAstNode::FileStatus::Ours) {
- m_currentFileStatus = prevFileStatus;
- }
- switch (m_currentFileStatus) {
- case ClangdAstNode::FileStatus::Ours:
- case ClangdAstNode::FileStatus::Unknown:
- collectFromNode(node);
- [[fallthrough]];
- case ClangdAstNode::FileStatus::Foreign:
- case ClangCodeModel::Internal::ClangdAstNode::FileStatus::Mixed: {
- const auto children = node.children();
- if (!children)
- return;
- for (const ClangdAstNode &childNode : *children)
- visitNode(childNode);
- break;
- }
- }
- m_currentFileStatus = prevFileStatus;
}
class InactiveRegionsParams : public JsonObject
diff --git a/src/plugins/clangcodemodel/clangdsemantichighlighting.h b/src/plugins/clangcodemodel/clangdsemantichighlighting.h
index 285ba2323e..b303f920f3 100644
--- a/src/plugins/clangcodemodel/clangdsemantichighlighting.h
+++ b/src/plugins/clangcodemodel/clangdsemantichighlighting.h
@@ -33,10 +33,7 @@ void doSemanticHighlighting(
const Utils::FilePath &filePath,
const QList<LanguageClient::ExpandedSemanticToken> &tokens,
const QString &docContents,
- const ClangdAstNode &ast,
- const QPointer<TextEditor::TextDocument> &textDocument,
int docRevision,
- const QVersionNumber &clangdVersion,
const TaskTimer &taskTimer
);
diff --git a/src/plugins/clangcodemodel/clangfixitoperation.cpp b/src/plugins/clangcodemodel/clangfixitoperation.cpp
index e173592ec0..802bdd17bb 100644
--- a/src/plugins/clangcodemodel/clangfixitoperation.cpp
+++ b/src/plugins/clangcodemodel/clangfixitoperation.cpp
@@ -71,10 +71,7 @@ QString ClangFixItOperation::firstRefactoringFileContent_forTestOnly() const
void ClangFixItOperation::applyFixitsToFile(TextEditor::RefactoringFile &refactoringFile,
const QList<ClangFixIt> fixIts)
{
- const Utils::ChangeSet changeSet = toChangeSet(refactoringFile, fixIts);
-
- refactoringFile.setChangeSet(changeSet);
- refactoringFile.apply();
+ refactoringFile.apply(toChangeSet(refactoringFile, fixIts));
}
Utils::ChangeSet ClangFixItOperation::toChangeSet(
diff --git a/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp b/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp
index a68f566309..f800979eae 100644
--- a/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp
+++ b/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp
@@ -540,11 +540,15 @@ void ClangModelManagerSupport::updateLanguageClient(Project *project)
generatorWatcher->deleteLater();
if (!isProjectDataUpToDate(project, projectInfo, jsonDbDir))
return;
- const GenerateCompilationDbResult result = generatorWatcher->result();
- if (!result.error.isEmpty()) {
+ if (generatorWatcher->future().resultCount() == 0) {
MessageManager::writeDisrupting(
- Tr::tr("Cannot use clangd: Failed to generate compilation database:\n%1")
- .arg(result.error));
+ Tr::tr("Cannot use clangd: Generating compilation database canceled."));
+ return;
+ }
+ const GenerateCompilationDbResult result = generatorWatcher->result();
+ if (!result) {
+ MessageManager::writeDisrupting(Tr::tr("Cannot use clangd: "
+ "Failed to generate compilation database:\n%1").arg(result.error()));
return;
}
Id previousId;
@@ -724,8 +728,8 @@ void ClangModelManagerSupport::updateStaleIndexEntries()
const QDateTime sourceIndexedTime = indexFilesIt->minLastModifiedTime;
bool rescan = false;
- QSet<FilePath> allIncludes = snapshot.allIncludesForDocument(sourceFile);
- for (const FilePath &includeFile : qAsConst(allIncludes)) {
+ const QSet<FilePath> allIncludes = snapshot.allIncludesForDocument(sourceFile);
+ for (const FilePath &includeFile : allIncludes) {
auto includeFileTimeIt = lastModifiedCache.find(includeFile);
if (includeFileTimeIt == lastModifiedCache.end()) {
includeFileTimeIt = lastModifiedCache.insert(includeFile,
diff --git a/src/plugins/clangcodemodel/clangutils.cpp b/src/plugins/clangcodemodel/clangutils.cpp
index 1b2550cbd3..b27117f056 100644
--- a/src/plugins/clangcodemodel/clangutils.cpp
+++ b/src/plugins/clangcodemodel/clangutils.cpp
@@ -146,23 +146,26 @@ static QJsonObject createFileObject(const FilePath &buildDir,
return fileObject;
}
-GenerateCompilationDbResult generateCompilationDB(QList<ProjectInfo::ConstPtr> projectInfoList,
- FilePath baseDir,
- CompilationDbPurpose purpose,
- ClangDiagnosticConfig warningsConfig,
- QStringList projectOptions,
- FilePath clangIncludeDir)
+void generateCompilationDB(
+ QPromise<expected_str<FilePath>> &promise,
+ const QList<ProjectInfo::ConstPtr> &projectInfoList,
+ const FilePath &baseDir,
+ CompilationDbPurpose purpose,
+ const ClangDiagnosticConfig &warningsConfig,
+ const QStringList &projectOptions,
+ const FilePath &clangIncludeDir)
{
- QTC_ASSERT(!baseDir.isEmpty(), return GenerateCompilationDbResult(QString(),
- Tr::tr("Could not retrieve build directory.")));
+ QTC_ASSERT(!baseDir.isEmpty(),
+ promise.addResult(make_unexpected(Tr::tr("Could not retrieve build directory."))); return);
QTC_ASSERT(!projectInfoList.isEmpty(),
- return GenerateCompilationDbResult(QString(), "Could not retrieve project info."));
+ promise.addResult(make_unexpected(Tr::tr("Could not retrieve project info."))); return);
QTC_CHECK(baseDir.ensureWritableDir());
QFile compileCommandsFile(baseDir.pathAppended("compile_commands.json").toFSPathString());
const bool fileOpened = compileCommandsFile.open(QIODevice::WriteOnly | QIODevice::Truncate);
if (!fileOpened) {
- return GenerateCompilationDbResult(QString(), Tr::tr("Could not create \"%1\": %2")
- .arg(compileCommandsFile.fileName(), compileCommandsFile.errorString()));
+ promise.addResult(make_unexpected(Tr::tr("Could not create \"%1\": %2")
+ .arg(compileCommandsFile.fileName(), compileCommandsFile.errorString())));
+ return;
}
compileCommandsFile.write("[");
@@ -182,6 +185,8 @@ GenerateCompilationDbResult generateCompilationDB(QList<ProjectInfo::ConstPtr> p
jsonProjectOptions);
}
for (const ProjectFile &projFile : projectPart->files) {
+ if (promise.isCanceled())
+ return;
const QJsonObject json
= createFileObject(baseDir,
args,
@@ -200,7 +205,7 @@ GenerateCompilationDbResult generateCompilationDB(QList<ProjectInfo::ConstPtr> p
compileCommandsFile.write("]");
compileCommandsFile.close();
- return GenerateCompilationDbResult(compileCommandsFile.fileName(), QString());
+ promise.addResult(FilePath::fromUserInput(compileCommandsFile.fileName()));
}
FilePath currentCppEditorDocumentFilePath()
@@ -262,7 +267,6 @@ QString DiagnosticTextInfo::clazyCheckName(const QString &option)
return option;
}
-
QJsonArray clangOptionsForFile(const ProjectFile &file, const ProjectPart &projectPart,
const QJsonArray &generalOptions, UsePrecompiledHeaders usePch,
bool clStyle)
diff --git a/src/plugins/clangcodemodel/clangutils.h b/src/plugins/clangcodemodel/clangutils.h
index 6b99be52e6..d78dab7231 100644
--- a/src/plugins/clangcodemodel/clangutils.h
+++ b/src/plugins/clangcodemodel/clangutils.h
@@ -53,23 +53,16 @@ Utils::FilePath currentCppEditorDocumentFilePath();
QString diagnosticCategoryPrefixRemoved(const QString &text);
-class GenerateCompilationDbResult
-{
-public:
- GenerateCompilationDbResult() = default;
- GenerateCompilationDbResult(const QString &filePath, const QString &error)
- : filePath(filePath), error(error)
- {}
-
- QString filePath;
- QString error;
-};
-
+using GenerateCompilationDbResult = Utils::expected_str<Utils::FilePath>;
enum class CompilationDbPurpose { Project, CodeModel };
-GenerateCompilationDbResult generateCompilationDB(QList<CppEditor::ProjectInfo::ConstPtr> projectInfo,
- Utils::FilePath baseDir, CompilationDbPurpose purpose,
- CppEditor::ClangDiagnosticConfig warningsConfig, QStringList projectOptions,
- Utils::FilePath clangIncludeDir);
+void generateCompilationDB(
+ QPromise<GenerateCompilationDbResult> &promise,
+ const QList<CppEditor::ProjectInfo::ConstPtr> &projectInfoList,
+ const Utils::FilePath &baseDir,
+ CompilationDbPurpose purpose,
+ const CppEditor::ClangDiagnosticConfig &warningsConfig,
+ const QStringList &projectOptions,
+ const Utils::FilePath &clangIncludeDir);
class DiagnosticTextInfo
{
diff --git a/src/plugins/clangcodemodel/test/clangdtests.cpp b/src/plugins/clangcodemodel/test/clangdtests.cpp
index f18c164155..84a155b535 100644
--- a/src/plugins/clangcodemodel/test/clangdtests.cpp
+++ b/src/plugins/clangcodemodel/test/clangdtests.cpp
@@ -1107,8 +1107,7 @@ void ClangdTestHighlighting::test_data()
QTest::newRow("call to function pointer alias") << 344 << 5 << 344 << 13
<< QList<int>{C_TYPE} << 0;
QTest::newRow("friend class declaration") << 350 << 18 << 350 << 27
- << (client()->versionNumber().majorVersion() >= 16
- ? QList<int>{C_TYPE, C_DECLARATION}: QList<int>{C_TYPE}) << 0;
+ << QList<int>{C_TYPE, C_DECLARATION} << 0;
QTest::newRow("friend class reference") << 351 << 34 << 351 << 43
<< QList<int>{C_TYPE} << 0;
QTest::newRow("function parameter of friend class type") << 351 << 45 << 351 << 50
@@ -1374,10 +1373,6 @@ void ClangdTestHighlighting::test_data()
<< QList<int>{C_PUNCTUATION} << int(CppEditor::SemanticHighlighter::AngleBracketClose);
QTest::newRow("macro in struct") << 795 << 9 << 795 << 14
<< QList<int>{C_MACRO, C_DECLARATION} << 0;
- if (client()->versionNumber() < QVersionNumber(17)) {
- QTest::newRow("#ifdef'ed out code") << 800 << 1 << 800 << 17
- << QList<int>{C_DISABLED_CODE} << 0;
- }
QTest::newRow("static function call (object)") << 819 << 5 << 819 << 6
<< QList<int>{C_LOCAL} << 0;
QTest::newRow("static function call (argument)") << 819 << 18 << 819 << 19
diff --git a/src/plugins/clangformat/ClangFormat.json.in b/src/plugins/clangformat/ClangFormat.json.in
index c444af0fcd..ba95c1c81d 100644
--- a/src/plugins/clangformat/ClangFormat.json.in
+++ b/src/plugins/clangformat/ClangFormat.json.in
@@ -13,7 +13,10 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "C++",
- "Description" : "clang-format indentation plugin.",
- "Url" : "http://www.qt.io",
+ "Description" : "Indent and format C++ code",
+ "LongDescription" : [
+ "ClangFormat is an alternative for the built-in indenter."
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/clangformat/clangformatbaseindenter.cpp b/src/plugins/clangformat/clangformatbaseindenter.cpp
index b491bd293f..295c86fcb6 100644
--- a/src/plugins/clangformat/clangformatbaseindenter.cpp
+++ b/src/plugins/clangformat/clangformatbaseindenter.cpp
@@ -34,6 +34,8 @@ using namespace std::chrono_literals;
namespace ClangFormat {
+Q_LOGGING_CATEGORY(clangIndenterLog, "qtc.dbg.clangformat", QtWarningMsg)
+
enum class ReplacementsToKeep { OnlyIndent, IndentAndBefore, All };
static Internal::LlvmFileSystemAdapter llvmFileSystemAdapter = {};
@@ -166,6 +168,7 @@ enum class CharacterContext {
LastAfterComma,
NewStatementOrContinuation,
IfOrElseWithoutScope,
+ BracketAfterFunctionCall,
Unknown
};
@@ -207,6 +210,21 @@ static bool comesDirectlyAfterIf(const QTextDocument *doc, int pos)
return pos > 0 && doc->characterAt(pos) == 'f' && doc->characterAt(pos - 1) == 'i';
}
+static bool startsWithKeyWord(const QString &keyWord, const QString &text)
+{
+ if (text.size() <= keyWord.size())
+ return false;
+
+ const QChar chAfter = text.at(keyWord.size());
+ return text.startsWith(keyWord) && !chAfter.isDigit() && !chAfter.isLetter() && chAfter != '_';
+}
+
+static bool startsWithKeyWords(const QString &text)
+{
+ return startsWithKeyWord("if", text) || startsWithKeyWord("while", text)
+ || startsWithKeyWord("for", text);
+}
+
static CharacterContext characterContext(const QTextBlock &currentBlock)
{
QTextBlock previousNonEmptyBlock = reverseFindLastEmptyBlock(currentBlock);
@@ -217,6 +235,11 @@ static CharacterContext characterContext(const QTextBlock &currentBlock)
if (prevLineText.isEmpty())
return CharacterContext::NewStatementOrContinuation;
+ const QString currentBlockText = currentBlock.text().trimmed();
+ if ((currentBlockText.isEmpty() || currentBlockText.endsWith(")"))
+ && prevLineText.endsWith("{") && !startsWithKeyWords(currentBlockText))
+ return CharacterContext::BracketAfterFunctionCall;
+
const QChar firstNonWhitespaceChar = findFirstNonWhitespaceCharacter(currentBlock);
if (prevLineText.endsWith(',')) {
if (firstNonWhitespaceChar == '}') {
@@ -266,6 +289,8 @@ static QByteArray dummyTextForContext(CharacterContext context, bool closingBrac
return "a";
case CharacterContext::IfOrElseWithoutScope:
return ";";
+ case CharacterContext::BracketAfterFunctionCall:
+ return ";";
case CharacterContext::NewStatementOrContinuation:
return "/*//*/";
case CharacterContext::Unknown:
@@ -296,6 +321,8 @@ static int forceIndentWithExtraText(QByteArray &buffer,
int firstNonWhitespace = Utils::indexOf(blockText,
[](const QChar &ch) { return !ch.isSpace(); });
int utf8Offset = Text::utf8NthLineOffset(block.document(), buffer, block.blockNumber() + 1);
+ int utf8EndOfLineOffset = utf8Offset + blockText.length();
+
if (firstNonWhitespace >= 0)
utf8Offset += firstNonWhitespace;
else
@@ -312,7 +339,9 @@ static int forceIndentWithExtraText(QByteArray &buffer,
&& nextBlockExistsAndEmpty(block)) {
// If the next line is also empty it's safer to use a comment line.
dummyText = "//";
- } else if (firstNonWhitespace < 0 || closingParenBlock || closingBraceBlock) {
+ } else if (
+ firstNonWhitespace < 0 || closingParenBlock || closingBraceBlock
+ || charContext == CharacterContext::BracketAfterFunctionCall) {
dummyText = dummyTextForContext(charContext, closingBraceBlock);
}
@@ -327,6 +356,13 @@ static int forceIndentWithExtraText(QByteArray &buffer,
extraLength += 3;
}
}
+
+ if (charContext == CharacterContext::BracketAfterFunctionCall) {
+ buffer.insert(utf8EndOfLineOffset + extraLength, dummyText);
+ extraLength += dummyText.length();
+ return extraLength;
+ }
+
buffer.insert(utf8Offset + extraLength, dummyText);
extraLength += dummyText.length();
@@ -551,6 +587,37 @@ ClangFormatBaseIndenter::~ClangFormatBaseIndenter()
delete d;
}
+static void printBuffer(QString str)
+{
+ for (const auto &line : str.split("\n")) {
+ qCDebug(clangIndenterLog) << line;
+ }
+}
+
+static void printDebugInfo(
+ const QByteArray &buffer,
+ clang::tooling::Replacements replacements,
+ const QString &additionalInfo)
+{
+ if (!clangIndenterLog().isInfoEnabled())
+ return;
+
+ QString str = QString::fromStdString(buffer.data());
+
+ if (replacements.empty()) {
+ std::string code = buffer.data();
+ llvm::Expected<std::string> code_new
+ = clang::tooling::applyAllReplacements(code, replacements);
+ if (!code_new)
+ return;
+
+ str = QString::fromStdString(code_new.get());
+ }
+ qCDebug(clangIndenterLog) << additionalInfo << str;
+
+ printBuffer(str);
+}
+
ChangeSet ClangFormatBaseIndenterPrivate::replacements(QByteArray buffer,
const QTextBlock &startBlock,
const QTextBlock &endBlock,
@@ -584,6 +651,8 @@ ChangeSet ClangFormatBaseIndenterPrivate::replacements(QByteArray buffer,
}
}
+ printDebugInfo(buffer, {}, "before");
+
if (replacementsToKeep != ReplacementsToKeep::IndentAndBefore || utf8Offset < rangeStart)
rangeStart = utf8Offset;
@@ -594,6 +663,8 @@ ChangeSet ClangFormatBaseIndenterPrivate::replacements(QByteArray buffer,
clang::tooling::Replacements clangReplacements = clang::format::reformat(
style, buffer.data(), ranges, m_fileName->toFSPathString().toStdString(), &status);
+ printDebugInfo(buffer, clangReplacements, "after");
+
clang::tooling::Replacements filtered;
if (status.FormatComplete) {
filtered = filteredReplacements(buffer,
@@ -602,6 +673,9 @@ ChangeSet ClangFormatBaseIndenterPrivate::replacements(QByteArray buffer,
utf8Length,
replacementsToKeep);
}
+
+ printDebugInfo(buffer, filtered, "filtered");
+
const bool canTryAgain = replacementsToKeep == ReplacementsToKeep::OnlyIndent
&& typedChar == QChar::Null && !secondTry;
if (canTryAgain && filtered.empty()) {
@@ -648,7 +722,7 @@ EditOperations ClangFormatBaseIndenter::format(const RangesInLines &rangesInLine
assumedFileName);
auto changedCode = clang::tooling::applyAllReplacements(buffer.data(), clangReplacements);
QTC_ASSERT(changedCode, {
- qDebug() << QString::fromStdString(llvm::toString(changedCode.takeError()));
+ qCDebug(clangIndenterLog) << QString::fromStdString(llvm::toString(changedCode.takeError()));
return {};
});
ranges = clang::tooling::calculateRangesAfterReplacements(clangReplacements, ranges);
@@ -724,12 +798,15 @@ void ClangFormatBaseIndenterPrivate::indent(const QTextCursor &cursor,
const QChar &typedChar,
int cursorPositionInEditor)
{
+ const QString blockText = cursor.block().text().trimmed();
if (cursor.hasSelection()) {
indentBlocks(m_doc->findBlock(cursor.selectionStart()),
m_doc->findBlock(cursor.selectionEnd()),
typedChar,
cursorPositionInEditor);
- } else {
+ } else if (
+ typedChar == QChar::Null || blockText.startsWith(typedChar) || blockText.endsWith(typedChar)
+ || blockText.isEmpty()) {
indentBlocks(cursor.block(), cursor.block(), typedChar, cursorPositionInEditor);
}
}
diff --git a/src/plugins/clangformat/clangformatbaseindenter.h b/src/plugins/clangformat/clangformatbaseindenter.h
index cb8041cf43..e6512f7862 100644
--- a/src/plugins/clangformat/clangformatbaseindenter.h
+++ b/src/plugins/clangformat/clangformatbaseindenter.h
@@ -5,10 +5,14 @@
#include <texteditor/indenter.h>
+#include <QLoggingCategory>
+
namespace clang::format { struct FormatStyle; }
namespace ClangFormat {
+Q_DECLARE_LOGGING_CATEGORY(clangIndenterLog)
+
class ClangFormatBaseIndenter : public TextEditor::Indenter
{
public:
diff --git a/src/plugins/clangformat/clangformatglobalconfigwidget.cpp b/src/plugins/clangformat/clangformatglobalconfigwidget.cpp
index 4dc344601d..2762982342 100644
--- a/src/plugins/clangformat/clangformatglobalconfigwidget.cpp
+++ b/src/plugins/clangformat/clangformatglobalconfigwidget.cpp
@@ -26,6 +26,7 @@
#include <QCheckBox>
#include <QComboBox>
+#include <QGroupBox>
#include <QLabel>
#include <QSpinBox>
@@ -106,9 +107,10 @@ ClangFormatGlobalConfigWidget::ClangFormatGlobalConfigWidget(ICodeStylePreferenc
QWidget *globalSettingsGroupBoxWidget = nullptr;
+ // clang-format off
Group globalSettingsGroupBox {
bindTo(&globalSettingsGroupBoxWidget),
- title(Tr::tr("ClangFormat settings:")),
+ title(Tr::tr("ClangFormat Settings:")),
Column {
m_useGlobalSettings,
Form {
@@ -127,6 +129,7 @@ ClangFormatGlobalConfigWidget::ClangFormatGlobalConfigWidget(ICodeStylePreferenc
globalSettingsGroupBox,
noMargin
}.attachTo(this);
+ // clang-format on
initCheckBoxes();
initIndentationOrFormattingCombobox();
diff --git a/src/plugins/clangformat/clangformatindenter.cpp b/src/plugins/clangformat/clangformatindenter.cpp
index 95d847150f..fc213da18c 100644
--- a/src/plugins/clangformat/clangformatindenter.cpp
+++ b/src/plugins/clangformat/clangformatindenter.cpp
@@ -33,7 +33,7 @@ namespace ClangFormat {
static bool isBeautifierPluginActivated()
{
- const QVector<ExtensionSystem::PluginSpec *> specs = ExtensionSystem::PluginManager::plugins();
+ const ExtensionSystem::PluginSpecs specs = ExtensionSystem::PluginManager::plugins();
return std::find_if(specs.begin(),
specs.end(),
[](ExtensionSystem::PluginSpec *spec) {
diff --git a/src/plugins/clangformat/clangformatutils.cpp b/src/plugins/clangformat/clangformatutils.cpp
index 7ccc9ad718..6732524f41 100644
--- a/src/plugins/clangformat/clangformatutils.cpp
+++ b/src/plugins/clangformat/clangformatutils.cpp
@@ -22,6 +22,7 @@
#include <utils/expected.h>
#include <QCryptographicHash>
+#include <QLoggingCategory>
using namespace clang;
using namespace format;
diff --git a/src/plugins/clangformat/tests/clangformat-test.cpp b/src/plugins/clangformat/tests/clangformat-test.cpp
index 51f78f90af..6e7cdd367b 100644
--- a/src/plugins/clangformat/tests/clangformat-test.cpp
+++ b/src/plugins/clangformat/tests/clangformat-test.cpp
@@ -111,6 +111,13 @@ private slots:
void testIndentFunctionArgumentOnNewLine();
void testIndentCommentOnNewLine();
void testUtf8SymbolLine();
+ void testFunctionCallClosingParenthesis();
+ void testFunctionCallClosingParenthesisEmptyLine();
+ void testNoIndentationInMiddleOfLine();
+ void testIndentationInMiddleOfLine();
+ void testIndentationInTheBegginingOfLine();
+ void testIndentationReturnAfterIf();
+ void testIndentationReturnAfterIfSomthingFunction();
private:
void insertLines(const std::vector<QString> &lines);
@@ -833,6 +840,165 @@ void ClangFormatTest::testUtf8SymbolLine()
"}"}));
}
+void ClangFormatTest::testFunctionCallClosingParenthesis()
+{
+ insertLines(
+ {"class X {",
+ "public:",
+ " QString add() const;",
+ " QString sub() const;",
+ "};",
+ "",
+ "QString X::add() const",
+ "{",
+ " return QString()",
+ "}",
+ "",
+ "QString X::sub() const",
+ "{}"});
+ m_indenter->indentBlock(m_doc->findBlockByNumber(8), ')', TextEditor::TabSettings());
+ QCOMPARE(
+ documentLines(),
+ (std::vector<QString>{
+ "class X {",
+ "public:",
+ " QString add() const;",
+ " QString sub() const;",
+ "};",
+ "",
+ "QString X::add() const",
+ "{",
+ " return QString()",
+ "}",
+ "",
+ "QString X::sub() const",
+ "{}",
+ }));
+}
+
+void ClangFormatTest::testFunctionCallClosingParenthesisEmptyLine()
+{
+ insertLines(
+ {"class X {",
+ "public:",
+ " QString add() const;",
+ " QString sub() const;",
+ "};",
+ "",
+ "QString X::add() const",
+ "{",
+ "",
+ " return QString()",
+ "}",
+ "",
+ "QString X::sub() const",
+ "{}"});
+ m_indenter->indentBlock(m_doc->findBlockByNumber(8), QChar::Null, TextEditor::TabSettings());
+ QCOMPARE(
+ documentLines(),
+ (std::vector<QString>{
+ "class X {",
+ "public:",
+ " QString add() const;",
+ " QString sub() const;",
+ "};",
+ "",
+ "QString X::add() const",
+ "{",
+ " ",
+ " return QString()",
+ "}",
+ "",
+ "QString X::sub() const",
+ "{}",
+ }));
+}
+
+void ClangFormatTest::testNoIndentationInMiddleOfLine()
+{
+ insertLines({"int main()",
+ "{",
+ " S s = {.i = 1}, .l = 2, .f = 3, .d = 4};",
+ " return 0;",
+ "}"});
+ m_cursor->setPosition(30);
+ m_extendedIndenter->indent(*m_cursor, '}', TextEditor::TabSettings(), 30);
+ QCOMPARE(documentLines(),
+ (std::vector<QString>{"int main()",
+ "{",
+ " S s = {.i = 1}, .l = 2, .f = 3, .d = 4};",
+ " return 0;",
+ "}"}));
+}
+
+void ClangFormatTest::testIndentationInMiddleOfLine()
+{
+ insertLines({"int main()",
+ "{",
+ " S s = {.i = 1,",
+ ".l = 2, .f = 3, .d = 4};",
+ " return 0;",
+ "}"});
+ m_cursor->setPosition(32);
+ m_extendedIndenter->indent(*m_cursor, QChar::Null, TextEditor::TabSettings(), 32);
+ QCOMPARE(documentLines(),
+ (std::vector<QString>{"int main()",
+ "{",
+ " S s = {.i = 1,",
+ " .l = 2, .f = 3, .d = 4};",
+ " return 0;",
+ "}"}));
+}
+
+void ClangFormatTest::testIndentationInTheBegginingOfLine()
+{
+ insertLines({"int main()",
+ "{",
+ " if () {",
+ " } else",
+ "}"});
+ m_cursor->setPosition(35);
+ m_extendedIndenter->indent(*m_cursor, '}', TextEditor::TabSettings(), 35);
+ QCOMPARE(documentLines(),
+ (std::vector<QString>{"int main()",
+ "{",
+ " if () {",
+ " } else",
+ "}"}));
+}
+
+void ClangFormatTest::testIndentationReturnAfterIf()
+{
+ insertLines({"int main()",
+ "{",
+ " if (true)",
+ " return 0;",
+ "}"});
+ m_indenter->indent(*m_cursor, QChar::Null, TextEditor::TabSettings());
+ QCOMPARE(documentLines(),
+ (std::vector<QString>{"int main()",
+ "{",
+ " if (true)",
+ " return 0;",
+ "}"}));
+}
+
+void ClangFormatTest::testIndentationReturnAfterIfSomthingFunction()
+{
+ insertLines({"int main()",
+ "{",
+ " if_somthing()",
+ " return 0;",
+ "}"});
+ m_indenter->indent(*m_cursor, QChar::Null, TextEditor::TabSettings());
+ QCOMPARE(documentLines(),
+ (std::vector<QString>{"int main()",
+ "{",
+ " if_somthing()",
+ " return 0;",
+ "}"}));
+}
+
QObject *createClangFormatTest()
{
return new ClangFormatTest;
diff --git a/src/plugins/clangtools/ClangTools.json.in b/src/plugins/clangtools/ClangTools.json.in
index d6d1280c40..04972829c0 100644
--- a/src/plugins/clangtools/ClangTools.json.in
+++ b/src/plugins/clangtools/ClangTools.json.in
@@ -13,7 +13,12 @@
"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."
],
"Category" : "Code Analyzer",
- "Description" : "ClangTools Plugin.",
- "Url" : "http://www.qt.io",
+ "Description" : "Find problems in C, C++, and Objective-C source code",
+ "LongDescription" : [
+ "Use Clang Tools for static code analysis:",
+ "- Clang-Tidy has diagnostics and fixes for typical programming errors, such as style violations or interface misuse.",
+ "- Clazy helps Clang understand Qt semantics. It displays Qt related compiler warnings, ranging from unnecessary memory allocation to misuse of API and has refactoring actions for fixing some of the issues."
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/clangtools/clangtool.cpp b/src/plugins/clangtools/clangtool.cpp
index dd4c7612bd..21b5ea6fbd 100644
--- a/src/plugins/clangtools/clangtool.cpp
+++ b/src/plugins/clangtools/clangtool.cpp
@@ -136,8 +136,8 @@ public:
setLayout(layout);
QPalette pal;
- pal.setColor(QPalette::Window, Utils::creatorTheme()->color(Theme::InfoBarBackground));
- pal.setColor(QPalette::WindowText, Utils::creatorTheme()->color(Theme::InfoBarText));
+ pal.setColor(QPalette::Window, Utils::creatorColor(Theme::InfoBarBackground));
+ pal.setColor(QPalette::WindowText, Utils::creatorColor(Theme::InfoBarText));
setPalette(pal);
setAutoFillBackground(true);
diff --git a/src/plugins/clangtools/clangtoolrunner.cpp b/src/plugins/clangtools/clangtoolrunner.cpp
index af3539b2ac..c91d9e9cfa 100644
--- a/src/plugins/clangtools/clangtoolrunner.cpp
+++ b/src/plugins/clangtools/clangtoolrunner.cpp
@@ -14,8 +14,6 @@
#include <cppeditor/cppprojectfile.h>
#include <cppeditor/cpptoolsreuse.h>
-#include <extensionsystem/pluginmanager.h>
-
#include <utils/async.h>
#include <utils/qtcprocess.h>
#include <utils/qtcassert.h>
@@ -163,13 +161,9 @@ GroupItem clangToolTask(const AnalyzeUnits &units,
process.setWorkingDirectory(input.outputDirPath); // Current clang-cl puts log file into working dir.
const ClangToolStorage &data = *storage;
-
- const QStringList args = checksArguments(unit, input)
- + mainToolArguments(data)
- + QStringList{"--"}
- + clangArguments(unit, input);
- const CommandLine commandLine = {data.executable, args};
-
+ const CommandLine commandLine{data.executable, {checksArguments(unit, input),
+ mainToolArguments(data), "--",
+ clangArguments(unit, input)}};
qCDebug(LOG).noquote() << "Starting" << commandLine.toUserOutput();
process.setCommand(commandLine);
};
@@ -208,7 +202,6 @@ GroupItem clangToolTask(const AnalyzeUnits &units,
data.setConcurrentCallData(&parseDiagnostics,
storage->outputFilePath,
input.diagnosticsFilter);
- data.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
};
const auto onReadDone = [storage, input, outputHandler, iterator](
const Async<expected_str<Diagnostics>> &data, DoneWith result) {
diff --git a/src/plugins/clangtools/clangtoolsdiagnosticview.cpp b/src/plugins/clangtools/clangtoolsdiagnosticview.cpp
index e3d63e1ec9..47d891ae3b 100644
--- a/src/plugins/clangtools/clangtoolsdiagnosticview.cpp
+++ b/src/plugins/clangtools/clangtoolsdiagnosticview.cpp
@@ -14,10 +14,8 @@
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/manhattanstyle.h>
-#include <cppeditor/cppeditorwidget.h>
#include <cppeditor/cppmodelmanager.h>
#include <cppeditor/cpprefactoringchanges.h>
-#include <cppeditor/cppsemanticinfo.h>
#include <debugger/analyzer/diagnosticlocation.h>
@@ -256,19 +254,7 @@ void DiagnosticView::suppressCurrentDiagnosticInline()
CppRefactoringChanges changes(CppModelManager::snapshot());
for (auto it = diagnosticsPerFileAndLine.cbegin(); it != diagnosticsPerFileAndLine.cend(); ++it) {
- const Utils::FilePath filePath = it.key();
- CppEditorWidget *editorWidget = nullptr;
- const QList<Core::IEditor *> editors = Core::DocumentModel::editorsForFilePath(filePath);
- for (Core::IEditor *editor : editors) {
- const auto textEditor = qobject_cast<TextEditor::BaseTextEditor *>(editor);
- if (textEditor)
- editorWidget = qobject_cast<CppEditorWidget *>(textEditor->editorWidget());
- if (editorWidget)
- break;
- }
- CppRefactoringFilePtr refactoringFile
- = editorWidget ? changes.file(editorWidget, editorWidget->semanticInfo().doc)
- : changes.cppFile(filePath);
+ const CppRefactoringFilePtr refactoringFile = changes.cppFile(it.key());
Utils::ChangeSet changeSet;
for (auto it2 = it.value().cbegin(); it2 != it.value().cend(); ++it2) {
@@ -328,8 +314,7 @@ void DiagnosticView::suppressCurrentDiagnosticInline()
changeSet.insert(insertStart, newText);
}
}
- refactoringFile->setChangeSet(changeSet);
- refactoringFile->apply();
+ refactoringFile->apply(changeSet);
}
filterModel->addSuppressedDiagnostics(diags);
diff --git a/src/plugins/clangtools/documentquickfixfactory.h b/src/plugins/clangtools/documentquickfixfactory.h
index 98f45af46e..6e99b1d345 100644
--- a/src/plugins/clangtools/documentquickfixfactory.h
+++ b/src/plugins/clangtools/documentquickfixfactory.h
@@ -3,7 +3,7 @@
#pragma once
-#include <cppeditor/cppquickfix.h>
+#include <cppeditor/quickfixes/cppquickfix.h>
namespace ClangTools {
namespace Internal {
diff --git a/src/plugins/clangtools/executableinfo.cpp b/src/plugins/clangtools/executableinfo.cpp
index 7cda766e7d..23dc807367 100644
--- a/src/plugins/clangtools/executableinfo.cpp
+++ b/src/plugins/clangtools/executableinfo.cpp
@@ -106,15 +106,14 @@ static ClazyChecks querySupportedClazyChecks(const FilePath &executablePath)
};
static const QString queryFlag = "-supported-checks-json";
- DataFromProcess<ClazyChecks>::Parameters params(CommandLine(executablePath, {queryFlag}),
- parser);
+ DataFromProcess<ClazyChecks>::Parameters params({executablePath, {queryFlag}}, parser);
params.environment.setupEnglishOutput();
params.errorHandler = handleProcessError;
auto checks = DataFromProcess<ClazyChecks>::getData(params);
if (!checks) {
// Some clazy 1.6.x versions have a bug where they expect an argument after the
// option.
- params.commandLine = CommandLine(executablePath, {queryFlag, "dummy"});
+ params.commandLine = {executablePath, {queryFlag, "dummy"}};
checks = DataFromProcess<ClazyChecks>::getData(params);
}
if (checks)
diff --git a/src/plugins/classview/ClassView.json.in b/src/plugins/classview/ClassView.json.in
index 753824248b..0c0572a048 100644
--- a/src/plugins/classview/ClassView.json.in
+++ b/src/plugins/classview/ClassView.json.in
@@ -13,7 +13,8 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "C++",
- "Description" : "Class View component.",
- "Url" : "http://www.qt.io",
+ "Description" : "View the class hierarchy of currently open projects",
+ "LongDescription" : [],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/clearcase/ClearCase.json.in b/src/plugins/clearcase/ClearCase.json.in
index 8bcfbb66bc..a4f997601d 100644
--- a/src/plugins/clearcase/ClearCase.json.in
+++ b/src/plugins/clearcase/ClearCase.json.in
@@ -15,8 +15,12 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Version Control",
- "Description" : "ClearCase integration.",
- "Url" : "http://www.qt.io",
+ "Description" : "Access an IBM Rational ClearCase server",
+ "LongDescription" : [
+ "You also need:",
+ "- ClearCase"
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES},
"Mimetypes" : [
diff --git a/src/plugins/clearcase/clearcaseplugin.cpp b/src/plugins/clearcase/clearcaseplugin.cpp
index bc4cc82958..9fa1a06c8a 100644
--- a/src/plugins/clearcase/clearcaseplugin.cpp
+++ b/src/plugins/clearcase/clearcaseplugin.cpp
@@ -2265,14 +2265,10 @@ void ClearCasePluginPrivate::diffGraphical(const QString &file1, const QString &
QString ClearCasePluginPrivate::runExtDiff(const FilePath &workingDir, const QStringList &arguments,
int timeOutS, QTextCodec *outputCodec)
{
- CommandLine diff("diff");
- diff.addArgs(m_settings.diffArgs.split(' ', Qt::SkipEmptyParts));
- diff.addArgs(arguments);
-
Process process;
process.setWorkingDirectory(workingDir);
process.setCodec(outputCodec ? outputCodec : QTextCodec::codecForName("UTF-8"));
- process.setCommand(diff);
+ process.setCommand({"diff", {m_settings.diffArgs.split(' ', Qt::SkipEmptyParts), arguments}});
process.runBlocking(seconds(timeOutS), EventLoopMode::On);
if (process.result() != ProcessResult::FinishedWithSuccess)
return {};
diff --git a/src/plugins/cmakeprojectmanager/CMakeLists.txt b/src/plugins/cmakeprojectmanager/CMakeLists.txt
index 492d628414..2c58b4f08b 100644
--- a/src/plugins/cmakeprojectmanager/CMakeLists.txt
+++ b/src/plugins/cmakeprojectmanager/CMakeLists.txt
@@ -1,7 +1,7 @@
add_qtc_plugin(CMakeProjectManager
PLUGIN_CLASS CMakeProjectPlugin
DEPENDS QmlJS
- PLUGIN_DEPENDS Core CppEditor ProjectExplorer TextEditor QtSupport
+ PLUGIN_DEPENDS Core CppEditor Debugger ProjectExplorer TextEditor QtSupport
SYSTEM_INCLUDES 3dparty/cmake
SOURCES
builddirparameters.cpp builddirparameters.h
diff --git a/src/plugins/cmakeprojectmanager/CMakeProjectManager.json.in b/src/plugins/cmakeprojectmanager/CMakeProjectManager.json.in
index fc239c6a57..72621cda10 100644
--- a/src/plugins/cmakeprojectmanager/CMakeProjectManager.json.in
+++ b/src/plugins/cmakeprojectmanager/CMakeProjectManager.json.in
@@ -13,8 +13,14 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Build Systems",
- "Description" : "CMake support.",
- "Url" : "http://www.qt.io",
+ "Description" : "Automate the configuration of build systems with CMake",
+ "LongDescription" : [
+ "CMake controls the software compilation process by using simple configuration files, called CMakeLists.txt files.",
+ "You also need:",
+ "- CMake",
+ "Qt Online Installer installs a CMake version that you can use."
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES},
"Mimetypes" : [
diff --git a/src/plugins/cmakeprojectmanager/builddirparameters.cpp b/src/plugins/cmakeprojectmanager/builddirparameters.cpp
index 71f6b82088..d78e7180a6 100644
--- a/src/plugins/cmakeprojectmanager/builddirparameters.cpp
+++ b/src/plugins/cmakeprojectmanager/builddirparameters.cpp
@@ -50,13 +50,13 @@ BuildDirParameters::BuildDirParameters(CMakeBuildSystem *buildSystem)
});
const Target *t = bc->target();
const Kit *k = t->kit();
- const Project *p = t->project();
- projectName = p->displayName();
+ project = t->project();
+ projectName = project->displayName();
sourceDirectory = bc->sourceDirectory();
if (sourceDirectory.isEmpty())
- sourceDirectory = p->projectDirectory();
+ sourceDirectory = project->projectDirectory();
buildDirectory = bc->buildDirectory();
cmakeBuildType = buildSystem->cmakeBuildType();
diff --git a/src/plugins/cmakeprojectmanager/builddirparameters.h b/src/plugins/cmakeprojectmanager/builddirparameters.h
index f2648f7773..b73993eb37 100644
--- a/src/plugins/cmakeprojectmanager/builddirparameters.h
+++ b/src/plugins/cmakeprojectmanager/builddirparameters.h
@@ -15,6 +15,10 @@ class MacroExpander;
class OutputLineParser;
} // namespace Utils
+namespace ProjectExplorer {
+class Project;
+}
+
namespace CMakeProjectManager::Internal {
class CMakeBuildSystem;
@@ -29,6 +33,7 @@ public:
CMakeTool *cmakeTool() const;
QString projectName;
+ ProjectExplorer::Project *project = nullptr;
Utils::FilePath sourceDirectory;
Utils::FilePath buildDirectory;
diff --git a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp
index 0106ec5896..274ebd8f83 100644
--- a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp
+++ b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp
@@ -34,6 +34,7 @@
#include <projectexplorer/devicesupport/idevice.h>
#include <projectexplorer/environmentaspectwidget.h>
#include <projectexplorer/environmentwidget.h>
+#include <projectexplorer/gcctoolchain.h>
#include <projectexplorer/kitaspects.h>
#include <projectexplorer/namedwidget.h>
#include <projectexplorer/processparameters.h>
@@ -96,6 +97,13 @@ const char USER_ENVIRONMENT_CHANGES_KEY[] = "CMake.Configure.UserEnvironmentChan
const char BASE_ENVIRONMENT_KEY[] = "CMake.Configure.BaseEnvironment";
const char GENERATE_QMLLS_INI_SETTING[] = "J.QtQuick/QmlJSEditor.GenerateQmllsIniFiles";
+const char CMAKE_TOOLCHAIN_FILE[] = "CMAKE_TOOLCHAIN_FILE";
+const char CMAKE_C_FLAGS_INIT[] = "CMAKE_C_FLAGS_INIT";
+const char CMAKE_CXX_FLAGS_INIT[] = "CMAKE_CXX_FLAGS_INIT";
+const char CMAKE_CXX_FLAGS[] = "CMAKE_CXX_FLAGS";
+const char CMAKE_CXX_FLAGS_DEBUG[] = "CMAKE_CXX_FLAGS_DEBUG";
+const char CMAKE_CXX_FLAGS_RELWITHDEBINFO[] = "CMAKE_CXX_FLAGS_RELWITHDEBINFO";
+
namespace Internal {
class CMakeBuildSettingsWidget : public NamedWidget
@@ -128,6 +136,7 @@ private:
void kitCMakeConfiguration();
void updateConfigureDetailsWidgetsSummary(
const QStringList &configurationArguments = QStringList());
+ void updatePackageManagerAutoSetup(CMakeConfig &initialList);
CMakeBuildConfiguration *m_buildConfig;
QTreeView *m_configView;
@@ -166,6 +175,16 @@ static QModelIndex mapToSource(const QAbstractItemView *view, const QModelIndex
return result;
}
+static CMakeConfigItem getPackageManagerAutoSetupParameter()
+{
+ const QByteArray key("CMAKE_PROJECT_INCLUDE_BEFORE");
+ const QByteArray value = QString(
+ "%{BuildConfig:BuildDirectory:NativeFilePath}/%1/auto-setup.cmake")
+ .arg(Constants::PACKAGE_MANAGER_DIR)
+ .toUtf8();
+ return CMakeConfigItem(key, CMakeConfigItem::FILEPATH, value);
+}
+
CMakeBuildSettingsWidget::CMakeBuildSettingsWidget(CMakeBuildConfiguration *bc) :
NamedWidget(Tr::tr("CMake")),
m_buildConfig(bc),
@@ -297,7 +316,8 @@ CMakeBuildSettingsWidget::CMakeBuildSettingsWidget(CMakeBuildConfiguration *bc)
m_batchEditButton->setToolTip(Tr::tr("Set or reset multiple values in the CMake configuration."));
m_showAdvancedCheckBox = new QCheckBox(Tr::tr("Advanced"));
- m_showAdvancedCheckBox->setChecked(settings().showAdvancedOptionsByDefault());
+ m_showAdvancedCheckBox->setChecked(
+ settings(m_buildConfig->project()).showAdvancedOptionsByDefault());
connect(m_configView->selectionModel(), &QItemSelectionModel::selectionChanged,
this, [this](const QItemSelection &, const QItemSelection &) {
@@ -342,7 +362,7 @@ CMakeBuildSettingsWidget::CMakeBuildSettingsWidget(CMakeBuildConfiguration *bc)
Column {
cmakeConfiguration,
Row {
- bc->initialCMakeArguments, br,
+ bc->initialCMakeArguments,
bc->additionalCMakeOptions
},
m_reconfigureButton,
@@ -586,11 +606,11 @@ void CMakeBuildSettingsWidget::reconfigureWithInitialParameters()
Core::ICore::dialogParent(),
Tr::tr("Re-configure with Initial Parameters"),
Tr::tr("Clear CMake configuration and configure with initial parameters?"),
- settings().askBeforeReConfigureInitialParams.askAgainCheckableDecider(),
+ settings(m_buildConfig->project()).askBeforeReConfigureInitialParams.askAgainCheckableDecider(),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::Yes);
- settings().writeSettings();
+ settings(m_buildConfig->project()).writeSettings();
if (reply != QMessageBox::Yes)
return;
@@ -603,6 +623,24 @@ void CMakeBuildSettingsWidget::reconfigureWithInitialParameters()
m_buildConfig->cmakeBuildSystem()->runCMake();
}
+void CMakeBuildSettingsWidget::updatePackageManagerAutoSetup(CMakeConfig &initialList)
+{
+ const bool usePackageManagerAutoSetup
+ = settings(m_buildConfig->project()).packageManagerAutoSetup();
+
+ const auto autoSetupParameter = getPackageManagerAutoSetupParameter();
+ auto it
+ = std::find_if(initialList.begin(), initialList.end(), [&autoSetupParameter](const CMakeConfigItem &item) {
+ return item.key == autoSetupParameter.key;
+ });
+ if (it != initialList.end()) {
+ if (!usePackageManagerAutoSetup && it->value == autoSetupParameter.value)
+ initialList.erase(it);
+ } else if (usePackageManagerAutoSetup) {
+ initialList.push_back(autoSetupParameter);
+ }
+}
+
void CMakeBuildSettingsWidget::updateInitialCMakeArguments()
{
CMakeConfig initialList = m_buildConfig->initialCMakeArguments.cmakeConfiguration();
@@ -636,6 +674,8 @@ void CMakeBuildSettingsWidget::updateInitialCMakeArguments()
}
}
+ updatePackageManagerAutoSetup(initialList);
+
m_buildConfig->initialCMakeArguments.setCMakeConfiguration(initialList);
// value() will contain only the unknown arguments (the non -D/-U arguments)
@@ -856,11 +896,11 @@ CMakeConfig CMakeBuildSettingsWidget::getQmlDebugCxxFlags()
const bool enable = m_buildConfig->qmlDebugging() == TriState::Enabled;
const CMakeConfig configList = m_buildConfig->cmakeBuildSystem()->configurationFromCMake();
- const QByteArrayList cxxFlagsPrev{"CMAKE_CXX_FLAGS",
- "CMAKE_CXX_FLAGS_DEBUG",
- "CMAKE_CXX_FLAGS_RELWITHDEBINFO",
- "CMAKE_CXX_FLAGS_INIT"};
- const QByteArrayList cxxFlags{"CMAKE_CXX_FLAGS_INIT", "CMAKE_CXX_FLAGS"};
+ const QByteArrayList cxxFlagsPrev{CMAKE_CXX_FLAGS,
+ CMAKE_CXX_FLAGS_DEBUG,
+ CMAKE_CXX_FLAGS_RELWITHDEBINFO,
+ CMAKE_CXX_FLAGS_INIT};
+ const QByteArrayList cxxFlags{CMAKE_CXX_FLAGS_INIT, CMAKE_CXX_FLAGS};
const QByteArray qmlDebug(QT_QML_DEBUG_PARAM);
CMakeConfig changedConfig;
@@ -1113,7 +1153,8 @@ static bool isWindowsARM64(const Kit *k)
&& targetAbi.wordWidth() == 64;
}
-static CommandLine defaultInitialCMakeCommand(const Kit *k, const QString &buildType)
+static CommandLine defaultInitialCMakeCommand(
+ const Kit *k, Project *project, const QString &buildType)
{
// Generator:
CMakeTool *tool = CMakeKitAspect::cmakeTool(k);
@@ -1127,11 +1168,8 @@ static CommandLine defaultInitialCMakeCommand(const Kit *k, const QString &build
cmd.addArg("-DCMAKE_BUILD_TYPE:STRING=" + buildType);
// Package manager auto setup
- if (settings().packageManagerAutoSetup()) {
- cmd.addArg(QString("-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH="
- "%{BuildConfig:BuildDirectory:NativeFilePath}/%1/auto-setup.cmake")
- .arg(Constants::PACKAGE_MANAGER_DIR));
- }
+ if (settings(project).packageManagerAutoSetup())
+ cmd.addArg(getPackageManagerAutoSetupParameter().toArgument());
// Cross-compilation settings:
if (!CMakeBuildConfiguration::isIos(k)) { // iOS handles this differently
@@ -1146,6 +1184,30 @@ static CommandLine defaultInitialCMakeCommand(const Kit *k, const QString &build
}
}
+ // GCC compiler and linker specific flags
+ for (Toolchain *tc : ToolchainKitAspect::toolChains(k)) {
+ if (auto *gccTc = tc->asGccToolchain()) {
+ const QStringList compilerFlags = gccTc->platformCodeGenFlags();
+
+ QLatin1String languageFlagsInit;
+ if (gccTc->language() == ProjectExplorer::Constants::C_LANGUAGE_ID)
+ languageFlagsInit = QLatin1String(CMAKE_C_FLAGS_INIT);
+ else if (gccTc->language() == ProjectExplorer::Constants::CXX_LANGUAGE_ID)
+ languageFlagsInit = QLatin1String(CMAKE_CXX_FLAGS_INIT);
+
+ if (!languageFlagsInit.isEmpty() && !compilerFlags.isEmpty())
+ cmd.addArg("-D" + languageFlagsInit + ":STRING=" + compilerFlags.join(" "));
+
+ const QStringList linkerFlags = gccTc->platformLinkerFlags();
+ if (!linkerFlags.isEmpty()) {
+ const QString joinedLinkerFlags = linkerFlags.join(" ");
+ cmd.addArg("-DCMAKE_EXE_LINKER_FLAGS_INIT:STRING=" + joinedLinkerFlags);
+ cmd.addArg("-DCMAKE_MODULE_LINKER_FLAGS_INIT:STRING=" + joinedLinkerFlags);
+ cmd.addArg("-DCMAKE_SHARED_LINKER_FLAGS_INIT:STRING=" + joinedLinkerFlags);
+ }
+ }
+ }
+
cmd.addArgs(CMakeConfigurationKitAspect::toArgumentsList(k));
cmd.addArgs(CMakeConfigurationKitAspect::additionalConfiguration(k), CommandLine::Raw);
@@ -1287,13 +1349,21 @@ static void addCMakeConfigurePresetToInitialArguments(QStringList &initialArgume
}
arg = argItem.toArgument();
- } else if (argItem.key == "CMAKE_TOOLCHAIN_FILE") {
+ } else if (argItem.key == CMAKE_TOOLCHAIN_FILE) {
const FilePath argFilePath = FilePath::fromString(argItem.expandedValue(k));
const FilePath presetFilePath = FilePath::fromUserInput(
QString::fromUtf8(presetItem.value));
if (argFilePath != presetFilePath)
arg = presetItem.toArgument();
+ } else if (argItem.key == CMAKE_CXX_FLAGS_INIT) {
+ // Append the preset value with at the initial parameters value (e.g. QML Debugging)
+ if (argItem.expandedValue(k) != QString::fromUtf8(presetItem.value)) {
+ argItem.value.append(" ");
+ argItem.value.append(presetItem.value);
+
+ arg = argItem.toArgument();
+ }
} else if (argItem.expandedValue(k) != QString::fromUtf8(presetItem.value)) {
arg = presetItem.toArgument();
}
@@ -1454,7 +1524,7 @@ CMakeBuildConfiguration::CMakeBuildConfiguration(Target *target, Id id)
? extraInfoMap.value(CMAKE_BUILD_TYPE).toString()
: info.typeName;
- CommandLine cmd = defaultInitialCMakeCommand(k, buildType);
+ CommandLine cmd = defaultInitialCMakeCommand(k, target->project(), buildType);
m_buildSystem->setIsMultiConfig(CMakeGeneratorKitAspect::isMultiConfigGenerator(k));
// Android magic:
@@ -1555,7 +1625,8 @@ CMakeBuildConfiguration::CMakeBuildConfiguration(Target *target, Id id)
: TriState::Default);
if (qt && qt->isQmlDebuggingSupported())
- cmd.addArg("-DCMAKE_CXX_FLAGS_INIT:STRING=%{" + QLatin1String(QT_QML_DEBUG_FLAG) + "}");
+ cmd.addArg(
+ QLatin1String("-D") + CMAKE_CXX_FLAGS_INIT + ":STRING=%{" + QT_QML_DEBUG_FLAG + "}");
// QT_QML_GENERATE_QMLLS_INI, if enabled via the settings checkbox:
if (Core::ICore::settings()->value(GENERATE_QMLLS_INI_SETTING).toBool()) {
@@ -1616,8 +1687,8 @@ bool CMakeBuildConfiguration::hasQmlDebugging(const CMakeConfig &config)
// Determine QML debugging flags. This must match what we do in
// CMakeBuildSettingsWidget::getQmlDebugCxxFlags()
// such that in doubt we leave the QML Debugging setting at "Leave at default"
- const QString cxxFlagsInit = config.stringValueOf("CMAKE_CXX_FLAGS_INIT");
- const QString cxxFlags = config.stringValueOf("CMAKE_CXX_FLAGS");
+ const QString cxxFlagsInit = config.stringValueOf(CMAKE_CXX_FLAGS_INIT);
+ const QString cxxFlags = config.stringValueOf(CMAKE_CXX_FLAGS);
return cxxFlagsInit.contains(QT_QML_DEBUG_PARAM) && cxxFlags.contains(QT_QML_DEBUG_PARAM);
}
@@ -2038,7 +2109,7 @@ void CMakeBuildConfiguration::addToEnvironment(Utils::Environment &env) const
if (tool && tool->cmakeExecutable().needsDevice())
return;
- const FilePath ninja = settings().ninjaPath();
+ const FilePath ninja = settings(nullptr).ninjaPath();
if (!ninja.isEmpty())
env.appendOrSetPath(ninja.isFile() ? ninja.parentDir() : ninja);
}
@@ -2133,9 +2204,20 @@ void InitialCMakeArgumentsAspect::setAllValues(const QString &values, QStringLis
if (!cmakeGenerator.isEmpty())
arguments.append(cmakeGenerator);
- m_cmakeConfiguration = CMakeConfig::fromArguments(arguments, additionalOptions);
- for (CMakeConfigItem &ci : m_cmakeConfiguration)
+ CMakeConfig config = CMakeConfig::fromArguments(arguments, additionalOptions);
+ // Join CMAKE_CXX_FLAGS_INIT values if more entries are present, or skip the same
+ // values like CMAKE_EXE_LINKER_FLAGS_INIT coming from both C and CXX compilers
+ QHash<QByteArray, CMakeConfigItem> uniqueConfig;
+ for (CMakeConfigItem &ci : config) {
ci.isInitial = true;
+ if (uniqueConfig.contains(ci.key)) {
+ if (uniqueConfig[ci.key].value != ci.value)
+ uniqueConfig[ci.key].value = uniqueConfig[ci.key].value + " " + ci.value;
+ } else {
+ uniqueConfig.insert(ci.key, ci);
+ }
+ }
+ m_cmakeConfiguration = uniqueConfig.values();
// Display the unknown arguments in "Additional CMake Options"
const QString additionalOptionsValue = ProcessArgs::joinArgs(additionalOptions);
diff --git a/src/plugins/cmakeprojectmanager/cmakebuildstep.cpp b/src/plugins/cmakeprojectmanager/cmakebuildstep.cpp
index 6663b311a4..aacd273f63 100644
--- a/src/plugins/cmakeprojectmanager/cmakebuildstep.cpp
+++ b/src/plugins/cmakeprojectmanager/cmakebuildstep.cpp
@@ -436,10 +436,15 @@ CommandLine CMakeBuildStep::cmakeCommand() const
CommandLine cmd{cmakeExecutable()};
FilePath buildDirectory = ".";
- if (buildConfiguration())
+ Project *project = nullptr;
+ if (buildConfiguration()) {
buildDirectory = buildConfiguration()->buildDirectory();
+ project = buildConfiguration()->project();
+ }
- cmd.addArgs({"--build", CMakeToolManager::mappedFilePath(buildDirectory).path()});
+ cmd.addArgs(
+ {"--build",
+ CMakeToolManager::mappedFilePath(project, buildDirectory).path()});
cmd.addArg("--target");
cmd.addArgs(Utils::transform(m_buildTargets, [this](const QString &s) {
@@ -600,7 +605,7 @@ QWidget *CMakeBuildStep::createConfigWidget()
if (!isCleanStep() && !m_buildPreset.isEmpty())
createAndAddEnvironmentWidgets(builder);
- builder.addItem(Layouting::noMargin);
+ builder.setNoMargins();
auto widget = builder.emerge();
updateDetails();
diff --git a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp
index b2b042a2b0..730cdaed4c 100644
--- a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp
+++ b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp
@@ -83,12 +83,17 @@ CMakeBuildSystem::CMakeBuildSystem(CMakeBuildConfiguration *bc)
// Cache mime check result for speed up
if (!isIgnored) {
- auto it = m_mimeBinaryCache.find(mimeType.name());
- if (it != m_mimeBinaryCache.end()) {
+ if (auto it = m_mimeBinaryCache.get<std::optional<bool>>(
+ [mimeType](const QHash<QString, bool> &cache) -> std::optional<bool> {
+ auto it = cache.find(mimeType.name());
+ if (it != cache.end())
+ return *it;
+ return {};
+ })) {
isIgnored = *it;
} else {
isIgnored = TreeScanner::isMimeBinary(mimeType, fn);
- m_mimeBinaryCache[mimeType.name()] = isIgnored;
+ m_mimeBinaryCache.writeLocked()->insert(mimeType.name(), isIgnored);
}
}
@@ -1204,7 +1209,9 @@ void CMakeBuildSystem::clearCMakeCache()
m_parameters.buildDirectory / "CMakeFiles",
m_parameters.buildDirectory / ".cmake/api/v1/reply",
m_parameters.buildDirectory / ".cmake/api/v1/reply.prev",
- m_parameters.buildDirectory / Constants::PACKAGE_MANAGER_DIR
+ m_parameters.buildDirectory / Constants::PACKAGE_MANAGER_DIR,
+ m_parameters.buildDirectory / "conan-dependencies",
+ m_parameters.buildDirectory / "vcpkg-dependencies"
};
for (const FilePath &path : pathsToDelete)
@@ -1641,7 +1648,7 @@ void CMakeBuildSystem::wireUpConnections()
connect(project(), &Project::projectFileIsDirty, this, [this] {
const bool isBuilding = BuildManager::isBuilding(project());
if (buildConfiguration()->isActive() && !isParsing() && !isBuilding) {
- if (settings().autorunCMake()) {
+ if (settings(project()).autorunCMake()) {
qCDebug(cmakeBuildSystemLog) << "Requesting parse due to dirty project file";
reparse(CMakeBuildSystem::REPARSE_FORCE_CMAKE_RUN);
}
@@ -2054,19 +2061,17 @@ const QList<TestCaseInfo> CMakeBuildSystem::testcasesInfo() const
CommandLine CMakeBuildSystem::commandLineForTests(const QList<QString> &tests,
const QStringList &options) const
{
- QStringList args = options;
const QSet<QString> testsSet = Utils::toSet(tests);
- auto current = Utils::transform<QSet<QString>>(m_testNames, &TestCaseInfo::name);
+ const auto current = Utils::transform<QSet<QString>>(m_testNames, &TestCaseInfo::name);
if (tests.isEmpty() || current == testsSet)
- return {m_ctestPath, args};
+ return {m_ctestPath, options};
QString testNumbers("0,0,0"); // start, end, stride
for (const TestCaseInfo &info : m_testNames) {
if (testsSet.contains(info.name))
testNumbers += QString(",%1").arg(info.number);
}
- args << "-I" << testNumbers;
- return {m_ctestPath, args};
+ return {m_ctestPath, {options, "-I", testNumbers}};
}
DeploymentData CMakeBuildSystem::deploymentDataFromFile() const
@@ -2300,11 +2305,14 @@ MakeInstallCommand CMakeBuildSystem::makeInstallCommand(const FilePath &installR
installTarget = "INSTALL";
FilePath buildDirectory = ".";
- if (auto bc = buildConfiguration())
+ Project *project = nullptr;
+ if (auto bc = buildConfiguration()) {
buildDirectory = bc->buildDirectory();
+ project = bc->project();
+ }
cmd.command.addArg("--build");
- cmd.command.addArg(CMakeToolManager::mappedFilePath(buildDirectory).path());
+ cmd.command.addArg(CMakeToolManager::mappedFilePath(project, buildDirectory).path());
cmd.command.addArg("--target");
cmd.command.addArg(installTarget);
diff --git a/src/plugins/cmakeprojectmanager/cmakebuildsystem.h b/src/plugins/cmakeprojectmanager/cmakebuildsystem.h
index dd85a52e22..32966c64bb 100644
--- a/src/plugins/cmakeprojectmanager/cmakebuildsystem.h
+++ b/src/plugins/cmakeprojectmanager/cmakebuildsystem.h
@@ -10,6 +10,7 @@
#include <projectexplorer/buildconfiguration.h>
#include <projectexplorer/buildsystem.h>
+#include <utils/synchronizedvalue.h>
#include <utils/temporarydirectory.h>
namespace ProjectExplorer {
@@ -221,7 +222,7 @@ private:
ProjectExplorer::TreeScanner m_treeScanner;
std::shared_ptr<ProjectExplorer::FolderNode> m_allFiles;
- QHash<QString, bool> m_mimeBinaryCache;
+ Utils::SynchronizedValue<QHash<QString, bool>> m_mimeBinaryCache;
bool m_waitingForParse = false;
bool m_combinedScanAndParseResult = false;
diff --git a/src/plugins/cmakeprojectmanager/cmakeformatter.cpp b/src/plugins/cmakeprojectmanager/cmakeformatter.cpp
index 4cbd4d2d50..c21b12a93a 100644
--- a/src/plugins/cmakeprojectmanager/cmakeformatter.cpp
+++ b/src/plugins/cmakeprojectmanager/cmakeformatter.cpp
@@ -79,7 +79,7 @@ public:
Space(10),
Group {
title(Tr::tr("Automatic Formatting on File Save")),
- autoFormatOnSave.groupChecker(),
+ groupChecker(autoFormatOnSave.groupChecker()),
// Conceptually, that's a Form, but this would look odd:
// xxxxxx [____]
// [x] xxxxxxxxxxxxxx
diff --git a/src/plugins/cmakeprojectmanager/cmakekitaspect.cpp b/src/plugins/cmakeprojectmanager/cmakekitaspect.cpp
index 3d8aa65475..bfafe8b2ea 100644
--- a/src/plugins/cmakeprojectmanager/cmakekitaspect.cpp
+++ b/src/plugins/cmakeprojectmanager/cmakekitaspect.cpp
@@ -148,7 +148,7 @@ private:
// KitAspectWidget interface
void makeReadOnly() override { m_comboBox->setEnabled(false); }
- void addToLayoutImpl(Layouting::LayoutItem &builder) override
+ void addToLayoutImpl(Layouting::Layout &builder) override
{
addMutableAction(m_comboBox);
builder.addItem(m_comboBox);
@@ -376,7 +376,7 @@ private:
// KitAspectWidget interface
void makeReadOnly() override { m_changeButton->setEnabled(false); }
- void addToLayoutImpl(Layouting::LayoutItem &parent) override
+ void addToLayoutImpl(Layouting::Layout &parent) override
{
addMutableAction(m_label);
parent.addItem(m_label);
@@ -665,7 +665,7 @@ QVariant CMakeGeneratorKitAspectFactory::defaultValue(const Kit *k) const
});
if (it != known.constEnd()) {
const bool hasNinja = [k, tool] {
- if (Internal::settings().ninjaPath().isEmpty()) {
+ if (Internal::settings(nullptr).ninjaPath().isEmpty()) {
auto findNinja = [](const Environment &env) -> bool {
return !env.searchInPath("ninja").isEmpty();
};
@@ -800,7 +800,7 @@ void CMakeGeneratorKitAspectFactory::upgrade(Kit *k)
QTC_ASSERT(k, return);
const QVariant value = k->value(GENERATOR_ID);
- if (value.type() != QVariant::Map) {
+ if (value.typeId() != QMetaType::QVariantMap) {
GeneratorInfo info;
const QString fullName = value.toString();
const int pos = fullName.indexOf(" - ");
@@ -875,7 +875,7 @@ public:
private:
// KitAspectWidget interface
- void addToLayoutImpl(Layouting::LayoutItem &parent) override
+ void addToLayoutImpl(Layouting::Layout &parent) override
{
addMutableAction(m_summaryLabel);
parent.addItem(m_summaryLabel);
diff --git a/src/plugins/cmakeprojectmanager/cmakeparser.cpp b/src/plugins/cmakeprojectmanager/cmakeparser.cpp
index 7da4e38391..c5c8c30d25 100644
--- a/src/plugins/cmakeprojectmanager/cmakeparser.cpp
+++ b/src/plugins/cmakeprojectmanager/cmakeparser.cpp
@@ -93,8 +93,8 @@ OutputLineParser::Result CMakeParser::handleLine(const QString &line, OutputForm
match.captured(2).toInt());
m_lines = 1;
LinkSpecs linkSpecs;
- addLinkSpecForAbsoluteFilePath(linkSpecs, m_lastTask.file, m_lastTask.line,
- match, 1);
+ addLinkSpecForAbsoluteFilePath(
+ linkSpecs, m_lastTask.file, m_lastTask.line, m_lastTask.column, match, 1);
m_errorOrWarningLine.file = m_lastTask.file;
m_errorOrWarningLine.line = m_lastTask.line;
@@ -107,8 +107,8 @@ OutputLineParser::Result CMakeParser::handleLine(const QString &line, OutputForm
m_lastTask = BuildSystemTask(Task::Error, QString(),
absoluteFilePath(FilePath::fromUserInput(match.captured(1))));
LinkSpecs linkSpecs;
- addLinkSpecForAbsoluteFilePath(linkSpecs, m_lastTask.file, m_lastTask.line,
- match, 1);
+ addLinkSpecForAbsoluteFilePath(
+ linkSpecs, m_lastTask.file, m_lastTask.line, m_lastTask.column, match, 1);
m_lines = 1;
return {Status::InProgress, linkSpecs};
}
@@ -121,8 +121,8 @@ OutputLineParser::Result CMakeParser::handleLine(const QString &line, OutputForm
match.captured(3).toInt());
m_lines = 1;
LinkSpecs linkSpecs;
- addLinkSpecForAbsoluteFilePath(linkSpecs, m_lastTask.file, m_lastTask.line,
- match, 1);
+ addLinkSpecForAbsoluteFilePath(
+ linkSpecs, m_lastTask.file, m_lastTask.line, m_lastTask.column, match, 1);
m_errorOrWarningLine.file = m_lastTask.file;
m_errorOrWarningLine.line = m_lastTask.line;
@@ -174,8 +174,13 @@ OutputLineParser::Result CMakeParser::handleLine(const QString &line, OutputForm
m_lastTask.line = match.captured(1).toInt();
m_expectTripleLineErrorData = LINE_DESCRIPTION;
LinkSpecs linkSpecs;
- addLinkSpecForAbsoluteFilePath(linkSpecs, m_lastTask.file, m_lastTask.line, 0,
- match.capturedStart());
+ addLinkSpecForAbsoluteFilePath(
+ linkSpecs,
+ m_lastTask.file,
+ m_lastTask.line,
+ m_lastTask.column,
+ 0,
+ match.capturedStart());
return {Status::InProgress, linkSpecs};
}
case LINE_DESCRIPTION:
diff --git a/src/plugins/cmakeprojectmanager/cmakeprocess.cpp b/src/plugins/cmakeprojectmanager/cmakeprocess.cpp
index c0a41b817d..618c42892f 100644
--- a/src/plugins/cmakeprojectmanager/cmakeprocess.cpp
+++ b/src/plugins/cmakeprojectmanager/cmakeprocess.cpp
@@ -103,7 +103,7 @@ void CMakeProcess::run(const BuildDirParameters &parameters, const QStringList &
}
// Copy the "package-manager" CMake code from the ${IDE:ResourcePath} to the build directory
- if (settings().packageManagerAutoSetup()) {
+ if (settings(parameters.project).packageManagerAutoSetup()) {
const FilePath localPackageManagerDir = buildDirectory.pathAppended(Constants::PACKAGE_MANAGER_DIR);
const FilePath idePackageManagerDir = FilePath::fromString(
parameters.expander->expand(QStringLiteral("%{IDE:ResourcePath}/package-manager")));
@@ -149,10 +149,11 @@ void CMakeProcess::run(const BuildDirParameters &parameters, const QStringList &
});
CommandLine commandLine(cmakeExecutable);
- commandLine.addArgs({"-S",
- CMakeToolManager::mappedFilePath(sourceDirectory).path(),
- "-B",
- CMakeToolManager::mappedFilePath(buildDirectory).path()});
+ commandLine.addArgs(
+ {"-S",
+ CMakeToolManager::mappedFilePath(parameters.project, sourceDirectory).path(),
+ "-B",
+ CMakeToolManager::mappedFilePath(parameters.project, buildDirectory).path()});
commandLine.addArgs(arguments);
TaskHub::clearTasks(ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM);
@@ -177,16 +178,9 @@ void CMakeProcess::stop()
QString addCMakePrefix(const QString &str)
{
- auto qColorToAnsiCode = [] (const QColor &color) {
- return QString::fromLatin1("\033[38;2;%1;%2;%3m")
- .arg(color.red()).arg(color.green()).arg(color.blue());
- };
- static const QColor bgColor = creatorTheme()->color(Theme::BackgroundColorNormal);
- static const QColor fgColor = creatorTheme()->color(Theme::TextColorNormal);
- static const QColor grey = StyleHelper::mergedColors(fgColor, bgColor, 80);
- static const QString prefixString = qColorToAnsiCode(grey) + Constants::OUTPUT_PREFIX
- + qColorToAnsiCode(fgColor);
- return QString("%1%2").arg(prefixString, str);
+ static const QString prefix
+ = ansiColoredText(Constants::OUTPUT_PREFIX, creatorColor(Theme::Token_Text_Muted));
+ return prefix + str;
}
QStringList addCMakePrefix(const QStringList &list)
diff --git a/src/plugins/cmakeprojectmanager/cmakeproject.cpp b/src/plugins/cmakeprojectmanager/cmakeproject.cpp
index 9e119bba88..1fafbc7d93 100644
--- a/src/plugins/cmakeprojectmanager/cmakeproject.cpp
+++ b/src/plugins/cmakeprojectmanager/cmakeproject.cpp
@@ -34,6 +34,7 @@ namespace CMakeProjectManager {
*/
CMakeProject::CMakeProject(const FilePath &fileName)
: Project(Utils::Constants::CMAKE_MIMETYPE, fileName)
+ , m_settings(this, true)
{
setId(CMakeProjectManager::Constants::CMAKE_PROJECT_ID);
setProjectLanguages(Core::Context(ProjectExplorer::Constants::CXX_LANGUAGE_ID));
@@ -120,6 +121,14 @@ Internal::PresetsData CMakeProject::combinePresets(Internal::PresetsData &cmakeP
result.include = cmakeUserPresetsData.include;
}
+ result.vendor = cmakePresetsData.vendor;
+ if (result.vendor) {
+ if (cmakeUserPresetsData.vendor)
+ result.vendor->insert(cmakeUserPresetsData.vendor.value());
+ } else {
+ result.vendor = cmakeUserPresetsData.vendor;
+ }
+
auto combinePresetsInternal = [](auto &presetsHash,
auto &presets,
auto &userPresets,
@@ -232,6 +241,11 @@ void CMakeProject::setupBuildPresets(Internal::PresetsData &presetsData)
}
}
+Internal::CMakeSpecificSettings &CMakeProject::settings()
+{
+ return m_settings;
+}
+
void CMakeProject::readPresets()
{
auto parsePreset = [](const Utils::FilePath &presetFile) -> Internal::PresetsData {
diff --git a/src/plugins/cmakeprojectmanager/cmakeproject.h b/src/plugins/cmakeprojectmanager/cmakeproject.h
index 6540dd4964..832977c41a 100644
--- a/src/plugins/cmakeprojectmanager/cmakeproject.h
+++ b/src/plugins/cmakeprojectmanager/cmakeproject.h
@@ -4,6 +4,7 @@
#pragma once
#include "cmake_global.h"
+#include "cmakespecificsettings.h"
#include "presetsparser.h"
#include <projectexplorer/project.h>
@@ -34,6 +35,8 @@ public:
void setOldPresetKits(const QList<ProjectExplorer::Kit *> &presetKits) const;
QList<ProjectExplorer::Kit *> oldPresetKits() const;
+ Internal::CMakeSpecificSettings &settings();
+
protected:
bool setupTarget(ProjectExplorer::Target *t) final;
@@ -50,6 +53,7 @@ private:
ProjectExplorer::Tasks m_issues;
Internal::PresetsData m_presetsData;
+ Internal::CMakeSpecificSettings m_settings;
};
} // namespace CMakeProjectManager
diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectconstants.h b/src/plugins/cmakeprojectmanager/cmakeprojectconstants.h
index ea9461ba32..04922b6e9d 100644
--- a/src/plugins/cmakeprojectmanager/cmakeprojectconstants.h
+++ b/src/plugins/cmakeprojectmanager/cmakeprojectconstants.h
@@ -41,10 +41,11 @@ const char CMAKE_BUILDCONFIGURATION_ID[] = "CMakeProjectManager.CMakeBuildConfig
const char M_CONTEXT[] = "CMakeEditor.ContextMenu";
namespace Settings {
-const char GENERAL_ID[] = "CMakeSpecifcSettings";
+const char GENERAL_ID[] = "CMakeSpecificSettings";
const char TOOLS_ID[] = "K.CMake.Tools";
const char FORMATTER_ID[] = "K.CMake.Formatter";
const char CATEGORY[] = "K.CMake";
+const char USE_GLOBAL_SETTINGS[] = "UseGlobalSettings";
} // namespace Settings
// Snippets
diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectimporter.cpp b/src/plugins/cmakeprojectmanager/cmakeprojectimporter.cpp
index 6df1e7452d..998ccbf7ca 100644
--- a/src/plugins/cmakeprojectmanager/cmakeprojectimporter.cpp
+++ b/src/plugins/cmakeprojectmanager/cmakeprojectimporter.cpp
@@ -12,6 +12,9 @@
#include "presetsmacros.h"
#include <coreplugin/messagemanager.h>
+#include <debugger/debuggeritem.h>
+#include <debugger/debuggeritemmanager.h>
+#include <debugger/debuggerkitaspect.h>
#include <projectexplorer/buildinfo.h>
#include <projectexplorer/kitaspects.h>
@@ -30,7 +33,9 @@
#include <QApplication>
#include <QLoggingCategory>
+#include <QUuid>
+using namespace Debugger;
using namespace ProjectExplorer;
using namespace QtSupport;
using namespace Utils;
@@ -60,6 +65,7 @@ struct DirectoryData
FilePath sysroot;
QtProjectImporter::QtVersionData qt;
QVector<ToolchainDescription> toolchains;
+ QVariant debugger;
};
static FilePaths scanDirectory(const FilePath &path, const QString &prefix)
@@ -205,6 +211,48 @@ FilePaths CMakeProjectImporter::presetCandidates()
return candidates;
}
+static QVariant findOrRegisterDebugger(
+ Environment &env, const std::optional<QVariantMap> &vendor, const QString &presetName)
+{
+ const QString debuggerKey("debugger");
+ if (!vendor || !vendor.value().contains(debuggerKey))
+ return {};
+
+ const QVariant debuggerVariant = vendor.value().value(debuggerKey);
+ FilePath debuggerPath = FilePath::fromUserInput(debuggerVariant.toString());
+ if (!debuggerPath.isEmpty()) {
+ if (debuggerPath.isRelativePath())
+ debuggerPath = env.searchInPath(debuggerPath.fileName());
+
+ const QString mainName = Tr::tr("CMake Preset (%1) %2 Debugger");
+ DebuggerItem debugger;
+ debugger.setCommand(debuggerPath);
+ debugger.setUnexpandedDisplayName(
+ mainName.arg(presetName).arg(debuggerPath.completeBaseName()));
+ debugger.setAutoDetected(false);
+ QString errorMessage;
+ debugger.reinitializeFromFile(&errorMessage, &env);
+ if (!errorMessage.isEmpty())
+ qCWarning(cmInputLog()) << "Error reinitializing debugger" << debuggerPath.toString()
+ << "Error:" << errorMessage;
+
+ return DebuggerItemManager::registerDebugger(debugger);
+ } else {
+ auto debuggerMap = debuggerVariant.toMap();
+ if (debuggerMap.isEmpty())
+ return {};
+
+ // Manually create an Id, otrhewise the Kit will not have a debugger set
+ if (!debuggerMap.contains("Id"))
+ debuggerMap.insert("Id", QUuid::createUuid().toString());
+
+ auto store = storeFromMap(debuggerMap);
+ DebuggerItem debugger(store);
+
+ return DebuggerItemManager::registerDebugger(debugger);
+ }
+}
+
Target *CMakeProjectImporter::preferredTarget(const QList<Target *> &possibleTargets)
{
for (Kit *kit : m_project->oldPresetKits()) {
@@ -835,6 +883,8 @@ QList<void *> CMakeProjectImporter::examineDirectory(const FilePath &importPath,
data->hasQmlDebugging = CMakeBuildConfiguration::hasQmlDebugging(config);
+ data->debugger = findOrRegisterDebugger(env, configurePreset.vendor, configurePreset.name);
+
QByteArrayList buildConfigurationTypes = {cache.valueOf("CMAKE_BUILD_TYPE")};
if (buildConfigurationTypes.front().isEmpty()) {
buildConfigurationTypes.clear();
@@ -1055,6 +1105,9 @@ Kit *CMakeProjectImporter::createKit(void *directoryData) const
if (!data->cmakePreset.isEmpty())
ensureBuildDirectory(*data, k);
+ if (data->debugger.isValid())
+ DebuggerKitAspect::setDebugger(k, data->debugger);
+
qCInfo(cmInputLog) << "Temporary Kit created.";
});
}
@@ -1071,6 +1124,11 @@ const QList<BuildInfo> CMakeProjectImporter::buildInfoList(void *directoryData)
&& data->hasQmlDebugging)
buildType = CMakeBuildConfigurationFactory::BuildTypeProfile;
BuildInfo info = CMakeBuildConfigurationFactory::createBuildInfo(buildType);
+
+ // For CMake Presets use the provided build type if is not mapped to a known type
+ if (!data->cmakePreset.isEmpty() && info.buildType == BuildConfiguration::Unknown)
+ info.typeName = info.displayName = QString::fromUtf8(data->cmakeBuildType);
+
info.buildDirectory = data->buildDirectory;
QVariantMap config = info.extraInfo.toMap(); // new empty, or existing one from createBuildInfo
diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.cpp b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.cpp
index 9730c957fb..f3e512eebb 100644
--- a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.cpp
+++ b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.cpp
@@ -18,6 +18,7 @@
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/editormanager/ieditor.h>
#include <coreplugin/icore.h>
+#include <coreplugin/helpmanager.h>
#include <coreplugin/messagemanager.h>
#include <coreplugin/modemanager.h>
@@ -41,6 +42,7 @@
#include <utils/checkablemessagebox.h>
#include <utils/utilsicons.h>
+#include <QDesktopServices>
#include <QMessageBox>
using namespace Core;
@@ -55,6 +57,9 @@ class CMakeManager final : public QObject
public:
CMakeManager();
+ static bool isCMakeUrl(const QUrl &url);
+ static void openCMakeUrl(const QUrl &url);
+
private:
void updateCmakeActions(Node *node);
void clearCMakeCache(BuildSystem *buildSystem);
@@ -80,6 +85,29 @@ private:
bool m_canDebugCMake = false;
};
+bool CMakeManager::isCMakeUrl(const QUrl &url)
+{
+ const QString address = url.toString();
+ return address.startsWith("qthelp://org.cmake.");
+}
+
+void CMakeManager::openCMakeUrl(const QUrl &url)
+{
+ QString urlPrefix = "https://cmake.org/cmake/help/";
+
+ QRegularExpression version("^.*\\.([0-9])\\.([0-9]+)\\.[0-9]+$");
+ auto match = version.match(url.authority());
+ if (match.hasMatch())
+ urlPrefix.append(QString("v%1.%2").arg(match.captured(1)).arg(match.captured(2)));
+ else
+ urlPrefix.append("latest");
+
+ const QString address = url.toString();
+ const QString doc("/doc");
+ QDesktopServices::openUrl(
+ QUrl(urlPrefix + address.mid(address.lastIndexOf(doc) + doc.length())));
+}
+
CMakeManager::CMakeManager()
{
namespace PEC = ProjectExplorer::Constants;
@@ -321,12 +349,16 @@ void CMakeManager::enableBuildFileMenus(Node *node)
void CMakeManager::reloadCMakePresets()
{
+ CMakeProject *project = qobject_cast<CMakeProject *>(ProjectTree::currentProject());
+ if (!project)
+ return;
+
QMessageBox::StandardButton clickedButton = CheckableMessageBox::question(
Core::ICore::dialogParent(),
Tr::tr("Reload CMake Presets"),
Tr::tr("Re-generates the kits that were created for CMake presets. All manual "
"modifications to the CMake project settings will be lost."),
- settings().askBeforePresetsReload.askAgainCheckableDecider(),
+ settings(project).askBeforePresetsReload.askAgainCheckableDecider(),
QMessageBox::Yes | QMessageBox::Cancel,
QMessageBox::Yes,
QMessageBox::Yes,
@@ -334,15 +366,11 @@ void CMakeManager::reloadCMakePresets()
{QMessageBox::Yes, Tr::tr("Reload")},
});
- settings().writeSettings();
+ settings(project).writeSettings();
if (clickedButton == QMessageBox::Cancel)
return;
- CMakeProject *project = static_cast<CMakeProject *>(ProjectTree::currentProject());
- if (!project)
- return;
-
const QSet<QString> oldPresets = Utils::transform<QSet>(project->presetsData().configurePresets,
[](const auto &preset) {
return preset.name;
@@ -458,4 +486,9 @@ void setupCMakeManager()
static CMakeManager theCMakeManager;
}
+void setupOnlineHelpManager()
+{
+ Core::HelpManager::addOnlineHelpHandler({CMakeManager::isCMakeUrl, CMakeManager::openCMakeUrl});
+}
+
} // CMakeProjectManager::Internal
diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.h b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.h
index ba3a025f13..42e6a1c4e3 100644
--- a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.h
+++ b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.h
@@ -6,5 +6,6 @@
namespace CMakeProjectManager::Internal {
void setupCMakeManager();
+void setupOnlineHelpManager();
} // CMakeProjectManager::Internal
diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs
index 1cbaac7b6d..98849a71f5 100644
--- a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs
+++ b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs
@@ -8,6 +8,7 @@ QtcPlugin {
Depends { name: "Core" }
Depends { name: "CppEditor" }
+ Depends { name: "Debugger" }
Depends { name: "QmlJS" }
Depends { name: "ProjectExplorer" }
Depends { name: "TextEditor" }
diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectplugin.cpp b/src/plugins/cmakeprojectmanager/cmakeprojectplugin.cpp
index 9c2a23dc06..f91c1438d2 100644
--- a/src/plugins/cmakeprojectmanager/cmakeprojectplugin.cpp
+++ b/src/plugins/cmakeprojectmanager/cmakeprojectplugin.cpp
@@ -105,6 +105,8 @@ class CMakeProjectPlugin final : public ExtensionSystem::IPlugin
{
// Delay the restoration to allow the devices to load first.
QTimer::singleShot(0, this, [] { CMakeToolManager::restoreCMakeTools(); });
+
+ setupOnlineHelpManager();
}
void updateContextActions(ProjectExplorer::Node *node)
diff --git a/src/plugins/cmakeprojectmanager/cmakespecificsettings.cpp b/src/plugins/cmakeprojectmanager/cmakespecificsettings.cpp
index 28668b3567..eeab46f946 100644
--- a/src/plugins/cmakeprojectmanager/cmakespecificsettings.cpp
+++ b/src/plugins/cmakeprojectmanager/cmakespecificsettings.cpp
@@ -1,30 +1,45 @@
// Copyright (C) 2018 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#include "cmakeproject.h"
#include "cmakespecificsettings.h"
#include "cmakeprojectconstants.h"
#include "cmakeprojectmanagertr.h"
-#include <coreplugin/icore.h>
#include <coreplugin/dialogs/ioptionspage.h>
+#include <coreplugin/icore.h>
+#include <projectexplorer/project.h>
#include <projectexplorer/projectexplorerconstants.h>
+#include <projectexplorer/projectimporter.h>
+#include <projectexplorer/projectpanelfactory.h>
#include <utils/hostosinfo.h>
#include <utils/layoutbuilder.h>
+#include <QVBoxLayout>
+
+using namespace ProjectExplorer;
using namespace Utils;
namespace CMakeProjectManager::Internal {
-CMakeSpecificSettings &settings()
+CMakeSpecificSettings &settings(Project *project)
{
- static CMakeSpecificSettings theSettings;
- return theSettings;
+ static CMakeSpecificSettings theSettings(nullptr, false);
+ if (!project)
+ return theSettings;
+
+ CMakeProject *cmakeProject = qobject_cast<CMakeProject *>(project);
+ if (!cmakeProject || cmakeProject->settings().useGlobalSettings)
+ return theSettings;
+
+ return cmakeProject->settings();
}
-CMakeSpecificSettings::CMakeSpecificSettings()
+CMakeSpecificSettings::CMakeSpecificSettings(Project *p, bool autoApply)
+ : project(p)
{
setLayouter([this] {
using namespace Layouting;
@@ -43,8 +58,8 @@ CMakeSpecificSettings::CMakeSpecificSettings()
// TODO: fixup of QTCREATORBUG-26289 , remove in Qt Creator 7 or so
Core::ICore::settings()->remove("CMakeSpecificSettings/NinjaPath");
- setSettingsGroup("CMakeSpecificSettings");
- setAutoApply(false);
+ setSettingsGroup(Constants::Settings::GENERAL_ID);
+ setAutoApply(autoApply);
autorunCMake.setSettingsKey("AutorunCMake");
autorunCMake.setDefaultValue(true);
@@ -105,6 +120,57 @@ CMakeSpecificSettings::CMakeSpecificSettings()
"Junctions are used for CMake configure, build and install operations."));
readSettings();
+
+ if (project) {
+ // Re-read the settings. Reading in constructor is too early
+ connect(project, &Project::settingsLoaded, this, [this] { readSettings(); });
+
+ connect(project->projectImporter(), &ProjectImporter::cmakePresetsUpdated, this, [this] {
+ // clear settings first
+ Store data;
+ project->setNamedSettings(Constants::Settings::GENERAL_ID, variantFromStore(data));
+
+ readSettings();
+ });
+ }
+}
+
+void CMakeSpecificSettings::readSettings()
+{
+ if (!project) {
+ AspectContainer::readSettings();
+ } else {
+ Store data = storeFromVariant(project->namedSettings(Constants::Settings::GENERAL_ID));
+ if (data.isEmpty()) {
+ CMakeProject *cmakeProject = static_cast<CMakeProject *>(project);
+ if (cmakeProject->presetsData().havePresets && cmakeProject->presetsData().vendor) {
+ useGlobalSettings = false;
+ data = storeFromMap(cmakeProject->presetsData().vendor.value());
+ fromMap(data);
+
+ // Write the new loaded CMakePresets settings into .user file
+ writeSettings();
+ } else {
+ useGlobalSettings = true;
+ AspectContainer::readSettings();
+ }
+ } else {
+ useGlobalSettings = data.value(Constants::Settings::USE_GLOBAL_SETTINGS, true).toBool();
+ fromMap(data);
+ }
+ }
+}
+
+void CMakeSpecificSettings::writeSettings() const
+{
+ if (!project) {
+ AspectContainer::writeSettings();
+ } else {
+ Store data;
+ toMap(data);
+ data.insert(Constants::Settings::USE_GLOBAL_SETTINGS, useGlobalSettings);
+ project->setNamedSettings(Constants::Settings::GENERAL_ID, variantFromStore(data));
+ }
}
class CMakeSpecificSettingsPage final : public Core::IOptionsPage
@@ -117,10 +183,87 @@ public:
setDisplayCategory("CMake");
setCategory(Constants::Settings::CATEGORY);
setCategoryIconPath(Constants::Icons::SETTINGS_CATEGORY);
- setSettingsProvider([] { return &settings(); });
+ setSettingsProvider([] { return &settings(nullptr); });
}
};
const CMakeSpecificSettingsPage settingsPage;
+class CMakeProjectSettingsWidget : public ProjectSettingsWidget
+{
+public:
+ explicit CMakeProjectSettingsWidget(Project *project)
+ : m_widget(new QWidget)
+ , m_project(qobject_cast<CMakeProject *>(project))
+ , m_displayedSettings(project, true)
+ {
+ setGlobalSettingsId(Constants::Settings::GENERAL_ID);
+
+ // Construct the widget layout from the aspect container
+ const auto layout = new QVBoxLayout(this);
+ layout->setContentsMargins(0, 0, 0, 0);
+ if (auto layouter = m_displayedSettings.layouter())
+ layouter().attachTo(m_widget);
+ layout->addWidget(m_widget);
+
+ setUseGlobalSettings(m_displayedSettings.useGlobalSettings);
+ m_widget->setEnabled(!useGlobalSettings());
+
+ if (m_project) {
+ connect(
+ this, &ProjectSettingsWidget::useGlobalSettingsChanged, this, [this](bool useGlobal) {
+ m_widget->setEnabled(!useGlobal);
+ m_displayedSettings.useGlobalSettings = useGlobal;
+ m_displayedSettings.copyFrom(
+ useGlobal ? settings(nullptr) : m_project->settings());
+
+ m_project->settings().useGlobalSettings = useGlobal;
+ m_project->settings().writeSettings();
+ });
+
+ // React on Global settings changes
+ connect(&settings(nullptr), &AspectContainer::changed, this, [this] {
+ if (m_displayedSettings.useGlobalSettings)
+ m_displayedSettings.copyFrom(settings(nullptr));
+ });
+
+ // Reflect changes to the project settings in the displayed settings
+ connect(&m_project->settings(), &AspectContainer::changed, this, [this] {
+ if (!m_displayedSettings.useGlobalSettings)
+ m_displayedSettings.copyFrom(m_project->settings());
+ });
+
+ // React on project settings changes in the "CMake" project settings
+ connect(&m_displayedSettings, &AspectContainer::changed, this, [this] {
+ if (!m_displayedSettings.useGlobalSettings) {
+ m_project->settings().copyFrom(m_displayedSettings);
+ m_project->settings().writeSettings();
+ }
+ });
+ } else {
+ // Only for CMake projects
+ setUseGlobalSettingsCheckBoxEnabled(false);
+ }
+ }
+
+ QWidget *m_widget = nullptr;
+ CMakeProject *m_project = nullptr;
+ CMakeSpecificSettings m_displayedSettings;
+};
+
+class CMakeProjectSettingsPanelFactory final : public ProjectPanelFactory
+{
+public:
+ CMakeProjectSettingsPanelFactory()
+ {
+ setPriority(120);
+ setDisplayName("CMake");
+ setCreateWidgetFunction([](Project *project) {
+ return new CMakeProjectSettingsWidget(project);
+ });
+ }
+};
+
+const CMakeProjectSettingsPanelFactory projectSettingsPane;
+
} // CMakeProjectManager::Internal
diff --git a/src/plugins/cmakeprojectmanager/cmakespecificsettings.h b/src/plugins/cmakeprojectmanager/cmakespecificsettings.h
index cbec63dffe..653234a889 100644
--- a/src/plugins/cmakeprojectmanager/cmakespecificsettings.h
+++ b/src/plugins/cmakeprojectmanager/cmakespecificsettings.h
@@ -5,12 +5,20 @@
#include <utils/aspects.h>
+namespace ProjectExplorer {
+class Project;
+}
+
namespace CMakeProjectManager::Internal {
class CMakeSpecificSettings final : public Utils::AspectContainer
{
+ ProjectExplorer::Project *project{nullptr};
public:
- CMakeSpecificSettings();
+ CMakeSpecificSettings(ProjectExplorer::Project *project, bool autoApply);
+
+ void readSettings() final;
+ void writeSettings() const final;
Utils::BoolAspect autorunCMake{this};
Utils::FilePathAspect ninjaPath{this};
@@ -20,8 +28,10 @@ public:
Utils::BoolAspect showSourceSubFolders{this};
Utils::BoolAspect showAdvancedOptionsByDefault{this};
Utils::BoolAspect useJunctionsForSourceAndBuildDirectories{this};
+
+ bool useGlobalSettings{true};
};
-CMakeSpecificSettings &settings();
+CMakeSpecificSettings &settings(ProjectExplorer::Project *project);
} // CMakeProjectManager::Internal
diff --git a/src/plugins/cmakeprojectmanager/cmaketool.cpp b/src/plugins/cmakeprojectmanager/cmaketool.cpp
index 4cb638659a..444ff299f9 100644
--- a/src/plugins/cmakeprojectmanager/cmaketool.cpp
+++ b/src/plugins/cmakeprojectmanager/cmaketool.cpp
@@ -36,7 +36,6 @@ static Q_LOGGING_CATEGORY(cmakeToolLog, "qtc.cmake.tool", QtWarningMsg);
const char CMAKE_INFORMATION_ID[] = "Id";
const char CMAKE_INFORMATION_COMMAND[] = "Binary";
const char CMAKE_INFORMATION_DISPLAYNAME[] = "DisplayName";
-const char CMAKE_INFORMATION_AUTORUN[] = "AutoRun";
const char CMAKE_INFORMATION_QCH_FILE_PATH[] = "QchFile";
// obsolete since Qt Creator 5. Kept for backward compatibility
const char CMAKE_INFORMATION_AUTO_CREATE_BUILD_DIRECTORY[] = "AutoCreateBuildDirectory";
@@ -113,7 +112,6 @@ CMakeTool::CMakeTool(const Store &map, bool fromSdk) :
Id::fromSetting(map.value(CMAKE_INFORMATION_ID)))
{
m_displayName = map.value(CMAKE_INFORMATION_DISPLAYNAME).toString();
- m_isAutoRun = map.value(CMAKE_INFORMATION_AUTORUN, true).toBool();
m_autoCreateBuildDirectory = map.value(CMAKE_INFORMATION_AUTO_CREATE_BUILD_DIRECTORY, false).toBool();
m_readerType = Internal::readerTypeFromString(
map.value(CMAKE_INFORMATION_READERTYPE).toString());
@@ -183,7 +181,6 @@ Store CMakeTool::toMap() const
data.insert(CMAKE_INFORMATION_ID, m_id.toSetting());
data.insert(CMAKE_INFORMATION_COMMAND, m_executable.toString());
data.insert(CMAKE_INFORMATION_QCH_FILE_PATH, m_qchFilePath.toString());
- data.insert(CMAKE_INFORMATION_AUTORUN, m_isAutoRun);
data.insert(CMAKE_INFORMATION_AUTO_CREATE_BUILD_DIRECTORY, m_autoCreateBuildDirectory);
if (m_readerType)
data.insert(CMAKE_INFORMATION_READERTYPE,
@@ -233,11 +230,6 @@ FilePath CMakeTool::cmakeExecutable(const FilePath &path)
return resolvedPath;
}
-bool CMakeTool::isAutoRun() const
-{
- return m_isAutoRun;
-}
-
QList<CMakeTool::Generator> CMakeTool::supportedGenerators() const
{
return isValid() ? m_introspection->m_generators : QList<CMakeTool::Generator>();
diff --git a/src/plugins/cmakeprojectmanager/cmaketool.h b/src/plugins/cmakeprojectmanager/cmaketool.h
index cf13ad49c5..51012e699c 100644
--- a/src/plugins/cmakeprojectmanager/cmaketool.h
+++ b/src/plugins/cmakeprojectmanager/cmaketool.h
@@ -79,8 +79,6 @@ public:
Utils::Id id() const { return m_id; }
Utils::Store toMap () const;
- void setAutorun(bool autoRun) { m_isAutoRun = autoRun; }
-
void setFilePath(const Utils::FilePath &executable);
Utils::FilePath filePath() const;
Utils::FilePath cmakeExecutable() const;
@@ -130,7 +128,6 @@ private:
Utils::FilePath m_executable;
Utils::FilePath m_qchFilePath;
- bool m_isAutoRun = true;
bool m_isAutoDetected = false;
QString m_detectionSource;
bool m_autoCreateBuildDirectory = false;
diff --git a/src/plugins/cmakeprojectmanager/cmaketoolmanager.cpp b/src/plugins/cmakeprojectmanager/cmaketoolmanager.cpp
index e20c3c58eb..fa184e016b 100644
--- a/src/plugins/cmakeprojectmanager/cmaketoolmanager.cpp
+++ b/src/plugins/cmakeprojectmanager/cmaketoolmanager.cpp
@@ -342,15 +342,6 @@ void CMakeToolManager::restoreCMakeTools()
updateDocumentation();
emit m_instance->cmakeToolsLoaded();
-
- // Store the default CMake tool "Autorun CMake" value globally
- // TODO: Remove in Qt Creator 13
- Internal::CMakeSpecificSettings &s = Internal::settings();
- if (s.autorunCMake() == s.autorunCMake.defaultValue()) {
- CMakeTool *cmake = defaultCMakeTool();
- s.autorunCMake.setValue(cmake ? cmake->isAutoRun() : true);
- s.writeSettings();
- }
}
void CMakeToolManager::updateDocumentation()
@@ -445,7 +436,7 @@ QString CMakeToolManager::toolTipForRstHelpFile(const FilePath &helpFile)
return tooltip;
}
-FilePath CMakeToolManager::mappedFilePath(const FilePath &path)
+FilePath CMakeToolManager::mappedFilePath(Project *project, const FilePath &path)
{
if (!HostOsInfo::isWindowsHost())
return path;
@@ -453,16 +444,14 @@ FilePath CMakeToolManager::mappedFilePath(const FilePath &path)
if (path.needsDevice())
return path;
- auto project = ProjectManager::startupProject();
auto environment = Environment::systemEnvironment();
if (project)
environment.modify(project->additionalEnvironment());
const bool enableJunctions
- = QVariant(
- environment.value_or("QTC_CMAKE_USE_JUNCTIONS",
- Internal::settings().useJunctionsForSourceAndBuildDirectories()
- ? "1"
- : "0"))
+ = QVariant(environment.value_or(
+ "QTC_CMAKE_USE_JUNCTIONS",
+ Internal::settings(project).useJunctionsForSourceAndBuildDirectories() ? "1"
+ : "0"))
.toBool();
if (!enableJunctions)
diff --git a/src/plugins/cmakeprojectmanager/cmaketoolmanager.h b/src/plugins/cmakeprojectmanager/cmaketoolmanager.h
index 1836f36c86..0917c2739f 100644
--- a/src/plugins/cmakeprojectmanager/cmaketoolmanager.h
+++ b/src/plugins/cmakeprojectmanager/cmaketoolmanager.h
@@ -14,6 +14,10 @@
#include <memory>
+namespace ProjectExplorer {
+class Project;
+}
+
namespace CMakeProjectManager {
class CMAKE_EXPORT CMakeToolManager : public QObject
@@ -44,7 +48,7 @@ public:
static QString toolTipForRstHelpFile(const Utils::FilePath &helpFile);
- static Utils::FilePath mappedFilePath(const Utils::FilePath &path);
+ static Utils::FilePath mappedFilePath(ProjectExplorer::Project *project, const Utils::FilePath &path);
public slots:
QList<Utils::Id> autoDetectCMakeForDevice(const Utils::FilePaths &searchPaths,
diff --git a/src/plugins/cmakeprojectmanager/cmaketoolsettingsaccessor.cpp b/src/plugins/cmakeprojectmanager/cmaketoolsettingsaccessor.cpp
index b877998a07..e8e5a8a64e 100644
--- a/src/plugins/cmakeprojectmanager/cmaketoolsettingsaccessor.cpp
+++ b/src/plugins/cmakeprojectmanager/cmaketoolsettingsaccessor.cpp
@@ -174,14 +174,8 @@ void CMakeToolSettingsAccessor::saveCMakeTools(const QList<CMakeTool *> &cmakeTo
data.insert(CMAKE_TOOL_DEFAULT_KEY, defaultId.toSetting());
int count = 0;
- const bool autoRun = settings().autorunCMake();
for (CMakeTool *item : cmakeTools) {
Utils::FilePath fi = item->cmakeExecutable();
-
- // Gobal Autorun value will be set for all tools
- // TODO: Remove in Qt Creator 13
- item->setAutorun(autoRun);
-
if (fi.needsDevice() || fi.isExecutableFile()) { // be graceful for device related stuff
Store tmp = item->toMap();
if (tmp.isEmpty())
diff --git a/src/plugins/cmakeprojectmanager/configmodel.cpp b/src/plugins/cmakeprojectmanager/configmodel.cpp
index 953486e2da..287615650c 100644
--- a/src/plugins/cmakeprojectmanager/configmodel.cpp
+++ b/src/plugins/cmakeprojectmanager/configmodel.cpp
@@ -570,8 +570,8 @@ QVariant ConfigModelTreeItem::data(int column, int role) const
mismatch = !dataItem->kitValue.isEmpty() && dataItem->kitValue != value;
else
mismatch = !dataItem->initialValue.isEmpty() && dataItem->initialValue != value;
- return Utils::creatorTheme()->color(mismatch ? Utils::Theme::TextColorError
- : Utils::Theme::TextColorNormal);
+ return Utils::creatorColor(mismatch ? Utils::Theme::TextColorError
+ : Utils::Theme::TextColorNormal);
};
const QString value = currentValue();
diff --git a/src/plugins/cmakeprojectmanager/fileapidataextractor.cpp b/src/plugins/cmakeprojectmanager/fileapidataextractor.cpp
index 4134da09e7..6f4050a3aa 100644
--- a/src/plugins/cmakeprojectmanager/fileapidataextractor.cpp
+++ b/src/plugins/cmakeprojectmanager/fileapidataextractor.cpp
@@ -209,8 +209,7 @@ static bool isChildOf(const FilePath &path, const FilePaths &prefixes)
static CMakeBuildTarget toBuildTarget(const TargetDetails &t,
const FilePath &sourceDirectory,
const FilePath &buildDirectory,
- bool relativeLibs,
- const QSet<FilePath> &artifacts)
+ bool relativeLibs)
{
const FilePath currentBuildDir = buildDirectory.resolvePath(t.buildDir);
@@ -306,28 +305,13 @@ static CMakeBuildTarget toBuildTarget(const TargetDetails &t,
if (f.role == "libraries")
tmp = tmp.parentDir();
- std::optional<QString> dllName;
if (buildDir.osType() == OsTypeWindows && (f.role == "libraries")) {
- part = FilePath::fromUserInput(part).fileName();
+ const auto partAsFilePath = FilePath::fromUserInput(part);
+ part = partAsFilePath.fileName();
// Skip object libraries on Windows. This case can happen with static qml plugins
if (part.endsWith(".obj") || part.endsWith(".o"))
continue;
-
- // Only consider dlls, not static libraries
- for (const QString &suffix :
- {QString(".lib"), QString(".dll.a"), QString(".a")}) {
- if (part.endsWith(suffix) && !dllName)
- dllName = part.chopped(suffix.length()).append(".dll");
- }
-
- // MinGW has libQt6Core.a -> Qt6Core.dll
- const QString mingwPrefix("lib");
- const QString mingwSuffix(".a");
- if (part.startsWith(mingwPrefix) && part.endsWith(mingwSuffix))
- dllName = part.chopped(mingwSuffix.length())
- .sliced(mingwPrefix.length())
- .append(".dll");
}
if (!tmp.isEmpty() && tmp.isDir()) {
@@ -336,23 +320,20 @@ static CMakeBuildTarget toBuildTarget(const TargetDetails &t,
// "/usr/local/lib" since these are usually in the standard search
// paths. There probably are more, but the naming schemes are arbitrary
// so we'd need to ask the linker ("ld --verbose | grep SEARCH_DIR").
- if (buildDir.osType() != OsTypeWindows
- && !isChildOf(tmp,
- {"/lib", "/lib64", "/usr/lib", "/usr/lib64", "/usr/local/lib"}))
+ if (buildDir.osType() == OsTypeWindows
+ || !isChildOf(tmp,
+ {"/lib",
+ "/lib64",
+ "/usr/lib",
+ "/usr/lib64",
+ "/usr/local/lib"})) {
librarySeachPaths.append(tmp);
- if (buildDir.osType() == OsTypeWindows && dllName) {
- const auto validPath = [&artifacts](const FilePath& path) {
- return path.exists() || artifacts.contains(path);
- };
- if (validPath(tmp.pathAppended(*dllName)))
- librarySeachPaths.append(tmp);
-
// Libraries often have their import libs in ../lib and the
// actual dll files in ../bin on windows. Qt is one example of that.
- if (tmp.fileName() == "lib") {
+ if (tmp.fileName() == "lib" && buildDir.osType() == OsTypeWindows) {
const FilePath path = tmp.parentDir().pathAppended("bin");
- if (path.isDir() && validPath(path.pathAppended(*dllName)))
+ if (path.isDir())
librarySeachPaths.append(path);
}
}
@@ -371,17 +352,12 @@ static QList<CMakeBuildTarget> generateBuildTargets(const QFuture<void> &cancelF
const FilePath &buildDirectory,
bool relativeLibs)
{
- QSet<FilePath> artifacts;
- for (const TargetDetails &t : input.targetDetails)
- for (const FilePath &p: t.artifacts)
- artifacts.insert(buildDirectory.resolvePath(p));
-
QList<CMakeBuildTarget> result;
result.reserve(input.targetDetails.size());
for (const TargetDetails &t : input.targetDetails) {
if (cancelFuture.isCanceled())
return {};
- result.append(toBuildTarget(t, sourceDirectory, buildDirectory, relativeLibs, artifacts));
+ result.append(toBuildTarget(t, sourceDirectory, buildDirectory, relativeLibs));
}
return result;
}
@@ -700,7 +676,7 @@ static void addCompileGroups(ProjectNode *targetRoot,
if (buildDirQmldirOrRcc || otherDirQmldirOrMetatypes || buildDirPluginCpp)
node->setIsGenerated(true);
- const bool showSourceFolders = settings().showSourceSubFolders()
+ const bool showSourceFolders = settings(targetRoot->getProject()).showSourceSubFolders()
&& defaultCMakeSourceGroupFolder(td.sourceGroups[si.sourceGroup]);
// Where does the file node need to go?
@@ -714,7 +690,7 @@ static void addCompileGroups(ProjectNode *targetRoot,
}
for (size_t i = 0; i < sourceGroupFileNodes.size(); ++i) {
- const bool showSourceFolders = settings().showSourceSubFolders()
+ const bool showSourceFolders = settings(targetRoot->getProject()).showSourceSubFolders()
&& defaultCMakeSourceGroupFolder(td.sourceGroups[i]);
std::vector<std::unique_ptr<FileNode>> &current = sourceGroupFileNodes[i];
diff --git a/src/plugins/cmakeprojectmanager/fileapiparser.cpp b/src/plugins/cmakeprojectmanager/fileapiparser.cpp
index f72bcd5a68..d404d124db 100644
--- a/src/plugins/cmakeprojectmanager/fileapiparser.cpp
+++ b/src/plugins/cmakeprojectmanager/fileapiparser.cpp
@@ -874,10 +874,11 @@ FileApiData FileApiParser::parseData(QPromise<std::shared_ptr<FileApiQtcData>> &
errorMessage);
if (codeModels.size() == 0) {
- errorMessage = Tr::tr("CMake project configuration failed. No CMake configuration for "
- "build type \"%1\" found.")
+ //: General Messages refers to the output view
+ errorMessage = Tr::tr(
+ "CMake project configuration failed. No CMake configuration for "
+ "build type \"%1\" found. Check General Messages for more information.")
.arg(cmakeBuildType);
- errorMessage += Tr::tr(" Check General messages for more information.");
return result;
}
diff --git a/src/plugins/cmakeprojectmanager/fileapireader.cpp b/src/plugins/cmakeprojectmanager/fileapireader.cpp
index cb76705cdc..b859469cbc 100644
--- a/src/plugins/cmakeprojectmanager/fileapireader.cpp
+++ b/src/plugins/cmakeprojectmanager/fileapireader.cpp
@@ -12,8 +12,6 @@
#include <coreplugin/messagemanager.h>
-#include <extensionsystem/pluginmanager.h>
-
#include <projectexplorer/projectexplorer.h>
#include <utils/algorithm.h>
@@ -132,7 +130,8 @@ void FileApiReader::parse(bool forceCMakeRun,
// * A query file is newer than the reply file
const bool hasArguments = !args.isEmpty();
const bool replyFileMissing = !replyFile.exists();
- const bool cmakeFilesChanged = m_parameters.cmakeTool() && settings().autorunCMake()
+ const bool cmakeFilesChanged = m_parameters.cmakeTool()
+ && settings(m_parameters.project).autorunCMake()
&& anyOf(m_cmakeFiles, [&replyFile](const CMakeFileInfo &info) {
return !info.isGenerated
&& info.path.lastModified() > replyFile.lastModified();
@@ -172,7 +171,7 @@ void FileApiReader::stop()
if (m_future) {
m_future->cancel();
- ExtensionSystem::PluginManager::futureSynchronizer()->addFuture(*m_future);
+ Utils::futureSynchronizer()->addFuture(*m_future);
}
m_future = {};
m_isParsing = false;
diff --git a/src/plugins/cmakeprojectmanager/presetsmacros.cpp b/src/plugins/cmakeprojectmanager/presetsmacros.cpp
index b7496d0c41..cfc161b417 100644
--- a/src/plugins/cmakeprojectmanager/presetsmacros.cpp
+++ b/src/plugins/cmakeprojectmanager/presetsmacros.cpp
@@ -139,11 +139,13 @@ void expand(const PresetType &preset, Environment &env, const FilePath &sourceDi
return presetEnv.value(macroName);
});
- bool append = true;
+ enum Operation { set, appendOrSet, prependOrSet };
+ Operation op = set;
if (key.compare("PATH", Qt::CaseInsensitive) == 0) {
+ op = appendOrSet;
const int index = value.indexOf("$penv{PATH}", 0, Qt::CaseInsensitive);
if (index != 0)
- append = false;
+ op = prependOrSet;
value.replace("$penv{PATH}", "", Qt::CaseInsensitive);
}
@@ -154,10 +156,17 @@ void expand(const PresetType &preset, Environment &env, const FilePath &sourceDi
// Make sure to expand the CMake macros also for environment variables
expandAllButEnv(preset, sourceDirectory, value);
- if (append)
+ switch (op) {
+ case set:
+ env.set(key, value);
+ break;
+ case appendOrSet:
env.appendOrSet(key, value);
- else
+ break;
+ case prependOrSet:
env.prependOrSet(key, value);
+ break;
+ }
});
}
diff --git a/src/plugins/cmakeprojectmanager/presetsparser.cpp b/src/plugins/cmakeprojectmanager/presetsparser.cpp
index 3befe4d99d..6c15f217f0 100644
--- a/src/plugins/cmakeprojectmanager/presetsparser.cpp
+++ b/src/plugins/cmakeprojectmanager/presetsparser.cpp
@@ -148,6 +148,30 @@ std::optional<PresetsDetails::Condition> parseCondition(const QJsonValue &jsonVa
return condition;
}
+bool parseVendor(const QJsonValue &jsonValue, std::optional<QVariantMap> &vendorSettings)
+{
+ // The whole section is optional
+ if (jsonValue.isUndefined())
+ return true;
+ if (!jsonValue.isObject())
+ return false;
+
+ const QJsonObject object = jsonValue.toObject();
+ const QJsonValue qtIo = object.value("qt.io/QtCreator/1.0");
+ if (qtIo.isUndefined())
+ return true;
+ if (!qtIo.isObject())
+ return false;
+
+ const QJsonObject qtIoObject = qtIo.toObject();
+ vendorSettings = QVariantMap();
+ for (const QString &settingKey : qtIoObject.keys()) {
+ const QJsonValue settingValue = qtIoObject.value(settingKey);
+ vendorSettings->insert(settingKey, settingValue.toVariant());
+ }
+ return true;
+}
+
bool parseConfigurePresets(const QJsonValue &jsonValue,
QList<PresetsDetails::ConfigurePreset> &configurePresets,
const Utils::FilePath &fileDir)
@@ -188,6 +212,9 @@ bool parseConfigurePresets(const QJsonValue &jsonValue,
if (object.contains("condition"))
preset.condition = parseCondition(object.value("condition"));
+ if (object.contains("vendor"))
+ parseVendor(object.value("vendor"), preset.vendor);
+
if (object.contains("displayName"))
preset.displayName = object.value("displayName").toString();
if (object.contains("description"))
@@ -378,6 +405,9 @@ bool parseBuildPresets(const QJsonValue &jsonValue,
if (object.contains("condition"))
preset.condition = parseCondition(object.value("condition"));
+ if (object.contains("vendor"))
+ parseVendor(object.value("vendor"), preset.vendor);
+
if (object.contains("displayName"))
preset.displayName = object.value("displayName").toString();
if (object.contains("description"))
@@ -502,13 +532,18 @@ bool PresetsParser::parse(const Utils::FilePath &jsonFile, QString &errorMessage
return false;
}
+ // optional
+ if (!parseVendor(root.value("vendor"), m_presetsData.vendor)) {
+ errorMessage = ::CMakeProjectManager::Tr::tr("Invalid \"vendor\" section in %1 file")
+ .arg(jsonFile.fileName());
+ }
+
return true;
}
-static QHash<QString, QString> merge(const QHash<QString, QString> &first,
- const QHash<QString, QString> &second)
+static QVariantMap merge(const QVariantMap &first, const QVariantMap &second)
{
- QHash<QString, QString> result = first;
+ QVariantMap result = first;
for (auto it = second.constKeyValueBegin(); it != second.constKeyValueEnd(); ++it) {
result[it->first] = it->second;
}
diff --git a/src/plugins/cmakeprojectmanager/presetsparser.h b/src/plugins/cmakeprojectmanager/presetsparser.h
index 6df09014af..cfbf565489 100644
--- a/src/plugins/cmakeprojectmanager/presetsparser.h
+++ b/src/plugins/cmakeprojectmanager/presetsparser.h
@@ -94,7 +94,7 @@ public:
std::optional<bool> hidden = false;
std::optional<QStringList> inherits;
std::optional<Condition> condition;
- std::optional<QHash<QString, QString>> vendor;
+ std::optional<QVariantMap> vendor;
std::optional<QString> displayName;
std::optional<QString> description;
std::optional<QString> generator;
@@ -120,7 +120,7 @@ public:
std::optional<bool> hidden = false;
std::optional<QStringList> inherits;
std::optional<Condition> condition;
- std::optional<QHash<QString, QString>> vendor;
+ std::optional<QVariantMap> vendor;
std::optional<QString> displayName;
std::optional<QString> description;
std::optional<Utils::Environment> environment;
@@ -142,7 +142,7 @@ public:
int version = 0;
bool havePresets = false;
QVersionNumber cmakeMinimimRequired;
- QHash<QString, QString> vendor;
+ std::optional<QVariantMap> vendor;
std::optional<QStringList> include;
Utils::FilePath fileDir;
QList<PresetsDetails::ConfigurePreset> configurePresets;
diff --git a/src/plugins/coco/Coco.json.in b/src/plugins/coco/Coco.json.in
index 01beb8c498..4da4961402 100644
--- a/src/plugins/coco/Coco.json.in
+++ b/src/plugins/coco/Coco.json.in
@@ -13,7 +13,10 @@
"",
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
- "Description" : "Squish Coco support. Squish Coco is a code coverage tool for Tcl, QML, C# and C/C++.",
- "Url" : "http://www.qt.io",
+ "Description" : "Access the Coco CoverageBrowser",
+ "LongDescription" : [
+ "View the results from the Coco CoverageBrowser to make tests more efficient and complete."
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/compilationdatabaseprojectmanager/CompilationDatabaseProjectManager.json.in b/src/plugins/compilationdatabaseprojectmanager/CompilationDatabaseProjectManager.json.in
index 4842c3d73c..9dd0696934 100644
--- a/src/plugins/compilationdatabaseprojectmanager/CompilationDatabaseProjectManager.json.in
+++ b/src/plugins/compilationdatabaseprojectmanager/CompilationDatabaseProjectManager.json.in
@@ -14,8 +14,14 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Build Systems",
- "Description" : "Compilation Database project support.",
- "Url" : "http://www.qt.io",
+ "Description" : "Open compilation databases as projects",
+ "LongDescription" : [
+ "The JSON compilation database format replays single builds independently of the build system.",
+ "A compilation database is a list of files and the compiler flags that are used to compile the files. It feeds the code model with the necessary information for correctly parsing the code when you open a file for editing.",
+ "You also need:",
+ "- A compilation database"
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES},
"Mimetypes" : [
"<?xml version='1.0'?>",
diff --git a/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseproject.cpp b/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseproject.cpp
index ebe7ff4475..af22b13520 100644
--- a/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseproject.cpp
+++ b/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseproject.cpp
@@ -332,7 +332,11 @@ CompilationDatabaseBuildSystem::CompilationDatabaseBuildSystem(Target *target)
this, &CompilationDatabaseBuildSystem::updateDeploymentData);
}
-CompilationDatabaseBuildSystem::~CompilationDatabaseBuildSystem() = default;
+CompilationDatabaseBuildSystem::~CompilationDatabaseBuildSystem()
+{
+ if (m_parser)
+ delete m_parser;
+}
void CompilationDatabaseBuildSystem::triggerParsing()
{
diff --git a/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseutils.h b/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseutils.h
index df0d4c9c9f..dd202efbf3 100644
--- a/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseutils.h
+++ b/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseutils.h
@@ -4,6 +4,7 @@
#pragma once
#include <cppeditor/cppprojectfile.h>
+#include <utils/synchronizedvalue.h>
#include <QHash>
@@ -29,7 +30,7 @@ public:
QStringList extras;
};
-using MimeBinaryCache = QHash<QString, bool>;
+using MimeBinaryCache = Utils::SynchronizedValue<QHash<QString, bool>>;
QStringList filterFromFileName(const QStringList &flags, const QString &fileName);
diff --git a/src/plugins/compilationdatabaseprojectmanager/compilationdbparser.cpp b/src/plugins/compilationdatabaseprojectmanager/compilationdbparser.cpp
index 4ab893a9e6..d613c81faf 100644
--- a/src/plugins/compilationdatabaseprojectmanager/compilationdbparser.cpp
+++ b/src/plugins/compilationdatabaseprojectmanager/compilationdbparser.cpp
@@ -8,8 +8,6 @@
#include <coreplugin/progressmanager/progressmanager.h>
-#include <extensionsystem/pluginmanager.h>
-
#include <projectexplorer/treescanner.h>
#include <utils/async.h>
@@ -132,6 +130,15 @@ CompilationDbParser::CompilationDbParser(const QString &projectName,
});
}
+CompilationDbParser::~CompilationDbParser()
+{
+ if (m_treeScanner && !m_treeScanner->isFinished()) {
+ auto future = m_treeScanner->future();
+ future.cancel();
+ future.waitForFinished();
+ }
+}
+
void CompilationDbParser::start()
{
// Check hash first.
@@ -160,12 +167,17 @@ void CompilationDbParser::start()
// Cache mime check result for speed up
if (!isIgnored) {
- auto it = m_mimeBinaryCache.find(mimeType.name());
- if (it != m_mimeBinaryCache.end()) {
+ if (auto it = m_mimeBinaryCache.get<std::optional<bool>>(
+ [mimeType](const QHash<QString, bool> &cache) -> std::optional<bool> {
+ auto it = cache.find(mimeType.name());
+ if (it != cache.end())
+ return *it;
+ return {};
+ })) {
isIgnored = *it;
} else {
isIgnored = TreeScanner::isMimeBinary(mimeType, fn);
- m_mimeBinaryCache[mimeType.name()] = isIgnored;
+ m_mimeBinaryCache.writeLocked()->insert(mimeType.name(), isIgnored);
}
}
@@ -190,7 +202,7 @@ void CompilationDbParser::start()
"CompilationDatabase.Parse");
++m_runningParserJobs;
m_parserWatcher.setFuture(future);
- ExtensionSystem::PluginManager::futureSynchronizer()->addFuture(future);
+ Utils::futureSynchronizer()->addFuture(future);
}
void CompilationDbParser::stop()
diff --git a/src/plugins/compilationdatabaseprojectmanager/compilationdbparser.h b/src/plugins/compilationdatabaseprojectmanager/compilationdbparser.h
index a06ba82581..065dfbe129 100644
--- a/src/plugins/compilationdatabaseprojectmanager/compilationdbparser.h
+++ b/src/plugins/compilationdatabaseprojectmanager/compilationdbparser.h
@@ -32,6 +32,7 @@ public:
MimeBinaryCache &mimeBinaryCache,
ProjectExplorer::BuildSystem::ParseGuard &&guard,
QObject *parent = nullptr);
+ ~CompilationDbParser();
void setPreviousProjectFileHash(const QByteArray &fileHash) { m_projectFileHash = fileHash; }
diff --git a/src/plugins/compilerexplorer/CompilerExplorer.json.in b/src/plugins/compilerexplorer/CompilerExplorer.json.in
index db771bd9f9..e7e0938fa8 100644
--- a/src/plugins/compilerexplorer/CompilerExplorer.json.in
+++ b/src/plugins/compilerexplorer/CompilerExplorer.json.in
@@ -15,7 +15,7 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Description" : "Integrates https://godbolt.org into Qt Creator.",
- "Url" : "http://www.qt.io",
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES},
"Mimetypes" : [
"<?xml version='1.0'?>",
diff --git a/src/plugins/compilerexplorer/compilerexplorer.qrc b/src/plugins/compilerexplorer/compilerexplorer.qrc
index ea46de321c..c4a75150d6 100644
--- a/src/plugins/compilerexplorer/compilerexplorer.qrc
+++ b/src/plugins/compilerexplorer/compilerexplorer.qrc
@@ -4,5 +4,7 @@
<file>wizard/cpp/file.qtce</file>
<file>wizard/python/wizard.json</file>
<file>wizard/python/file.qtce</file>
+ <file>wizard/qtcpp/file.qtce</file>
+ <file>wizard/qtcpp/wizard.json</file>
</qresource>
</RCC>
diff --git a/src/plugins/compilerexplorer/compilerexploreraspects.cpp b/src/plugins/compilerexplorer/compilerexploreraspects.cpp
index 9096b7dabe..1c1a8e6b96 100644
--- a/src/plugins/compilerexplorer/compilerexploreraspects.cpp
+++ b/src/plugins/compilerexplorer/compilerexploreraspects.cpp
@@ -91,7 +91,7 @@ void LibrarySelectionAspect::setVariantValue(const QVariant &value, Announcement
setValue(map, howToAnnounce);
}
-void LibrarySelectionAspect::addToLayout(Layouting::LayoutItem &parent)
+void LibrarySelectionAspect::addToLayout(Layouting::Layout &parent)
{
using namespace Layouting;
@@ -223,12 +223,12 @@ void LibrarySelectionAspect::addToLayout(Layouting::LayoutItem &parent)
// clang-format off
QStackedWidget *stack = static_cast<QStackedWidget*>(
Stack {
- noMargin,
Row { noMargin, displayLabel, editBtn },
Row { noMargin, nameCombo, versionCombo, clearBtn }
}.emerge()
);
// clang-format on
+ stack->setContentsMargins({});
connect(editBtn, &QPushButton::clicked, stack, [stack] { stack->setCurrentIndex(1); });
connect(this, &LibrarySelectionAspect::returnToDisplay, stack, [stack] {
stack->setCurrentIndex(0);
diff --git a/src/plugins/compilerexplorer/compilerexploreraspects.h b/src/plugins/compilerexplorer/compilerexploreraspects.h
index c89604b7aa..bb2e86a268 100644
--- a/src/plugins/compilerexplorer/compilerexploreraspects.h
+++ b/src/plugins/compilerexplorer/compilerexploreraspects.h
@@ -68,7 +68,7 @@ public:
LibrarySelectionAspect(Utils::AspectContainer *container = nullptr);
- void addToLayout(Layouting::LayoutItem &parent) override;
+ void addToLayout(Layouting::Layout &parent) override;
using ResultCallback = std::function<void(QList<QStandardItem *>)>;
using FillCallback = std::function<void(ResultCallback)>;
diff --git a/src/plugins/compilerexplorer/compilerexplorereditor.cpp b/src/plugins/compilerexplorer/compilerexplorereditor.cpp
index e1e564c733..1d00f8cb95 100644
--- a/src/plugins/compilerexplorer/compilerexplorereditor.cpp
+++ b/src/plugins/compilerexplorer/compilerexplorereditor.cpp
@@ -258,13 +258,13 @@ SourceEditorWidget::SourceEditorWidget(const std::shared_ptr<SourceSettings> &se
settings->languageId,
addCompilerButton,
removeSourceButton,
- customMargin({6, 0, 0, 0}), spacing(0),
+ customMargins(6, 0, 0, 0), spacing(0),
}.attachTo(toolBar);
Column {
toolBar,
m_codeEditor,
- noMargin(), spacing(0),
+ noMargin, spacing(0),
}.attachTo(this);
// clang-format on
@@ -370,7 +370,6 @@ CompilerWidget::CompilerWidget(const std::shared_ptr<SourceSettings> &sourceSett
connect(m_asmEditor, &AsmEditorWidget::gotFocus, this, &CompilerWidget::gotFocus);
auto advButton = new QToolButton;
- QSplitter *splitter{nullptr};
auto advDlg = new QAction;
advDlg->setIcon(Utils::Icons::SETTINGS_TOOLBAR.icon());
@@ -400,22 +399,20 @@ CompilerWidget::CompilerWidget(const std::shared_ptr<SourceSettings> &sourceSett
});
// clang-format off
-
Row {
m_compilerSettings->compiler,
advButton,
removeCompilerBtn,
- customMargin({6, 0, 0, 0}), spacing(0),
+ customMargins(6, 0, 0, 0), spacing(0),
}.attachTo(toolBar);
Column {
toolBar,
Splitter {
- bindTo(&splitter),
m_asmEditor,
createTerminal()
},
- noMargin(), spacing(0),
+ noMargin, spacing(0),
}.attachTo(this);
// clang-format on
@@ -426,27 +423,27 @@ SearchableTerminal *CompilerWidget::createTerminal()
{
m_resultTerminal = new SearchableTerminal();
m_resultTerminal->setAllowBlinkingCursor(false);
- std::array<QColor, 20> colors{Utils::creatorTheme()->color(Utils::Theme::TerminalAnsi0),
- Utils::creatorTheme()->color(Utils::Theme::TerminalAnsi1),
- Utils::creatorTheme()->color(Utils::Theme::TerminalAnsi2),
- Utils::creatorTheme()->color(Utils::Theme::TerminalAnsi3),
- Utils::creatorTheme()->color(Utils::Theme::TerminalAnsi4),
- Utils::creatorTheme()->color(Utils::Theme::TerminalAnsi5),
- Utils::creatorTheme()->color(Utils::Theme::TerminalAnsi6),
- Utils::creatorTheme()->color(Utils::Theme::TerminalAnsi7),
- Utils::creatorTheme()->color(Utils::Theme::TerminalAnsi8),
- Utils::creatorTheme()->color(Utils::Theme::TerminalAnsi9),
- Utils::creatorTheme()->color(Utils::Theme::TerminalAnsi10),
- Utils::creatorTheme()->color(Utils::Theme::TerminalAnsi11),
- Utils::creatorTheme()->color(Utils::Theme::TerminalAnsi12),
- Utils::creatorTheme()->color(Utils::Theme::TerminalAnsi13),
- Utils::creatorTheme()->color(Utils::Theme::TerminalAnsi14),
- Utils::creatorTheme()->color(Utils::Theme::TerminalAnsi15),
-
- Utils::creatorTheme()->color(Utils::Theme::TerminalForeground),
- Utils::creatorTheme()->color(Utils::Theme::TerminalBackground),
- Utils::creatorTheme()->color(Utils::Theme::TerminalSelection),
- Utils::creatorTheme()->color(Utils::Theme::TerminalFindMatch)};
+ std::array<QColor, 20> colors{Utils::creatorColor(Utils::Theme::TerminalAnsi0),
+ Utils::creatorColor(Utils::Theme::TerminalAnsi1),
+ Utils::creatorColor(Utils::Theme::TerminalAnsi2),
+ Utils::creatorColor(Utils::Theme::TerminalAnsi3),
+ Utils::creatorColor(Utils::Theme::TerminalAnsi4),
+ Utils::creatorColor(Utils::Theme::TerminalAnsi5),
+ Utils::creatorColor(Utils::Theme::TerminalAnsi6),
+ Utils::creatorColor(Utils::Theme::TerminalAnsi7),
+ Utils::creatorColor(Utils::Theme::TerminalAnsi8),
+ Utils::creatorColor(Utils::Theme::TerminalAnsi9),
+ Utils::creatorColor(Utils::Theme::TerminalAnsi10),
+ Utils::creatorColor(Utils::Theme::TerminalAnsi11),
+ Utils::creatorColor(Utils::Theme::TerminalAnsi12),
+ Utils::creatorColor(Utils::Theme::TerminalAnsi13),
+ Utils::creatorColor(Utils::Theme::TerminalAnsi14),
+ Utils::creatorColor(Utils::Theme::TerminalAnsi15),
+
+ Utils::creatorColor(Utils::Theme::TerminalForeground),
+ Utils::creatorColor(Utils::Theme::TerminalBackground),
+ Utils::creatorColor(Utils::Theme::TerminalSelection),
+ Utils::creatorColor(Utils::Theme::TerminalFindMatch)};
m_resultTerminal->setColors(colors);
diff --git a/src/plugins/compilerexplorer/wizard/qtcpp/file.qtce b/src/plugins/compilerexplorer/wizard/qtcpp/file.qtce
new file mode 100644
index 0000000000..896dacbbfe
--- /dev/null
+++ b/src/plugins/compilerexplorer/wizard/qtcpp/file.qtce
@@ -0,0 +1,18 @@
+{
+ "Sources": [
+ {
+ "Compilers": [
+ {
+ "ExecuteCode": true,
+ "Id": "clang_trunk",
+ "Libraries": {
+ "pcre2": "1042",
+ "qt": "660"
+ },
+ "Options": "-O3"
+ }
+ ],
+ "Source": "#include <QString>\\n#include <QDebug>\\n\\nint main() {\\n QString msg = \\"Hello World!\\";\\n qDebug() << msg;\\n return 0;\\n}"
+ }
+ ]
+}
diff --git a/src/plugins/compilerexplorer/wizard/qtcpp/wizard.json b/src/plugins/compilerexplorer/wizard/qtcpp/wizard.json
new file mode 100644
index 0000000000..e0d6edab85
--- /dev/null
+++ b/src/plugins/compilerexplorer/wizard/qtcpp/wizard.json
@@ -0,0 +1,37 @@
+{
+ "version": 1,
+ "supportedProjectTypes": [],
+ "id": "QTCE.QtCppSource",
+ "category": "U.CompilerExplorer",
+ "trDescription": "Creates an example CompilerExplorer setup for C++ with Qt Libraries.",
+ "trDisplayName": "Compiler Explorer Qt & C++ Source",
+ "trDisplayCategory": "Compiler Explorer",
+ "icon": ":/compilerexplorer/logos/ce.ico",
+ "iconKind": "Plain",
+ "options": {
+ "key": "DefaultSuffix",
+ "value": "%{JS: Util.preferredSuffix('application/compiler-explorer')}"
+ },
+ "pages": [
+ {
+ "trDisplayName": "Location",
+ "trShortTitle": "Location",
+ "typeId": "File"
+ },
+ {
+ "trDisplayName": "Project Management",
+ "trShortTitle": "Summary",
+ "typeId": "Summary"
+ }
+ ],
+ "generators": [
+ {
+ "typeId": "File",
+ "data": {
+ "source": "file.qtce",
+ "target": "%{JS: Util.fileName(value('TargetPath'), value('DefaultSuffix'))}",
+ "openInEditor": true
+ }
+ }
+ ]
+}
diff --git a/src/plugins/conan/Conan.json.in b/src/plugins/conan/Conan.json.in
index b81813c0a6..4d5fd7412e 100644
--- a/src/plugins/conan/Conan.json.in
+++ b/src/plugins/conan/Conan.json.in
@@ -13,7 +13,11 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Experimental" : true,
- "Description" : "Conan integration.",
- "Url" : "http://www.qt.io",
+ "Description" : "Integrate C or C++ libraries into projects",
+ "LongDescription" : [
+ "You also need:",
+ "- Conan"
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/copilot/Copilot.json.in b/src/plugins/copilot/Copilot.json.in
index 81f326770d..7e04ed8b0d 100644
--- a/src/plugins/copilot/Copilot.json.in
+++ b/src/plugins/copilot/Copilot.json.in
@@ -13,7 +13,12 @@
"",
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
- "Description" : "Copilot support",
- "Url" : "http://www.qt.io",
+ "Description" : "View suggestions from GitHub Copilot in code editor",
+ "LongDescription" : [
+ "You also need:",
+ "- An active GitHub Copilot subscription",
+ "- GitHub Copilot Neovim plugin installed (requires Node.js)"
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/copilot/copilotsettings.cpp b/src/plugins/copilot/copilotsettings.cpp
index 9ddfa4edfb..af16b56db5 100644
--- a/src/plugins/copilot/copilotsettings.cpp
+++ b/src/plugins/copilot/copilotsettings.cpp
@@ -50,18 +50,22 @@ CopilotSettings::CopilotSettings()
// Vim, Linux/macOS:
FilePath::fromUserInput("~/.vim/pack/github/start/copilot.vim/dist/agent.js"),
FilePath::fromUserInput("~/.vim/pack/github/start/copilot.vim/copilot/dist/agent.js"),
+ FilePath::fromUserInput("~/.vim/pack/github/start/copilot.vim/dist/language-server.js"),
// Neovim, Linux/macOS:
FilePath::fromUserInput("~/.config/nvim/pack/github/start/copilot.vim/dist/agent.js"),
FilePath::fromUserInput("~/.config/nvim/pack/github/start/copilot.vim/copilot/dist/agent.js"),
+ FilePath::fromUserInput("~/.config/nvim/pack/github/start/copilot.vim/dist/language-server.js"),
// Vim, Windows (PowerShell command):
FilePath::fromUserInput("~/vimfiles/pack/github/start/copilot.vim/dist/agent.js"),
FilePath::fromUserInput("~/vimfiles/pack/github/start/copilot.vim/copilot/dist/agent.js"),
+ FilePath::fromUserInput("~/vimfiles/pack/github/start/copilot.vim/dist/language-server.js"),
// Neovim, Windows (PowerShell command):
FilePath::fromUserInput("~/AppData/Local/nvim/pack/github/start/copilot.vim/dist/agent.js"),
- FilePath::fromUserInput("~/AppData/Local/nvim/pack/github/start/copilot.vim/copilot/dist/agent.js")
+ FilePath::fromUserInput("~/AppData/Local/nvim/pack/github/start/copilot.vim/copilot/dist/agent.js"),
+ FilePath::fromUserInput("~/AppData/Local/nvim/pack/github/start/copilot.vim/dist/language-server.js")
};
// clang-format on
@@ -168,41 +172,38 @@ CopilotSettings::CopilotSettings()
setLayouter([this] {
using namespace Layouting;
- auto warningLabel = new QLabel;
- warningLabel->setWordWrap(true);
- warningLabel->setTextInteractionFlags(
- Qt::LinksAccessibleByMouse | Qt::LinksAccessibleByKeyboard | Qt::TextSelectableByMouse);
- warningLabel->setText(
- Tr::tr("Enabling %1 is subject to your agreement and abidance with your applicable "
- "%1 terms. It is your responsibility to know and accept the requirements and "
- "parameters of using tools like %1. This may include, but is not limited to, "
- "ensuring you have the rights to allow %1 access to your code, as well as "
- "understanding any implications of your use of %1 and suggestions produced "
- "(like copyright, accuracy, etc.).")
- .arg("Copilot"));
-
- auto authWidget = new AuthWidget();
-
- auto helpLabel = new QLabel();
- helpLabel->setTextFormat(Qt::MarkdownText);
- helpLabel->setWordWrap(true);
- helpLabel->setTextInteractionFlags(
- Qt::LinksAccessibleByMouse | Qt::LinksAccessibleByKeyboard | Qt::TextSelectableByMouse);
- helpLabel->setOpenExternalLinks(true);
- connect(helpLabel, &QLabel::linkHovered, [](const QString &link) {
- QToolTip::showText(QCursor::pos(), link);
- });
-
// clang-format off
- helpLabel->setText(Tr::tr(
- "The Copilot plugin requires node.js and the Copilot neovim plugin. "
- "If you install the neovim plugin as described in %1, "
- "the plugin will find the agent.js file automatically.\n\n"
- "Otherwise you need to specify the path to the %2 "
- "file from the Copilot neovim plugin.",
- "Markdown text for the copilot instruction label")
- .arg("[README.md](https://github.com/github/copilot.vim)")
- .arg("[agent.js](https://github.com/github/copilot.vim/tree/release/dist)"));
+
+ Label warningLabel {
+ wordWrap(true),
+ textInteractionFlags(
+ Qt::LinksAccessibleByMouse | Qt::LinksAccessibleByKeyboard | Qt::TextSelectableByMouse),
+ text(Tr::tr("Enabling %1 is subject to your agreement and abidance with your applicable "
+ "%1 terms. It is your responsibility to know and accept the requirements and "
+ "parameters of using tools like %1. This may include, but is not limited to, "
+ "ensuring you have the rights to allow %1 access to your code, as well as "
+ "understanding any implications of your use of %1 and suggestions produced "
+ "(like copyright, accuracy, etc.).")
+ .arg("Copilot")),
+ };
+
+ Label helpLabel {
+ textFormat(Qt::MarkdownText),
+ wordWrap(true),
+ textInteractionFlags(
+ Qt::LinksAccessibleByMouse | Qt::LinksAccessibleByKeyboard | Qt::TextSelectableByMouse),
+ openExternalLinks(true),
+ onLinkHovered([](const QString &link) { QToolTip::showText(QCursor::pos(), link); }, this),
+ text(Tr::tr(
+ "The Copilot plugin requires node.js and the Copilot neovim plugin. "
+ "If you install the neovim plugin as described in %1, "
+ "the plugin will find the agent.js file automatically.\n\n"
+ "Otherwise you need to specify the path to the %2 "
+ "file from the Copilot neovim plugin.",
+ "Markdown text for the copilot instruction label")
+ .arg("[README.md](https://github.com/github/copilot.vim)")
+ .arg("[language-server.js](https://github.com/github/copilot.vim/tree/release/dist)"))
+ };
return Column {
Group {
@@ -213,7 +214,7 @@ CopilotSettings::CopilotSettings()
}
},
Form {
- authWidget, br,
+ new AuthWidget, br,
enableCopilot, br,
nodeJsPath, br,
distPath, br,
diff --git a/src/plugins/coreplugin/Core.json.in b/src/plugins/coreplugin/Core.json.in
index 8f121d61a3..aa99a3a586 100644
--- a/src/plugins/coreplugin/Core.json.in
+++ b/src/plugins/coreplugin/Core.json.in
@@ -15,7 +15,7 @@
],
"Category" : "Core",
"Description" : "The core plugin for the Qt IDE.",
- "Url" : "http://www.qt.io",
+ "Url" : "https://www.qt.io",
"Arguments" : [
{
"Name" : "-color",
diff --git a/src/plugins/coreplugin/actionmanager/actionmanager.cpp b/src/plugins/coreplugin/actionmanager/actionmanager.cpp
index 9176ad5660..b9bbf23f85 100644
--- a/src/plugins/coreplugin/actionmanager/actionmanager.cpp
+++ b/src/plugins/coreplugin/actionmanager/actionmanager.cpp
@@ -875,7 +875,7 @@ void ActionManagerPrivate::readUserSettings(Id id, Command *cmd)
settings->beginGroup(kKeyboardSettingsKeyV2);
if (settings->contains(id.toKey())) {
const QVariant v = settings->value(id.toKey());
- if (QMetaType::Type(v.type()) == QMetaType::QStringList) {
+ if (v.typeId() == QMetaType::QStringList) {
cmd->setKeySequences(Utils::transform<QList>(v.toStringList(), [](const QString &s) {
return QKeySequence::fromString(s);
}));
diff --git a/src/plugins/coreplugin/actionsfilter.cpp b/src/plugins/coreplugin/actionsfilter.cpp
index 93efa1dd2c..8b5f23df71 100644
--- a/src/plugins/coreplugin/actionsfilter.cpp
+++ b/src/plugins/coreplugin/actionsfilter.cpp
@@ -10,8 +10,6 @@
#include "icore.h"
#include "locator/locatormanager.h"
-#include <extensionsystem/pluginmanager.h>
-
#include <utils/algorithm.h>
#include <utils/async.h>
#include <utils/fuzzymatcher.h>
@@ -192,7 +190,6 @@ LocatorMatcherTasks ActionsFilter::matchers()
storage->reportOutput(m_entries);
return SetupResult::StopWithSuccess;
}
- async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
async.setConcurrentCallData(matches, *storage, m_entries);
return SetupResult::Continue;
};
diff --git a/src/plugins/coreplugin/coreconstants.h b/src/plugins/coreplugin/coreconstants.h
index a259717ba8..d8d2388fb1 100644
--- a/src/plugins/coreplugin/coreconstants.h
+++ b/src/plugins/coreplugin/coreconstants.h
@@ -119,6 +119,7 @@ const char GOTOPREVINHISTORY[] = "QtCreator.GotoPreviousInHistory";
const char GO_BACK[] = "QtCreator.GoBack";
const char GO_FORWARD[] = "QtCreator.GoForward";
const char GOTOLASTEDIT[] = "QtCreator.GotoLastEdit";
+const char REOPEN_CLOSED_EDITOR[] = "QtCreator.ReopenClosedEditor";
const char ABOUT_QTCREATOR[] = "QtCreator.AboutQtCreator";
const char ABOUT_PLUGINS[] = "QtCreator.AboutPlugins";
const char CHANGE_LOG[] = "QtCreator.ChangeLog";
diff --git a/src/plugins/coreplugin/corejsextensions.cpp b/src/plugins/coreplugin/corejsextensions.cpp
index 28f4790c2b..05c3ea82e5 100644
--- a/src/plugins/coreplugin/corejsextensions.cpp
+++ b/src/plugins/coreplugin/corejsextensions.cpp
@@ -29,6 +29,11 @@ QString UtilsJsExtension::qtCreatorVersion() const
return appInfo().displayVersion;
}
+QString UtilsJsExtension::qtCreatorIdeVersion() const
+{
+ return QCoreApplication::applicationVersion();
+}
+
QString UtilsJsExtension::toNativeSeparators(const QString &in) const
{
return QDir::toNativeSeparators(in);
diff --git a/src/plugins/coreplugin/corejsextensions.h b/src/plugins/coreplugin/corejsextensions.h
index 13dc81ccfd..1ca8685fdb 100644
--- a/src/plugins/coreplugin/corejsextensions.h
+++ b/src/plugins/coreplugin/corejsextensions.h
@@ -20,6 +20,7 @@ public:
// General information
Q_INVOKABLE QString qtVersion() const;
Q_INVOKABLE QString qtCreatorVersion() const;
+ Q_INVOKABLE QString qtCreatorIdeVersion() const;
// File name conversions:
Q_INVOKABLE QString toNativeSeparators(const QString &in) const;
diff --git a/src/plugins/coreplugin/coreplugin.cpp b/src/plugins/coreplugin/coreplugin.cpp
index dd99f47450..0777bd2042 100644
--- a/src/plugins/coreplugin/coreplugin.cpp
+++ b/src/plugins/coreplugin/coreplugin.cpp
@@ -251,6 +251,9 @@ bool CorePlugin::initialize(const QStringList &arguments, QString *errorMessage)
expander->registerVariable("HostOs:ExecutableSuffix",
Tr::tr("The platform executable suffix."),
[] { return QString(Utils::HostOsInfo::withExecutableSuffix("")); });
+ expander->registerFileVariables("IDE:Executable",
+ Tr::tr("The path to the running %1 itself.").arg(QGuiApplication::applicationDisplayName()),
+ []() { return FilePath::fromUserInput(QCoreApplication::applicationFilePath()); });
expander->registerVariable("IDE:ResourcePath",
Tr::tr("The directory where %1 finds its pre-installed resources.")
.arg(QGuiApplication::applicationDisplayName()),
diff --git a/src/plugins/coreplugin/designmode.cpp b/src/plugins/coreplugin/designmode.cpp
index cb5a6129df..f69e3d0e03 100644
--- a/src/plugins/coreplugin/designmode.cpp
+++ b/src/plugins/coreplugin/designmode.cpp
@@ -25,6 +25,18 @@
using namespace Utils;
+/*!
+ \class Core::DesignMode
+ \inmodule QtCreator
+
+ \brief The DesignMode class implements the mode for the Design mode, which is
+ for example used by \QMLD and \QD.
+
+ Other plugins can register themselves with registerDesignWidget(),
+ giving a list of MIME types that the editor understands, as well as an instance
+ to the main editor widget itself.
+*/
+
namespace Core {
struct DesignEditorInfo
diff --git a/src/plugins/coreplugin/designmode.h b/src/plugins/coreplugin/designmode.h
index 1f90d08dc5..7a656353e4 100644
--- a/src/plugins/coreplugin/designmode.h
+++ b/src/plugins/coreplugin/designmode.h
@@ -12,13 +12,6 @@ class FancyMainWindow;
namespace Core {
class IEditor;
-/**
- * A global mode for Design pane - used by Bauhaus (QML Designer) and
- * Qt Designer. Other plugins can register themselves by registerDesignWidget()
- * and giving a list of mimetypes that the editor understands, as well as an instance
- * to the main editor widget itself.
- */
-
class CORE_EXPORT DesignMode final : public IMode
{
Q_OBJECT
diff --git a/src/plugins/coreplugin/dialogs/shortcutsettings.cpp b/src/plugins/coreplugin/dialogs/shortcutsettings.cpp
index 4f7c0fb006..4c7de88fd9 100644
--- a/src/plugins/coreplugin/dialogs/shortcutsettings.cpp
+++ b/src/plugins/coreplugin/dialogs/shortcutsettings.cpp
@@ -30,6 +30,7 @@
#include <QLineEdit>
#include <QPointer>
#include <QPushButton>
+#include <QTimer>
#include <QTreeWidgetItem>
#include <array>
@@ -341,7 +342,7 @@ ShortcutInput::ShortcutInput()
QPalette palette = m_warningLabel->palette();
palette.setColor(QPalette::Active,
QPalette::WindowText,
- Utils::creatorTheme()->color(Utils::Theme::TextColorError));
+ Utils::creatorColor(Utils::Theme::TextColorError));
m_warningLabel->setPalette(palette);
connect(m_warningLabel, &QLabel::linkActivated, this, &ShortcutInput::showConflictsRequested);
@@ -420,6 +421,7 @@ private:
QGridLayout *m_shortcutLayout;
std::vector<std::unique_ptr<ShortcutInput>> m_shortcutInputs;
QPointer<QPushButton> m_addButton = nullptr;
+ QTimer m_updateTimer;
};
ShortcutSettingsWidget::ShortcutSettingsWidget()
@@ -428,7 +430,12 @@ ShortcutSettingsWidget::ShortcutSettingsWidget()
setTargetHeader(Tr::tr("Shortcut"));
setResetVisible(true);
+ m_updateTimer.setSingleShot(true);
+ m_updateTimer.setInterval(100);
+
connect(ActionManager::instance(), &ActionManager::commandListChanged,
+ &m_updateTimer, qOverload<>(&QTimer::start));
+ connect(&m_updateTimer, &QTimer::timeout,
this, &ShortcutSettingsWidget::initialize);
connect(this, &ShortcutSettingsWidget::currentCommandChanged,
this, &ShortcutSettingsWidget::handleCurrentCommandChanged);
@@ -718,15 +725,15 @@ bool ShortcutSettingsWidget::markCollisions(ShortcutItem *item, int index)
}
if (currentIsConflicting) {
currentItem->m_item->setForeground(2,
- Utils::creatorTheme()->color(
- Utils::Theme::TextColorError));
+ Utils::creatorColor(
+ Utils::Theme::TextColorError));
hasCollision = true;
}
}
}
item->m_item->setForeground(2,
hasCollision
- ? Utils::creatorTheme()->color(Utils::Theme::TextColorError)
+ ? Utils::creatorColor(Utils::Theme::TextColorError)
: commandList()->palette().windowText());
return hasCollision;
}
diff --git a/src/plugins/coreplugin/editormanager/editormanager.cpp b/src/plugins/coreplugin/editormanager/editormanager.cpp
index 9041d2bd4a..c8ef2a4f1a 100644
--- a/src/plugins/coreplugin/editormanager/editormanager.cpp
+++ b/src/plugins/coreplugin/editormanager/editormanager.cpp
@@ -261,10 +261,10 @@ void EditorManagerPlaceHolder::showEvent(QShowEvent *)
*/
/*!
- \fn void Core::EditorManager::editorCreated(Core::IEditor *editor, const QString &fileName)
+ \fn void Core::EditorManager::editorCreated(Core::IEditor *editor, const Utils::FilePath &filePath)
- This signal is emitted after an \a editor was created for \a fileName, but
- before it was opened in an editor view.
+ This signal is emitted after an \a editor was created for the file at
+ \a filePath, but before it was opened in an editor view.
*/
/*!
\fn void Core::EditorManager::editorOpened(Core::IEditor *editor)
@@ -596,6 +596,14 @@ void EditorManagerPrivate::init()
goForward.addToContainer(Constants::M_WINDOW, Constants::G_WINDOW_NAVIGATE);
goForward.addOnTriggered(this, &EditorManager::goForwardInNavigationHistory);
+ // Reopen last closed document
+ ActionBuilder reopenLastClosedDocument(this, Constants::REOPEN_CLOSED_EDITOR);
+ reopenLastClosedDocument.setText(::Core::Tr::tr("Reopen Last Closed Document"));
+ reopenLastClosedDocument.bindContextAction(&m_reopenLastClosedDocumenAction);
+ reopenLastClosedDocument.setContext(editDesignContext);
+ reopenLastClosedDocument.addToContainer(Constants::M_WINDOW, Constants::G_WINDOW_NAVIGATE);
+ reopenLastClosedDocument.addOnTriggered(this, &EditorManagerPrivate::reopenLastClosedDocument);
+
// Go to last edit
ActionBuilder gotoLastEdit(this, Constants::GOTOLASTEDIT);
gotoLastEdit.setText(::Core::Tr::tr("Go to Last Edit"));
@@ -1480,6 +1488,7 @@ bool EditorManagerPrivate::activateEditorForEntry(EditorView *view, DocumentMode
void EditorManagerPrivate::closeEditorOrDocument(IEditor *editor)
{
QTC_ASSERT(editor, return);
+ EditorManager::addCurrentPositionToNavigationHistory();
QList<IEditor *> visible = EditorManager::visibleEditors();
if (Utils::contains(visible,
[&editor](IEditor *other) {
@@ -1724,6 +1733,8 @@ void EditorManagerPrivate::setCurrentEditor(IEditor *editor, bool ignoreNavigati
updateActions();
+ if (QTC_GUARD(!d->m_currentView.isEmpty()) && d->m_currentView.constFirst() != previousView)
+ emit d->currentViewChanged();
if (d->m_currentEditor != previousEditor)
emit m_instance->currentEditorChanged(d->m_currentEditor);
}
@@ -1742,6 +1753,8 @@ void EditorManagerPrivate::setCurrentView(EditorView *view)
previousView->update();
if (d->m_currentView.constFirst())
view->update();
+
+ emit d->currentViewChanged();
}
setCurrentEditor(view->currentEditor());
@@ -1869,6 +1882,8 @@ void EditorManagerPrivate::addEditorArea(EditorArea *area)
// If we didn't find a better view, so be it
},
Qt::QueuedConnection);
+ connect(area, &SplitterOrView::splitStateChanged, d, &EditorManagerPrivate::viewCountChanged);
+ emit d->viewCountChanged();
}
void EditorManagerPrivate::splitNewWindow(EditorView *view)
@@ -2068,6 +2083,7 @@ void EditorManagerPrivate::updateActions()
EditorView *view = currentEditorView();
d->m_goBackAction->setEnabled(view ? view->canGoBack() : false);
d->m_goForwardAction->setEnabled(view ? view->canGoForward() : false);
+ d->m_reopenLastClosedDocumenAction->setEnabled(view ? view->canReopen() : false);
SplitterOrView *viewParent = (view ? view->parentSplitterOrView() : nullptr);
SplitterOrView *parentSplitter = (viewParent ? viewParent->findParentSplitter() : nullptr);
@@ -2220,6 +2236,22 @@ void EditorManagerPrivate::gotoPreviousSplit()
activateView(prevView);
}
+void EditorManagerPrivate::addClosedDocumentToCloseHistory(IEditor *editor)
+{
+ EditorView *view = EditorManagerPrivate::viewForEditor(editor);
+ QTC_ASSERT(view, return);
+ view->addClosedEditorToCloseHistory(editor);
+ EditorManagerPrivate::updateActions();
+}
+
+void EditorManagerPrivate::reopenLastClosedDocument()
+{
+ EditorView *view = EditorManagerPrivate::currentEditorView();
+ QTC_ASSERT(view, return);
+ view->reopenLastClosedDocument();
+ EditorManagerPrivate::updateActions();
+}
+
void EditorManagerPrivate::makeCurrentEditorWritable()
{
if (IDocument* doc = EditorManager::currentDocument())
@@ -2280,31 +2312,33 @@ void EditorManagerPrivate::editorAreaDestroyed(QObject *area)
}
}
// check if the destroyed editor area had the current view or current editor
- if (currentEditorView())
- return;
- // we need to set a new current editor or view
- if (!newActiveArea) {
- // some window managers behave weird and don't activate another window
- // or there might be a Qt Creator toplevel activated that doesn't have editor windows
- newActiveArea = d->m_editorAreas.first();
- }
-
- // check if the focusWidget points to some view
- SplitterOrView *focusSplitterOrView = nullptr;
- QWidget *candidate = newActiveArea->focusWidget();
- while (candidate && candidate != newActiveArea) {
- if ((focusSplitterOrView = qobject_cast<SplitterOrView *>(candidate)))
- break;
- candidate = candidate->parentWidget();
+ if (!currentEditorView()) {
+ // we need to set a new current editor or view
+ if (!newActiveArea) {
+ // some window managers behave weird and don't activate another window
+ // or there might be a Qt Creator toplevel activated that doesn't have editor windows
+ newActiveArea = d->m_editorAreas.first();
+ }
+
+ // check if the focusWidget points to some view
+ SplitterOrView *focusSplitterOrView = nullptr;
+ QWidget *candidate = newActiveArea->focusWidget();
+ while (candidate && candidate != newActiveArea) {
+ if ((focusSplitterOrView = qobject_cast<SplitterOrView *>(candidate)))
+ break;
+ candidate = candidate->parentWidget();
+ }
+ // focusWidget might have been 0
+ if (!focusSplitterOrView)
+ focusSplitterOrView = newActiveArea->findFirstView()->parentSplitterOrView();
+ QTC_ASSERT(focusSplitterOrView, focusSplitterOrView = newActiveArea);
+ EditorView *focusView
+ = focusSplitterOrView->findFirstView(); // can be just focusSplitterOrView
+ QTC_ASSERT(focusView, focusView = newActiveArea->findFirstView());
+ if (QTC_GUARD(focusView))
+ EditorManagerPrivate::activateView(focusView);
}
- // focusWidget might have been 0
- if (!focusSplitterOrView)
- focusSplitterOrView = newActiveArea->findFirstView()->parentSplitterOrView();
- QTC_ASSERT(focusSplitterOrView, focusSplitterOrView = newActiveArea);
- EditorView *focusView = focusSplitterOrView->findFirstView(); // can be just focusSplitterOrView
- QTC_ASSERT(focusView, focusView = newActiveArea->findFirstView());
- QTC_ASSERT(focusView, return);
- EditorManagerPrivate::activateView(focusView);
+ emit viewCountChanged();
}
void EditorManagerPrivate::autoSave()
@@ -2665,6 +2699,14 @@ QList<EditorView *> EditorManagerPrivate::allEditorViews()
return views;
}
+bool EditorManagerPrivate::hasMoreThanOneview()
+{
+ if (d->m_editorAreas.size() > 1)
+ return true;
+ QTC_ASSERT(d->m_editorAreas.size() > 0, return false);
+ return d->m_editorAreas.constFirst()->isSplitter();
+}
+
/*!
Returns the pointer to the instance. Only use for connecting to signals.
*/
@@ -2761,7 +2803,6 @@ void EditorManager::slotCloseCurrentEditorOrDocument()
{
if (!d->m_currentEditor)
return;
- addCurrentPositionToNavigationHistory();
d->closeEditorOrDocument(d->m_currentEditor);
}
@@ -3015,6 +3056,9 @@ bool EditorManager::closeDocuments(const QList<DocumentModel::Entry *> &entries)
*/
bool EditorManager::closeEditors(const QList<IEditor*> &editorsToClose, bool askAboutModifiedEditors)
{
+ for (IEditor *editor : editorsToClose)
+ EditorManagerPrivate::addClosedDocumentToCloseHistory(editor);
+
return EditorManagerPrivate::closeEditors(editorsToClose,
askAboutModifiedEditors ? EditorManagerPrivate::CloseFlag::CloseWithAsking
: EditorManagerPrivate::CloseFlag::CloseWithoutAsking);
@@ -3428,18 +3472,7 @@ void EditorManager::addCurrentPositionToNavigationHistory(const QByteArray &save
*/
void EditorManager::setLastEditLocation(const IEditor* editor)
{
- IDocument *document = editor->document();
- if (!document)
- return;
-
- const QByteArray &state = editor->saveState();
- EditLocation location;
- location.document = document;
- location.filePath = document->filePath();
- location.id = document->id();
- location.state = state;
-
- d->m_globalLastEditLocation = location;
+ d->m_globalLastEditLocation = EditLocation::forEditor(editor);
}
/*!
diff --git a/src/plugins/coreplugin/editormanager/editormanager_p.h b/src/plugins/coreplugin/editormanager/editormanager_p.h
index 789acd16e1..34a6745721 100644
--- a/src/plugins/coreplugin/editormanager/editormanager_p.h
+++ b/src/plugins/coreplugin/editormanager/editormanager_p.h
@@ -61,6 +61,7 @@ public:
static EditorArea *mainEditorArea();
static EditorView *currentEditorView();
static QList<EditorView *> allEditorViews();
+ static bool hasMoreThanOneview();
static void setCurrentEditor(IEditor *editor, bool ignoreNavigationHistory = false);
static IEditor *openEditor(EditorView *view,
const Utils::FilePath &filePath,
@@ -123,6 +124,8 @@ public:
const Utils::FilePath &newFilePath,
Utils::Id originalType = {});
+ static void addClosedDocumentToCloseHistory(IEditor *editor);
+
public slots:
static bool saveDocument(Core::IDocument *document);
static bool saveDocumentAs(Core::IDocument *document);
@@ -132,11 +135,15 @@ public slots:
static void gotoPreviousSplit();
static void gotoNextSplit();
+ static void reopenLastClosedDocument();
+
void handleDocumentStateChange(Core::IDocument *document);
void editorAreaDestroyed(QObject *area);
signals:
void placeholderTextChanged(const QString &text);
+ void currentViewChanged();
+ void viewCountChanged();
private:
static void gotoNextDocHistory();
@@ -213,6 +220,7 @@ private:
QAction *m_gotoPreviousDocHistoryAction = nullptr;
QAction *m_goBackAction = nullptr;
QAction *m_goForwardAction = nullptr;
+ QAction *m_reopenLastClosedDocumenAction = nullptr;
QAction *m_gotoLastEditAction = nullptr;
QAction *m_splitAction = nullptr;
QAction *m_splitSideBySideAction = nullptr;
diff --git a/src/plugins/coreplugin/editormanager/editorview.cpp b/src/plugins/coreplugin/editormanager/editorview.cpp
index 6aa20510cb..42ad07d249 100644
--- a/src/plugins/coreplugin/editormanager/editorview.cpp
+++ b/src/plugins/coreplugin/editormanager/editorview.cpp
@@ -14,10 +14,11 @@
#include <utils/algorithm.h>
#include <utils/infobar.h>
-#include <utils/qtcassert.h>
-#include <utils/theme/theme.h>
#include <utils/layoutbuilder.h>
#include <utils/link.h>
+#include <utils/overlaywidget.h>
+#include <utils/qtcassert.h>
+#include <utils/theme/theme.h>
#include <utils/utilsicons.h>
#include <QFileInfo>
@@ -38,14 +39,16 @@ namespace Core::Internal {
// EditorView
-EditorView::EditorView(SplitterOrView *parentSplitterOrView, QWidget *parent) :
- QWidget(parent),
- m_parentSplitterOrView(parentSplitterOrView),
- m_toolBar(new EditorToolBar(this)),
- m_container(new QStackedWidget(this)),
- m_infoBarDisplay(new InfoBarDisplay(this)),
- m_statusHLine(Layouting::createHr(this)),
- m_statusWidget(new QFrame(this))
+EditorView::EditorView(SplitterOrView *parentSplitterOrView, QWidget *parent)
+ : QWidget(parent)
+ , m_parentSplitterOrView(parentSplitterOrView)
+ , m_toolBar(new EditorToolBar(this))
+ , m_container(new QStackedWidget(this))
+ , m_infoBarDisplay(new InfoBarDisplay(this))
+ , m_statusHLine(Layouting::createHr(this))
+ , m_statusWidget(new QFrame(this))
+ , m_backMenu(new QMenu(this))
+ , m_forwardMenu(new QMenu(this))
{
auto tl = new QVBoxLayout(this);
tl->setSpacing(0);
@@ -66,6 +69,8 @@ EditorView::EditorView(SplitterOrView *parentSplitterOrView, QWidget *parent) :
connect(m_toolBar, &EditorToolBar::splitNewWindowClicked, this, &EditorView::splitNewWindow);
connect(m_toolBar, &EditorToolBar::closeSplitClicked, this, &EditorView::closeSplit);
m_toolBar->setMenuProvider([this](QMenu *menu) { fillListContextMenu(menu); });
+ m_toolBar->setGoBackMenu(m_backMenu);
+ m_toolBar->setGoForwardMenu(m_forwardMenu);
tl->addWidget(m_toolBar);
}
@@ -125,6 +130,33 @@ EditorView::EditorView(SplitterOrView *parentSplitterOrView, QWidget *parent) :
this, &EditorView::openDroppedFiles);
updateNavigatorActions();
+
+ auto currentViewOverlay = new OverlayWidget;
+ currentViewOverlay->attachToWidget(this);
+ currentViewOverlay->setAttribute(Qt::WA_OpaquePaintEvent);
+ currentViewOverlay->setResizeFunction([this](QWidget *w, const QSize &) {
+ const QRect toolbarRect = m_toolBar->geometry();
+ w->setGeometry(toolbarRect.x(), toolbarRect.bottom() - 1, toolbarRect.width(), 2);
+ });
+ currentViewOverlay->setPaintFunction([](QWidget *w, QPainter &p, QPaintEvent *) {
+ p.fillRect(w->rect(), w->palette().color(QPalette::Highlight));
+ });
+ currentViewOverlay->setVisible(false);
+ const auto updateCurrentViewOverlay = [this, currentViewOverlay] {
+ currentViewOverlay->setVisible(
+ EditorManagerPrivate::hasMoreThanOneview()
+ && EditorManagerPrivate::currentEditorView() == this);
+ };
+ connect(
+ EditorManagerPrivate::instance(),
+ &EditorManagerPrivate::currentViewChanged,
+ currentViewOverlay,
+ updateCurrentViewOverlay);
+ connect(
+ EditorManagerPrivate::instance(),
+ &EditorManagerPrivate::viewCountChanged,
+ currentViewOverlay,
+ updateCurrentViewOverlay);
}
EditorView::~EditorView() = default;
@@ -232,22 +264,17 @@ bool EditorView::canGoBack() const
return m_currentNavigationHistoryPosition > 0;
}
-void EditorView::updateEditorHistory(IEditor *editor, QList<EditLocation> &history)
+bool EditorView::canReopen() const
{
- if (!editor)
- return;
- IDocument *document = editor->document();
-
- if (!document)
- return;
+ return !m_closedEditorHistory.isEmpty();
+}
- QByteArray state = editor->saveState();
+void EditorView::updateEditorHistory(IEditor *editor, QList<EditLocation> &history)
+{
+ IDocument *document = editor ? editor->document() : nullptr;
+ QTC_ASSERT(document, return);
- EditLocation location;
- location.document = document;
- location.filePath = document->filePath();
- location.id = document->id();
- location.state = state;
+ const auto location = EditLocation::forEditor(editor);
for (int i = 0; i < history.size(); ++i) {
const EditLocation &item = history.at(i);
@@ -275,11 +302,11 @@ void EditorView::paintEvent(QPaintEvent *)
QRect rect = m_container->geometry();
if (creatorTheme()->flag(Theme::FlatToolBars)) {
- painter.fillRect(rect, creatorTheme()->color(Theme::EditorPlaceholderColor));
+ painter.fillRect(rect, creatorColor(Theme::EditorPlaceholderColor));
} else {
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setPen(Qt::NoPen);
- painter.setBrush(creatorTheme()->color(Theme::EditorPlaceholderColor));
+ painter.setBrush(creatorColor(Theme::EditorPlaceholderColor));
const int r = 3;
painter.drawRoundedRect(rect.adjusted(r , r, -r, -r), r * 2, r * 2);
}
@@ -463,24 +490,11 @@ constexpr int navigationHistorySize = 100;
void EditorView::addCurrentPositionToNavigationHistory(const QByteArray &saveState)
{
IEditor *editor = currentEditor();
- if (!editor)
- return;
- IDocument *document = editor->document();
- if (!document)
+ if (!editor || !editor->document())
return;
- QByteArray state;
- if (saveState.isNull())
- state = editor->saveState();
- else
- state = saveState;
-
- EditLocation location;
- location.document = document;
- location.filePath = document->filePath();
- location.id = document->id();
- location.state = state;
+ const auto location = EditLocation::forEditor(editor, saveState);
m_currentNavigationHistoryPosition = qMin(m_currentNavigationHistoryPosition, m_navigationHistory.size()); // paranoia
m_navigationHistory.insert(m_currentNavigationHistoryPosition, location);
++m_currentNavigationHistoryPosition;
@@ -496,6 +510,21 @@ void EditorView::addCurrentPositionToNavigationHistory(const QByteArray &saveSta
updateNavigatorActions();
}
+void EditorView::addClosedEditorToCloseHistory(IEditor *editor)
+{
+ static const int MAX_ITEMS = 20;
+
+ if (!editor || !editor->document())
+ return;
+
+ EditLocation location = EditLocation::forEditor(editor);
+
+ m_closedEditorHistory.push_back(location);
+
+ if (m_closedEditorHistory.size() > MAX_ITEMS)
+ m_closedEditorHistory.removeFirst();
+}
+
void EditorView::cutForwardNavigationHistory()
{
while (m_currentNavigationHistoryPosition < m_navigationHistory.size() - 1)
@@ -504,6 +533,35 @@ void EditorView::cutForwardNavigationHistory()
void EditorView::updateNavigatorActions()
{
+ static const int MAX_ITEMS = 20;
+ FilePath last;
+ m_backMenu->clear();
+ int count = 0;
+ for (int i = m_currentNavigationHistoryPosition - 1; i >= 0; i--) {
+ const EditLocation &loc = m_navigationHistory.at(i);
+ if (loc.filePath != last) {
+ m_backMenu->addAction(loc.displayName(), this, [this, i] { goToNavigationHistory(i); });
+ last = loc.filePath;
+ ++count;
+ if (count >= MAX_ITEMS)
+ break;
+ }
+ }
+ last = {};
+ m_forwardMenu->clear();
+ count = 0;
+ for (int i = m_currentNavigationHistoryPosition + 1; i < m_navigationHistory.size(); i++) {
+ const EditLocation &loc = m_navigationHistory.at(i);
+ if (loc.filePath != last) {
+ m_forwardMenu->addAction(loc.displayName(), this, [this, i] {
+ goToNavigationHistory(i);
+ });
+ last = loc.filePath;
+ ++count;
+ if (count >= MAX_ITEMS)
+ break;
+ }
+ }
m_toolBar->setCanGoBack(canGoBack());
m_toolBar->setCanGoForward(canGoForward());
}
@@ -524,7 +582,6 @@ void EditorView::updateCurrentPositionInNavigationHistory()
if (!editor || !editor->document())
return;
- IDocument *document = editor->document();
EditLocation *location;
if (m_currentNavigationHistoryPosition < m_navigationHistory.size()) {
location = &m_navigationHistory[m_currentNavigationHistoryPosition];
@@ -532,10 +589,7 @@ void EditorView::updateCurrentPositionInNavigationHistory()
m_navigationHistory.append(EditLocation());
location = &m_navigationHistory[m_navigationHistory.size()-1];
}
- location->document = document;
- location->filePath = document->filePath();
- location->id = document->id();
- location->state = editor->saveState();
+ *location = EditLocation::forEditor(editor);
}
static bool fileNameWasRemoved(const FilePath &filePath)
@@ -543,31 +597,45 @@ static bool fileNameWasRemoved(const FilePath &filePath)
return !filePath.isEmpty() && !filePath.exists();
}
+bool EditorView::openEditorFromNavigationHistory(int index)
+{
+ EditLocation location = m_navigationHistory.at(index);
+ IEditor *editor = nullptr;
+ if (location.document) {
+ editor = EditorManagerPrivate::activateEditorForDocument(
+ this, location.document, EditorManager::IgnoreNavigationHistory);
+ }
+ if (!editor) {
+ if (fileNameWasRemoved(location.filePath))
+ return false;
+ editor = EditorManagerPrivate::openEditor(
+ this, location.filePath, location.id, EditorManager::IgnoreNavigationHistory);
+ if (!editor)
+ return false;
+ }
+ editor->restoreState(location.state);
+ return true;
+}
+
+void EditorView::goToNavigationHistory(int index)
+{
+ if (index >= m_navigationHistory.size())
+ return;
+ updateCurrentPositionInNavigationHistory();
+ if (!openEditorFromNavigationHistory(index))
+ m_navigationHistory.removeAt(index);
+ m_currentNavigationHistoryPosition = index;
+ updateNavigatorActions();
+}
+
void EditorView::goBackInNavigationHistory()
{
updateCurrentPositionInNavigationHistory();
while (m_currentNavigationHistoryPosition > 0) {
--m_currentNavigationHistoryPosition;
- EditLocation location = m_navigationHistory.at(m_currentNavigationHistoryPosition);
- IEditor *editor = nullptr;
- if (location.document) {
- editor = EditorManagerPrivate::activateEditorForDocument(this, location.document,
- EditorManager::IgnoreNavigationHistory);
- }
- if (!editor) {
- if (fileNameWasRemoved(location.filePath)) {
- m_navigationHistory.removeAt(m_currentNavigationHistoryPosition);
- continue;
- }
- editor = EditorManagerPrivate::openEditor(this, location.filePath, location.id,
- EditorManager::IgnoreNavigationHistory);
- if (!editor) {
- m_navigationHistory.removeAt(m_currentNavigationHistoryPosition);
- continue;
- }
- }
- editor->restoreState(location.state);
- break;
+ if (openEditorFromNavigationHistory(m_currentNavigationHistoryPosition))
+ break;
+ m_navigationHistory.removeAt(m_currentNavigationHistoryPosition);
}
updateNavigatorActions();
}
@@ -579,32 +647,22 @@ void EditorView::goForwardInNavigationHistory()
return;
++m_currentNavigationHistoryPosition;
while (m_currentNavigationHistoryPosition < m_navigationHistory.size()) {
- IEditor *editor = nullptr;
- EditLocation location = m_navigationHistory.at(m_currentNavigationHistoryPosition);
- if (location.document) {
- editor = EditorManagerPrivate::activateEditorForDocument(this, location.document,
- EditorManager::IgnoreNavigationHistory);
- }
- if (!editor) {
- if (fileNameWasRemoved(location.filePath)) {
- m_navigationHistory.removeAt(m_currentNavigationHistoryPosition);
- continue;
- }
- editor = EditorManagerPrivate::openEditor(this, location.filePath, location.id,
- EditorManager::IgnoreNavigationHistory);
- if (!editor) {
- m_navigationHistory.removeAt(m_currentNavigationHistoryPosition);
- continue;
- }
- }
- editor->restoreState(location.state);
- break;
+ if (openEditorFromNavigationHistory(m_currentNavigationHistoryPosition))
+ break;
+ m_navigationHistory.removeAt(m_currentNavigationHistoryPosition);
}
if (m_currentNavigationHistoryPosition >= m_navigationHistory.size())
m_currentNavigationHistoryPosition = qMax<int>(m_navigationHistory.size() - 1, 0);
updateNavigatorActions();
}
+void EditorView::reopenLastClosedDocument()
+{
+ if (m_closedEditorHistory.isEmpty())
+ return;
+ goToEditLocation(m_closedEditorHistory.takeLast());
+}
+
void EditorView::goToEditLocation(const EditLocation &location)
{
IEditor *editor = nullptr;
@@ -1005,4 +1063,29 @@ EditLocation EditLocation::load(const QByteArray &data)
return loc;
}
+EditLocation EditLocation::forEditor(const IEditor *editor, const QByteArray &saveState)
+{
+ QTC_ASSERT(editor, return EditLocation());
+
+ IDocument *document = editor->document();
+ QTC_ASSERT(document, return EditLocation());
+
+ const QByteArray &state = saveState.isEmpty() ? editor->saveState() : saveState;
+
+ EditLocation location;
+ location.document = document;
+ location.filePath = document->filePath();
+ location.id = document->id();
+ location.state = state;
+
+ return location;
+}
+
+QString EditLocation::displayName() const
+{
+ if (document)
+ return document->displayName();
+ return filePath.fileName();
+}
+
} // Core::Internal
diff --git a/src/plugins/coreplugin/editormanager/editorview.h b/src/plugins/coreplugin/editormanager/editorview.h
index 6a81a7b4b7..374c6df21f 100644
--- a/src/plugins/coreplugin/editormanager/editorview.h
+++ b/src/plugins/coreplugin/editormanager/editorview.h
@@ -40,6 +40,9 @@ class EditLocation
public:
QByteArray save() const;
static EditLocation load(const QByteArray &data);
+ static EditLocation forEditor(const IEditor *editor, const QByteArray &saveState = {});
+
+ QString displayName() const;
QPointer<IDocument> document;
Utils::FilePath filePath;
@@ -82,13 +85,17 @@ public:
bool canGoForward() const;
bool canGoBack() const;
+ bool canReopen() const;
void goBackInNavigationHistory();
void goForwardInNavigationHistory();
+ void reopenLastClosedDocument();
+
void goToEditLocation(const EditLocation &location);
void addCurrentPositionToNavigationHistory(const QByteArray &saveState = QByteArray());
+ void addClosedEditorToCloseHistory(IEditor *editor);
void cutForwardNavigationHistory();
QList<EditLocation> editorHistory() const { return m_editorHistory; }
@@ -124,6 +131,8 @@ private:
void checkProjectLoaded(IEditor *editor);
void updateCurrentPositionInNavigationHistory();
+ bool openEditorFromNavigationHistory(int index);
+ void goToNavigationHistory(int index);
SplitterOrView *m_parentSplitterOrView;
EditorToolBar *m_toolBar;
@@ -140,7 +149,10 @@ private:
QLabel *m_emptyViewLabel;
QList<EditLocation> m_navigationHistory;
+ QMenu *m_backMenu;
+ QMenu *m_forwardMenu;
QList<EditLocation> m_editorHistory;
+ QList<EditLocation> m_closedEditorHistory;
int m_currentNavigationHistoryPosition = 0;
};
diff --git a/src/plugins/coreplugin/editormanager/ieditorfactory.cpp b/src/plugins/coreplugin/editormanager/ieditorfactory.cpp
index 7528e12a85..22aa24e737 100644
--- a/src/plugins/coreplugin/editormanager/ieditorfactory.cpp
+++ b/src/plugins/coreplugin/editormanager/ieditorfactory.cpp
@@ -47,23 +47,23 @@ static void mimeTypeFactoryLookup(const Utils::MimeType &mimeType,
\brief The IEditorFactory class creates suitable editors for documents
according to their MIME type.
- Whenever a user wants to edit or create a document, the EditorManager
- scans all IEditorFactory instances for suitable editors. The selected
- IEditorFactory is then asked to create an editor.
+ When a user wants to edit or create a document, the EditorManager
+ scans all IEditorFactory instances for suitable editors and selects one
+ to create an editor.
Implementations should set the properties of the IEditorFactory subclass in
their constructor with IEditorFactory::setId(), IEditorFactory::setDisplayName(),
- IEditorFactory::setMimeTypes(), and setEditorCreator()
+ IEditorFactory::setMimeTypes(), and IEditorFactory::setEditorCreator().
IEditorFactory instances automatically register themselves in \QC in their
constructor.
- There are two varieties of editors: Internal and external. Internal editors
- open within the main editing area of Qt Creator. An IEditorFactory defines
- an internal editor by using the \c setEditorCreator function. External
+ There are two varieties of editors: internal and external. Internal editors
+ open within the main editing area of \QC. An IEditorFactory instance defines
+ an internal editor by using the setEditorCreator() function. External
editors are external applications and are defined by using the
- \c setEditorStarter function. They are accessible by the user using
- the \uicontrol{Open With} dialog
+ setEditorStarter() function. The user can access them from the
+ \uicontrol{Open With} dialog.
\sa Core::IEditor
\sa Core::IDocument
@@ -167,7 +167,7 @@ IEditorFactory *IEditorFactory::editorFactoryForId(const Utils::Id &id)
/*!
Returns all available internal and external editors for the \a mimeType in the
- default order: Editor types ordered by MIME type hierarchy, internal editors
+ default order: editor types are ordered by MIME type hierarchy, internal editors
first.
*/
const EditorFactories IEditorFactory::defaultEditorFactories(const MimeType &mimeType)
@@ -283,9 +283,10 @@ IEditor *IEditorFactory::createEditor() const
}
/*!
- Starts an external editor.
+ Opens the file at \a filePath in an external editor.
- Uses the function set with setEditorStarter() to start the editor.
+ Returns \c true on success or \c false on failure with the error in
+ \a errorMessage.
\sa setEditorStarter()
*/
@@ -299,7 +300,7 @@ bool IEditorFactory::startEditor(const FilePath &filePath, QString *errorMessage
Sets the function that is used to create an editor instance in
createEditor() to \a creator.
- This is mutually exclusive with the use of setEditorStarter.
+ This is mutually exclusive with the use of setEditorStarter().
\sa createEditor()
*/
@@ -313,12 +314,16 @@ void IEditorFactory::setEditorCreator(const std::function<IEditor *()> &creator)
}
/*!
- Opens the editor with \a fileName. Returns \c true on success or \c false
- on failure along with the error in \a errorMessage.
+ \fn void Core::IEditorFactory::setEditorStarter(const std::function<bool(const Utils::FilePath &, QString *)> &starter);
- This is mutually exclusive with the use of setEditorCreator.
-*/
+ Sets the function that is used to open a file for a given \c FilePath to
+ \a starter.
+
+ The function should return \c true on success, or return \c false and set the
+ \c QString to an error message at failure.
+ This is mutually exclusive with the use of setEditorCreator().
+*/
void IEditorFactory::setEditorStarter(const std::function<bool(const FilePath &, QString *)> &starter)
{
QTC_CHECK(!m_starter);
diff --git a/src/plugins/coreplugin/editortoolbar.cpp b/src/plugins/coreplugin/editortoolbar.cpp
index ee4d912d85..857a32b637 100644
--- a/src/plugins/coreplugin/editortoolbar.cpp
+++ b/src/plugins/coreplugin/editortoolbar.cpp
@@ -39,6 +39,24 @@ enum {
namespace Core {
+class ButtonWithMenu : public QToolButton
+{
+public:
+ ButtonWithMenu(QWidget *parent = nullptr)
+ : QToolButton(parent)
+ {}
+
+protected:
+ void mousePressEvent(QMouseEvent *e) override
+ {
+ if (e->button() == Qt::RightButton) {
+ showMenu();
+ return;
+ }
+ QToolButton::mousePressEvent(e);
+ }
+};
+
struct EditorToolBarPrivate
{
explicit EditorToolBarPrivate(QWidget *parent, EditorToolBar *q);
@@ -51,8 +69,8 @@ struct EditorToolBarPrivate
EditorToolBar::MenuProvider m_menuProvider;
QAction *m_goBackAction;
QAction *m_goForwardAction;
- QToolButton *m_backButton;
- QToolButton *m_forwardButton;
+ ButtonWithMenu *m_backButton;
+ ButtonWithMenu *m_forwardButton;
QToolButton *m_splitButton;
QAction *m_horizontalSplitAction;
QAction *m_verticalSplitAction;
@@ -68,27 +86,27 @@ struct EditorToolBarPrivate
bool m_isStandalone;
};
-EditorToolBarPrivate::EditorToolBarPrivate(QWidget *parent, EditorToolBar *q) :
- m_editorList(new QComboBox(q)),
- m_closeEditorButton(new QToolButton(q)),
- m_lockButton(new QToolButton(q)),
- m_dragHandle(new QToolButton(q)),
- m_dragHandleMenu(nullptr),
- m_goBackAction(new QAction(Utils::Icons::PREV_TOOLBAR.icon(), Tr::tr("Go Back"), parent)),
- m_goForwardAction(new QAction(Utils::Icons::NEXT_TOOLBAR.icon(), Tr::tr("Go Forward"), parent)),
- m_backButton(new QToolButton(q)),
- m_forwardButton(new QToolButton(q)),
- m_splitButton(new QToolButton(q)),
- m_horizontalSplitAction(new QAction(Utils::Icons::SPLIT_HORIZONTAL.icon(),
- Tr::tr("Split"), parent)),
- m_verticalSplitAction(new QAction(Utils::Icons::SPLIT_VERTICAL.icon(),
- Tr::tr("Split Side by Side"), parent)),
- m_splitNewWindowAction(new QAction(Tr::tr("Open in New Window"), parent)),
- m_closeSplitButton(new QToolButton(q)),
- m_activeToolBar(nullptr),
- m_toolBarPlaceholder(new QWidget(q)),
- m_defaultToolBar(new QWidget(q)),
- m_isStandalone(false)
+EditorToolBarPrivate::EditorToolBarPrivate(QWidget *parent, EditorToolBar *q)
+ : m_editorList(new QComboBox(q))
+ , m_closeEditorButton(new QToolButton(q))
+ , m_lockButton(new QToolButton(q))
+ , m_dragHandle(new QToolButton(q))
+ , m_dragHandleMenu(nullptr)
+ , m_goBackAction(new QAction(Utils::Icons::PREV_TOOLBAR.icon(), Tr::tr("Go Back"), parent))
+ , m_goForwardAction(new QAction(Utils::Icons::NEXT_TOOLBAR.icon(), Tr::tr("Go Forward"), parent))
+ , m_backButton(new ButtonWithMenu(q))
+ , m_forwardButton(new ButtonWithMenu(q))
+ , m_splitButton(new QToolButton(q))
+ , m_horizontalSplitAction(
+ new QAction(Utils::Icons::SPLIT_HORIZONTAL.icon(), Tr::tr("Split"), parent))
+ , m_verticalSplitAction(
+ new QAction(Utils::Icons::SPLIT_VERTICAL.icon(), Tr::tr("Split Side by Side"), parent))
+ , m_splitNewWindowAction(new QAction(Tr::tr("Open in New Window"), parent))
+ , m_closeSplitButton(new QToolButton(q))
+ , m_activeToolBar(nullptr)
+ , m_toolBarPlaceholder(new QWidget(q))
+ , m_defaultToolBar(new QWidget(q))
+ , m_isStandalone(false)
{
}
@@ -347,6 +365,16 @@ void EditorToolBar::setCanGoForward(bool canGoForward)
d->m_goForwardAction->setEnabled(canGoForward);
}
+void EditorToolBar::setGoBackMenu(QMenu *menu)
+{
+ d->m_backButton->setMenu(menu);
+}
+
+void EditorToolBar::setGoForwardMenu(QMenu *menu)
+{
+ d->m_forwardButton->setMenu(menu);
+}
+
void EditorToolBar::updateActionShortcuts()
{
d->m_closeEditorButton->setToolTip(ActionManager::command(Constants::CLOSE)->stringWithAppendedShortcut(Tr::tr("Close Document")));
diff --git a/src/plugins/coreplugin/editortoolbar.h b/src/plugins/coreplugin/editortoolbar.h
index a2aa089da7..72925c0fd6 100644
--- a/src/plugins/coreplugin/editortoolbar.h
+++ b/src/plugins/coreplugin/editortoolbar.h
@@ -19,10 +19,13 @@ class IDocument;
struct EditorToolBarPrivate;
-/**
- * Fakes an IEditor-like toolbar for design mode widgets such as Qt Designer and Bauhaus.
- * Creates a combobox for open files and lock and close buttons on the right.
- */
+/*!
+ \class Core::EditorToolBar
+ \inmodule QtCreator
+
+ Implements the editor toolbar for editor views and for design mode widgets such as
+ \QD and \QMLD. Creates a combobox for open files and lock and close buttons on the right.
+*/
class CORE_EXPORT EditorToolBar : public Utils::StyledBar
{
Q_OBJECT
@@ -56,6 +59,8 @@ public:
void setNavigationVisible(bool isVisible);
void setCanGoBack(bool canGoBack);
void setCanGoForward(bool canGoForward);
+ void setGoBackMenu(QMenu *menu);
+ void setGoForwardMenu(QMenu *menu);
void removeToolbarForEditor(IEditor *editor);
void setCloseSplitEnabled(bool enable);
void setCloseSplitIcon(const QIcon &icon);
diff --git a/src/plugins/coreplugin/fancyactionbar.cpp b/src/plugins/coreplugin/fancyactionbar.cpp
index 7197236100..9a873b1af3 100644
--- a/src/plugins/coreplugin/fancyactionbar.cpp
+++ b/src/plugins/coreplugin/fancyactionbar.cpp
@@ -141,7 +141,7 @@ void FancyToolButton::paintEvent(QPaintEvent *event)
&& m_fader > 0 && isEnabled() && !isDown() && !isChecked()) {
painter.save();
if (creatorTheme()->flag(Theme::FlatToolBars)) {
- const QColor hoverColor = creatorTheme()->color(Theme::FancyToolButtonHoverColor);
+ const QColor hoverColor = creatorColor(Theme::FancyToolButtonHoverColor);
QColor fadedHoverColor = hoverColor;
fadedHoverColor.setAlpha(int(m_fader * hoverColor.alpha()));
painter.fillRect(rect(), fadedHoverColor);
@@ -152,7 +152,7 @@ void FancyToolButton::paintEvent(QPaintEvent *event)
painter.restore();
} else if (isDown() || isChecked()) {
painter.save();
- const QColor selectedColor = creatorTheme()->color(Theme::FancyToolButtonSelectedColor);
+ const QColor selectedColor = creatorColor(Theme::FancyToolButtonSelectedColor);
if (creatorTheme()->flag(Theme::FlatToolBars)) {
painter.fillRect(rect(), selectedColor);
} else {
@@ -200,8 +200,8 @@ void FancyToolButton::paintEvent(QPaintEvent *event)
- QPoint(iconRect.width() / 2, iconRect.height() / 2);
textOffset = textOffset - QPoint(0, lineHeight + 3);
const QRectF r(0, textOffset.y(), rect().width(), lineHeight);
- painter.setPen(creatorTheme()->color(isEnabled() ? Theme::PanelTextColorLight
- : Theme::IconsDisabledColor));
+ painter.setPen(creatorColor(isEnabled() ? Theme::PanelTextColorLight
+ : Theme::IconsDisabledColor));
// draw project name
const int margin = 6;
@@ -227,12 +227,12 @@ void FancyToolButton::paintEvent(QPaintEvent *event)
// draw the two text lines for the build configuration
painter.setPen(
- creatorTheme()->color(isEnabled()
- // Intentionally using the "Unselected" colors,
- // because the text color won't change in the pressed
- // state as they would do on the mode buttons.
- ? Theme::FancyTabWidgetEnabledUnselectedTextColor
- : Theme::FancyTabWidgetDisabledUnselectedTextColor));
+ creatorColor(isEnabled()
+ // Intentionally using the "Unselected" colors,
+ // because the text color won't change in the pressed
+ // state as they would do on the mode buttons.
+ ? Theme::FancyTabWidgetEnabledUnselectedTextColor
+ : Theme::FancyTabWidgetDisabledUnselectedTextColor));
for (int i = 0; i < 2; ++i) {
const QString &buildConfigText = splitBuildConfiguration[i];
@@ -264,7 +264,7 @@ void FancyActionBar::paintEvent(QPaintEvent *event)
// this paints the background of the bottom portion of the
// left tab bar
painter.fillRect(event->rect(), StyleHelper::baseColor());
- painter.setPen(creatorTheme()->color(Theme::FancyToolBarSeparatorColor));
+ painter.setPen(creatorColor(Theme::FancyToolBarSeparatorColor));
painter.drawLine(borderRect.topLeft(), borderRect.topRight());
} else {
painter.setPen(StyleHelper::sidebarShadow());
@@ -320,7 +320,7 @@ void FancyToolButton::hoverOverlay(QPainter *painter, const QRect &spanRect)
overlay.fill(Qt::transparent);
overlay.setDevicePixelRatio(dpr);
- const QColor hoverColor = creatorTheme()->color(Theme::FancyToolButtonHoverColor);
+ const QColor hoverColor = creatorColor(Theme::FancyToolButtonHoverColor);
const QRect rect(QPoint(), logicalSize);
const QRectF borderRect = QRectF(rect).adjusted(0.5, 0.5, -0.5, -0.5);
diff --git a/src/plugins/coreplugin/fancytabwidget.cpp b/src/plugins/coreplugin/fancytabwidget.cpp
index 05fb7e3e13..18d4603e19 100644
--- a/src/plugins/coreplugin/fancytabwidget.cpp
+++ b/src/plugins/coreplugin/fancytabwidget.cpp
@@ -272,7 +272,7 @@ static void paintIcon(QPainter *painter, const QRect &rect,
painter->setOpacity(1.0);
QRect accentRect = rect;
accentRect.setWidth(2);
- painter->fillRect(accentRect, creatorTheme()->color(Theme::IconsBaseColor));
+ painter->fillRect(accentRect, creatorColor(Theme::IconsBaseColor));
}
}
@@ -302,16 +302,16 @@ static void paintIconAndText(QPainter *painter, const QRect &rect,
if (selected && creatorTheme()->flag(Theme::FlatToolBars)) {
QRect accentRect = rect;
accentRect.setWidth(2);
- painter->fillRect(accentRect, creatorTheme()->color(Theme::IconsBaseColor));
+ painter->fillRect(accentRect, creatorColor(Theme::IconsBaseColor));
}
if (enabled) {
painter->setPen(
- selected ? creatorTheme()->color(Theme::FancyTabWidgetEnabledSelectedTextColor)
- : creatorTheme()->color(Theme::FancyTabWidgetEnabledUnselectedTextColor));
+ selected ? creatorColor(Theme::FancyTabWidgetEnabledSelectedTextColor)
+ : creatorColor(Theme::FancyTabWidgetEnabledUnselectedTextColor));
} else {
painter->setPen(
- selected ? creatorTheme()->color(Theme::FancyTabWidgetDisabledSelectedTextColor)
- : creatorTheme()->color(Theme::FancyTabWidgetDisabledUnselectedTextColor));
+ selected ? creatorColor(Theme::FancyTabWidgetDisabledSelectedTextColor)
+ : creatorColor(Theme::FancyTabWidgetDisabledUnselectedTextColor));
}
painter->translate(0, -1);
@@ -338,7 +338,7 @@ void FancyTabBar::paintTab(QPainter *painter, int tabIndex) const
if (selected) {
if (creatorTheme()->flag(Theme::FlatToolBars)) {
// background color of a fancy tab that is active
- painter->fillRect(rect, creatorTheme()->color(Theme::FancyTabBarSelectedBackgroundColor));
+ painter->fillRect(rect, creatorColor(Theme::FancyTabBarSelectedBackgroundColor));
} else {
paintSelectedTabBackground(painter, rect);
}
@@ -349,7 +349,7 @@ void FancyTabBar::paintTab(QPainter *painter, int tabIndex) const
painter->save();
painter->setOpacity(fader);
if (creatorTheme()->flag(Theme::FlatToolBars))
- painter->fillRect(rect, creatorTheme()->color(Theme::FancyToolButtonHoverColor));
+ painter->fillRect(rect, creatorColor(Theme::FancyToolButtonHoverColor));
else
FancyToolButton::hoverOverlay(painter, rect);
painter->restore();
@@ -461,7 +461,7 @@ FancyTabWidget::FancyTabWidget(QWidget *parent)
QVBoxLayout *vlayout;
using namespace Layouting;
- Row { fancyButton, noMargin() }.attachTo(bar);
+ Row { fancyButton, noMargin }.attachTo(bar);
Row {
Widget {
bindTo(&m_selectionWidget),
@@ -471,13 +471,13 @@ FancyTabWidget::FancyTabWidget(QWidget *parent)
st,
Widget {
bindTo(&m_cornerWidgetContainer),
- Column { st, spacing(0), noMargin() },
+ Column { st, spacing(0), noMargin },
},
- spacing(0), noMargin(),
+ spacing(0), noMargin,
},
},
Column { bindTo(&vlayout), m_modesStack, m_statusBar, spacing(0) },
- spacing(1), noMargin(),
+ spacing(1), noMargin,
}.attachTo(this);
m_selectionWidget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
diff --git a/src/plugins/coreplugin/find/findplugin.cpp b/src/plugins/coreplugin/find/findplugin.cpp
index 0e5ef38be8..56042cb478 100644
--- a/src/plugins/coreplugin/find/findplugin.cpp
+++ b/src/plugins/coreplugin/find/findplugin.cpp
@@ -3,12 +3,6 @@
#include "findplugin.h"
-#include "currentdocumentfind.h"
-#include "findtoolbar.h"
-#include "findtoolwindow.h"
-#include "ifindfilter.h"
-#include "searchresultwindow.h"
-#include "textfindconstants.h"
#include "../actionmanager/actioncontainer.h"
#include "../actionmanager/actionmanager.h"
#include "../actionmanager/command.h"
@@ -16,6 +10,13 @@
#include "../coreplugintr.h"
#include "../icontext.h"
#include "../icore.h"
+#include "../session.h"
+#include "currentdocumentfind.h"
+#include "findtoolbar.h"
+#include "findtoolwindow.h"
+#include "ifindfilter.h"
+#include "searchresultwindow.h"
+#include "textfindconstants.h"
#include <extensionsystem/pluginmanager.h>
@@ -75,6 +76,10 @@ public:
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ void restore(const Store &s);
+ Store save() const;
+
+ // TODO deprecated since QtC 14.0
void writeSettings(QtcSettings *settings) const;
void readSettings(QtcSettings *settings);
@@ -105,6 +110,41 @@ static Utils::Key completionSettingsArrayPrefix() { return "FindCompletions"; }
static Utils::Key completionSettingsTextKey() { return "Text"; }
static Utils::Key completionSettingsFlagsKey() { return "Flags"; }
+void CompletionModel::restore(const Store &s)
+{
+ beginResetModel();
+ const QStringList texts = s.value(completionSettingsTextKey()).toStringList();
+ const QList<FindFlags> flags
+ = transform(s.value(completionSettingsFlagsKey()).toList(), [](const QVariant &v) {
+ return FindFlags(v.toInt());
+ });
+ const int size = texts.size();
+ m_entries.clear();
+ m_entries.reserve(size);
+ for (int i = 0; i < size; ++i) {
+ CompletionEntry entry;
+ entry.text = texts.at(i);
+ entry.findFlags = i < flags.size() ? flags.at(i) : FindFlags();
+ if (!entry.text.isEmpty())
+ m_entries.append(entry);
+ }
+ endResetModel();
+}
+
+Store CompletionModel::save() const
+{
+ if (m_entries.isEmpty())
+ return {};
+ const QStringList texts = transform(m_entries, [](const CompletionEntry &e) { return e.text; });
+ const QVariantList flags = transform(m_entries, [](const CompletionEntry &e) {
+ return QVariant::fromValue(int(e.findFlags));
+ });
+ Store s;
+ s.insert(completionSettingsTextKey(), texts);
+ s.insert(completionSettingsFlagsKey(), flags);
+ return s;
+}
+
void CompletionModel::writeSettings(QtcSettings *settings) const
{
if (m_entries.isEmpty()) {
@@ -215,13 +255,20 @@ void Find::initialize()
d->m_findDialog = new Internal::FindToolWindow;
d->m_searchResultWindow = new SearchResultWindow(d->m_findDialog);
ExtensionSystem::PluginManager::addObject(d->m_searchResultWindow);
+
QObject::connect(ICore::instance(), &ICore::saveSettingsRequested, d, &FindPrivate::writeSettings);
+ QObject::connect(
+ SessionManager::instance(),
+ &SessionManager::aboutToSaveSession,
+ d,
+ &FindPrivate::writeSettings);
+ QObject::connect(
+ SessionManager::instance(), &SessionManager::sessionLoaded, d, &FindPrivate::readSettings);
}
void Find::extensionsInitialized()
{
d->setupFilterMenuItems();
- d->readSettings();
}
void Find::aboutToShutdown()
@@ -369,6 +416,8 @@ bool Find::hasFindFlag(FindFlag flag)
void FindPrivate::writeSettings()
{
+ // TODO for backwards compatibility
+ // deprecated since QtC 14.0
QtcSettings *settings = ICore::settings();
settings->beginGroup("Find");
settings->setValueWithDefault("Backward", bool(m_findFlags & FindBackward), false);
@@ -384,26 +433,70 @@ void FindPrivate::writeSettings()
m_findToolBar->writeSettings();
m_findDialog->writeSettings();
m_searchResultWindow->writeSettings();
+
+ // save in session
+ Store s;
+ if (m_findFlags & FindBackward)
+ s.insert("Backward", true);
+ if (m_findFlags & FindCaseSensitively)
+ s.insert("CaseSensitively", true);
+ if (m_findFlags & FindWholeWords)
+ s.insert("WholeWords", true);
+ if (m_findFlags & FindRegularExpression)
+ s.insert("RegularExpression", true);
+ if (m_findFlags & FindPreserveCase)
+ s.insert("PreserveCase", true);
+ const Store completion = m_findCompletionModel.save();
+ if (!completion.isEmpty())
+ s.insert("FindCompletions", variantFromStore(completion));
+ if (!m_replaceCompletions.isEmpty())
+ s.insert("ReplaceStrings", m_replaceCompletions);
+ const Store toolbar = m_findToolBar->save();
+ if (!toolbar.isEmpty())
+ s.insert("ToolBar", variantFromStore(toolbar));
+ const Store advanced = m_findDialog->save();
+ if (!advanced.isEmpty())
+ s.insert("AdvancedSearch", variantFromStore(advanced));
+ SessionManager::setValue("Find", variantFromStore(s));
}
void FindPrivate::readSettings()
{
- QtcSettings *settings = ICore::settings();
- settings->beginGroup("Find");
- {
- QSignalBlocker blocker(m_instance);
- Find::setBackward(settings->value("Backward", false).toBool());
- Find::setCaseSensitive(settings->value("CaseSensitively", false).toBool());
- Find::setWholeWord(settings->value("WholeWords", false).toBool());
- Find::setRegularExpression(settings->value("RegularExpression", false).toBool());
- Find::setPreserveCase(settings->value("PreserveCase", false).toBool());
+ const Store s = storeFromVariant(SessionManager::value("Find"));
+ if (s.isEmpty() && SessionManager::isDefaultVirgin()) {
+ // TODO compatibility path when opening Qt Creator
+ // TODO deprecated since QtC 14.0
+ QtcSettings *settings = ICore::settings();
+ settings->beginGroup("Find");
+ {
+ QSignalBlocker blocker(m_instance);
+ Find::setBackward(settings->value("Backward", false).toBool());
+ Find::setCaseSensitive(settings->value("CaseSensitively", false).toBool());
+ Find::setWholeWord(settings->value("WholeWords", false).toBool());
+ Find::setRegularExpression(settings->value("RegularExpression", false).toBool());
+ Find::setPreserveCase(settings->value("PreserveCase", false).toBool());
+ }
+ m_findCompletionModel.readSettings(settings);
+ m_replaceCompletions = settings->value("ReplaceStrings").toStringList();
+ m_replaceCompletionModel.setStringList(m_replaceCompletions);
+ settings->endGroup();
+ m_findToolBar->readSettings();
+ m_findDialog->readSettings();
+ } else if (!s.empty()) {
+ {
+ QSignalBlocker blocker(m_instance);
+ Find::setBackward(s.value("Backward", false).toBool());
+ Find::setCaseSensitive(s.value("CaseSensitively", false).toBool());
+ Find::setWholeWord(s.value("WholeWords", false).toBool());
+ Find::setRegularExpression(s.value("RegularExpression", false).toBool());
+ Find::setPreserveCase(s.value("PreserveCase", false).toBool());
+ }
+ m_findCompletionModel.restore(storeFromVariant(s.value("FindCompletions")));
+ m_replaceCompletions = s.value("ReplaceStrings").toStringList();
+ m_replaceCompletionModel.setStringList(m_replaceCompletions);
+ m_findToolBar->restore(storeFromVariant(s.value("ToolBar")));
+ m_findDialog->restore(storeFromVariant(s.value("AdvancedSearch")));
}
- m_findCompletionModel.readSettings(settings);
- m_replaceCompletions = settings->value("ReplaceStrings").toStringList();
- m_replaceCompletionModel.setStringList(m_replaceCompletions);
- settings->endGroup();
- m_findToolBar->readSettings();
- m_findDialog->readSettings();
emit m_instance->findFlagsChanged(); // would have been done in the setXXX methods above
}
diff --git a/src/plugins/coreplugin/find/findtoolbar.cpp b/src/plugins/coreplugin/find/findtoolbar.cpp
index 4460367896..681fa40706 100644
--- a/src/plugins/coreplugin/find/findtoolbar.cpp
+++ b/src/plugins/coreplugin/find/findtoolbar.cpp
@@ -1047,6 +1047,39 @@ void FindToolBar::resizeEvent(QResizeEvent *event)
QMetaObject::invokeMethod(this, &FindToolBar::updateToolBar, Qt::QueuedConnection);
}
+void FindToolBar::restore(const Store &s)
+{
+ FindFlags flags;
+ if (s.value("Backward", false).toBool())
+ flags |= FindBackward;
+ if (s.value("CaseSensitively", false).toBool())
+ flags |= FindCaseSensitively;
+ if (s.value("WholeWords", false).toBool())
+ flags |= FindWholeWords;
+ if (s.value("RegularExpression", false).toBool())
+ flags |= FindRegularExpression;
+ if (s.value("PreserveCase", false).toBool())
+ flags |= FindPreserveCase;
+ m_findFlags = flags;
+ findFlagsChanged();
+}
+
+Store FindToolBar::save() const
+{
+ Store s;
+ if (m_findFlags & FindBackward)
+ s.insert("Backward", true);
+ if (m_findFlags & FindCaseSensitively)
+ s.insert("CaseSensitively", true);
+ if (m_findFlags & FindWholeWords)
+ s.insert("WholeWords", true);
+ if (m_findFlags & FindRegularExpression)
+ s.insert("RegularExpression", true);
+ if (m_findFlags & FindPreserveCase)
+ s.insert("PreserveCase", true);
+ return s;
+}
+
void FindToolBar::writeSettings()
{
Utils::QtcSettings *settings = ICore::settings();
diff --git a/src/plugins/coreplugin/find/findtoolbar.h b/src/plugins/coreplugin/find/findtoolbar.h
index baca57597c..1080407dae 100644
--- a/src/plugins/coreplugin/find/findtoolbar.h
+++ b/src/plugins/coreplugin/find/findtoolbar.h
@@ -6,6 +6,7 @@
#include "currentdocumentfind.h"
#include <utils/id.h>
+#include <utils/store.h>
#include <utils/styledbar.h>
#include <QTimer>
@@ -18,7 +19,9 @@ class QSpacerItem;
class QToolButton;
QT_END_NAMESPACE
-namespace Utils { class FancyLineEdit; }
+namespace Utils {
+class FancyLineEdit;
+} // namespace Utils
namespace Core {
@@ -43,6 +46,10 @@ public:
explicit FindToolBar(CurrentDocumentFind *currentDocumentFind);
~FindToolBar() override;
+ void restore(const Utils::Store &s);
+ Utils::Store save() const;
+
+ // TODO deprecated since QtC 14.0
void readSettings();
void writeSettings();
diff --git a/src/plugins/coreplugin/find/findtoolwindow.cpp b/src/plugins/coreplugin/find/findtoolwindow.cpp
index a239d310be..bc8e241dd4 100644
--- a/src/plugins/coreplugin/find/findtoolwindow.cpp
+++ b/src/plugins/coreplugin/find/findtoolwindow.cpp
@@ -346,6 +346,30 @@ void FindToolWindow::replace()
filter->replaceAll(term, Find::findFlags());
}
+void FindToolWindow::restore(const Utils::Store &s)
+{
+ const QString currentFilter = s.value("CurrentFilter").toString();
+ for (int i = 0; i < m_filters.size(); ++i) {
+ IFindFilter *filter = m_filters.at(i);
+ filter->restore(storeFromVariant(s.value(filter->id().toUtf8())));
+ if (filter->id() == currentFilter)
+ setCurrentFilterIndex(i);
+ }
+}
+
+Store FindToolWindow::save() const
+{
+ Store s;
+ if (m_currentFilter && (m_filters.isEmpty() || m_filters.first() != m_currentFilter))
+ s.insert("CurrentFilter", m_currentFilter->id());
+ for (IFindFilter *filter : std::as_const(m_filters)) {
+ const Store store = filter->save();
+ if (!store.isEmpty())
+ s.insert(filter->id().toUtf8(), variantFromStore(store));
+ }
+ return s;
+}
+
void FindToolWindow::writeSettings()
{
Utils::QtcSettings *settings = ICore::settings();
diff --git a/src/plugins/coreplugin/find/findtoolwindow.h b/src/plugins/coreplugin/find/findtoolwindow.h
index 91b045295a..77226a6b43 100644
--- a/src/plugins/coreplugin/find/findtoolwindow.h
+++ b/src/plugins/coreplugin/find/findtoolwindow.h
@@ -3,6 +3,8 @@
#pragma once
+#include <utils/store.h>
+
#include <QList>
#include <QWidget>
@@ -33,6 +35,11 @@ public:
void setFindText(const QString &text);
void setCurrentFilter(IFindFilter *filter);
+
+ void restore(const Utils::Store &s);
+ Utils::Store save() const;
+
+ // TODO deprecated since QtC 14.0
void readSettings();
void writeSettings();
diff --git a/src/plugins/coreplugin/find/highlightscrollbarcontroller.cpp b/src/plugins/coreplugin/find/highlightscrollbarcontroller.cpp
index a88b8e4fba..dc45883b91 100644
--- a/src/plugins/coreplugin/find/highlightscrollbarcontroller.cpp
+++ b/src/plugins/coreplugin/find/highlightscrollbarcontroller.cpp
@@ -193,7 +193,7 @@ void HighlightScrollBarOverlay::drawHighlights(QPainter *painter,
for (const QMap<Utils::Theme::Color, QMap<int, int>> &colors : std::as_const(m_highlightCache)) {
const auto itColorEnd = colors.constEnd();
for (auto itColor = colors.constBegin(); itColor != itColorEnd; ++itColor) {
- const QColor &color = creatorTheme()->color(itColor.key());
+ const QColor &color = creatorColor(itColor.key());
const QMap<int, int> &positions = itColor.value();
const auto itPosEnd = positions.constEnd();
const auto firstPos = int(docStart / lineHeight);
diff --git a/src/plugins/coreplugin/find/ifindfilter.cpp b/src/plugins/coreplugin/find/ifindfilter.cpp
index 704f827bf8..eca6c4f2a9 100644
--- a/src/plugins/coreplugin/find/ifindfilter.cpp
+++ b/src/plugins/coreplugin/find/ifindfilter.cpp
@@ -6,6 +6,8 @@
#include "../coreicons.h"
#include "../coreplugintr.h"
+#include <utils/qtcsettings.h>
+
#include <QApplication>
#include <QKeySequence>
#include <QPainter>
@@ -30,7 +32,7 @@ using namespace Utils;
and \uicontrol {Files in File System} where the user provides a directory and file
patterns to search.
- \image qtcreator-search-filesystem.png
+ \image qtcreator-search-reg-exp.webp {Search Results view with search criteria}
To make your find scope available to the user, you need to implement this
class, and register an instance of your subclass in the plugin manager.
@@ -38,7 +40,7 @@ using namespace Utils;
A common way to present the search results to the user, is to use the
shared \uicontrol{Search Results} pane.
- \image qtcreator-search-results.webp {Search Results view}
+ \image qtcreator-search-results-reg-exp.webp {Search Results view with search results}
If you want to implement a find filter that is doing a file based text
search, you should use \l Core::BaseTextFind, which already implements all
@@ -178,18 +180,6 @@ using namespace Utils;
*/
/*!
- \fn void Core::IFindFilter::writeSettings(QSettings *settings)
- Called at shutdown to write the state of the additional options
- for this find filter to the \a settings.
-*/
-
-/*!
- \fn void Core::IFindFilter::readSettings(QSettings *settings)
- Called at startup to read the state of the additional options
- for this find filter from the \a settings.
-*/
-
-/*!
\fn void Core::IFindFilter::enabledChanged(bool enabled)
This signal is emitted when the \a enabled state of this find filter
@@ -265,6 +255,60 @@ FindFlags IFindFilter::supportedFindFlags() const
}
/*!
+ Returns a Store with the find filter's settings to store
+ in the session. Default values should not be saved.
+ The default implementation returns an empty store.
+
+ \sa restore()
+*/
+Store IFindFilter::save() const
+{
+ return {};
+}
+
+/*!
+ Restores the find filter's settings from the Store \a s.
+ Settings that are not present in the store should be reset to
+ the default.
+ The default implementation does nothing.
+
+ \sa save()
+*/
+void IFindFilter::restore([[maybe_unused]] const Utils::Store &s) {}
+
+/*!
+ Called at shutdown to write the state of the additional options
+ for this find filter to the \a settings.
+
+ \deprecated [14.0] Implement save() instead.
+*/
+void IFindFilter::writeSettings(Utils::QtcSettings *settings)
+{
+ settings->remove(settingsKey()); // make sure defaults are removed
+ storeToSettings(settingsKey(), settings, save());
+}
+
+/*!
+ Called at startup to read the state of the additional options
+ for this find filter from the \a settings.
+
+ \deprecated [14.0] Implement restore() instead.
+*/
+void IFindFilter::readSettings(Utils::QtcSettings *settings)
+{
+ restore(storeFromSettings(settingsKey(), settings));
+}
+
+/*!
+ \internal
+ \deprecated [14.0]
+*/
+QByteArray IFindFilter::settingsKey() const
+{
+ return id().toUtf8();
+}
+
+/*!
Returns icons for the find flags \a flags.
*/
QPixmap IFindFilter::pixmapForFindFlags(FindFlags flags)
diff --git a/src/plugins/coreplugin/find/ifindfilter.h b/src/plugins/coreplugin/find/ifindfilter.h
index 2d6325eda0..a76dd2808f 100644
--- a/src/plugins/coreplugin/find/ifindfilter.h
+++ b/src/plugins/coreplugin/find/ifindfilter.h
@@ -6,6 +6,7 @@
#include "../core_global.h"
#include <utils/filesearch.h>
+#include <utils/store.h>
QT_BEGIN_NAMESPACE
class QWidget;
@@ -42,8 +43,13 @@ public:
{ Q_UNUSED(txt) Q_UNUSED(findFlags) }
virtual QWidget *createConfigWidget() { return nullptr; }
- virtual void writeSettings(Utils::QtcSettings *settings) { Q_UNUSED(settings) }
- virtual void readSettings(Utils::QtcSettings *settings) { Q_UNUSED(settings) }
+ virtual Utils::Store save() const;
+ virtual void restore(const Utils::Store &s);
+
+ // deprecated in 14.0
+ virtual void writeSettings(Utils::QtcSettings *settings);
+ virtual void readSettings(Utils::QtcSettings *settings);
+ virtual QByteArray settingsKey() const;
static QPixmap pixmapForFindFlags(Utils::FindFlags flags);
static QString descriptionForFindFlags(Utils::FindFlags flags);
diff --git a/src/plugins/coreplugin/find/searchresultwidget.cpp b/src/plugins/coreplugin/find/searchresultwidget.cpp
index c7827e6118..e206fb3879 100644
--- a/src/plugins/coreplugin/find/searchresultwidget.cpp
+++ b/src/plugins/coreplugin/find/searchresultwidget.cpp
@@ -67,8 +67,8 @@ SearchResultWidget::SearchResultWidget(QWidget *parent) :
QFrame *topWidget = new QFrame;
QPalette pal;
- pal.setColor(QPalette::Window, creatorTheme()->color(Theme::InfoBarBackground));
- pal.setColor(QPalette::WindowText, creatorTheme()->color(Theme::InfoBarText));
+ pal.setColor(QPalette::Window, creatorColor(Theme::InfoBarBackground));
+ pal.setColor(QPalette::WindowText, creatorColor(Theme::InfoBarText));
topWidget->setPalette(pal);
if (creatorTheme()->flag(Theme::DrawSearchResultWidgetFrame)) {
topWidget->setFrameStyle(QFrame::Panel | QFrame::Raised);
diff --git a/src/plugins/coreplugin/find/searchresultwindow.cpp b/src/plugins/coreplugin/find/searchresultwindow.cpp
index 8412a57d70..eea0c43d4e 100644
--- a/src/plugins/coreplugin/find/searchresultwindow.cpp
+++ b/src/plugins/coreplugin/find/searchresultwindow.cpp
@@ -288,10 +288,13 @@ using namespace Core::Internal;
This enum type specifies whether the search results should be sorted or
ordered:
- \value AddSorted
- The search results are sorted.
+ \value AddSortedByContent
+ The search results are sorted alphabetically.
+ \value AddSortedByPosition
+ The search results are sorted by the search results' reported line
+ numbers.
\value AddOrdered
- The search results are ordered.
+ The search results are ordered as they are reported.
*/
/*!
@@ -331,7 +334,7 @@ using namespace Core::Internal;
\brief The SearchResultWindow class is the implementation of a commonly
shared \uicontrol{Search Results} output pane.
- \image qtcreator-search-results.webp {Search Results view}
+ \image qtcreator-search-results-reg-exp.webp {Search Results view with search results}
Whenever you want to show the user a list of search results, or want
to present UI for a global search and replace, use the single instance
diff --git a/src/plugins/coreplugin/generalsettings.cpp b/src/plugins/coreplugin/generalsettings.cpp
index 71cfacbb40..1ff36dc805 100644
--- a/src/plugins/coreplugin/generalsettings.cpp
+++ b/src/plugins/coreplugin/generalsettings.cpp
@@ -240,7 +240,7 @@ void GeneralSettingsWidget::fillLanguageBox() const
if (hasQmFilesForLocale(locale, creatorTrPath.toString())) {
QLocale tmpLocale(locale);
QString languageItem = QLocale::languageToString(tmpLocale.language()) + QLatin1String(" (")
- + QLocale::countryToString(tmpLocale.country()) + QLatin1Char(')');
+ + QLocale::territoryToString(tmpLocale.territory()) + QLatin1Char(')');
m_languageBox->addItem(languageItem, locale);
if (locale == currentLocale)
m_languageBox->setCurrentIndex(m_languageBox->count() - 1);
diff --git a/src/plugins/coreplugin/helpmanager.cpp b/src/plugins/coreplugin/helpmanager.cpp
index 01fc7eff47..9f694c2802 100644
--- a/src/plugins/coreplugin/helpmanager.cpp
+++ b/src/plugins/coreplugin/helpmanager.cpp
@@ -103,5 +103,11 @@ void setBlockedDocumentation(const QStringList &fileNames)
m_instance->setBlockedDocumentation(fileNames);
}
+void addOnlineHelpHandler(const OnlineHelpHandler &handler)
+{
+ if (checkInstance())
+ m_instance->addOnlineHelpHandler(handler);
+}
+
} // HelpManager
} // Core
diff --git a/src/plugins/coreplugin/helpmanager.h b/src/plugins/coreplugin/helpmanager.h
index 55d449e696..2d24116d23 100644
--- a/src/plugins/coreplugin/helpmanager.h
+++ b/src/plugins/coreplugin/helpmanager.h
@@ -48,5 +48,11 @@ CORE_EXPORT QByteArray fileData(const QUrl &url);
CORE_EXPORT void showHelpUrl(const QUrl &url, HelpViewerLocation location = HelpModeAlways);
CORE_EXPORT void showHelpUrl(const QString &url, HelpViewerLocation location = HelpModeAlways);
+struct CORE_EXPORT OnlineHelpHandler {
+ std::function<bool(QUrl)> handlesUrl;
+ std::function<void(QUrl)> openUrl;
+};
+CORE_EXPORT void addOnlineHelpHandler(const OnlineHelpHandler &handler);
+
} // HelpManager
} // Core
diff --git a/src/plugins/coreplugin/helpmanager_implementation.h b/src/plugins/coreplugin/helpmanager_implementation.h
index 798a5a340e..d3b097f1f8 100644
--- a/src/plugins/coreplugin/helpmanager_implementation.h
+++ b/src/plugins/coreplugin/helpmanager_implementation.h
@@ -23,6 +23,7 @@ public:
virtual QMultiMap<QString, QUrl> linksForKeyword(const QString &keyword) = 0;
virtual QByteArray fileData(const QUrl &url) = 0;
virtual void showHelpUrl(const QUrl &url, HelpViewerLocation location = HelpModeAlways) = 0;
+ virtual void addOnlineHelpHandler(const OnlineHelpHandler &handler) = 0;
};
} // HelpManager
diff --git a/src/plugins/coreplugin/icore.cpp b/src/plugins/coreplugin/icore.cpp
index a625895b68..a4098963a6 100644
--- a/src/plugins/coreplugin/icore.cpp
+++ b/src/plugins/coreplugin/icore.cpp
@@ -54,9 +54,9 @@
#include <utils/checkablemessagebox.h>
#include <utils/dropsupport.h>
#include <utils/environment.h>
-#include <utils/fileutils.h>
#include <utils/fsengine/fileiconprovider.h>
#include <utils/fsengine/fsengine.h>
+#include <utils/guiutils.h>
#include <utils/historycompleter.h>
#include <utils/hostosinfo.h>
#include <utils/mimeutils.h>
@@ -301,7 +301,6 @@ public:
WindowSupport *m_windowSupport = nullptr;
EditorManager *m_editorManager = nullptr;
ExternalToolManager *m_externalToolManager = nullptr;
- MessageManager *m_messageManager = nullptr;
ProgressManagerPrivate *m_progressManager = nullptr;
JsExpander *m_jsExpander = nullptr;
VcsManager *m_vcsManager = nullptr;
@@ -397,7 +396,7 @@ ICore::ICore()
QCoreApplication::exit(exitCode);
});
- Utils::FileUtils::setDialogParentGetter(&ICore::dialogParent);
+ Utils::setDialogParentGetter(&ICore::dialogParent);
d->m_progressManager->init(); // needs the status bar manager
MessageManager::init();
@@ -570,8 +569,6 @@ bool ICore::showWarningWithOptions(const QString &title, const QString &text,
If \a scope is \c QSettings::SystemScope, only the installation settings
shipped with the current version of \QC will be read. This
functionality exists for internal purposes only.
-
- \sa settingsDatabase()
*/
QtcSettings *ICore::settings(QSettings::Scope scope)
{
@@ -1071,6 +1068,8 @@ QString uiConfigInformation()
QTC_ADD_UIELEMENT_FONT(Body2);
QTC_ADD_UIELEMENT_FONT(ButtonMedium);
QTC_ADD_UIELEMENT_FONT(ButtonSmall);
+ QTC_ADD_UIELEMENT_FONT(LabelMedium);
+ QTC_ADD_UIELEMENT_FONT(LabelSmall);
QTC_ADD_UIELEMENT_FONT(CaptionStrong);
QTC_ADD_UIELEMENT_FONT(Caption);
QTC_ADD_UIELEMENT_FONT(IconStandard);
@@ -1397,7 +1396,6 @@ void ICorePrivate::init()
m_rightNavigationWidget = new NavigationWidget(m_toggleRightSideBarAction, Side::Right);
m_rightPaneWidget = new RightPaneWidget();
- m_messageManager = new MessageManager;
m_editorManager = new EditorManager(this);
m_externalToolManager = new ExternalToolManager();
@@ -1456,8 +1454,7 @@ ICorePrivate::~ICorePrivate()
delete m_externalToolManager;
m_externalToolManager = nullptr;
- delete m_messageManager;
- m_messageManager = nullptr;
+ MessageManager::destroy();
delete m_shortcutSettings;
m_shortcutSettings = nullptr;
delete m_toolSettings;
diff --git a/src/plugins/coreplugin/iversioncontrol.cpp b/src/plugins/coreplugin/iversioncontrol.cpp
index f70e4be492..3ff7de5bcd 100644
--- a/src/plugins/coreplugin/iversioncontrol.cpp
+++ b/src/plugins/coreplugin/iversioncontrol.cpp
@@ -143,7 +143,7 @@ QString IVersionControl::refreshTopic(const FilePath &repository)
it will be used. Otherwise it will be refreshed using the items provided by
\c setTopicFileTracker() and \c setTopicRefresher().
- \sa setTopicFileTracker(), setTopicRefresher().
+ \sa setTopicFileTracker(), setTopicRefresher()
*/
QString IVersionControl::vcsTopic(const FilePath &topLevel)
diff --git a/src/plugins/coreplugin/iwizardfactory.cpp b/src/plugins/coreplugin/iwizardfactory.cpp
index dc7a6da3e5..544c25a17e 100644
--- a/src/plugins/coreplugin/iwizardfactory.cpp
+++ b/src/plugins/coreplugin/iwizardfactory.cpp
@@ -401,7 +401,7 @@ QSet<Id> IWizardFactory::pluginFeatures()
{
if (s_plugins.isEmpty()) {
// Implicitly create a feature for each plugin loaded:
- const QVector<ExtensionSystem::PluginSpec *> pluginVector = ExtensionSystem::PluginManager::plugins();
+ const ExtensionSystem::PluginSpecs pluginVector = ExtensionSystem::PluginManager::plugins();
for (const ExtensionSystem::PluginSpec *s : pluginVector) {
if (s->state() == ExtensionSystem::PluginSpec::Running)
s_plugins.insert(Id::fromString(s->name()));
@@ -464,7 +464,7 @@ static QIcon iconWithText(const QIcon &icon, const QString &text)
font.setPixelSize(fontSize);
font.setStretch(85);
QPainter p(&pixmap);
- p.setPen(Utils::creatorTheme()->color(Theme::PanelTextColorDark));
+ p.setPen(Utils::creatorColor(Theme::PanelTextColorDark));
p.setFont(font);
QTextOption textOption(Qt::AlignHCenter | Qt::AlignBottom);
textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
diff --git a/src/plugins/coreplugin/locator/directoryfilter.cpp b/src/plugins/coreplugin/locator/directoryfilter.cpp
index 871e89753e..23b853682d 100644
--- a/src/plugins/coreplugin/locator/directoryfilter.cpp
+++ b/src/plugins/coreplugin/locator/directoryfilter.cpp
@@ -6,8 +6,6 @@
#include "locator.h"
#include "../coreplugintr.h"
-#include <extensionsystem/pluginmanager.h>
-
#include <utils/algorithm.h>
#include <utils/async.h>
#include <utils/fileutils.h>
@@ -89,7 +87,6 @@ DirectoryFilter::DirectoryFilter(Id id)
return SetupResult::StopWithSuccess; // Group stops, skips async task
};
const auto onSetup = [this](Async<FilePaths> &async) {
- async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
async.setConcurrentCallData(&refresh, m_directories, m_filters, m_exclusionFilters,
displayName());
};
@@ -150,42 +147,6 @@ void DirectoryFilter::restoreState(const QJsonObject &object)
.toArray(QJsonArray::fromStringList(kExclusionFiltersDefault)));
}
-void DirectoryFilter::restoreState(const QByteArray &state)
-{
- if (isOldSetting(state)) {
- // TODO read old settings, remove some time after Qt Creator 4.15
- QString name;
- QStringList directories;
- QString shortcut;
- bool defaultFilter;
- QStringList files;
-
- QDataStream in(state);
- in >> name;
- in >> directories;
- in >> m_filters;
- in >> shortcut;
- in >> defaultFilter;
- in >> files;
- m_cache.setFilePaths(FileUtils::toFilePathList(files));
- if (!in.atEnd()) // Qt Creator 4.3 and later
- in >> m_exclusionFilters;
- else
- m_exclusionFilters.clear();
-
- if (m_isCustomFilter) {
- m_directories = Utils::transform(directories, [](const QString &d) {
- return FilePath::fromString(d);
- });
- }
- setDisplayName(name);
- setShortcutString(shortcut);
- setIncludedByDefault(defaultFilter);
- } else {
- ILocatorFilter::restoreState(state);
- }
-}
-
class DirectoryFilterOptions : public QDialog
{
public:
diff --git a/src/plugins/coreplugin/locator/directoryfilter.h b/src/plugins/coreplugin/locator/directoryfilter.h
index 452a1b20e7..8300d03d46 100644
--- a/src/plugins/coreplugin/locator/directoryfilter.h
+++ b/src/plugins/coreplugin/locator/directoryfilter.h
@@ -12,7 +12,6 @@ class CORE_EXPORT DirectoryFilter : public ILocatorFilter
{
public:
DirectoryFilter(Utils::Id id);
- void restoreState(const QByteArray &state) override;
bool openConfigDialog(QWidget *parent, bool &needsRefresh) override;
protected:
diff --git a/src/plugins/coreplugin/locator/filesystemfilter.cpp b/src/plugins/coreplugin/locator/filesystemfilter.cpp
index 00890829b4..6b484635e4 100644
--- a/src/plugins/coreplugin/locator/filesystemfilter.cpp
+++ b/src/plugins/coreplugin/locator/filesystemfilter.cpp
@@ -10,8 +10,6 @@
#include "../vcsmanager.h"
#include "locatormanager.h"
-#include <extensionsystem/pluginmanager.h>
-
#include <utils/algorithm.h>
#include <utils/async.h>
#include <utils/checkablemessagebox.h>
@@ -312,7 +310,6 @@ LocatorMatcherTasks FileSystemFilter::matchers()
const auto onSetup = [storage, includeHidden = m_includeHidden, shortcut = shortcutString()]
(Async<void> &async) {
- async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
async.setConcurrentCallData(matches, *storage, shortcut,
DocumentManager::fileDialogInitialDirectory(), includeHidden);
};
@@ -392,25 +389,4 @@ void FileSystemFilter::restoreState(const QJsonObject &object)
m_includeHidden = object.value(kIncludeHiddenKey).toBool(s_includeHiddenDefault);
}
-void FileSystemFilter::restoreState(const QByteArray &state)
-{
- if (isOldSetting(state)) {
- // TODO read old settings, remove some time after Qt Creator 4.15
- QDataStream in(state);
- in >> m_includeHidden;
-
- // An attempt to prevent setting this on old configuration
- if (!in.atEnd()) {
- QString shortcut;
- bool defaultFilter;
- in >> shortcut;
- in >> defaultFilter;
- setShortcutString(shortcut);
- setIncludedByDefault(defaultFilter);
- }
- } else {
- ILocatorFilter::restoreState(state);
- }
-}
-
} // namespace Core::Internal
diff --git a/src/plugins/coreplugin/locator/filesystemfilter.h b/src/plugins/coreplugin/locator/filesystemfilter.h
index 3dfac11b3a..74198ec9ae 100644
--- a/src/plugins/coreplugin/locator/filesystemfilter.h
+++ b/src/plugins/coreplugin/locator/filesystemfilter.h
@@ -11,7 +11,6 @@ class FileSystemFilter : public ILocatorFilter
{
public:
FileSystemFilter();
- void restoreState(const QByteArray &state) final;
bool openConfigDialog(QWidget *parent, bool &needsRefresh) final;
protected:
diff --git a/src/plugins/coreplugin/locator/ilocatorfilter.cpp b/src/plugins/coreplugin/locator/ilocatorfilter.cpp
index e9a999d536..6a32b294a3 100644
--- a/src/plugins/coreplugin/locator/ilocatorfilter.cpp
+++ b/src/plugins/coreplugin/locator/ilocatorfilter.cpp
@@ -5,8 +5,6 @@
#include "../coreplugintr.h"
-#include <extensionsystem/pluginmanager.h>
-
#include <solutions/tasking/tasktreerunner.h>
#include <utils/algorithm.h>
@@ -239,98 +237,6 @@ private:
QList<std::optional<LocatorFilterEntries>> m_outputData;
};
-// This instance of this object is created by LocatorMatcher tree.
-// It starts a separate thread which collects and deduplicates the results reported
-// by LocatorStorage instances. The ResultsCollector is started as a first task in
-// LocatorMatcher and runs in parallel to all the filters started by LocatorMatcher.
-// When all the results are reported (the expected number of reports is set with setFilterCount()),
-// the ResultsCollector finishes. The intermediate results are reported with
-// serialOutputDataReady() signal.
-// The object of ResultsCollector is registered in Tasking namespace under the
-// ResultsCollectorTask name.
-class ResultsCollector : public QObject
-{
- Q_OBJECT
-
-public:
- ~ResultsCollector();
- void setFilterCount(int count);
- void start();
-
- bool isRunning() const { return m_watcher.get(); }
-
- std::shared_ptr<ResultsDeduplicator> deduplicator() const { return m_deduplicator; }
-
-signals:
- void serialOutputDataReady(const LocatorFilterEntries &serialOutputData);
- void done();
-
-private:
- int m_filterCount = 0;
- std::unique_ptr<QFutureWatcher<LocatorFilterEntries>> m_watcher;
- std::shared_ptr<ResultsDeduplicator> m_deduplicator;
-};
-
-ResultsCollector::~ResultsCollector()
-{
- if (!isRunning())
- return;
-
- m_deduplicator->cancel();
- if (ExtensionSystem::PluginManager::futureSynchronizer()) {
- ExtensionSystem::PluginManager::futureSynchronizer()->addFuture(m_watcher->future());
- return;
- }
- m_watcher->future().waitForFinished();
-}
-
-void ResultsCollector::setFilterCount(int count)
-{
- QTC_ASSERT(!isRunning(), return);
- QTC_ASSERT(count >= 0, return);
-
- m_filterCount = count;
-}
-
-void ResultsCollector::start()
-{
- QTC_ASSERT(!m_watcher, return);
- QTC_ASSERT(!isRunning(), return);
- if (m_filterCount == 0) {
- emit done();
- return;
- }
-
- m_deduplicator.reset(new ResultsDeduplicator(m_filterCount));
- m_watcher.reset(new QFutureWatcher<LocatorFilterEntries>);
- connect(m_watcher.get(), &QFutureWatcherBase::resultReadyAt, this, [this](int index) {
- emit serialOutputDataReady(m_watcher->resultAt(index));
- });
- connect(m_watcher.get(), &QFutureWatcherBase::finished, this, [this] {
- emit done();
- m_watcher.release()->deleteLater();
- m_deduplicator.reset();
- });
-
- // TODO: When filterCount == 1, deliver results directly and finish?
- auto deduplicate = [](QPromise<LocatorFilterEntries> &promise,
- const std::shared_ptr<ResultsDeduplicator> &deduplicator) {
- deduplicator->run(promise);
- };
- m_watcher->setFuture(Utils::asyncRun(deduplicate, m_deduplicator));
-}
-
-class ResultsCollectorTaskAdapter : public TaskAdapter<ResultsCollector>
-{
-public:
- ResultsCollectorTaskAdapter() {
- connect(task(), &ResultsCollector::done, this, [this] { emit done(DoneResult::Success); });
- }
- void start() final { task()->start(); }
-};
-
-using ResultsCollectorTask = CustomTask<ResultsCollectorTaskAdapter>;
-
class LocatorStoragePrivate
{
public:
@@ -427,58 +333,60 @@ void LocatorMatcher::start()
QTC_ASSERT(!isRunning(), return);
d->m_output = {};
- struct CollectorStorage
+ const int filterCount = d->m_tasks.size();
+ if (filterCount <= 0)
+ return;
+
+ struct ResultsCollector
{
- ResultsCollector *m_collector = nullptr;
+ ~ResultsCollector() {
+ if (m_deduplicator)
+ m_deduplicator->cancel();
+ }
+ std::shared_ptr<ResultsDeduplicator> m_deduplicator;
};
- Storage<CollectorStorage> collectorStorage;
- const int filterCount = d->m_tasks.size();
- const auto onCollectorSetup = [this, filterCount, collectorStorage](ResultsCollector &collector) {
- collectorStorage->m_collector = &collector;
- collector.setFilterCount(filterCount);
- connect(&collector, &ResultsCollector::serialOutputDataReady,
- this, [this](const LocatorFilterEntries &serialOutputData) {
+ const Storage<ResultsCollector> collectorStorage;
+ const LoopList iterator(d->m_tasks);
+
+ const auto onCollectorSetup = [this, filterCount, collectorStorage](
+ Async<LocatorFilterEntries> &async) {
+ const std::shared_ptr<ResultsDeduplicator> deduplicator(new ResultsDeduplicator(filterCount));
+ collectorStorage->m_deduplicator = deduplicator;
+ Async<LocatorFilterEntries> *asyncPtr = &async;
+ connect(asyncPtr, &AsyncBase::resultReadyAt, this, [this, asyncPtr](int index) {
+ const LocatorFilterEntries serialOutputData = asyncPtr->resultAt(index);
d->m_output += serialOutputData;
emit serialOutputDataReady(serialOutputData);
});
+ // TODO: When filterCount == 1, deliver results directly and finish?
+ async.setConcurrentCallData(&ResultsDeduplicator::run, deduplicator);
};
- const auto onCollectorDone = [collectorStorage] {
- collectorStorage->m_collector = nullptr;
- };
-
- QList<GroupItem> parallelTasks {parallelLimit(d->m_parallelLimit)};
+ const auto onCollectorDone = [collectorStorage] { collectorStorage->m_deduplicator->cancel(); };
- const auto onSetup = [this, collectorStorage](const Storage<LocatorStorage> &storage,
- int index) {
- return [this, collectorStorage, storage, index] {
- ResultsCollector *collector = collectorStorage->m_collector;
- QTC_ASSERT(collector, return);
- *storage = std::make_shared<LocatorStoragePrivate>(d->m_input, index,
- collector->deduplicator());
+ const auto onTaskTreeSetup = [iterator, input = d->m_input, collectorStorage](TaskTree &taskTree) {
+ const std::shared_ptr<ResultsDeduplicator> deduplicator = collectorStorage->m_deduplicator;
+ const Storage<LocatorStorage> storage = iterator->storage;
+ const auto onSetup = [storage, input, index = iterator.iteration(), deduplicator] {
+ *storage = std::make_shared<LocatorStoragePrivate>(input, index, deduplicator);
};
- };
-
- int index = 0;
- for (const LocatorMatcherTask &task : std::as_const(d->m_tasks)) {
- const auto storage = task.storage;
- const Group group {
+ taskTree.setRecipe({
finishAllAndSuccess,
storage,
- onGroupSetup(onSetup(storage, index)),
- onGroupDone([storage] { storage->finalize(); }),
- task.task
- };
- parallelTasks << group;
- ++index;
- }
+ onGroupSetup(onSetup),
+ iterator->task,
+ onGroupDone([storage] { storage->finalize(); })
+ });
+ };
const Group root {
parallel,
collectorStorage,
- ResultsCollectorTask(onCollectorSetup, onCollectorDone),
+ AsyncTask<LocatorFilterEntries>(onCollectorSetup, onCollectorDone),
Group {
- parallelTasks
+ parallelLimit(d->m_parallelLimit),
+ iterator,
+ TaskTreeTask(onTaskTreeSetup)
}
};
d->m_taskTreeRunner.start(root, {}, [this](DoneWith result) {
@@ -671,15 +579,6 @@ void ILocatorFilter::restoreState(const QByteArray &state)
setShortcutString(obj.value(kShortcutStringKey).toString(m_defaultShortcut));
setIncludedByDefault(obj.value(kIncludedByDefaultKey).toBool(m_defaultIncludedByDefault));
restoreState(obj);
- } else {
- // TODO read old settings, remove some time after Qt Creator 4.15
- m_shortcut = m_defaultShortcut;
- m_includedByDefault = m_defaultIncludedByDefault;
-
- // TODO this reads legacy settings from Qt Creator < 4.15
- QDataStream in(state);
- in >> m_shortcut;
- in >> m_includedByDefault;
}
}
@@ -1061,17 +960,6 @@ void ILocatorFilter::restoreState(const QJsonObject &object)
}
/*!
- Returns if \a state must be restored via pre-4.15 settings reading.
-*/
-bool ILocatorFilter::isOldSetting(const QByteArray &state)
-{
- if (state.isEmpty())
- return false;
- const QJsonDocument doc = QJsonDocument::fromJson(state);
- return !doc.isObject();
-}
-
-/*!
\enum Core::ILocatorFilter::Priority
This enum value holds the priority that is used for ordering the results
@@ -1484,7 +1372,6 @@ LocatorMatcherTask LocatorFileCache::matcher() const
// no provider is set or it returned empty generator
that->bumpExecutionId();
- async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
async.setConcurrentCallData(&filter, *storage, *that);
return SetupResult::Continue;
};
@@ -1509,5 +1396,3 @@ LocatorMatcherTask LocatorFileCache::matcher() const
}
} // Core
-
-#include "ilocatorfilter.moc"
diff --git a/src/plugins/coreplugin/locator/ilocatorfilter.h b/src/plugins/coreplugin/locator/ilocatorfilter.h
index 4a4bcc5c0e..368f031381 100644
--- a/src/plugins/coreplugin/locator/ilocatorfilter.h
+++ b/src/plugins/coreplugin/locator/ilocatorfilter.h
@@ -226,8 +226,8 @@ public:
std::optional<QString> defaultSearchText() const;
void setDefaultSearchText(const QString &defaultSearchText);
- virtual QByteArray saveState() const;
- virtual void restoreState(const QByteArray &state);
+ QByteArray saveState() const;
+ void restoreState(const QByteArray &state);
virtual bool openConfigDialog(QWidget *parent, bool &needsRefresh);
bool isConfigurable() const;
@@ -272,8 +272,6 @@ protected:
void setRefreshRecipe(const std::optional<Tasking::GroupItem> &recipe);
std::optional<Tasking::GroupItem> refreshRecipe() const;
- static bool isOldSetting(const QByteArray &state);
-
private:
virtual LocatorMatcherTasks matchers() = 0;
diff --git a/src/plugins/coreplugin/locator/locatorsettingspage.cpp b/src/plugins/coreplugin/locator/locatorsettingspage.cpp
index d379a5c858..4eeeb1a0ac 100644
--- a/src/plugins/coreplugin/locator/locatorsettingspage.cpp
+++ b/src/plugins/coreplugin/locator/locatorsettingspage.cpp
@@ -274,11 +274,9 @@ public:
m_refreshInterval->setSingleStep(5);
m_refreshInterval->setValue(60);
- auto relativePathsLabel = new QLabel(Tr::tr("Show Paths in Relation to Active Project:"));
- relativePathsLabel->setToolTip(Tr::tr("Locator filters show relative paths to the active project when possible."));
-
- m_relativePaths = new QCheckBox;
- m_relativePaths->setToolTip(relativePathsLabel->toolTip());
+ m_relativePaths = new QCheckBox(Tr::tr("Show Paths in Relation to Active Project"));
+ m_relativePaths->setToolTip(
+ Tr::tr("Locator filters show relative paths to the active project when possible."));
auto filterEdit = new FancyLineEdit;
filterEdit->setFiltering(true);
@@ -322,15 +320,14 @@ public:
Column buttons{addButton, m_removeButton, m_editButton, st};
- Grid{filterEdit,
- br,
- m_filterList,
- buttons,
- br,
- Span(2, Row{refreshIntervalLabel, m_refreshInterval, st}),
- br,
- Span(2, Row{relativePathsLabel, m_relativePaths, st})}
- .attachTo(this);
+ // clang-format off
+ Grid {
+ filterEdit, br,
+ m_filterList, buttons, br,
+ Span(2, Row{refreshIntervalLabel, m_refreshInterval, st}), br,
+ Span(2, Row{m_relativePaths, st})
+ }.attachTo(this);
+ // clang-format on
connect(filterEdit, &FancyLineEdit::filterChanged, this, &LocatorSettingsWidget::setFilter);
connect(m_filterList->selectionModel(),
diff --git a/src/plugins/coreplugin/locator/locatorwidget.cpp b/src/plugins/coreplugin/locator/locatorwidget.cpp
index e6acfee42f..c5444344a6 100644
--- a/src/plugins/coreplugin/locator/locatorwidget.cpp
+++ b/src/plugins/coreplugin/locator/locatorwidget.cpp
@@ -57,8 +57,8 @@ public:
LocatorModel(QObject *parent = nullptr)
: QAbstractListModel(parent)
- , m_backgroundColor(Utils::creatorTheme()->color(Theme::TextColorHighlightBackground))
- , m_foregroundColor(Utils::creatorTheme()->color(Theme::TextColorNormal))
+ , m_backgroundColor(Utils::creatorColor(Theme::TextColorHighlightBackground))
+ , m_foregroundColor(Utils::creatorColor(Theme::TextColorNormal))
{}
void clear();
diff --git a/src/plugins/coreplugin/locator/opendocumentsfilter.cpp b/src/plugins/coreplugin/locator/opendocumentsfilter.cpp
index 719de23098..ede24d5b89 100644
--- a/src/plugins/coreplugin/locator/opendocumentsfilter.cpp
+++ b/src/plugins/coreplugin/locator/opendocumentsfilter.cpp
@@ -6,8 +6,6 @@
#include "../coreplugintr.h"
#include "../editormanager/documentmodel.h"
-#include <extensionsystem/pluginmanager.h>
-
#include <utils/algorithm.h>
#include <utils/async.h>
#include <utils/link.h>
@@ -77,7 +75,6 @@ LocatorMatcherTasks OpenDocumentsFilter::matchers()
const auto onSetup = [storage](Async<void> &async) {
const QList<Entry> editorsData = Utils::transform(DocumentModel::entries(),
[](const DocumentModel::Entry *e) { return Entry{e->filePath(), e->displayName()}; });
- async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
async.setConcurrentCallData(matchEditors, *storage, editorsData);
};
diff --git a/src/plugins/coreplugin/locator/spotlightlocatorfilter.cpp b/src/plugins/coreplugin/locator/spotlightlocatorfilter.cpp
index b360ad595d..d8f07178b4 100644
--- a/src/plugins/coreplugin/locator/spotlightlocatorfilter.cpp
+++ b/src/plugins/coreplugin/locator/spotlightlocatorfilter.cpp
@@ -6,8 +6,6 @@
#include "../coreplugintr.h"
#include "../messagemanager.h"
-#include <extensionsystem/pluginmanager.h>
-
#include <utils/algorithm.h>
#include <utils/async.h>
#include <utils/commandline.h>
@@ -198,7 +196,6 @@ LocatorMatcherTasks SpotlightLocatorFilter::matchers()
? insensArgs : sensArgs;
const CommandLine cmd(FilePath::fromString(command), expander->expand(args),
CommandLine::Raw);
- async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
async.setConcurrentCallData(matches, *storage, cmd, sortResults);
return SetupResult::Continue;
};
diff --git a/src/plugins/coreplugin/locator/urllocatorfilter.cpp b/src/plugins/coreplugin/locator/urllocatorfilter.cpp
index 1c67d9e032..7457f0907c 100644
--- a/src/plugins/coreplugin/locator/urllocatorfilter.cpp
+++ b/src/plugins/coreplugin/locator/urllocatorfilter.cpp
@@ -207,34 +207,6 @@ void UrlLocatorFilter::restoreState(const QJsonObject &object)
&QVariant::toString);
}
-void UrlLocatorFilter::restoreState(const QByteArray &state)
-{
- if (isOldSetting(state)) {
- // TODO read old settings, remove some time after Qt Creator 4.15
- QDataStream in(state);
-
- QString value;
- in >> value;
- m_remoteUrls = value.split('^', Qt::SkipEmptyParts);
-
- QString shortcut;
- in >> shortcut;
- setShortcutString(shortcut);
-
- bool defaultFilter;
- in >> defaultFilter;
- setIncludedByDefault(defaultFilter);
-
- if (!in.atEnd()) {
- QString name;
- in >> name;
- setDisplayName(name);
- }
- } else {
- ILocatorFilter::restoreState(state);
- }
-}
-
bool UrlLocatorFilter::openConfigDialog(QWidget *parent, bool &needsRefresh)
{
Q_UNUSED(needsRefresh)
diff --git a/src/plugins/coreplugin/locator/urllocatorfilter.h b/src/plugins/coreplugin/locator/urllocatorfilter.h
index 18535569c5..cc57903479 100644
--- a/src/plugins/coreplugin/locator/urllocatorfilter.h
+++ b/src/plugins/coreplugin/locator/urllocatorfilter.h
@@ -24,7 +24,6 @@ public:
UrlLocatorFilter(Utils::Id id);
UrlLocatorFilter(const QString &displayName, Utils::Id id);
- void restoreState(const QByteArray &state) final;
bool openConfigDialog(QWidget *parent, bool &needsRefresh) final;
void addDefaultUrl(const QString &urlTemplate);
diff --git a/src/plugins/coreplugin/loggingviewer.cpp b/src/plugins/coreplugin/loggingviewer.cpp
index d6aad89714..427b0a064c 100644
--- a/src/plugins/coreplugin/loggingviewer.cpp
+++ b/src/plugins/coreplugin/loggingviewer.cpp
@@ -715,7 +715,7 @@ LoggingViewManagerWidget::LoggingViewManagerWidget(QWidget *parent)
Splitter {
bindTo(&splitter),
Column {
- noMargin(),
+ noMargin,
Row {
spacing(0),
save,
@@ -729,7 +729,7 @@ LoggingViewManagerWidget::LoggingViewManagerWidget(QWidget *parent)
m_logView
},
Column {
- noMargin(),
+ noMargin,
Row {
qtInternal,
filterEdit,
diff --git a/src/plugins/coreplugin/manhattanstyle.cpp b/src/plugins/coreplugin/manhattanstyle.cpp
index c4cba4845b..7a51c63378 100644
--- a/src/plugins/coreplugin/manhattanstyle.cpp
+++ b/src/plugins/coreplugin/manhattanstyle.cpp
@@ -269,15 +269,15 @@ void ManhattanStyle::unpolish(QApplication *app)
QPalette panelPalette(const QPalette &oldPalette, bool lightColored = false)
{
- QColor color = creatorTheme()->color(lightColored ? Theme::PanelTextColorDark
- : Theme::PanelTextColorLight);
+ QColor color = creatorColor(lightColored ? Theme::PanelTextColorDark
+ : Theme::PanelTextColorLight);
QPalette pal = oldPalette;
pal.setBrush(QPalette::All, QPalette::WindowText, color);
pal.setBrush(QPalette::All, QPalette::ButtonText, color);
if (lightColored)
color.setAlpha(100);
else
- color = creatorTheme()->color(Theme::IconsDisabledColor);
+ color = creatorColor(Theme::IconsDisabledColor);
pal.setBrush(QPalette::Disabled, QPalette::WindowText, color);
pal.setBrush(QPalette::Disabled, QPalette::ButtonText, color);
return pal;
@@ -328,7 +328,7 @@ void ManhattanStyle::polish(QWidget *widget)
QPalette palette = panelPalette(widget->palette(), isLightColored);
if (!isLightColored)
palette.setBrush(QPalette::All, QPalette::WindowText,
- creatorTheme()->color(Theme::ComboBoxTextColor));
+ creatorColor(Theme::ComboBoxTextColor));
widget->setPalette(palette);
widget->setMaximumHeight(height - 2);
widget->setAttribute(Qt::WA_Hover);
@@ -450,9 +450,9 @@ static void drawPrimitiveTweakedForDarkTheme(QStyle::PrimitiveElement element,
const bool isSunken = option->state & QStyle::State_Sunken;
const QColor frameColor = isEnabled ? option->palette.color(QPalette::Mid).darker(132)
- : creatorTheme()->color(Theme::BackgroundColorDisabled);
+ : creatorColor(Theme::BackgroundColorDisabled);
const QColor indicatorColor = isEnabled ? option->palette.color(QPalette::Mid).darker(90)
- : creatorTheme()->color(Theme::BackgroundColorDisabled);
+ : creatorColor(Theme::BackgroundColorDisabled);
const QColor bgColor = isSunken ? option->palette.color(QPalette::Mid).darker()
: option->palette.color(QPalette::Window);
const QColor hlColor = option->palette.color(QPalette::Highlight);
@@ -563,16 +563,15 @@ static void drawPrimitiveTweakedForDarkTheme(QStyle::PrimitiveElement element,
break;
}
case QStyle::PE_IndicatorTabClose: {
- QWindow *window = widget ? widget->window()->windowHandle() : nullptr;
+ const qreal devicePixelRatio = painter->device()->devicePixelRatio();
QRect iconRect = QRect(0, 0, 16, 16);
iconRect.moveCenter(option->rect.center());
const QIcon::Mode mode = !isEnabled ? QIcon::Disabled : QIcon::Normal;
const static QIcon closeIcon = Utils::Icons::CLOSE_FOREGROUND.icon();
if (option->state & QStyle::State_MouseOver && widget)
widget->style()->drawPrimitive(QStyle::PE_PanelButtonCommand, option, painter, widget);
- const int devicePixelRatio = widget ? widget->devicePixelRatio() : 1;
const QPixmap iconPx =
- closeIcon.pixmap(window, iconRect.size() * devicePixelRatio, mode);
+ closeIcon.pixmap(iconRect.size() * devicePixelRatio, devicePixelRatio, mode);
painter->drawPixmap(iconRect, iconPx);
break;
}
@@ -613,7 +612,7 @@ void ManhattanStyle::drawPrimitiveForPanelWidget(PrimitiveElement element,
QRect rect = option->rect;
switch (element) {
case PE_IndicatorDockWidgetResizeHandle:
- painter->fillRect(option->rect, creatorTheme()->color(Theme::DockWidgetResizeHandleColor));
+ painter->fillRect(option->rect, creatorColor(Theme::DockWidgetResizeHandleColor));
break;
case PE_FrameDockWidget:
QCommonStyle::drawPrimitive(element, option, painter, widget);
@@ -631,7 +630,7 @@ void ManhattanStyle::drawPrimitiveForPanelWidget(PrimitiveElement element,
painter->setOpacity(0.75);
QBrush baseBrush = option->palette.base();
if (widget && qobject_cast<const QSpinBox *>(widget->parentWidget()))
- baseBrush = creatorTheme()->color(Theme::DScontrolBackgroundDisabled);
+ baseBrush = creatorColor(Theme::DScontrolBackgroundDisabled);
painter->fillRect(backgroundRect, baseBrush);
painter->restore();
} else {
@@ -725,7 +724,7 @@ void ManhattanStyle::drawPrimitiveForPanelWidget(PrimitiveElement element,
painter->setPen(StyleHelper::sidebarShadow());
if (pressed) {
StyleHelper::drawPanelBgRect(
- painter, rect, creatorTheme()->color(Theme::FancyToolButtonSelectedColor));
+ painter, rect, creatorColor(Theme::FancyToolButtonSelectedColor));
if (StyleHelper::toolbarStyle() == StyleHelper::ToolbarStyleCompact
&& !creatorTheme()->flag(Theme::FlatToolBars)) {
const QRectF borderRect = QRectF(rect).adjusted(0.5, 0.5, -0.5, -0.5);
@@ -736,13 +735,13 @@ void ManhattanStyle::drawPrimitiveForPanelWidget(PrimitiveElement element,
} else if (option->state & State_Enabled && option->state & State_MouseOver) {
if (widget->property(StyleHelper::C_TOOLBAR_ACTIONWIDGET).toBool()) {
painter->save();
- painter->setBrush(creatorTheme()->color(Theme::FancyToolButtonHoverColor));
+ painter->setBrush(creatorColor(Theme::FancyToolButtonHoverColor));
painter->drawRoundedRect(rect, 5, 5);
painter->restore();
} else {
StyleHelper::drawPanelBgRect(painter,
rect,
- creatorTheme()->color(
+ creatorColor(
Theme::FancyToolButtonHoverColor));
}
}
@@ -868,9 +867,8 @@ void ManhattanStyle::drawControl(
const bool enabled = mbi->state & State_Enabled;
QStyleOptionMenuItem item = *mbi;
item.rect = mbi->rect;
- const QColor color = creatorTheme()->color(enabled
- ? Theme::MenuItemTextColorNormal
- : Theme::MenuItemTextColorDisabled);
+ const QColor color = creatorColor(enabled ? Theme::MenuItemTextColorNormal
+ : Theme::MenuItemTextColorDisabled);
if (color.isValid()) {
QPalette pal = mbi->palette;
pal.setBrush(QPalette::Text, color);
@@ -897,24 +895,23 @@ void ManhattanStyle::drawControl(
item.rect = mbi->rect;
QPalette pal = mbi->palette;
pal.setBrush(QPalette::ButtonText, dis
- ? creatorTheme()->color(Theme::MenuBarItemTextColorDisabled)
- : creatorTheme()->color(Theme::MenuBarItemTextColorNormal));
+ ? creatorColor(Theme::MenuBarItemTextColorDisabled)
+ : creatorColor(Theme::MenuBarItemTextColorNormal));
item.palette = pal;
QCommonStyle::drawControl(element, &item, painter, widget);
if (act) {
// Fill|
const QColor fillColor = StyleHelper::alphaBlendedColors(
- StyleHelper::baseColor(), creatorTheme()->color(Theme::FancyToolButtonHoverColor));
+ StyleHelper::baseColor(), creatorColor(Theme::FancyToolButtonHoverColor));
painter->fillRect(option->rect, fillColor);
QPalette pal = mbi->palette;
uint alignment = Qt::AlignCenter | Qt::TextShowMnemonic | Qt::TextDontClip | Qt::TextSingleLine;
if (!styleHint(SH_UnderlineShortcut, mbi, widget))
alignment |= Qt::TextHideMnemonic;
- pal.setBrush(QPalette::Text, creatorTheme()->color(dis
- ? Theme::IconsDisabledColor
- : Theme::PanelTextColorLight));
+ pal.setBrush(QPalette::Text, creatorColor(dis ? Theme::IconsDisabledColor
+ : Theme::PanelTextColorLight));
drawItemText(painter, item.rect, alignment, pal, !dis, mbi->text, QPalette::Text);
}
}
@@ -981,7 +978,7 @@ void ManhattanStyle::drawControl(
}
painter->setPen((option->state & State_Enabled)
? option->palette.color(QPalette::WindowText)
- : creatorTheme()->color(Theme::IconsDisabledColor));
+ : creatorColor(Theme::IconsDisabledColor));
painter->drawText(editRect.adjusted(1, 0, -1, 0), Qt::AlignLeft | Qt::AlignVCenter, text);
painter->restore();
@@ -1072,7 +1069,7 @@ void ManhattanStyle::drawControl(
// toolbar should draw the top or bottom outline
// (needed for the find toolbar for instance)
const QColor hightLight = creatorTheme()->flag(Theme::FlatToolBars)
- ? creatorTheme()->color(Theme::FancyToolBarSeparatorColor)
+ ? creatorColor(Theme::FancyToolBarSeparatorColor)
: StyleHelper::sidebarHighlight();
const QColor borderColor = drawLightColored
? QColor(255, 255, 255, 180) : hightLight;
@@ -1158,7 +1155,7 @@ void ManhattanStyle::drawComplexControl(ComplexControl control, const QStyleOpti
label.palette = panelPalette(option->palette, lightColored(widget));
if (widget && widget->property(StyleHelper::C_HIGHLIGHT_WIDGET).toBool()) {
label.palette.setColor(QPalette::ButtonText,
- creatorTheme()->color(Theme::IconsWarningToolBarColor));
+ creatorColor(Theme::IconsWarningToolBarColor));
}
int fw = pixelMetric(PM_DefaultFrameWidth, option, widget);
label.rect = button.adjusted(fw, fw, -fw, -fw);
@@ -1270,7 +1267,7 @@ void ManhattanStyle::drawButtonSeparator(QPainter *painter, const QRect &rect, b
const QRectF borderRect = QRectF(rect).adjusted(0.5, 0.5, -0.5, -0.5);
if (creatorTheme()->flag(Theme::FlatToolBars)) {
const int margin = 3;
- painter->setPen(creatorTheme()->color(Theme::FancyToolBarSeparatorColor));
+ painter->setPen(creatorColor(Theme::FancyToolBarSeparatorColor));
painter->drawLine(borderRect.topRight() + QPointF(0, margin),
borderRect.bottomRight() - QPointF(0, margin));
} else {
diff --git a/src/plugins/coreplugin/messagemanager.cpp b/src/plugins/coreplugin/messagemanager.cpp
index 4eca635b84..d8e723ecf2 100644
--- a/src/plugins/coreplugin/messagemanager.cpp
+++ b/src/plugins/coreplugin/messagemanager.cpp
@@ -5,121 +5,88 @@
#include "messageoutputwindow.h"
-#include <extensionsystem/pluginmanager.h>
#include <utils/qtcassert.h>
#include <QFont>
-#include <QThread>
-#include <QTime>
+
+#include <memory>
/*!
- \class Core::MessageManager
+ \namespace Core::MessageManager
\inheaderfile coreplugin/messagemanager.h
\ingroup mainclasses
\inmodule QtCreator
- \brief The MessageManager class is used to post messages in the
+ \brief The MessageManager namespace is used to post messages in the
\uicontrol{General Messages} pane.
*/
-namespace Core {
+namespace Core::MessageManager {
-static MessageManager *m_instance = nullptr;
-static Internal::MessageOutputWindow *m_messageOutputWindow = nullptr;
-
-/*!
- \internal
-*/
-MessageManager *MessageManager::instance()
-{
- return m_instance;
-}
+static std::unique_ptr<Internal::MessageOutputWindow> s_messageOutputWindow;
enum class Flag { Silent, Flash, Disrupt };
static void showOutputPane(Flag flags)
{
- QTC_ASSERT(m_messageOutputWindow, return);
-
+ QTC_ASSERT(s_messageOutputWindow, return);
switch (flags) {
- case Core::Flag::Silent:
+ case Flag::Silent:
break;
- case Core::Flag::Flash:
- m_messageOutputWindow->flash();
+ case Flag::Flash:
+ s_messageOutputWindow->flash();
break;
- case Core::Flag::Disrupt:
- m_messageOutputWindow->popup(IOutputPane::ModeSwitch | IOutputPane::WithFocus);
+ case Flag::Disrupt:
+ s_messageOutputWindow->popup(IOutputPane::ModeSwitch | IOutputPane::WithFocus);
break;
}
}
static void doWrite(const QString &text, Flag flags)
{
- QTC_ASSERT(m_messageOutputWindow, return);
-
+ QTC_ASSERT(s_messageOutputWindow, return);
showOutputPane(flags);
- m_messageOutputWindow->append(text + '\n');
+ s_messageOutputWindow->append(text + '\n');
}
-static void write(const QString &text, Flag flags)
+static void writeImpl(const QString &text, Flag flags)
{
- QTC_ASSERT(m_instance, return);
- if (QThread::currentThread() == m_instance->thread())
- doWrite(text, flags);
- else
- QMetaObject::invokeMethod(m_instance, [text, flags] {
- doWrite(text, flags);
- }, Qt::QueuedConnection);
+ QTC_ASSERT(s_messageOutputWindow, return);
+ QMetaObject::invokeMethod(s_messageOutputWindow.get(), [text, flags] { doWrite(text, flags); });
}
/*!
\internal
*/
-MessageManager::MessageManager()
+void init()
{
- m_instance = this;
- m_messageOutputWindow = nullptr;
+ s_messageOutputWindow.reset(new Internal::MessageOutputWindow);
}
/*!
\internal
*/
-MessageManager::~MessageManager()
+void destroy()
{
- if (m_messageOutputWindow) {
- ExtensionSystem::PluginManager::removeObject(m_messageOutputWindow);
- delete m_messageOutputWindow;
- }
- m_instance = nullptr;
+ s_messageOutputWindow.reset();
}
/*!
\internal
*/
-void MessageManager::init()
+void setFont(const QFont &font)
{
- m_messageOutputWindow = new Internal::MessageOutputWindow;
- ExtensionSystem::PluginManager::addObject(m_messageOutputWindow);
+ QTC_ASSERT(s_messageOutputWindow, return);
+ s_messageOutputWindow->setFont(font);
}
/*!
\internal
*/
-void MessageManager::setFont(const QFont &font)
+void setWheelZoomEnabled(bool enabled)
{
- QTC_ASSERT(m_messageOutputWindow, return);
-
- m_messageOutputWindow->setFont(font);
-}
-
-/*!
- \internal
-*/
-void MessageManager::setWheelZoomEnabled(bool enabled)
-{
- QTC_ASSERT(m_messageOutputWindow, return);
-
- m_messageOutputWindow->setWheelZoomEnabled(enabled);
+ QTC_ASSERT(s_messageOutputWindow, return);
+ s_messageOutputWindow->setWheelZoomEnabled(enabled);
}
/*!
@@ -132,9 +99,9 @@ void MessageManager::setWheelZoomEnabled(bool enabled)
\sa writeFlashing()
\sa writeDisrupting()
*/
-void MessageManager::writeSilently(const QString &message)
+void writeSilently(const QString &message)
{
- Core::write(message, Flag::Silent);
+ writeImpl(message, Flag::Silent);
}
/*!
@@ -149,9 +116,9 @@ void MessageManager::writeSilently(const QString &message)
\sa writeSilently()
\sa writeDisrupting()
*/
-void MessageManager::writeFlashing(const QString &message)
+void writeFlashing(const QString &message)
{
- Core::write(message, Flag::Flash);
+ writeImpl(message, Flag::Flash);
}
/*!
@@ -164,15 +131,15 @@ void MessageManager::writeFlashing(const QString &message)
\sa writeSilently()
\sa writeFlashing()
*/
-void MessageManager::writeDisrupting(const QString &message)
+void writeDisrupting(const QString &message)
{
- Core::write(message, Flag::Disrupt);
+ writeImpl(message, Flag::Disrupt);
}
/*!
\overload writeSilently()
*/
-void MessageManager::writeSilently(const QStringList &messages)
+void writeSilently(const QStringList &messages)
{
writeSilently(messages.join('\n'));
}
@@ -180,7 +147,7 @@ void MessageManager::writeSilently(const QStringList &messages)
/*!
\overload writeFlashing()
*/
-void MessageManager::writeFlashing(const QStringList &messages)
+void writeFlashing(const QStringList &messages)
{
writeFlashing(messages.join('\n'));
}
@@ -188,9 +155,9 @@ void MessageManager::writeFlashing(const QStringList &messages)
/*!
\overload writeDisrupting()
*/
-void MessageManager::writeDisrupting(const QStringList &messages)
+void writeDisrupting(const QStringList &messages)
{
writeDisrupting(messages.join('\n'));
}
-} // namespace Core
+} // namespace Core::MessageManager
diff --git a/src/plugins/coreplugin/messagemanager.h b/src/plugins/coreplugin/messagemanager.h
index 98eb0635a1..87a4b6f649 100644
--- a/src/plugins/coreplugin/messagemanager.h
+++ b/src/plugins/coreplugin/messagemanager.h
@@ -5,49 +5,26 @@
#include "core_global.h"
-#include <QMetaType>
-#include <QObject>
+#include <QStringList>
QT_BEGIN_NAMESPACE
class QFont;
QT_END_NAMESPACE
-namespace Core {
+namespace Core::MessageManager {
-class ICore;
+CORE_EXPORT void setFont(const QFont &font);
+CORE_EXPORT void setWheelZoomEnabled(bool enabled);
-namespace Internal {
-class ICorePrivate;
-class MainWindow;
-}
+CORE_EXPORT void writeSilently(const QString &message);
+CORE_EXPORT void writeFlashing(const QString &message);
+CORE_EXPORT void writeDisrupting(const QString &message);
-class CORE_EXPORT MessageManager : public QObject
-{
- Q_OBJECT
+CORE_EXPORT void writeSilently(const QStringList &messages);
+CORE_EXPORT void writeFlashing(const QStringList &messages);
+CORE_EXPORT void writeDisrupting(const QStringList &messages);
-public:
- static MessageManager *instance();
+void init();
+void destroy();
- static void setFont(const QFont &font);
- static void setWheelZoomEnabled(bool enabled);
-
- static void writeSilently(const QString &message);
- static void writeFlashing(const QString &message);
- static void writeDisrupting(const QString &message);
-
- static void writeSilently(const QStringList &messages);
- static void writeFlashing(const QStringList &messages);
- static void writeDisrupting(const QStringList &messages);
-
-private:
- MessageManager();
- ~MessageManager() override;
-
- static void init();
-
- friend class ICore;
- friend class Internal::ICorePrivate;
- friend class Internal::MainWindow;
-};
-
-} // namespace Core
+} // namespace Core::MessageManager
diff --git a/src/plugins/coreplugin/minisplitter.cpp b/src/plugins/coreplugin/minisplitter.cpp
index cdf6d16d8e..56adc7a608 100644
--- a/src/plugins/coreplugin/minisplitter.cpp
+++ b/src/plugins/coreplugin/minisplitter.cpp
@@ -138,7 +138,7 @@ void MiniSplitterHandle::resizeEvent(QResizeEvent *event)
void MiniSplitterHandle::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
- const QColor color = Utils::creatorTheme()->color(
+ const QColor color = Utils::creatorColor(
m_lightColored ? Utils::Theme::FancyToolBarSeparatorColor
: Utils::Theme::SplitterColor);
painter.fillRect(event->rect(), color);
diff --git a/src/plugins/coreplugin/outputpanemanager.cpp b/src/plugins/coreplugin/outputpanemanager.cpp
index 48ba622418..a36b55a902 100644
--- a/src/plugins/coreplugin/outputpanemanager.cpp
+++ b/src/plugins/coreplugin/outputpanemanager.cpp
@@ -410,18 +410,18 @@ OutputPaneManager::OutputPaneManager(QWidget *parent) :
m_opToolBarWidgets,
minMaxButton,
closeButton,
- spacing(0), noMargin(),
+ spacing(0), noMargin,
}.attachTo(toolBar);
Column {
toolBar,
m_outputWidgetPane,
new FindToolBarPlaceHolder(this),
- spacing(0), noMargin(),
+ spacing(0), noMargin,
}.attachTo(this);
Row {
- spacing(creatorTheme()->flag(Theme::FlatToolBars) ? 9 : 4), customMargin({5, 0, 0, 0}),
+ spacing(creatorTheme()->flag(Theme::FlatToolBars) ? 9 : 4), customMargins(5, 0, 0, 0),
}.attachTo(m_buttonsWidget);
StatusBarManager::addStatusBarWidget(m_buttonsWidget, StatusBarManager::Second);
@@ -503,7 +503,7 @@ void OutputPaneManager::initialize()
QWidget *toolButtonsContainer = new QWidget(m_instance->m_opToolBarWidgets);
using namespace Layouting;
- Row toolButtonsRow { spacing(0), noMargin() };
+ Row toolButtonsRow { spacing(0), noMargin };
const QList<QWidget *> toolBarWidgets = outPane->toolBarWidgets();
for (QWidget *toolButton : toolBarWidgets)
toolButtonsRow.addItem(toolButton);
@@ -940,7 +940,7 @@ void OutputPaneToggleButton::paintEvent(QPaintEvent*)
c = Theme::BackgroundColorSelected;
if (c != Theme::BackgroundColorDark)
- StyleHelper::drawPanelBgRect(&p, bgRect(rect()), creatorTheme()->color(c));
+ StyleHelper::drawPanelBgRect(&p, bgRect(rect()), creatorColor(c));
} else {
const QImage *image = nullptr;
if (isDown()) {
@@ -974,7 +974,7 @@ void OutputPaneToggleButton::paintEvent(QPaintEvent*)
if (m_flashTimer->state() == QTimeLine::Running)
{
- QColor c = creatorTheme()->color(Theme::OutputPaneButtonFlashColor);
+ QColor c = creatorColor(Theme::OutputPaneButtonFlashColor);
c.setAlpha (m_flashTimer->currentFrame());
if (creatorTheme()->flag(Theme::FlatToolBars))
StyleHelper::drawPanelBgRect(&p, bgRect(rect()), c);
@@ -983,10 +983,10 @@ void OutputPaneToggleButton::paintEvent(QPaintEvent*)
}
p.setFont(font());
- p.setPen(creatorTheme()->color(Theme::OutputPaneToggleButtonTextColorChecked));
+ p.setPen(creatorColor(Theme::OutputPaneToggleButtonTextColorChecked));
p.drawText((numberAreaWidth() - numberWidth) / 2, baseLine, m_number);
if (!isChecked())
- p.setPen(creatorTheme()->color(Theme::OutputPaneToggleButtonTextColorUnchecked));
+ p.setPen(creatorColor(Theme::OutputPaneToggleButtonTextColorUnchecked));
int leftPart = numberAreaWidth() + buttonBorderWidth;
int labelWidth = 0;
if (!m_badgeNumberLabel.text().isEmpty()) {
@@ -1083,15 +1083,15 @@ void BadgeLabel::paint(QPainter *p, int x, int y, bool isChecked)
const QRectF rect(QRect(QPoint(x, y), m_size));
p->save();
- p->setBrush(creatorTheme()->color(isChecked? Theme::BadgeLabelBackgroundColorChecked
- : Theme::BadgeLabelBackgroundColorUnchecked));
+ p->setBrush(creatorColor(isChecked? Theme::BadgeLabelBackgroundColorChecked
+ : Theme::BadgeLabelBackgroundColorUnchecked));
p->setPen(Qt::NoPen);
p->setRenderHint(QPainter::Antialiasing, true);
p->drawRoundedRect(rect, m_padding, m_padding, Qt::AbsoluteSize);
p->setFont(m_font);
- p->setPen(creatorTheme()->color(isChecked ? Theme::BadgeLabelTextColorChecked
- : Theme::BadgeLabelTextColorUnchecked));
+ p->setPen(creatorColor(isChecked ? Theme::BadgeLabelTextColorChecked
+ : Theme::BadgeLabelTextColorUnchecked));
p->drawText(rect, Qt::AlignCenter, m_text);
p->restore();
diff --git a/src/plugins/coreplugin/plugindialog.cpp b/src/plugins/coreplugin/plugindialog.cpp
index 05d63aedc7..edca2360b3 100644
--- a/src/plugins/coreplugin/plugindialog.cpp
+++ b/src/plugins/coreplugin/plugindialog.cpp
@@ -98,7 +98,7 @@ void PluginDialog::closeDialog()
void PluginDialog::showInstallWizard()
{
- if (PluginInstallWizard::exec())
+ if (executePluginInstallWizard())
m_isRestartRequired = true;
}
diff --git a/src/plugins/coreplugin/plugininstallwizard.cpp b/src/plugins/coreplugin/plugininstallwizard.cpp
index 7e63e1d508..250ed0a31a 100644
--- a/src/plugins/coreplugin/plugininstallwizard.cpp
+++ b/src/plugins/coreplugin/plugininstallwizard.cpp
@@ -71,7 +71,6 @@ static FilePath pluginInstallPath(bool installIntoApplication)
}
namespace Core {
-namespace Internal {
class SourcePage : public WizardPage
{
@@ -145,7 +144,7 @@ struct ArchiveIssue
// Async. Result is set if any issue was found.
void checkContents(QPromise<ArchiveIssue> &promise, const FilePath &tempDir)
{
- PluginSpec *coreplugin = PluginManager::specForPlugin(CorePlugin::instance());
+ PluginSpec *coreplugin = PluginManager::specForPlugin(Internal::CorePlugin::instance());
// look for plugin
QDirIterator it(tempDir.path(), libraryNameFilter(), QDir::Files | QDir::NoSymLinks,
@@ -154,7 +153,7 @@ void checkContents(QPromise<ArchiveIssue> &promise, const FilePath &tempDir)
if (promise.isCanceled())
return;
it.next();
- expected_str<PluginSpec *> spec = readCppPluginSpec(it.filePath());
+ expected_str<PluginSpec *> spec = readCppPluginSpec(FilePath::fromUserInput(it.filePath()));
if (spec) {
// Is a Qt Creator plugin. Let's see if we find a Core dependency and check the
// version
@@ -242,7 +241,6 @@ public:
return SetupResult::StopWithError;
async.setConcurrentCallData(checkContents, m_tempDir->path());
- async.setFutureSynchronizer(PluginManager::futureSynchronizer());
return SetupResult::Continue;
};
const auto onCheckerDone = [this](const Async<ArchiveIssue> &async) {
@@ -398,15 +396,19 @@ static bool copyPluginFile(const FilePath &src, const FilePath &dest)
return true;
}
-bool PluginInstallWizard::exec()
+bool executePluginInstallWizard(const FilePath &archive)
{
Wizard wizard(ICore::dialogParent());
wizard.setWindowTitle(Tr::tr("Install Plugin"));
Data data;
- auto filePage = new SourcePage(&data, &wizard);
- wizard.addPage(filePage);
+ if (archive.isEmpty()) {
+ auto filePage = new SourcePage(&data, &wizard);
+ wizard.addPage(filePage);
+ } else {
+ data.sourcePath = archive;
+ }
auto checkArchivePage = new CheckArchivePage(&data, &wizard);
wizard.addPage(checkArchivePage);
@@ -439,5 +441,4 @@ bool PluginInstallWizard::exec()
return false;
}
-} // namespace Internal
} // namespace Core
diff --git a/src/plugins/coreplugin/plugininstallwizard.h b/src/plugins/coreplugin/plugininstallwizard.h
index 6f9b80af8a..c5b1fb5d65 100644
--- a/src/plugins/coreplugin/plugininstallwizard.h
+++ b/src/plugins/coreplugin/plugininstallwizard.h
@@ -3,16 +3,14 @@
#pragma once
+#include "core_global.h"
+
+#include <utils/filepath.h>
+
#include <QCoreApplication>
namespace Core {
-namespace Internal {
-class PluginInstallWizard
-{
-public:
- static bool exec();
-};
+CORE_EXPORT bool executePluginInstallWizard(const Utils::FilePath &archive = {});
-} // namespace Internal
} // namespace Core
diff --git a/src/plugins/coreplugin/progressmanager/futureprogress.cpp b/src/plugins/coreplugin/progressmanager/futureprogress.cpp
index 62459bcfea..c798575af2 100644
--- a/src/plugins/coreplugin/progressmanager/futureprogress.cpp
+++ b/src/plugins/coreplugin/progressmanager/futureprogress.cpp
@@ -308,7 +308,7 @@ void FutureProgress::paintEvent(QPaintEvent *)
QPainter p(this);
if (creatorTheme()->flag(Theme::FlatToolBars)) {
p.fillRect(rect(), StyleHelper::baseColor());
- p.fillRect(rect(), creatorTheme()->color(Theme::FancyToolButtonSelectedColor));
+ p.fillRect(rect(), creatorColor(Theme::FancyToolButtonSelectedColor));
} else {
QLinearGradient grad = StyleHelper::statusBarGradient(rect());
p.fillRect(rect(), grad);
diff --git a/src/plugins/coreplugin/progressmanager/progressbar.cpp b/src/plugins/coreplugin/progressmanager/progressbar.cpp
index e63a21efdd..5047fa6600 100644
--- a/src/plugins/coreplugin/progressmanager/progressbar.cpp
+++ b/src/plugins/coreplugin/progressmanager/progressbar.cpp
@@ -245,7 +245,7 @@ void ProgressBar::paintEvent(QPaintEvent *)
textRect.setHeight(fm.height() + 4);
p.setFont(fnt);
- p.setPen(creatorTheme()->color(Theme::ProgressBarTitleColor));
+ p.setPen(creatorColor(Theme::ProgressBarTitleColor));
p.drawText(textRect, alignment | Qt::AlignBottom, elidedtitle);
if (!m_subtitle.isEmpty()) {
@@ -255,7 +255,7 @@ void ProgressBar::paintEvent(QPaintEvent *)
subtextRect.moveTop(progressY + progressHeight);
p.setFont(fnt);
- p.setPen(creatorTheme()->color(Theme::ProgressBarTitleColor));
+ p.setPen(creatorColor(Theme::ProgressBarTitleColor));
p.drawText(subtextRect, alignment | Qt::AlignBottom, elidedsubtitle);
}
}
@@ -274,12 +274,11 @@ void ProgressBar::paintEvent(QPaintEvent *)
themeColor = Theme::ProgressBarColorError;
else if (m_finished)
themeColor = Theme::ProgressBarColorFinished;
- const QColor c = creatorTheme()->color(themeColor);
+ const QColor c = creatorColor(themeColor);
//draw the progress bar
if (creatorTheme()->flag(Theme::FlatToolBars)) {
- p.fillRect(rect.adjusted(2, 2, -2, -2),
- creatorTheme()->color(Theme::ProgressBarBackgroundColor));
+ p.fillRect(rect.adjusted(2, 2, -2, -2), creatorColor(Theme::ProgressBarBackgroundColor));
p.fillRect(inner, c);
} else {
const static QImage bar(StyleHelper::dpiSpecificImageFile(
diff --git a/src/plugins/coreplugin/progressmanager/progressmanager.cpp b/src/plugins/coreplugin/progressmanager/progressmanager.cpp
index 5aee0ad266..a3e17b0036 100644
--- a/src/plugins/coreplugin/progressmanager/progressmanager.cpp
+++ b/src/plugins/coreplugin/progressmanager/progressmanager.cpp
@@ -768,7 +768,7 @@ FutureProgress *ProgressManager::addTask(const QFuture<void> &future, const QStr
Shows a progress indicator for task given by the QFutureInterface object
\a futureInterface.
The progress indicator shows the specified \a title along with the progress bar.
- The progress indicator will increase monotonically with time, at \a expectedSeconds
+ The progress indicator will increase monotonically with time, at \a expectedDuration
it will reach about 80%, and continue to increase with a decreasingly slower rate.
The \a type of a task will specify a logical grouping with other
diff --git a/src/plugins/coreplugin/welcomepagehelper.cpp b/src/plugins/coreplugin/welcomepagehelper.cpp
index c2b28c1f83..3fc432f7d0 100644
--- a/src/plugins/coreplugin/welcomepagehelper.cpp
+++ b/src/plugins/coreplugin/welcomepagehelper.cpp
@@ -41,7 +41,7 @@ using namespace StyleHelper::SpacingTokens;
static QColor themeColor(Theme::Color role)
{
- return creatorTheme()->color(role);
+ return creatorColor(role);
}
namespace WelcomePageHelpers {
@@ -51,7 +51,7 @@ void setBackgroundColor(QWidget *widget, Theme::Color colorRole)
QPalette palette = creatorTheme()->palette();
const QPalette::ColorRole role = QPalette::Window;
palette.setBrush(role, {});
- palette.setColor(role, creatorTheme()->color(colorRole));
+ palette.setColor(role, creatorColor(colorRole));
widget->setPalette(palette);
widget->setBackgroundRole(role);
widget->setAutoFillBackground(true);
@@ -195,23 +195,23 @@ void Button::paintEvent(QPaintEvent *event)
switch (m_role) {
case MediumPrimary:
case SmallPrimary: {
- const QBrush fill(creatorTheme()->color(isDown()
- ? Theme::Token_Accent_Subtle
- : hovered ? Theme::Token_Accent_Muted
- : Theme::Token_Accent_Default));
+ const QBrush fill(creatorColor(isDown()
+ ? Theme::Token_Accent_Subtle
+ : hovered ? Theme::Token_Accent_Muted
+ : Theme::Token_Accent_Default));
drawCardBackground(&p, bgR, fill, QPen(Qt::NoPen), brRectRounding);
break;
}
case MediumSecondary:
case SmallSecondary: {
- const QPen outline(creatorTheme()->color(Theme::Token_Text_Default), hovered ? 2 : 1);
+ const QPen outline(creatorColor(Theme::Token_Text_Default), hovered ? 2 : 1);
drawCardBackground(&p, bgR, QBrush(Qt::NoBrush), outline, brRectRounding);
break;
}
case SmallList: {
if (isChecked() || hovered) {
- const QBrush fill(creatorTheme()->color(isChecked() ? Theme::Token_Foreground_Muted
- : Theme::Token_Foreground_Subtle));
+ const QBrush fill(creatorColor(isChecked() ? Theme::Token_Foreground_Muted
+ : Theme::Token_Foreground_Subtle));
drawCardBackground(&p, bgR, fill, QPen(Qt::NoPen), brRectRounding);
}
break;
@@ -329,11 +329,11 @@ void SearchBox::leaveEvent(QEvent *event)
static void paintCommonBackground(QPainter *p, const QRectF &rect, const QWidget *widget)
{
- const QBrush fill(creatorTheme()->color(Theme::Token_Background_Muted));
+ const QBrush fill(creatorColor(Theme::Token_Background_Muted));
const Theme::Color c = widget->hasFocus() ? Theme::Token_Stroke_Strong :
widget->underMouse() ? Theme::Token_Stroke_Muted
: Theme::Token_Stroke_Subtle;
- const QPen pen(creatorTheme()->color(c));
+ const QPen pen(creatorColor(c));
drawCardBackground(p, rect, fill, pen);
}
@@ -1203,7 +1203,7 @@ ListModel *SectionedGridView::addSection(const Section &section, const QList<Lis
st,
seeAllLink,
Space(ExVPaddingGapXl),
- customMargin({0, ExPaddingGapL, 0, VPaddingL}),
+ customMargins(0, ExPaddingGapL, 0, VPaddingL),
}.emerge();
m_sectionLabels.append(sectionLabel);
auto scrollArea = qobject_cast<QScrollArea *>(widget(0));
@@ -1274,7 +1274,7 @@ void SectionedGridView::zoomInSection(const Section &section)
st,
backLink,
Space(ExVPaddingGapXl),
- customMargin({0, ExPaddingGapL, 0, VPaddingL}),
+ customMargins(0, ExPaddingGapL, 0, VPaddingL),
}.emerge();
auto gridView = new GridView(zoomedInWidget);
diff --git a/src/plugins/coreplugin/welcomepagehelper.h b/src/plugins/coreplugin/welcomepagehelper.h
index c32e803029..eb3082129a 100644
--- a/src/plugins/coreplugin/welcomepagehelper.h
+++ b/src/plugins/coreplugin/welcomepagehelper.h
@@ -31,11 +31,12 @@ namespace WelcomePageHelpers {
constexpr QSize WelcomeThumbnailSize(214, 160);
-class CORE_EXPORT TextFormat {
+class CORE_EXPORT TextFormat
+{
public:
QColor color() const
{
- return Utils::creatorTheme()->color(themeColor);
+ return Utils::creatorColor(themeColor);
}
QFont font(bool underlined = false) const
diff --git a/src/plugins/cpaster/CodePaster.json.in b/src/plugins/cpaster/CodePaster.json.in
index af6025becb..ee852df216 100644
--- a/src/plugins/cpaster/CodePaster.json.in
+++ b/src/plugins/cpaster/CodePaster.json.in
@@ -12,7 +12,10 @@
"",
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
- "Description" : "Codepaster plugin for pushing/fetching diff from server.",
- "Url" : "http://www.qt.io",
+ "Description" : "Access code-pasting services",
+ "LongDescription" : [
+ "Paste snippets of text, such as code or diffs, to a server or fetch them from the server."
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/cppcheck/Cppcheck.json.in b/src/plugins/cppcheck/Cppcheck.json.in
index f2206e0fca..dd9ba185d1 100644
--- a/src/plugins/cppcheck/Cppcheck.json.in
+++ b/src/plugins/cppcheck/Cppcheck.json.in
@@ -14,7 +14,11 @@
"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."
],
"Category" : "Code Analyzer",
- "Description" : "Cppcheck static analyzer tool integration. See http://cppcheck.sourceforge.net.",
- "Url" : "http://www.qt.io",
+ "Description" : "Find errors in C++ code with static analysis",
+ "LongDescription" : [
+ "You also need:",
+ "- cppcheck"
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/cppcheck/cppcheckrunner.cpp b/src/plugins/cppcheck/cppcheckrunner.cpp
index cf8633c8dd..bcf663e46e 100644
--- a/src/plugins/cppcheck/cppcheckrunner.cpp
+++ b/src/plugins/cppcheck/cppcheckrunner.cpp
@@ -113,16 +113,17 @@ void CppcheckRunner::checkQueued()
if (m_queue.isEmpty() || !m_binary.isExecutableFile())
return;
+ CommandLine commandLine{m_binary, m_arguments, CommandLine::Raw};
FilePaths files = m_queue.begin().value();
- QString arguments = m_arguments + ' ' + m_queue.begin().key();
+ commandLine.addArgs(m_queue.begin().key(), CommandLine::Raw);
m_currentFiles.clear();
- int argumentsLength = arguments.length();
+ int argumentsLength = commandLine.arguments().length();
while (!files.isEmpty()) {
- argumentsLength += files.first().toString().size() + 1; // +1 for separator
+ argumentsLength += files.first().toString().size() + 3; // +1 for separator +2 for quotes
if (argumentsLength >= m_maxArgumentsLength)
break;
m_currentFiles.push_back(files.first());
- arguments += ' ' + files.first().toString();
+ commandLine.addArg(files.first().toString());
files.pop_front();
}
@@ -131,7 +132,7 @@ void CppcheckRunner::checkQueued()
else
m_queue.begin().value() = files;
- m_process.setCommand(CommandLine(m_binary, arguments, CommandLine::Raw));
+ m_process.setCommand(commandLine);
m_process.start();
}
@@ -140,7 +141,7 @@ void CppcheckRunner::handleDone()
if (m_process.result() == ProcessResult::FinishedWithSuccess)
m_tool.finishParsing();
else
- Core::MessageManager::writeSilently(m_process.exitMessage());
+ m_tool.finishWithFail(m_process.exitMessage());
m_currentFiles.clear();
m_process.close();
diff --git a/src/plugins/cppcheck/cppchecksettings.cpp b/src/plugins/cppcheck/cppchecksettings.cpp
index 142d3ac236..144ed112f3 100644
--- a/src/plugins/cppcheck/cppchecksettings.cpp
+++ b/src/plugins/cppcheck/cppchecksettings.cpp
@@ -111,7 +111,7 @@ CppcheckSettings::CppcheckSettings()
readSettings();
}
-std::function<Layouting::LayoutItem()> CppcheckSettings::layouter()
+std::function<Layouting::Layout()> CppcheckSettings::layouter()
{
return [this] {
using namespace Layouting;
diff --git a/src/plugins/cppcheck/cppchecksettings.h b/src/plugins/cppcheck/cppchecksettings.h
index 8842b2b65f..c5b4a78a67 100644
--- a/src/plugins/cppcheck/cppchecksettings.h
+++ b/src/plugins/cppcheck/cppchecksettings.h
@@ -14,7 +14,7 @@ class CppcheckSettings final : public Utils::AspectContainer
public:
CppcheckSettings();
- std::function<Layouting::LayoutItem()> layouter();
+ std::function<Layouting::Layout()> layouter();
Utils::FilePathAspect binary{this};
Utils::BoolAspect warning{this};
diff --git a/src/plugins/cppcheck/cppchecktool.cpp b/src/plugins/cppcheck/cppchecktool.cpp
index 5bfcf941a3..85a17d92df 100644
--- a/src/plugins/cppcheck/cppchecktool.cpp
+++ b/src/plugins/cppcheck/cppchecktool.cpp
@@ -101,7 +101,7 @@ void CppcheckTool::updateArguments()
arguments.push_back("--template=\"{file},{line},{severity},{id},{message}\"");
- m_runner->reconfigure(s.binary(), arguments.join(' '));
+ m_runner->reconfigure(s.binary.effectiveBinary(), arguments.join(' '));
}
QStringList CppcheckTool::additionalArguments(const CppEditor::ProjectPart &part) const
@@ -304,4 +304,12 @@ void CppcheckTool::finishParsing()
m_progress->reportFinished();
}
+void CppcheckTool::finishWithFail(const QString &exitMessage)
+{
+ if (!exitMessage.isEmpty())
+ Core::MessageManager::writeSilently(exitMessage);
+ QTC_ASSERT(m_progress, return);
+ m_progress->cancelAndFinish();
+}
+
} // Cppcheck::Internal
diff --git a/src/plugins/cppcheck/cppchecktool.h b/src/plugins/cppcheck/cppchecktool.h
index d14485fd5e..e8e61efaef 100644
--- a/src/plugins/cppcheck/cppchecktool.h
+++ b/src/plugins/cppcheck/cppchecktool.h
@@ -42,6 +42,7 @@ public:
void parseOutputLine(const QString &line);
void parseErrorLine(const QString &line);
void finishParsing();
+ void finishWithFail(const QString &exitMessage);
private:
void updateArguments();
diff --git a/src/plugins/cppeditor/CMakeLists.txt b/src/plugins/cppeditor/CMakeLists.txt
index 3a137a4d64..aef17fe4e3 100644
--- a/src/plugins/cppeditor/CMakeLists.txt
+++ b/src/plugins/cppeditor/CMakeLists.txt
@@ -53,7 +53,6 @@ add_qtc_plugin(CppEditor
cppincludehierarchy.cpp cppincludehierarchy.h
cppincludesfilter.cpp cppincludesfilter.h
cppindexingsupport.cpp cppindexingsupport.h
- cppinsertvirtualmethods.cpp cppinsertvirtualmethods.h
cpplocalrenaming.cpp cpplocalrenaming.h
cpplocalsymbols.cpp cpplocalsymbols.h
cpplocatordata.cpp cpplocatordata.h
@@ -71,14 +70,6 @@ add_qtc_plugin(CppEditor
cppprojectpartchooser.cpp cppprojectpartchooser.h
cppprojectupdater.cpp cppprojectupdater.h
cppqtstyleindenter.cpp cppqtstyleindenter.h
- cppquickfix.cpp cppquickfix.h
- cppquickfixassistant.cpp cppquickfixassistant.h
- cppquickfixes.cpp cppquickfixes.h
- cppquickfixprojectsettings.cpp cppquickfixprojectsettings.h
- cppquickfixprojectsettingswidget.cpp cppquickfixprojectsettingswidget.h
- cppquickfixsettings.cpp cppquickfixsettings.h
- cppquickfixsettingspage.cpp cppquickfixsettingspage.h
- cppquickfixsettingswidget.cpp cppquickfixsettingswidget.h
cpprefactoringchanges.cpp cpprefactoringchanges.h
cppselectionchanger.cpp cppselectionchanger.h
cppsemanticinfo.h
@@ -104,6 +95,39 @@ add_qtc_plugin(CppEditor
insertionpointlocator.cpp insertionpointlocator.h
projectinfo.cpp projectinfo.h
projectpart.cpp projectpart.h
+ quickfixes/assigntolocalvariable.cpp quickfixes/assigntolocalvariable.h
+ quickfixes/bringidentifierintoscope.cpp quickfixes/bringidentifierintoscope.h
+ quickfixes/completeswitchstatement.cpp quickfixes/completeswitchstatement.h
+ quickfixes/convertnumericliteral.cpp quickfixes/convertnumericliteral.h
+ quickfixes/convertqt4connect.cpp quickfixes/convertqt4connect.h
+ quickfixes/convertstringliteral.cpp quickfixes/convertstringliteral.h
+ quickfixes/converttocamelcase.cpp quickfixes/converttocamelcase.h
+ quickfixes/converttometamethodcall.cpp quickfixes/converttometamethodcall.h
+ quickfixes/cppcodegenerationquickfixes.cpp quickfixes/cppcodegenerationquickfixes.h
+ quickfixes/cppinsertvirtualmethods.cpp quickfixes/cppinsertvirtualmethods.h
+ quickfixes/cppquickfix.cpp quickfixes/cppquickfix.h
+ quickfixes/cppquickfixassistant.cpp quickfixes/cppquickfixassistant.h
+ quickfixes/cppquickfixhelpers.h quickfixes/cppquickfixhelpers.cpp
+ quickfixes/cppquickfixprojectsettings.cpp quickfixes/cppquickfixprojectsettings.h
+ quickfixes/cppquickfixprojectsettingswidget.cpp quickfixes/cppquickfixprojectsettingswidget.h
+ quickfixes/cppquickfixsettings.cpp quickfixes/cppquickfixsettings.h
+ quickfixes/cppquickfixsettingspage.cpp quickfixes/cppquickfixsettingspage.h
+ quickfixes/cppquickfixsettingswidget.cpp quickfixes/cppquickfixsettingswidget.h
+ quickfixes/convertfromandtopointer.cpp quickfixes/convertfromandtopointer.h
+ quickfixes/createdeclarationfromuse.cpp quickfixes/createdeclarationfromuse.h
+ quickfixes/extractfunction.cpp quickfixes/extractfunction.h
+ quickfixes/extractliteralasparameter.cpp quickfixes/extractliteralasparameter.h
+ quickfixes/insertfunctiondefinition.cpp quickfixes/insertfunctiondefinition.h
+ quickfixes/logicaloperationquickfixes.cpp quickfixes/logicaloperationquickfixes.h
+ quickfixes/moveclasstoownfile.cpp quickfixes/moveclasstoownfile.h
+ quickfixes/movefunctiondefinition.cpp quickfixes/movefunctiondefinition.h
+ quickfixes/rearrangeparamdeclarationlist.cpp quickfixes/rearrangeparamdeclarationlist.h
+ quickfixes/reformatpointerdeclaration.cpp quickfixes/reformatpointerdeclaration.h
+ quickfixes/removeusingnamespace.cpp quickfixes/removeusingnamespace.h
+ quickfixes/rewritecomment.cpp quickfixes/rewritecomment.cpp
+ quickfixes/rewritecontrolstatements.cpp quickfixes/rewritecontrolstatements.h
+ quickfixes/splitsimpledeclaration.cpp quickfixes/splitsimpledeclaration.h
+ quickfixes/synchronizememberfunctionorder.cpp quickfixes/synchronizememberfunctionorder.h
resourcepreviewhoverhandler.cpp resourcepreviewhoverhandler.h
searchsymbols.cpp searchsymbols.h
semantichighlighter.cpp semantichighlighter.h
@@ -112,7 +136,7 @@ add_qtc_plugin(CppEditor
typehierarchybuilder.cpp typehierarchybuilder.h
wrappablelineedit.cpp wrappablelineedit.h
EXPLICIT_MOC
- cppquickfixsettingswidget.h
+ quickfixes/cppquickfixsettingswidget.h
)
extend_qtc_plugin(CppEditor
@@ -127,7 +151,6 @@ extend_qtc_plugin(CppEditor
cpplocatorfilter_test.cpp cpplocatorfilter_test.h
cppmodelmanager_test.cpp cppmodelmanager_test.h
cpppointerdeclarationformatter_test.cpp cpppointerdeclarationformatter_test.h
- cppquickfix_test.cpp cppquickfix_test.h
cpprenaming_test.cpp cpprenaming_test.h
cppsourceprocessertesthelper.cpp cppsourceprocessertesthelper.h
cppsourceprocessor_test.cpp cppsourceprocessor_test.h
@@ -137,9 +160,10 @@ extend_qtc_plugin(CppEditor
followsymbol_switchmethoddecldef_test.cpp followsymbol_switchmethoddecldef_test.h
modelmanagertesthelper.cpp modelmanagertesthelper.h
projectinfo_test.cpp projectinfo_test.h
+ quickfixes/cppquickfix_test.cpp quickfixes/cppquickfix_test.h
symbolsearcher_test.cpp symbolsearcher_test.h
typehierarchybuilder_test.cpp typehierarchybuilder_test.h
EXPLICIT_MOC
cppdoxygen_test.h
- cppquickfix_test.h
+ quickfixes/cppquickfix_test.h
)
diff --git a/src/plugins/cppeditor/CppEditor.json.in b/src/plugins/cppeditor/CppEditor.json.in
index 0e9d50eb4b..169df1ed65 100644
--- a/src/plugins/cppeditor/CppEditor.json.in
+++ b/src/plugins/cppeditor/CppEditor.json.in
@@ -13,8 +13,9 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "C++",
- "Description" : "C/C++ editor component.",
- "Url" : "http://www.qt.io",
+ "Description" : "Edit C++ source and header files",
+ "LongDescription" : [],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES},
"Mimetypes" : [
diff --git a/src/plugins/cppeditor/clangdsettings.cpp b/src/plugins/cppeditor/clangdsettings.cpp
index fe3bc84b23..daa78aad7e 100644
--- a/src/plugins/cppeditor/clangdsettings.cpp
+++ b/src/plugins/cppeditor/clangdsettings.cpp
@@ -770,7 +770,7 @@ ClangdSettingsWidget::ClangdSettingsWidget(const ClangdSettings::Data &settingsD
if (!isForProject) {
m_sessionsModel.setStringList(settingsData.sessionsWithOneClangd);
m_sessionsModel.sort(0);
- m_sessionsGroupBox = new QGroupBox(Tr::tr("Sessions with a single clangd instance"));
+ m_sessionsGroupBox = new QGroupBox(Tr::tr("Sessions with a Single Clangd Instance"));
const auto sessionsView = new Utils::ListView;
sessionsView->setModel(&m_sessionsModel);
sessionsView->setToolTip(
diff --git a/src/plugins/cppeditor/compileroptionsbuilder.cpp b/src/plugins/cppeditor/compileroptionsbuilder.cpp
index a860edbee3..8bd1219a5d 100644
--- a/src/plugins/cppeditor/compileroptionsbuilder.cpp
+++ b/src/plugins/cppeditor/compileroptionsbuilder.cpp
@@ -899,8 +899,8 @@ void CompilerOptionsBuilder::evaluateCompilerFlags()
continue;
}
- // GCC option that clang doesn't know.
- if (option.contains("direct-extern-access"))
+ // GCC options that clang doesn't know.
+ if (option.contains("direct-extern-access") || option == "-fnothrow-opt")
continue;
// These were already parsed into ProjectPart::includedFiles.
diff --git a/src/plugins/cppeditor/cppbuiltinmodelmanagersupport.cpp b/src/plugins/cppeditor/cppbuiltinmodelmanagersupport.cpp
index 0133292703..3a82a41132 100644
--- a/src/plugins/cppeditor/cppbuiltinmodelmanagersupport.cpp
+++ b/src/plugins/cppeditor/cppbuiltinmodelmanagersupport.cpp
@@ -111,7 +111,8 @@ void BuiltinModelManagerSupport::followSymbol(const CursorInEditor &data,
SymbolFinder finder;
m_followSymbol->findLink(data, processLinkCallback,
resolveTarget, CppModelManager::snapshot(),
- data.editorWidget()->semanticInfo().doc, &finder, inNextSplit);
+ data.editorWidget() ? data.editorWidget()->semanticInfo().doc : data.cppDocument(),
+ &finder, inNextSplit);
}
void BuiltinModelManagerSupport::followSymbolToType(const CursorInEditor &data,
diff --git a/src/plugins/cppeditor/cppcodegen_test.cpp b/src/plugins/cppeditor/cppcodegen_test.cpp
index 468e703930..b48ffa77ba 100644
--- a/src/plugins/cppeditor/cppcodegen_test.cpp
+++ b/src/plugins/cppeditor/cppcodegen_test.cpp
@@ -261,7 +261,7 @@ void CodegenTest::testProtectedBetweenPublicAndPrivate()
Should insert at line 18, column 1, with "private slots:\n" as prefix and "\n"
as suffix.
- This is the typical Qt Designer case, with test-input like what the integration
+ This is the typical \QD case, with test-input like what the integration
generates.
*/
void CodegenTest::testQtdesignerIntegration()
diff --git a/src/plugins/cppeditor/cppcodemodelinspectordialog.cpp b/src/plugins/cppeditor/cppcodemodelinspectordialog.cpp
index 542edbd57a..56c62dfca0 100644
--- a/src/plugins/cppeditor/cppcodemodelinspectordialog.cpp
+++ b/src/plugins/cppeditor/cppcodemodelinspectordialog.cpp
@@ -1445,11 +1445,11 @@ CppCodeModelInspectorDialog::CppCodeModelInspectorDialog(QWidget *parent)
Tab("&Defines",
Column {
Group {
- title("Toolchain Defines"),
+ title(QString("Toolchain Defines")),
Column { m_partToolchainDefinesEdit },
},
Group {
- title("Project Defines"),
+ title(QString("Project Defines")),
Column { m_partProjectDefinesEdit },
}
}
diff --git a/src/plugins/cppeditor/cppcodemodelsettings.cpp b/src/plugins/cppeditor/cppcodemodelsettings.cpp
index 53c009d4c3..5dde29a49c 100644
--- a/src/plugins/cppeditor/cppcodemodelsettings.cpp
+++ b/src/plugins/cppeditor/cppcodemodelsettings.cpp
@@ -83,6 +83,7 @@ bool operator==(const CppEditor::CppCodeModelSettings &s1,
&& s1.useBuiltinPreprocessor == s2.useBuiltinPreprocessor
&& s1.indexerFileSizeLimitInMb == s2.indexerFileSizeLimitInMb
&& s1.m_categorizeFindReferences == s2.m_categorizeFindReferences
+ && s1.interactiveFollowSymbol == s2.interactiveFollowSymbol
&& s1.ignoreFiles == s2.ignoreFiles && s1.ignorePattern == s2.ignorePattern;
}
@@ -204,6 +205,16 @@ void CppCodeModelSettings::setCategorizeFindReferences(bool categorize)
globalInstance().m_categorizeFindReferences = categorize;
}
+bool CppCodeModelSettings::isInteractiveFollowSymbol()
+{
+ return globalInstance().interactiveFollowSymbol;
+}
+
+void CppCodeModelSettings::setInteractiveFollowSymbol(bool interactive)
+{
+ globalInstance().interactiveFollowSymbol = interactive;
+}
+
CppCodeModelProjectSettings::CppCodeModelProjectSettings(ProjectExplorer::Project *project)
: m_project(project)
{
diff --git a/src/plugins/cppeditor/cppcodemodelsettings.h b/src/plugins/cppeditor/cppcodemodelsettings.h
index 9bf2b6862d..9f2f3d1718 100644
--- a/src/plugins/cppeditor/cppcodemodelsettings.h
+++ b/src/plugins/cppeditor/cppcodemodelsettings.h
@@ -55,6 +55,9 @@ public:
static bool categorizeFindReferences();
static void setCategorizeFindReferences(bool categorize);
+ static bool isInteractiveFollowSymbol();
+ static void setInteractiveFollowSymbol(bool interactive);
+
QString ignorePattern;
PCHUsage pchUsage = PchUse_BuildSystem;
int indexerFileSizeLimitInMb = 5;
@@ -63,7 +66,10 @@ public:
bool useBuiltinPreprocessor = true;
bool ignoreFiles = false;
bool enableIndexing = true;
- bool m_categorizeFindReferences = false; // Ephemeral!
+
+ // Ephemeral!
+ bool m_categorizeFindReferences = false;
+ bool interactiveFollowSymbol = true;
private:
CppCodeModelSettings(Utils::QtcSettings *s) { fromSettings(s); }
@@ -76,6 +82,14 @@ private:
namespace Internal {
void setupCppCodeModelSettingsPage();
void setupCppCodeModelProjectSettingsPanel();
+
+class NonInteractiveFollowSymbolMarker
+{
+public:
+ NonInteractiveFollowSymbolMarker() { CppCodeModelSettings::setInteractiveFollowSymbol(false); }
+ ~NonInteractiveFollowSymbolMarker() { CppCodeModelSettings::setInteractiveFollowSymbol(true); }
+};
+
} // namespace Internal
} // namespace CppEditor
diff --git a/src/plugins/cppeditor/cppcodestylesettingspage.cpp b/src/plugins/cppeditor/cppcodestylesettingspage.cpp
index 04acf12880..2364dee814 100644
--- a/src/plugins/cppeditor/cppcodestylesettingspage.cpp
+++ b/src/plugins/cppeditor/cppcodestylesettingspage.cpp
@@ -244,12 +244,14 @@ public:
sizePolicy.setVerticalPolicy(QSizePolicy::Preferred);
m_statementMacros->setToolTip(
- Tr::tr("Macros that can be used as statements without a trailing semicolon"));
+ Tr::tr("Macros that can be used as statements without a trailing semicolon."));
m_statementMacros->setSizePolicy(sizePolicy);
+ // clang-format off
const Group statementMacrosGroup {
- title(Tr::tr("Statement macros")),
+ title(Tr::tr("Statement Macros")),
Column { m_statementMacros}
};
+ // clang-format on
QObject::connect(m_statementMacros, &QPlainTextEdit::textChanged, q, [this] {
m_handlingStatementMacroChange = true;
q->slotCodeStyleSettingsChanged();
diff --git a/src/plugins/cppeditor/cppeditor.qbs b/src/plugins/cppeditor/cppeditor.qbs
index c535488de9..c563c76097 100644
--- a/src/plugins/cppeditor/cppeditor.qbs
+++ b/src/plugins/cppeditor/cppeditor.qbs
@@ -119,8 +119,6 @@ QtcPlugin {
"cppincludesfilter.h",
"cppindexingsupport.cpp",
"cppindexingsupport.h",
- "cppinsertvirtualmethods.cpp",
- "cppinsertvirtualmethods.h",
"cpplocalrenaming.cpp",
"cpplocalrenaming.h",
"cpplocalsymbols.cpp",
@@ -153,22 +151,6 @@ QtcPlugin {
"cppprojectinfogenerator.h",
"cppprojectupdater.cpp",
"cppprojectupdater.h",
- "cppquickfix.cpp",
- "cppquickfix.h",
- "cppquickfixassistant.cpp",
- "cppquickfixassistant.h",
- "cppquickfixes.cpp",
- "cppquickfixes.h",
- "cppquickfixprojectsettings.cpp",
- "cppquickfixprojectsettings.h",
- "cppquickfixprojectsettingswidget.cpp",
- "cppquickfixprojectsettingswidget.h",
- "cppquickfixsettings.cpp",
- "cppquickfixsettings.h",
- "cppquickfixsettingspage.cpp",
- "cppquickfixsettingspage.h",
- "cppquickfixsettingswidget.cpp",
- "cppquickfixsettingswidget.h",
"cppqtstyleindenter.cpp",
"cppqtstyleindenter.h",
"cpprefactoringchanges.cpp",
@@ -234,6 +216,79 @@ QtcPlugin {
]
Group {
+ name: "Quickfixes"
+ prefix: "quickfixes/"
+ files: [
+ "assigntolocalvariable.cpp",
+ "assigntolocalvariable.h",
+ "bringidentifierintoscope.cpp",
+ "bringidentifierintoscope.h",
+ "completeswitchstatement.cpp",
+ "completeswitchstatement.h",
+ "convertfromandtopointer.cpp",
+ "convertfromandtopointer.h",
+ "convertnumericliteral.cpp",
+ "convertnumericliteral.h",
+ "convertqt4connect.cpp",
+ "convertqt4connect.h",
+ "convertstringliteral.cpp",
+ "convertstringliteral.h",
+ "converttocamelcase.cpp",
+ "converttocamelcase.h",
+ "converttometamethodcall.cpp",
+ "converttometamethodcall.h",
+ "cppcodegenerationquickfixes.cpp",
+ "cppcodegenerationquickfixes.h",
+ "cppinsertvirtualmethods.cpp",
+ "cppinsertvirtualmethods.h",
+ "cppquickfix.cpp",
+ "cppquickfix.h",
+ "cppquickfixassistant.cpp",
+ "cppquickfixassistant.h",
+ "cppquickfixhelpers.cpp",
+ "cppquickfixhelpers.h",
+ "cppquickfixprojectsettings.cpp",
+ "cppquickfixprojectsettings.h",
+ "cppquickfixprojectsettingswidget.cpp",
+ "cppquickfixprojectsettingswidget.h",
+ "cppquickfixsettings.cpp",
+ "cppquickfixsettings.h",
+ "cppquickfixsettingspage.cpp",
+ "cppquickfixsettingspage.h",
+ "cppquickfixsettingswidget.cpp",
+ "cppquickfixsettingswidget.h",
+ "createdeclarationfromuse.cpp",
+ "createdeclarationfromuse.h",
+ "extractfunction.cpp",
+ "extractfunction.h",
+ "extractliteralasparameter.cpp",
+ "extractliteralasparameter.h",
+ "insertfunctiondefinition.cpp",
+ "insertfunctiondefinition.h",
+ "logicaloperationquickfixes.cpp",
+ "logicaloperationquickfixes.h",
+ "moveclasstoownfile.cpp",
+ "moveclasstoownfile.h",
+ "movefunctiondefinition.cpp",
+ "movefunctiondefinition.h",
+ "rearrangeparamdeclarationlist.cpp",
+ "rearrangeparamdeclarationlist.h",
+ "reformatpointerdeclaration.cpp",
+ "reformatpointerdeclaration.h",
+ "removeusingnamespace.cpp",
+ "removeusingnamespace.h",
+ "rewritecomment.cpp",
+ "rewritecomment.h",
+ "rewritecontrolstatements.cpp",
+ "rewritecontrolstatements.h",
+ "splitsimpledeclaration.cpp",
+ "splitsimpledeclaration.h",
+ "synchronizememberfunctionorder.cpp",
+ "synchronizememberfunctionorder.h",
+ ]
+ }
+
+ Group {
name: "TestCase"
condition: qtc.withPluginTests || qtc.withAutotests
files: [
@@ -244,6 +299,16 @@ QtcPlugin {
QtcTestFiles {
cpp.defines: outer.concat(['SRCDIR="' + FileInfo.path(filePath) + '"'])
+
+ Group {
+ name: "Quickfix tests"
+ prefix: "quickfixes/"
+ files: [
+ "cppquickfix_test.cpp",
+ "cppquickfix_test.h",
+ ]
+ }
+
files: [
"compileroptionsbuilder_test.cpp",
"compileroptionsbuilder_test.h",
@@ -263,8 +328,6 @@ QtcPlugin {
"cppmodelmanager_test.h",
"cpppointerdeclarationformatter_test.cpp",
"cpppointerdeclarationformatter_test.h",
- "cppquickfix_test.cpp",
- "cppquickfix_test.h",
"cpprenaming_test.cpp",
"cpprenaming_test.h",
"cppsourceprocessor_test.cpp",
diff --git a/src/plugins/cppeditor/cppeditor.qrc b/src/plugins/cppeditor/cppeditor.qrc
index c28111d21e..3a5ace117d 100644
--- a/src/plugins/cppeditor/cppeditor.qrc
+++ b/src/plugins/cppeditor/cppeditor.qrc
@@ -4,5 +4,58 @@
<file>images/dark_qt_h.png</file>
<file>images/dark_qt_c.png</file>
<file>testcases/highlightingtestcase.cpp</file>
+ <file>testcases/move-class/complex/complex.pro</file>
+ <file>testcases/move-class/complex/main.cpp</file>
+ <file>testcases/move-class/complex/main.cpp_expected</file>
+ <file>testcases/move-class/complex/theclass.cpp_expected</file>
+ <file>testcases/move-class/complex/theheader.h</file>
+ <file>testcases/move-class/complex/theheader.h_expected</file>
+ <file>testcases/move-class/complex/thesource.cpp</file>
+ <file>testcases/move-class/complex/thesource.cpp_expected</file>
+ <file>testcases/move-class/complex/complex.pro_expected</file>
+ <file>testcases/move-class/nested/main.cpp</file>
+ <file>testcases/move-class/nested/nested.pro</file>
+ <file>testcases/move-class/match1/match1.pro</file>
+ <file>testcases/move-class/match1/TheClass.h</file>
+ <file>testcases/move-class/match2/match2.pro</file>
+ <file>testcases/move-class/match2/theclass.h</file>
+ <file>testcases/move-class/match3/match3.pro</file>
+ <file>testcases/move-class/match3/the_class.h</file>
+ <file>testcases/move-class/single/single.pro</file>
+ <file>testcases/move-class/single/theheader.h</file>
+ <file>testcases/move-class/header-only/header-only.pro</file>
+ <file>testcases/move-class/header-only/header-only.pro_expected</file>
+ <file>testcases/move-class/header-only/theclass.h_expected</file>
+ <file>testcases/move-class/header-only/theheader.h</file>
+ <file>testcases/move-class/header-only/thesource.cpp</file>
+ <file>testcases/move-class/header-only/theheader.h_expected</file>
+ <file>testcases/move-class/header-only/thesource.cpp_expected</file>
+ <file>testcases/move-class/decl-in-source/decl-in-source.pro</file>
+ <file>testcases/move-class/decl-in-source/decl-in-source.pro_expected</file>
+ <file>testcases/move-class/decl-in-source/theclass.h_expected</file>
+ <file>testcases/move-class/decl-in-source/thesource.cpp</file>
+ <file>testcases/move-class/decl-in-source/thesource.cpp_expected</file>
+ <file>testcases/move-class/template/template.pro</file>
+ <file>testcases/move-class/template/template.pro_expected</file>
+ <file>testcases/move-class/template/theclass.h_expected</file>
+ <file>testcases/move-class/template/theheader.h</file>
+ <file>testcases/move-class/template/theheader.h_expected</file>
+ <file>testcases/move-class/complex/theclass.h_expected</file>
+ <file>testcases/reorder-member-impls/different-locations/different-locations.pro</file>
+ <file>testcases/reorder-member-impls/different-locations/header.h</file>
+ <file>testcases/reorder-member-impls/different-locations/header.h_expected</file>
+ <file>testcases/reorder-member-impls/different-locations/impl1.cpp</file>
+ <file>testcases/reorder-member-impls/different-locations/impl1.cpp_expected</file>
+ <file>testcases/reorder-member-impls/different-locations/impl2.cpp</file>
+ <file>testcases/reorder-member-impls/different-locations/impl2.cpp_expected</file>
+ <file>testcases/reorder-member-impls/already-sorted/already-sorted.pro</file>
+ <file>testcases/reorder-member-impls/already-sorted/header.h</file>
+ <file>testcases/reorder-member-impls/no-out-of-line/header.h</file>
+ <file>testcases/reorder-member-impls/no-out-of-line/no-out-of-line.pro</file>
+ <file>testcases/reorder-member-impls/already-sorted/header.h_expected</file>
+ <file>testcases/reorder-member-impls/no-out-of-line/header.h_expected</file>
+ <file>testcases/reorder-member-impls/templates/header.h</file>
+ <file>testcases/reorder-member-impls/templates/header.h_expected</file>
+ <file>testcases/reorder-member-impls/templates/templates.pro</file>
</qresource>
</RCC>
diff --git a/src/plugins/cppeditor/cppeditordocument.cpp b/src/plugins/cppeditor/cppeditordocument.cpp
index 6f2a67fede..1f5adc4baa 100644
--- a/src/plugins/cppeditor/cppeditordocument.cpp
+++ b/src/plugins/cppeditor/cppeditordocument.cpp
@@ -11,7 +11,7 @@
#include "cppeditorconstants.h"
#include "cppeditortr.h"
#include "cpphighlighter.h"
-#include "cppquickfixassistant.h"
+#include "quickfixes/cppquickfixassistant.h"
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/session.h>
diff --git a/src/plugins/cppeditor/cppeditorplugin.cpp b/src/plugins/cppeditor/cppeditorplugin.cpp
index 6718d4cbc8..63122a538b 100644
--- a/src/plugins/cppeditor/cppeditorplugin.cpp
+++ b/src/plugins/cppeditor/cppeditorplugin.cpp
@@ -16,12 +16,12 @@
#include "cppmodelmanager.h"
#include "cppoutline.h"
#include "cppprojectupdater.h"
-#include "cppquickfixes.h"
-#include "cppquickfixprojectsettingswidget.h"
-#include "cppquickfixsettingspage.h"
#include "cpptoolsreuse.h"
#include "cpptoolssettings.h"
#include "cpptypehierarchy.h"
+#include "quickfixes/cppquickfix.h"
+#include "quickfixes/cppquickfixprojectsettingswidget.h"
+#include "quickfixes/cppquickfixsettingspage.h"
#include "resourcepreviewhoverhandler.h"
#ifdef WITH_TESTS
@@ -31,12 +31,10 @@
#include "cppdoxygen_test.h"
#include "cpphighlighter.h"
#include "cppincludehierarchy_test.h"
-#include "cppinsertvirtualmethods.h"
#include "cpplocalsymbols_test.h"
#include "cpplocatorfilter_test.h"
#include "cppmodelmanager_test.h"
#include "cpppointerdeclarationformatter_test.h"
-#include "cppquickfix_test.h"
#include "cpprenaming_test.h"
#include "cppsourceprocessor_test.h"
#include "cppuseselections_test.h"
@@ -177,7 +175,7 @@ class CppEditorPlugin final : public ExtensionSystem::IPlugin
public:
~CppEditorPlugin() final
{
- destroyCppQuickFixes();
+ destroyCppQuickFixFactories();
delete d;
d = nullptr;
}
@@ -214,7 +212,7 @@ void CppEditorPlugin::initialize()
setupMenus();
registerVariables();
- createCppQuickFixes();
+ createCppQuickFixFactories();
registerTests();
SnippetProvider::registerGroup(Constants::CPP_SNIPPETS_GROUP_ID, Tr::tr("C++", "SnippetProvider"),
@@ -514,8 +512,6 @@ void CppEditorPlugin::registerTests()
addTest<Tests::FileAndTokenActionsTest>();
addTest<Tests::FollowSymbolTest>();
addTest<Tests::IncludeHierarchyTest>();
- addTest<Tests::InsertVirtualMethodsTest>();
- addTest<Tests::QuickfixTest>();
addTest<Tests::GlobalRenamingTest>();
addTest<Tests::SelectionsTest>();
#endif
diff --git a/src/plugins/cppeditor/cppeditorwidget.cpp b/src/plugins/cppeditor/cppeditorwidget.cpp
index 5882c8d164..609b35051c 100644
--- a/src/plugins/cppeditor/cppeditorwidget.cpp
+++ b/src/plugins/cppeditor/cppeditorwidget.cpp
@@ -14,11 +14,11 @@
#include "cpplocalrenaming.h"
#include "cppmodelmanager.h"
#include "cpppreprocessordialog.h"
-#include "cppquickfixassistant.h"
#include "cppselectionchanger.h"
#include "cppsemanticinfo.h"
#include "cppuseselectionsupdater.h"
#include "doxygengenerator.h"
+#include "quickfixes/cppquickfixassistant.h"
#include <coreplugin/actionmanager/actioncontainer.h>
#include <coreplugin/actionmanager/actionmanager.h>
@@ -623,6 +623,7 @@ void CppEditorWidget::renameUsages(const QString &replacement, QTextCursor curso
const CursorInEditor cursorInEditor{cursor, textDocument()->filePath(), this, textDocument()};
CppModelManager::globalRename(cursorInEditor, replacement);
};
+ NonInteractiveFollowSymbolMarker niMarker;
CppModelManager::followSymbol(CursorInEditor{cursor,
textDocument()->filePath(),
this,
diff --git a/src/plugins/cppeditor/cppfunctiondecldeflink.cpp b/src/plugins/cppeditor/cppfunctiondecldeflink.cpp
index e489045f35..e6722206df 100644
--- a/src/plugins/cppeditor/cppfunctiondecldeflink.cpp
+++ b/src/plugins/cppeditor/cppfunctiondecldeflink.cpp
@@ -8,8 +8,8 @@
#include "cppeditortr.h"
#include "cppeditorwidget.h"
#include "cpplocalsymbols.h"
-#include "cppquickfixassistant.h"
#include "cpptoolsreuse.h"
+#include "quickfixes/cppquickfixassistant.h"
#include "symbolfinder.h"
#include <coreplugin/actionmanager/actionmanager.h>
@@ -21,8 +21,6 @@
#include <cplusplus/Overview.h>
#include <cplusplus/TypeOfExpression.h>
-#include <extensionsystem/pluginmanager.h>
-
#include <texteditor/refactoroverlay.h>
#include <texteditor/texteditorconstants.h>
@@ -239,7 +237,7 @@ void FunctionDeclDefLinkFinder::startFindLinkAt(
m_watcher.reset(new QFutureWatcher<std::shared_ptr<FunctionDeclDefLink> >());
connect(m_watcher.get(), &QFutureWatcherBase::finished, this, &FunctionDeclDefLinkFinder::onFutureDone);
m_watcher->setFuture(Utils::asyncRun(findLinkHelper, result, refactoringChanges));
- ExtensionSystem::PluginManager::futureSynchronizer()->addFuture(m_watcher->future());
+ Utils::futureSynchronizer()->addFuture(m_watcher->future());
}
bool FunctionDeclDefLink::isValid() const
@@ -269,13 +267,11 @@ void FunctionDeclDefLink::apply(CppEditorWidget *editor, bool jumpToMatch)
const int targetStart = newTargetFile->position(targetLine, targetColumn);
const int targetEnd = targetStart + targetInitial.size();
if (targetInitial == newTargetFile->textOf(targetStart, targetEnd)) {
- const ChangeSet changeset = changes(snapshot, targetStart);
- newTargetFile->setChangeSet(changeset);
if (jumpToMatch) {
const int jumpTarget = newTargetFile->position(targetFunction->line(), targetFunction->column());
newTargetFile->setOpenEditor(true, jumpTarget);
}
- newTargetFile->apply();
+ newTargetFile->apply(changes(snapshot, targetStart));
} else {
ToolTip::show(editor->toolTipPosition(linkSelection),
Tr::tr("Target file was changed, could not apply changes"));
diff --git a/src/plugins/cppeditor/cpplocatorfilter.cpp b/src/plugins/cppeditor/cpplocatorfilter.cpp
index 8fab8f4dcd..7526d3dc9f 100644
--- a/src/plugins/cppeditor/cpplocatorfilter.cpp
+++ b/src/plugins/cppeditor/cpplocatorfilter.cpp
@@ -11,8 +11,6 @@
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/editormanager/ieditor.h>
-#include <extensionsystem/pluginmanager.h>
-
#include <utils/algorithm.h>
#include <utils/async.h>
#include <utils/fuzzymatcher.h>
@@ -108,7 +106,6 @@ LocatorMatcherTask locatorMatcher(IndexItem::ItemType type, const EntryFromIndex
Storage<LocatorStorage> storage;
const auto onSetup = [=](Async<void> &async) {
- async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
async.setConcurrentCallData(matchesFor, *storage, type, converter);
};
return {AsyncTask<void>(onSetup), storage};
@@ -306,7 +303,6 @@ LocatorMatcherTask currentDocumentMatcher()
Storage<LocatorStorage> storage;
const auto onSetup = [=](Async<void> &async) {
- async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
async.setConcurrentCallData(matchesForCurrentDocument, *storage, currentFileName());
};
return {AsyncTask<void>(onSetup), storage};
diff --git a/src/plugins/cppeditor/cppmodelmanager.cpp b/src/plugins/cppeditor/cppmodelmanager.cpp
index fbb727eb72..6f2aebcca3 100644
--- a/src/plugins/cppeditor/cppmodelmanager.cpp
+++ b/src/plugins/cppeditor/cppmodelmanager.cpp
@@ -1330,11 +1330,8 @@ static QSet<QString> filteredFilesRemoved(const QSet<QString> &files,
for (const QRegularExpression &rx: std::as_const(regexes)) {
QRegularExpressionMatch match = rx.match(filePath.absoluteFilePath().path());
if (match.hasMatch()) {
- const QString msg = Tr::tr("C++ Indexer: Skipping file \"%1\" "
- "because its path matches the ignore pattern.")
- .arg(filePath.displayName());
- QMetaObject::invokeMethod(MessageManager::instance(),
- [msg] { MessageManager::writeSilently(msg); });
+ MessageManager::writeSilently(Tr::tr("C++ Indexer: Skipping file \"%1\" "
+ "because its path matches the ignore pattern.").arg(filePath.displayName()));
skip = true;
break;
}
@@ -2013,8 +2010,7 @@ void CppModelManager::renameIncludes(const QList<std::pair<FilePath, FilePath>>
newString);
}
}
- file->setChangeSet(changeSet);
- file->apply();
+ file->apply(changeSet);
}
}
diff --git a/src/plugins/cppeditor/cppmodelmanager_test.cpp b/src/plugins/cppeditor/cppmodelmanager_test.cpp
index 566c9e02de..21351659a9 100644
--- a/src/plugins/cppeditor/cppmodelmanager_test.cpp
+++ b/src/plugins/cppeditor/cppmodelmanager_test.cpp
@@ -43,6 +43,7 @@ using CPlusPlus::Document;
// FIXME: Clean up the namespaces
using CppEditor::Tests::ModelManagerTestHelper;
using CppEditor::Tests::ProjectOpenerAndCloser;
+using CppEditor::Tests::SourceFilesRefreshGuard;
using CppEditor::Tests::TemporaryCopiedDir;
using CppEditor::Tests::TemporaryDir;
using CppEditor::Tests::TestCase;
@@ -987,30 +988,6 @@ void ModelManagerTest::testRenameIncludes_data()
<< "subdir2/header2.h" << "subdir1/header2_moved.h" << false;
}
-class SourceFilesRefreshGuard : public QObject
-{
-public:
- SourceFilesRefreshGuard()
- {
- connect(CppModelManager::instance(), &CppModelManager::sourceFilesRefreshed, this, [this] {
- m_refreshed = true;
- });
- }
-
- void reset() { m_refreshed = false; }
- bool wait()
- {
- for (int i = 0; i < 10 && !m_refreshed; ++i) {
- CppEditor::Tests::waitForSignalOrTimeout(
- CppModelManager::instance(), &CppModelManager::sourceFilesRefreshed, 1000);
- }
- return m_refreshed;
- }
-
-private:
- bool m_refreshed = false;
-};
-
void ModelManagerTest::testRenameIncludes()
{
// Set up project.
diff --git a/src/plugins/cppeditor/cppoutlinemodel.cpp b/src/plugins/cppeditor/cppoutlinemodel.cpp
index c03236f274..6cd11c6df5 100644
--- a/src/plugins/cppeditor/cppoutlinemodel.cpp
+++ b/src/plugins/cppeditor/cppoutlinemodel.cpp
@@ -117,7 +117,7 @@ public:
return false;
};
if (isFwdDecl())
- return Utils::creatorTheme()->color(Utils::Theme::TextColorDisabled);
+ return Utils::creatorColor(Utils::Theme::TextColorDisabled);
return TreeItem::data(column, role);
}
diff --git a/src/plugins/cppeditor/cppquickfix.cpp b/src/plugins/cppeditor/cppquickfix.cpp
deleted file mode 100644
index 0741c842ba..0000000000
--- a/src/plugins/cppeditor/cppquickfix.cpp
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#include "cppquickfix.h"
-
-#include "cppquickfixassistant.h"
-#include "cpprefactoringchanges.h"
-
-using namespace CPlusPlus;
-using namespace TextEditor;
-
-namespace CppEditor::Internal {
-
-const QStringList magicQObjectFunctions()
-{
- static QStringList list{"metaObject", "qt_metacast", "qt_metacall", "qt_static_metacall"};
- return list;
-}
-
-CppQuickFixOperation::CppQuickFixOperation(const CppQuickFixInterface &interface, int priority)
- : QuickFixOperation(priority), CppQuickFixInterface(interface)
-{}
-
-CppQuickFixOperation::~CppQuickFixOperation() = default;
-
-} // namespace CppEditor::Internal
diff --git a/src/plugins/cppeditor/cppquickfix_test.cpp b/src/plugins/cppeditor/cppquickfix_test.cpp
deleted file mode 100644
index 0a5b71c814..0000000000
--- a/src/plugins/cppeditor/cppquickfix_test.cpp
+++ /dev/null
@@ -1,9951 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#include "cppquickfix_test.h"
-
-#include "cppcodestylepreferences.h"
-#include "cppeditorwidget.h"
-#include "cppmodelmanager.h"
-#include "cppquickfixassistant.h"
-#include "cppquickfixes.h"
-#include "cppquickfixsettings.h"
-#include "cppsourceprocessertesthelper.h"
-#include "cpptoolssettings.h"
-
-#include <utils/fileutils.h>
-
-#include <QDebug>
-#include <QDir>
-#include <QtTest>
-
-/*!
- Tests for quick-fixes.
- */
-using namespace Core;
-using namespace CPlusPlus;
-using namespace TextEditor;
-using namespace Utils;
-
-using CppEditor::Tests::TemporaryDir;
-using CppEditor::Tests::Internal::TestIncludePaths;
-
-typedef QByteArray _;
-
-namespace CppEditor::Internal::Tests {
-typedef QList<TestDocumentPtr> QuickFixTestDocuments;
-}
-Q_DECLARE_METATYPE(CppEditor::Internal::Tests::QuickFixTestDocuments)
-
-namespace CppEditor {
-namespace Internal {
-namespace Tests {
-
-/// Tests the offered operations provided by a given CppQuickFixFactory
-class QuickFixOfferedOperationsTest : public BaseQuickFixTestCase
-{
-public:
- QuickFixOfferedOperationsTest(const QList<TestDocumentPtr> &testDocuments,
- CppQuickFixFactory *factory,
- const ProjectExplorer::HeaderPaths &headerPaths
- = ProjectExplorer::HeaderPaths(),
- const QStringList &expectedOperations = QStringList());
-};
-
-QList<TestDocumentPtr> singleDocument(const QByteArray &original,
- const QByteArray &expected)
-{
- return {CppTestDocument::create("file.cpp", original, expected)};
-}
-
-BaseQuickFixTestCase::BaseQuickFixTestCase(const QList<TestDocumentPtr> &testDocuments,
- const ProjectExplorer::HeaderPaths &headerPaths,
- const QByteArray &clangFormatSettings)
- : m_testDocuments(testDocuments)
- , m_cppCodeStylePreferences(0)
- , m_restoreHeaderPaths(false)
-{
- QVERIFY(succeededSoFar());
- m_succeededSoFar = false;
-
- // Check if there is exactly one cursor marker
- unsigned cursorMarkersCount = 0;
- for (const TestDocumentPtr &document : std::as_const(m_testDocuments)) {
- if (document->hasCursorMarker())
- ++cursorMarkersCount;
- }
- QVERIFY2(cursorMarkersCount == 1, "Exactly one cursor marker is allowed.");
-
- // Write documents to disk
- m_temporaryDirectory.reset(new TemporaryDir);
- QVERIFY(m_temporaryDirectory->isValid());
- for (const TestDocumentPtr &document : std::as_const(m_testDocuments)) {
- if (QFileInfo(document->m_fileName).isRelative())
- document->setBaseDirectory(m_temporaryDirectory->path());
- document->writeToDisk();
- }
-
- // Create .clang-format file
- if (!clangFormatSettings.isEmpty())
- m_temporaryDirectory->createFile(".clang-format", clangFormatSettings);
-
- // Set appropriate include paths
- if (!headerPaths.isEmpty()) {
- m_restoreHeaderPaths = true;
- m_headerPathsToRestore = CppModelManager::headerPaths();
- CppModelManager::setHeaderPaths(headerPaths);
- }
-
- // Update Code Model
- QSet<FilePath> filePaths;
- for (const TestDocumentPtr &document : std::as_const(m_testDocuments))
- filePaths << document->filePath();
- QVERIFY(parseFiles(filePaths));
-
- // Open Files
- for (const TestDocumentPtr &document : std::as_const(m_testDocuments)) {
- QVERIFY(openCppEditor(document->filePath(), &document->m_editor,
- &document->m_editorWidget));
- closeEditorAtEndOfTestCase(document->m_editor);
-
- // Set cursor position
- if (document->hasCursorMarker()) {
- if (document->hasAnchorMarker()) {
- document->m_editor->setCursorPosition(document->m_anchorPosition);
- document->m_editor->select(document->m_cursorPosition);
- } else {
- document->m_editor->setCursorPosition(document->m_cursorPosition);
- }
- } else {
- document->m_editor->setCursorPosition(0);
- }
-
- // Rehighlight
- waitForRehighlightedSemanticDocument(document->m_editorWidget);
- }
-
- // Enforce the default cpp code style, so we are independent of config file settings.
- // This is needed by e.g. the GenerateGetterSetter quick fix.
- m_cppCodeStylePreferences = CppToolsSettings::cppCodeStyle();
- QVERIFY(m_cppCodeStylePreferences);
- m_cppCodeStylePreferencesOriginalDelegateId = m_cppCodeStylePreferences->currentDelegateId();
- m_cppCodeStylePreferences->setCurrentDelegate("qt");
-
- // Find the document having the cursor marker
- for (const TestDocumentPtr &document : std::as_const(m_testDocuments)) {
- if (document->hasCursorMarker()){
- m_documentWithMarker = document;
- break;
- }
- }
-
- QVERIFY(m_documentWithMarker);
- m_succeededSoFar = true;
-}
-
-BaseQuickFixTestCase::~BaseQuickFixTestCase()
-{
- // Restore default cpp code style
- if (m_cppCodeStylePreferences)
- m_cppCodeStylePreferences->setCurrentDelegate(m_cppCodeStylePreferencesOriginalDelegateId);
-
- // Restore include paths
- if (m_restoreHeaderPaths)
- CppModelManager::setHeaderPaths(m_headerPathsToRestore);
-
- // Remove created files from file system
- for (const TestDocumentPtr &testDocument : std::as_const(m_testDocuments))
- QVERIFY(testDocument->filePath().removeFile());
-}
-
-/// Leading whitespace is not removed, so we can check if the indetation ranges
-/// have been set correctly by the quick-fix.
-static QString &removeTrailingWhitespace(QString &input)
-{
- const QStringList lines = input.split(QLatin1Char('\n'));
- input.resize(0);
- for (int i = 0, total = lines.size(); i < total; ++i) {
- QString line = lines.at(i);
- while (line.length() > 0) {
- QChar lastChar = line[line.length() - 1];
- if (lastChar == QLatin1Char(' ') || lastChar == QLatin1Char('\t'))
- line.chop(1);
- else
- break;
- }
- input.append(line);
-
- const bool isLastLine = i == lines.size() - 1;
- if (!isLastLine)
- input.append(QLatin1Char('\n'));
- }
- return input;
-}
-
-QuickFixOperationTest::QuickFixOperationTest(const QList<TestDocumentPtr> &testDocuments,
- CppQuickFixFactory *factory,
- const ProjectExplorer::HeaderPaths &headerPaths,
- int operationIndex,
- const QByteArray &expectedFailMessage,
- const QByteArray &clangFormatSettings)
- : BaseQuickFixTestCase(testDocuments, headerPaths, clangFormatSettings)
-{
- if (factory->clangdReplacement() && CppModelManager::isClangCodeModelActive())
- return;
-
- QVERIFY(succeededSoFar());
-
- // Perform operation if there is one
- CppQuickFixInterface quickFixInterface(m_documentWithMarker->m_editorWidget, ExplicitlyInvoked);
- QuickFixOperations operations;
- factory->match(quickFixInterface, operations);
- if (operations.isEmpty()) {
- QEXPECT_FAIL("CompleteSwitchCaseStatement_QTCREATORBUG-25998", "FIXME", Abort);
- QVERIFY(testDocuments.first()->m_expectedSource.isEmpty());
- return;
- }
-
- QVERIFY(operationIndex < operations.size());
- const QuickFixOperation::Ptr operation = operations.at(operationIndex);
- operation->perform();
-
- // Compare all files
- for (const TestDocumentPtr &testDocument : std::as_const(m_testDocuments)) {
- // Check
- QString result = testDocument->m_editorWidget->document()->toPlainText();
- removeTrailingWhitespace(result);
- QEXPECT_FAIL("escape string literal: raw string literal", "FIXME", Continue);
- QEXPECT_FAIL("escape string literal: unescape adjacent literals", "FIXME", Continue);
- if (!expectedFailMessage.isEmpty())
- QEXPECT_FAIL("", expectedFailMessage.data(), Continue);
- else if (result != testDocument->m_expectedSource) {
- qDebug() << "---" << testDocument->m_expectedSource;
- qDebug() << "+++" << result;
- }
- QCOMPARE(result, testDocument->m_expectedSource);
-
- // Undo the change
- for (int i = 0; i < 100; ++i)
- testDocument->m_editorWidget->undo();
- result = testDocument->m_editorWidget->document()->toPlainText();
- QCOMPARE(result, testDocument->m_source);
- }
-}
-
-void QuickFixOperationTest::run(const QList<TestDocumentPtr> &testDocuments,
- CppQuickFixFactory *factory,
- const QString &headerPath,
- int operationIndex)
-{
- ProjectExplorer::HeaderPaths headerPaths;
- headerPaths.push_back(ProjectExplorer::HeaderPath::makeUser(headerPath));
- QuickFixOperationTest(testDocuments, factory, headerPaths, operationIndex);
-}
-
-QuickFixOfferedOperationsTest::QuickFixOfferedOperationsTest(
- const QList<TestDocumentPtr> &testDocuments,
- CppQuickFixFactory *factory,
- const ProjectExplorer::HeaderPaths &headerPaths,
- const QStringList &expectedOperations)
- : BaseQuickFixTestCase(testDocuments, headerPaths)
-{
- // Get operations
- CppQuickFixInterface quickFixInterface(m_documentWithMarker->m_editorWidget, ExplicitlyInvoked);
- QuickFixOperations actualOperations;
- factory->match(quickFixInterface, actualOperations);
-
- // Convert to QStringList
- QStringList actualOperationsAsStringList;
- for (const QuickFixOperation::Ptr &operation : std::as_const(actualOperations))
- actualOperationsAsStringList << operation->description();
-
- QCOMPARE(actualOperationsAsStringList, expectedOperations);
-}
-
-/// Delegates directly to AddIncludeForUndefinedIdentifierOp for easier testing.
-class AddIncludeForUndefinedIdentifierTestFactory : public CppQuickFixFactory
-{
-public:
- AddIncludeForUndefinedIdentifierTestFactory(const QString &include)
- : m_include(include) {}
-
- void doMatch(const CppQuickFixInterface &cppQuickFixInterface, QuickFixOperations &result) override
- {
- result << new AddIncludeForUndefinedIdentifierOp(cppQuickFixInterface, 0, m_include);
- }
-
-private:
- const QString m_include;
-};
-
-class AddForwardDeclForUndefinedIdentifierTestFactory : public CppQuickFixFactory
-{
-public:
- AddForwardDeclForUndefinedIdentifierTestFactory(const QString &className, int symbolPos)
- : m_className(className), m_symbolPos(symbolPos) {}
-
- void doMatch(const CppQuickFixInterface &cppQuickFixInterface, QuickFixOperations &result) override
- {
- result << new AddForwardDeclForUndefinedIdentifierOp(cppQuickFixInterface, 0,
- m_className, m_symbolPos);
- }
-
-private:
- const QString m_className;
- const int m_symbolPos;
-};
-
-} // namespace Tests
-} // namespace Internal
-
-typedef QSharedPointer<CppQuickFixFactory> CppQuickFixFactoryPtr;
-
-} // namespace CppEditor
-
-namespace CppEditor::Internal::Tests {
-
-class QuickFixSettings
-{
- const CppQuickFixSettings original = *CppQuickFixSettings::instance();
-
-public:
- CppQuickFixSettings *operator->() { return CppQuickFixSettings::instance(); }
- ~QuickFixSettings() { *CppQuickFixSettings::instance() = original; }
-};
-
-void QuickfixTest::testGeneric_data()
-{
- QTest::addColumn<CppQuickFixFactoryPtr>("factory");
- QTest::addColumn<QByteArray>("original");
- QTest::addColumn<QByteArray>("expected");
-
- // Checks: All enum values are added as case statements for a blank switch.
- QTest::newRow("CompleteSwitchCaseStatement_basic1")
- << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _(
- "enum EnumType { V1, V2 };\n"
- "\n"
- "void f()\n"
- "{\n"
- " EnumType t;\n"
- " @switch (t) {\n"
- " }\n"
- "}\n"
- ) << _(
- "enum EnumType { V1, V2 };\n"
- "\n"
- "void f()\n"
- "{\n"
- " EnumType t;\n"
- " switch (t) {\n"
- " case V1:\n"
- " break;\n"
- " case V2:\n"
- " break;\n"
- " }\n"
- "}\n"
- );
-
- // Same as above for enum class.
- QTest::newRow("CompleteSwitchCaseStatement_basic1_enum class")
- << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _(
- "enum class EnumType { V1, V2 };\n"
- "\n"
- "void f()\n"
- "{\n"
- " EnumType t;\n"
- " @switch (t) {\n"
- " }\n"
- "}\n"
- ) << _(
- "enum class EnumType { V1, V2 };\n"
- "\n"
- "void f()\n"
- "{\n"
- " EnumType t;\n"
- " switch (t) {\n"
- " case EnumType::V1:\n"
- " break;\n"
- " case EnumType::V2:\n"
- " break;\n"
- " }\n"
- "}\n"
- );
-
- // Same as above with the cursor somewhere in the body.
- QTest::newRow("CompleteSwitchCaseStatement_basic1_enum class, cursor in the body")
- << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _(
- "enum class EnumType { V1, V2 };\n"
- "\n"
- "void f()\n"
- "{\n"
- " EnumType t;\n"
- " switch (t) {\n"
- " @}\n"
- "}\n"
- ) << _(
- "enum class EnumType { V1, V2 };\n"
- "\n"
- "void f()\n"
- "{\n"
- " EnumType t;\n"
- " switch (t) {\n"
- " case EnumType::V1:\n"
- " break;\n"
- " case EnumType::V2:\n"
- " break;\n"
- " }\n"
- "}\n"
- );
-
- // Checks: All enum values are added as case statements for a blank switch when
- // the variable is declared alongside the enum definition.
- QTest::newRow("CompleteSwitchCaseStatement_basic1_enum_with_declaration")
- << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _(
- "enum EnumType { V1, V2 } t;\n"
- "\n"
- "void f()\n"
- "{\n"
- " @switch (t) {\n"
- " }\n"
- "}\n"
- ) << _(
- "enum EnumType { V1, V2 } t;\n"
- "\n"
- "void f()\n"
- "{\n"
- " switch (t) {\n"
- " case V1:\n"
- " break;\n"
- " case V2:\n"
- " break;\n"
- " }\n"
- "}\n"
- );
-
- // Same as above for enum class.
- QTest::newRow("CompleteSwitchCaseStatement_basic1_enum_with_declaration_enumClass")
- << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _(
- "enum class EnumType { V1, V2 } t;\n"
- "\n"
- "void f()\n"
- "{\n"
- " @switch (t) {\n"
- " }\n"
- "}\n"
- ) << _(
- "enum class EnumType { V1, V2 } t;\n"
- "\n"
- "void f()\n"
- "{\n"
- " switch (t) {\n"
- " case EnumType::V1:\n"
- " break;\n"
- " case EnumType::V2:\n"
- " break;\n"
- " }\n"
- "}\n"
- );
-
- // Checks: All enum values are added as case statements for a blank switch
- // for anonymous enums.
- QTest::newRow("CompleteSwitchCaseStatement_basic1_anonymous_enum")
- << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _(
- "enum { V1, V2 } t;\n"
- "\n"
- "void f()\n"
- "{\n"
- " @switch (t) {\n"
- " }\n"
- "}\n"
- ) << _(
- "enum { V1, V2 } t;\n"
- "\n"
- "void f()\n"
- "{\n"
- " switch (t) {\n"
- " case V1:\n"
- " break;\n"
- " case V2:\n"
- " break;\n"
- " }\n"
- "}\n"
- );
-
- // Checks: All enum values are added as case statements for a blank switch with a default case.
- QTest::newRow("CompleteSwitchCaseStatement_basic2")
- << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _(
- "enum EnumType { V1, V2 };\n"
- "\n"
- "void f()\n"
- "{\n"
- " EnumType t;\n"
- " @switch (t) {\n"
- " default:\n"
- " break;\n"
- " }\n"
- "}\n"
- ) << _(
- "enum EnumType { V1, V2 };\n"
- "\n"
- "void f()\n"
- "{\n"
- " EnumType t;\n"
- " switch (t) {\n"
- " case V1:\n"
- " break;\n"
- " case V2:\n"
- " break;\n"
- " default:\n"
- " break;\n"
- " }\n"
- "}\n"
- );
-
- // Same as above for enum class.
- QTest::newRow("CompleteSwitchCaseStatement_basic2_enumClass")
- << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _(
- "enum class EnumType { V1, V2 };\n"
- "\n"
- "void f()\n"
- "{\n"
- " EnumType t;\n"
- " @switch (t) {\n"
- " default:\n"
- " break;\n"
- " }\n"
- "}\n"
- ) << _(
- "enum class EnumType { V1, V2 };\n"
- "\n"
- "void f()\n"
- "{\n"
- " EnumType t;\n"
- " switch (t) {\n"
- " case EnumType::V1:\n"
- " break;\n"
- " case EnumType::V2:\n"
- " break;\n"
- " default:\n"
- " break;\n"
- " }\n"
- "}\n"
- );
-
- // Checks: Enum type in class is found.
- QTest::newRow("CompleteSwitchCaseStatement_enumTypeInClass")
- << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _(
- "struct C { enum EnumType { V1, V2 }; };\n"
- "\n"
- "void f(C::EnumType t) {\n"
- " @switch (t) {\n"
- " }\n"
- "}\n"
- ) << _(
- "struct C { enum EnumType { V1, V2 }; };\n"
- "\n"
- "void f(C::EnumType t) {\n"
- " switch (t) {\n"
- " case C::V1:\n"
- " break;\n"
- " case C::V2:\n"
- " break;\n"
- " }\n"
- "}\n"
- );
-
- // Same as above for enum class.
- QTest::newRow("CompleteSwitchCaseStatement_enumClassInClass")
- << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _(
- "struct C { enum class EnumType { V1, V2 }; };\n"
- "\n"
- "void f(C::EnumType t) {\n"
- " @switch (t) {\n"
- " }\n"
- "}\n"
- ) << _(
- "struct C { enum class EnumType { V1, V2 }; };\n"
- "\n"
- "void f(C::EnumType t) {\n"
- " switch (t) {\n"
- " case C::EnumType::V1:\n"
- " break;\n"
- " case C::EnumType::V2:\n"
- " break;\n"
- " }\n"
- "}\n"
- );
-
- // Checks: Enum type in namespace is found.
- QTest::newRow("CompleteSwitchCaseStatement_enumTypeInNamespace")
- << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _(
- "namespace N { enum EnumType { V1, V2 }; };\n"
- "\n"
- "void f(N::EnumType t) {\n"
- " @switch (t) {\n"
- " }\n"
- "}\n"
- ) << _(
- "namespace N { enum EnumType { V1, V2 }; };\n"
- "\n"
- "void f(N::EnumType t) {\n"
- " switch (t) {\n"
- " case N::V1:\n"
- " break;\n"
- " case N::V2:\n"
- " break;\n"
- " }\n"
- "}\n"
- );
-
- // Same as above for enum class.
- QTest::newRow("CompleteSwitchCaseStatement_enumClassInNamespace")
- << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _(
- "namespace N { enum class EnumType { V1, V2 }; };\n"
- "\n"
- "void f(N::EnumType t) {\n"
- " @switch (t) {\n"
- " }\n"
- "}\n"
- ) << _(
- "namespace N { enum class EnumType { V1, V2 }; };\n"
- "\n"
- "void f(N::EnumType t) {\n"
- " switch (t) {\n"
- " case N::EnumType::V1:\n"
- " break;\n"
- " case N::EnumType::V2:\n"
- " break;\n"
- " }\n"
- "}\n"
- );
-
- // Checks: The missing enum value is added.
- QTest::newRow("CompleteSwitchCaseStatement_oneValueMissing")
- << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _(
- "enum EnumType { V1, V2 };\n"
- "\n"
- "void f()\n"
- "{\n"
- " EnumType t;\n"
- " @switch (t) {\n"
- " case V2:\n"
- " break;\n"
- " default:\n"
- " break;\n"
- " }\n"
- "}\n"
- ) << _(
- "enum EnumType { V1, V2 };\n"
- "\n"
- "void f()\n"
- "{\n"
- " EnumType t;\n"
- " switch (t) {\n"
- " case V1:\n"
- " break;\n"
- " case V2:\n"
- " break;\n"
- " default:\n"
- " break;\n"
- " }\n"
- "}\n"
- );
-
- // Checks: Same as above for enum class.
- QTest::newRow("CompleteSwitchCaseStatement_oneValueMissing_enumClass")
- << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _(
- "enum class EnumType { V1, V2 };\n"
- "\n"
- "void f()\n"
- "{\n"
- " EnumType t;\n"
- " @switch (t) {\n"
- " case EnumType::V2:\n"
- " break;\n"
- " default:\n"
- " break;\n"
- " }\n"
- "}\n"
- ) << _(
- "enum class EnumType { V1, V2 };\n"
- "\n"
- "void f()\n"
- "{\n"
- " EnumType t;\n"
- " switch (t) {\n"
- " case EnumType::V1:\n"
- " break;\n"
- " case EnumType::V2:\n"
- " break;\n"
- " default:\n"
- " break;\n"
- " }\n"
- "}\n"
- );
-
- // Checks: Find the correct enum type despite there being a declaration with the same name.
- QTest::newRow("CompleteSwitchCaseStatement_QTCREATORBUG10366_1")
- << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _(
- "enum test { TEST_1, TEST_2 };\n"
- "\n"
- "void f() {\n"
- " enum test test;\n"
- " @switch (test) {\n"
- " }\n"
- "}\n"
- ) << _(
- "enum test { TEST_1, TEST_2 };\n"
- "\n"
- "void f() {\n"
- " enum test test;\n"
- " switch (test) {\n"
- " case TEST_1:\n"
- " break;\n"
- " case TEST_2:\n"
- " break;\n"
- " }\n"
- "}\n"
- );
-
- // Same as above for enum class.
- QTest::newRow("CompleteSwitchCaseStatement_QTCREATORBUG10366_1_enumClass")
- << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _(
- "enum class test { TEST_1, TEST_2 };\n"
- "\n"
- "void f() {\n"
- " enum test test;\n"
- " @switch (test) {\n"
- " }\n"
- "}\n"
- ) << _(
- "enum class test { TEST_1, TEST_2 };\n"
- "\n"
- "void f() {\n"
- " enum test test;\n"
- " switch (test) {\n"
- " case test::TEST_1:\n"
- " break;\n"
- " case test::TEST_2:\n"
- " break;\n"
- " }\n"
- "}\n"
- );
-
- // Checks: Find the correct enum type despite there being a declaration with the same name.
- QTest::newRow("CompleteSwitchCaseStatement_QTCREATORBUG10366_2")
- << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _(
- "enum test1 { Wrong11, Wrong12 };\n"
- "enum test { Right1, Right2 };\n"
- "enum test2 { Wrong21, Wrong22 };\n"
- "\n"
- "int main() {\n"
- " enum test test;\n"
- " @switch (test) {\n"
- " }\n"
- "}\n"
- ) << _(
- "enum test1 { Wrong11, Wrong12 };\n"
- "enum test { Right1, Right2 };\n"
- "enum test2 { Wrong21, Wrong22 };\n"
- "\n"
- "int main() {\n"
- " enum test test;\n"
- " switch (test) {\n"
- " case Right1:\n"
- " break;\n"
- " case Right2:\n"
- " break;\n"
- " }\n"
- "}\n"
- );
-
- // Same as above for enum class.
- QTest::newRow("CompleteSwitchCaseStatement_QTCREATORBUG10366_2_enumClass")
- << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _(
- "enum class test1 { Wrong11, Wrong12 };\n"
- "enum class test { Right1, Right2 };\n"
- "enum class test2 { Wrong21, Wrong22 };\n"
- "\n"
- "int main() {\n"
- " enum test test;\n"
- " @switch (test) {\n"
- " }\n"
- "}\n"
- ) << _(
- "enum class test1 { Wrong11, Wrong12 };\n"
- "enum class test { Right1, Right2 };\n"
- "enum class test2 { Wrong21, Wrong22 };\n"
- "\n"
- "int main() {\n"
- " enum test test;\n"
- " switch (test) {\n"
- " case test::Right1:\n"
- " break;\n"
- " case test::Right2:\n"
- " break;\n"
- " }\n"
- "}\n"
- );
-
- // Checks: Do not crash on incomplete case statetement.
- QTest::newRow("CompleteSwitchCaseStatement_doNotCrashOnIncompleteCase")
- << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _(
- "enum E {};\n"
- "void f(E o)\n"
- "{\n"
- " @switch (o)\n"
- " {\n"
- " case\n"
- " }\n"
- "}\n"
- ) << _(
- ""
- );
-
- // Same as above for enum class.
- QTest::newRow("CompleteSwitchCaseStatement_doNotCrashOnIncompleteCase_enumClass")
- << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _(
- "enum class E {};\n"
- "void f(E o)\n"
- "{\n"
- " @switch (o)\n"
- " {\n"
- " case\n"
- " }\n"
- "}\n"
- ) << _(
- ""
- );
-
- // Checks: complete switch statement where enum is goes via a template type parameter
- QTest::newRow("CompleteSwitchCaseStatement_QTCREATORBUG-24752")
- << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _(
- "enum E {EA, EB};\n"
- "template<typename T> struct S {\n"
- " static T theType() { return T(); }\n"
- "};\n"
- "int main() {\n"
- " @switch (S<E>::theType()) {\n"
- " }\n"
- "}\n"
- ) << _(
- "enum E {EA, EB};\n"
- "template<typename T> struct S {\n"
- " static T theType() { return T(); }\n"
- "};\n"
- "int main() {\n"
- " switch (S<E>::theType()) {\n"
- " case EA:\n"
- " break;\n"
- " case EB:\n"
- " break;\n"
- " }\n"
- "}\n"
- );
-
- // Same as above for enum class.
- QTest::newRow("CompleteSwitchCaseStatement_QTCREATORBUG-24752_enumClass")
- << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _(
- "enum class E {A, B};\n"
- "template<typename T> struct S {\n"
- " static T theType() { return T(); }\n"
- "};\n"
- "int main() {\n"
- " @switch (S<E>::theType()) {\n"
- " }\n"
- "}\n"
- ) << _(
- "enum class E {A, B};\n"
- "template<typename T> struct S {\n"
- " static T theType() { return T(); }\n"
- "};\n"
- "int main() {\n"
- " switch (S<E>::theType()) {\n"
- " case E::A:\n"
- " break;\n"
- " case E::B:\n"
- " break;\n"
- " }\n"
- "}\n"
- );
-
- // Checks: Complete switch statement where enum is return type of a template function
- // which is outside the scope of the return value.
- // TODO: Type minimization.
- QTest::newRow("CompleteSwitchCaseStatement_QTCREATORBUG-25998")
- << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _(
- "template <typename T> T enumCast(int value) { return static_cast<T>(value); }\n"
- "class Test {\n"
- " enum class E { V1, V2 };"
- " void func(int i) {\n"
- " @switch (enumCast<E>(i)) {\n"
- " }\n"
- " }\n"
- "};\n"
- ) << _(
- "template <typename T> T enumCast(int value) { return static_cast<T>(value); }\n"
- "class Test {\n"
- " enum class E { V1, V2 };"
- " void func(int i) {\n"
- " switch (enumCast<E>(i)) {\n"
- " case Test::E::V1:\n"
- " break;\n"
- " case Test::E::V2:\n"
- " break;\n"
- " }\n"
- " }\n"
- "};\n"
- );
-
- // Checks: No special treatment for reference to non const.
-
- // Check: Quick fix is not triggered on a member function.
- QTest::newRow("GenerateGetterSetter_notTriggeringOnMemberFunction")
- << CppQuickFixFactoryPtr(new GenerateGetterSetter)
- << _("class Something { void @f(); };\n") << _();
-
- // Check: Quick fix is not triggered on an member array;
- QTest::newRow("GenerateGetterSetter_notTriggeringOnMemberArray")
- << CppQuickFixFactoryPtr(new GenerateGetterSetter)
- << _("class Something { void @a[10]; };\n") << _();
-
- QTest::newRow("MoveDeclarationOutOfIf_ifOnly")
- << CppQuickFixFactoryPtr(new MoveDeclarationOutOfIf) << _(
- "void f()\n"
- "{\n"
- " if (Foo *@foo = g())\n"
- " h();\n"
- "}\n"
- ) << _(
- "void f()\n"
- "{\n"
- " Foo *foo = g();\n"
- " if (foo)\n"
- " h();\n"
- "}\n"
- );
-
- QTest::newRow("MoveDeclarationOutOfIf_ifElse")
- << CppQuickFixFactoryPtr(new MoveDeclarationOutOfIf) << _(
- "void f()\n"
- "{\n"
- " if (Foo *@foo = g())\n"
- " h();\n"
- " else\n"
- " i();\n"
- "}\n"
- ) << _(
- "void f()\n"
- "{\n"
- " Foo *foo = g();\n"
- " if (foo)\n"
- " h();\n"
- " else\n"
- " i();\n"
- "}\n"
- );
-
- QTest::newRow("MoveDeclarationOutOfIf_ifElseIf")
- << CppQuickFixFactoryPtr(new MoveDeclarationOutOfIf) << _(
- "void f()\n"
- "{\n"
- " if (Foo *foo = g()) {\n"
- " if (Bar *@bar = x()) {\n"
- " h();\n"
- " j();\n"
- " }\n"
- " } else {\n"
- " i();\n"
- " }\n"
- "}\n"
- ) << _(
- "void f()\n"
- "{\n"
- " if (Foo *foo = g()) {\n"
- " Bar *bar = x();\n"
- " if (bar) {\n"
- " h();\n"
- " j();\n"
- " }\n"
- " } else {\n"
- " i();\n"
- " }\n"
- "}\n"
- );
-
- QTest::newRow("MoveDeclarationOutOfWhile_singleWhile")
- << CppQuickFixFactoryPtr(new MoveDeclarationOutOfWhile) << _(
- "void f()\n"
- "{\n"
- " while (Foo *@foo = g())\n"
- " j();\n"
- "}\n"
- ) << _(
- "void f()\n"
- "{\n"
- " Foo *foo;\n"
- " while ((foo = g()) != 0)\n"
- " j();\n"
- "}\n"
- );
-
- QTest::newRow("MoveDeclarationOutOfWhile_whileInWhile")
- << CppQuickFixFactoryPtr(new MoveDeclarationOutOfWhile) << _(
- "void f()\n"
- "{\n"
- " while (Foo *foo = g()) {\n"
- " while (Bar *@bar = h()) {\n"
- " i();\n"
- " j();\n"
- " }\n"
- " }\n"
- "}\n"
- ) << _(
- "void f()\n"
- "{\n"
- " while (Foo *foo = g()) {\n"
- " Bar *bar;\n"
- " while ((bar = h()) != 0) {\n"
- " i();\n"
- " j();\n"
- " }\n"
- " }\n"
- "}\n"
- );
-
- // Check: Just a basic test since the main functionality is tested in
- // cpppointerdeclarationformatter_test.cpp
- QTest::newRow("ReformatPointerDeclaration")
- << CppQuickFixFactoryPtr(new ReformatPointerDeclaration)
- << _("char@*s;")
- << _("char *s;");
-
- // Check from source file: If there is no header file, insert the definition after the class.
- QByteArray original =
- "struct Foo\n"
- "{\n"
- " Foo();@\n"
- "};\n";
-
- QTest::newRow("InsertDefFromDecl_basic")
- << CppQuickFixFactoryPtr(new InsertDefFromDecl) << original
- << original + _(
- "\n"
- "Foo::Foo()\n"
- "{\n\n"
- "}\n"
- );
-
- QTest::newRow("InsertDefFromDecl_freeFunction")
- << CppQuickFixFactoryPtr(new InsertDefFromDecl)
- << _("void free()@;\n")
- << _(
- "void free()\n"
- "{\n\n"
- "}\n"
- );
-
- // Check not triggering when it is a statement
- QTest::newRow("InsertDefFromDecl_notTriggeringStatement")
- << CppQuickFixFactoryPtr(new InsertDefFromDecl) << _(
- "class Foo {\n"
- "public:\n"
- " Foo() {}\n"
- "};\n"
- "void freeFunc() {\n"
- " Foo @f();"
- "}\n"
- ) << _();
-
- // Check: Add local variable for a free function.
- QTest::newRow("AssignToLocalVariable_freeFunction")
- << CppQuickFixFactoryPtr(new AssignToLocalVariable) << _(
- "int foo() {return 1;}\n"
- "void bar() {fo@o();}\n"
- ) << _(
- "int foo() {return 1;}\n"
- "void bar() {auto localFoo = foo();}\n"
- );
-
- // Check: Add local variable for a member function.
- QTest::newRow("AssignToLocalVariable_memberFunction")
- << CppQuickFixFactoryPtr(new AssignToLocalVariable) << _(
- "class Foo {public: int* fooFunc();}\n"
- "void bar() {\n"
- " Foo *f = new Foo;\n"
- " @f->fooFunc();\n"
- "}\n"
- ) << _(
- "class Foo {public: int* fooFunc();}\n"
- "void bar() {\n"
- " Foo *f = new Foo;\n"
- " auto localFooFunc = f->fooFunc();\n"
- "}\n"
- );
-
- // Check: Add local variable for a member function, cursor in the middle (QTCREATORBUG-10355)
- QTest::newRow("AssignToLocalVariable_memberFunction2ndGrade1")
- << CppQuickFixFactoryPtr(new AssignToLocalVariable) << _(
- "struct Foo {int* func();};\n"
- "struct Baz {Foo* foo();};\n"
- "void bar() {\n"
- " Baz *b = new Baz;\n"
- " b->foo@()->func();\n"
- "}"
- ) << _(
- "struct Foo {int* func();};\n"
- "struct Baz {Foo* foo();};\n"
- "void bar() {\n"
- " Baz *b = new Baz;\n"
- " auto localFunc = b->foo()->func();\n"
- "}"
- );
-
- // Check: Add local variable for a member function, cursor on function call (QTCREATORBUG-10355)
- QTest::newRow("AssignToLocalVariable_memberFunction2ndGrade2")
- << CppQuickFixFactoryPtr(new AssignToLocalVariable) << _(
- "struct Foo {int* func();};\n"
- "struct Baz {Foo* foo();};\n"
- "void bar() {\n"
- " Baz *b = new Baz;\n"
- " b->foo()->f@unc();\n"
- "}"
- ) << _(
- "struct Foo {int* func();};\n"
- "struct Baz {Foo* foo();};\n"
- "void bar() {\n"
- " Baz *b = new Baz;\n"
- " auto localFunc = b->foo()->func();\n"
- "}"
- );
-
- // Check: Add local variable for a static member function.
- QTest::newRow("AssignToLocalVariable_staticMemberFunction")
- << CppQuickFixFactoryPtr(new AssignToLocalVariable) << _(
- "class Foo {public: static int* fooFunc();}\n"
- "void bar() {\n"
- " Foo::fooF@unc();\n"
- "}"
- ) << _(
- "class Foo {public: static int* fooFunc();}\n"
- "void bar() {\n"
- " auto localFooFunc = Foo::fooFunc();\n"
- "}"
- );
-
- // Check: Add local variable for a new Expression.
- QTest::newRow("AssignToLocalVariable_newExpression")
- << CppQuickFixFactoryPtr(new AssignToLocalVariable) << _(
- "class Foo {}\n"
- "void bar() {\n"
- " new Fo@o;\n"
- "}"
- ) << _(
- "class Foo {}\n"
- "void bar() {\n"
- " auto localFoo = new Foo;\n"
- "}"
- );
-
- // Check: No trigger for function inside member initialization list.
- QTest::newRow("AssignToLocalVariable_noInitializationList")
- << CppQuickFixFactoryPtr(new AssignToLocalVariable) << _(
- "class Foo\n"
- "{\n"
- " public: Foo : m_i(fooF@unc()) {}\n"
- " int fooFunc() {return 2;}\n"
- " int m_i;\n"
- "};\n"
- ) << _();
-
- // Check: No trigger for void functions.
- QTest::newRow("AssignToLocalVariable_noVoidFunction")
- << CppQuickFixFactoryPtr(new AssignToLocalVariable) << _(
- "void foo() {}\n"
- "void bar() {fo@o();}"
- ) << _();
-
- // Check: No trigger for void member functions.
- QTest::newRow("AssignToLocalVariable_noVoidMemberFunction")
- << CppQuickFixFactoryPtr(new AssignToLocalVariable) << _(
- "class Foo {public: void fooFunc();}\n"
- "void bar() {\n"
- " Foo *f = new Foo;\n"
- " @f->fooFunc();\n"
- "}"
- ) << _();
-
- // Check: No trigger for void static member functions.
- QTest::newRow("AssignToLocalVariable_noVoidStaticMemberFunction")
- << CppQuickFixFactoryPtr(new AssignToLocalVariable) << _(
- "class Foo {public: static void fooFunc();}\n"
- "void bar() {\n"
- " Foo::fo@oFunc();\n"
- "}"
- ) << _();
-
- // Check: No trigger for functions in expressions.
- QTest::newRow("AssignToLocalVariable_noFunctionInExpression")
- << CppQuickFixFactoryPtr(new AssignToLocalVariable) << _(
- "int foo(int a) {return a;}\n"
- "int bar() {return 1;}"
- "void baz() {foo(@bar() + bar());}"
- ) << _();
-
- // Check: No trigger for functions in functions. (QTCREATORBUG-9510)
- QTest::newRow("AssignToLocalVariable_noFunctionInFunction")
- << CppQuickFixFactoryPtr(new AssignToLocalVariable) << _(
- "int foo(int a, int b) {return a + b;}\n"
- "int bar(int a) {return a;}\n"
- "void baz() {\n"
- " int a = foo(ba@r(), bar());\n"
- "}\n"
- ) << _();
-
- // Check: No trigger for functions in return statements (classes).
- QTest::newRow("AssignToLocalVariable_noReturnClass1")
- << CppQuickFixFactoryPtr(new AssignToLocalVariable) << _(
- "class Foo {public: static void fooFunc();}\n"
- "Foo* bar() {\n"
- " return new Fo@o;\n"
- "}"
- ) << _();
-
- // Check: No trigger for functions in return statements (classes). (QTCREATORBUG-9525)
- QTest::newRow("AssignToLocalVariable_noReturnClass2")
- << CppQuickFixFactoryPtr(new AssignToLocalVariable) << _(
- "class Foo {public: int fooFunc();}\n"
- "int bar() {\n"
- " return (new Fo@o)->fooFunc();\n"
- "}"
- ) << _();
-
- // Check: No trigger for functions in return statements (functions).
- QTest::newRow("AssignToLocalVariable_noReturnFunc1")
- << CppQuickFixFactoryPtr(new AssignToLocalVariable) << _(
- "class Foo {public: int fooFunc();}\n"
- "int bar() {\n"
- " return Foo::fooFu@nc();\n"
- "}"
- ) << _();
-
- // Check: No trigger for functions in return statements (functions). (QTCREATORBUG-9525)
- QTest::newRow("AssignToLocalVariable_noReturnFunc2")
- << CppQuickFixFactoryPtr(new AssignToLocalVariable) << _(
- "int bar() {\n"
- " return list.firs@t().foo;\n"
- "}\n"
- ) << _();
-
- // Check: No trigger for functions which does not match in signature.
- QTest::newRow("AssignToLocalVariable_noSignatureMatch")
- << CppQuickFixFactoryPtr(new AssignToLocalVariable) << _(
- "int someFunc(int);\n"
- "\n"
- "void f()\n"
- "{\n"
- " some@Func();\n"
- "}"
- ) << _();
-
- QTest::newRow("ExtractLiteralAsParameter_freeFunction")
- << CppQuickFixFactoryPtr(new ExtractLiteralAsParameter) << _(
- "void foo(const char *a, long b = 1)\n"
- "{return 1@56 + 123 + 156;}\n"
- ) << _(
- "void foo(const char *a, long b = 1, int newParameter = 156)\n"
- "{return newParameter + 123 + newParameter;}\n"
- );
-
- QTest::newRow("ExtractLiteralAsParameter_memberFunction")
- << CppQuickFixFactoryPtr(new ExtractLiteralAsParameter) << _(
- "class Narf {\n"
- "public:\n"
- " int zort();\n"
- "};\n\n"
- "int Narf::zort()\n"
- "{ return 15@5 + 1; }\n"
- ) << _(
- "class Narf {\n"
- "public:\n"
- " int zort(int newParameter = 155);\n"
- "};\n\n"
- "int Narf::zort(int newParameter)\n"
- "{ return newParameter + 1; }\n"
- );
-
- QTest::newRow("ExtractLiteralAsParameter_memberFunctionInline")
- << CppQuickFixFactoryPtr(new ExtractLiteralAsParameter) << _(
- "class Narf {\n"
- "public:\n"
- " int zort()\n"
- " { return 15@5 + 1; }\n"
- "};\n"
- ) << _(
- "class Narf {\n"
- "public:\n"
- " int zort(int newParameter = 155)\n"
- " { return newParameter + 1; }\n"
- "};\n"
- );
-
- // Check: optimize postcrement
- QTest::newRow("OptimizeForLoop_postcrement")
- << CppQuickFixFactoryPtr(new OptimizeForLoop)
- << _("void foo() {f@or (int i = 0; i < 3; i++) {}}\n")
- << _("void foo() {for (int i = 0; i < 3; ++i) {}}\n");
-
- // Check: optimize condition
- QTest::newRow("OptimizeForLoop_condition")
- << CppQuickFixFactoryPtr(new OptimizeForLoop)
- << _("void foo() {f@or (int i = 0; i < 3 + 5; ++i) {}}\n")
- << _("void foo() {for (int i = 0, total = 3 + 5; i < total; ++i) {}}\n");
-
- // Check: optimize fliped condition
- QTest::newRow("OptimizeForLoop_flipedCondition")
- << CppQuickFixFactoryPtr(new OptimizeForLoop)
- << _("void foo() {f@or (int i = 0; 3 + 5 > i; ++i) {}}\n")
- << _("void foo() {for (int i = 0, total = 3 + 5; total > i; ++i) {}}\n");
-
- // Check: if "total" used, create other name.
- QTest::newRow("OptimizeForLoop_alterVariableName")
- << CppQuickFixFactoryPtr(new OptimizeForLoop)
- << _("void foo() {f@or (int i = 0, total = 0; i < 3 + 5; ++i) {}}\n")
- << _("void foo() {for (int i = 0, total = 0, totalX = 3 + 5; i < totalX; ++i) {}}\n");
-
- // Check: optimize postcrement and condition
- QTest::newRow("OptimizeForLoop_optimizeBoth")
- << CppQuickFixFactoryPtr(new OptimizeForLoop)
- << _("void foo() {f@or (int i = 0; i < 3 + 5; i++) {}}\n")
- << _("void foo() {for (int i = 0, total = 3 + 5; i < total; ++i) {}}\n");
-
- // Check: empty initializier
- QTest::newRow("OptimizeForLoop_emptyInitializer")
- << CppQuickFixFactoryPtr(new OptimizeForLoop)
- << _("int i; void foo() {f@or (; i < 3 + 5; ++i) {}}\n")
- << _("int i; void foo() {for (int total = 3 + 5; i < total; ++i) {}}\n");
-
- // Check: wrong initializier type -> no trigger
- QTest::newRow("OptimizeForLoop_wrongInitializer")
- << CppQuickFixFactoryPtr(new OptimizeForLoop)
- << _("int i; void foo() {f@or (double a = 0; i < 3 + 5; ++i) {}}\n")
- << _();
-
- // Check: No trigger when numeric
- QTest::newRow("OptimizeForLoop_noTriggerNumeric1")
- << CppQuickFixFactoryPtr(new OptimizeForLoop)
- << _("void foo() {fo@r (int i = 0; i < 3; ++i) {}}\n")
- << _();
-
- // Check: No trigger when numeric
- QTest::newRow("OptimizeForLoop_noTriggerNumeric2")
- << CppQuickFixFactoryPtr(new OptimizeForLoop)
- << _("void foo() {fo@r (int i = 0; i < -3; ++i) {}}\n")
- << _();
-
- // Escape String Literal as UTF-8 (no-trigger)
- QTest::newRow("EscapeStringLiteral_notrigger")
- << CppQuickFixFactoryPtr(new EscapeStringLiteral)
- << _("const char *notrigger = \"@abcdef \\a\\n\\\\\";\n")
- << _();
-
- // Escape String Literal as UTF-8
- QTest::newRow("EscapeStringLiteral")
- << CppQuickFixFactoryPtr(new EscapeStringLiteral)
- << _("const char *utf8 = \"@\xe3\x81\x82\xe3\x81\x84\";\n")
- << _("const char *utf8 = \"\\xe3\\x81\\x82\\xe3\\x81\\x84\";\n");
-
- // Unescape String Literal as UTF-8 (from hexdecimal escape sequences)
- QTest::newRow("UnescapeStringLiteral_hex")
- << CppQuickFixFactoryPtr(new EscapeStringLiteral)
- << _("const char *hex_escaped = \"@\\xe3\\x81\\x82\\xe3\\x81\\x84\";\n")
- << _("const char *hex_escaped = \"\xe3\x81\x82\xe3\x81\x84\";\n");
-
- // Unescape String Literal as UTF-8 (from octal escape sequences)
- QTest::newRow("UnescapeStringLiteral_oct")
- << CppQuickFixFactoryPtr(new EscapeStringLiteral)
- << _("const char *oct_escaped = \"@\\343\\201\\202\\343\\201\\204\";\n")
- << _("const char *oct_escaped = \"\xe3\x81\x82\xe3\x81\x84\";\n");
-
- // Unescape String Literal as UTF-8 (triggered but no change)
- QTest::newRow("UnescapeStringLiteral_noconv")
- << CppQuickFixFactoryPtr(new EscapeStringLiteral)
- << _("const char *escaped_ascii = \"@\\x1b\";\n")
- << _("const char *escaped_ascii = \"\\x1b\";\n");
-
- // Unescape String Literal as UTF-8 (no conversion because of invalid utf-8)
- QTest::newRow("UnescapeStringLiteral_invalid")
- << CppQuickFixFactoryPtr(new EscapeStringLiteral)
- << _("const char *escaped = \"@\\xe3\\x81\";\n")
- << _("const char *escaped = \"\\xe3\\x81\";\n");
-
- QTest::newRow("ConvertFromPointer")
- << CppQuickFixFactoryPtr(new ConvertFromAndToPointer)
- << _("void foo() {\n"
- " QString *@str;\n"
- " if (!str->isEmpty())\n"
- " str->clear();\n"
- " f1(*str);\n"
- " f2(str);\n"
- "}\n")
- << _("void foo() {\n"
- " QString str;\n"
- " if (!str.isEmpty())\n"
- " str.clear();\n"
- " f1(str);\n"
- " f2(&str);\n"
- "}\n");
-
- QTest::newRow("ConvertToPointer")
- << CppQuickFixFactoryPtr(new ConvertFromAndToPointer)
- << _("void foo() {\n"
- " QString @str;\n"
- " if (!str.isEmpty())\n"
- " str.clear();\n"
- " f1(str);\n"
- " f2(&str);\n"
- "}\n")
- << _("void foo() {\n"
- " QString *str = new QString;\n"
- " if (!str->isEmpty())\n"
- " str->clear();\n"
- " f1(*str);\n"
- " f2(str);\n"
- "}\n");
-
- QTest::newRow("ConvertReferenceToPointer")
- << CppQuickFixFactoryPtr(new ConvertFromAndToPointer)
- << _("void foo() {\n"
- " QString narf;"
- " QString &@str = narf;\n"
- " if (!str.isEmpty())\n"
- " str.clear();\n"
- " f1(str);\n"
- " f2(&str);\n"
- "}\n")
- << _("void foo() {\n"
- " QString narf;"
- " QString *str = &narf;\n"
- " if (!str->isEmpty())\n"
- " str->clear();\n"
- " f1(*str);\n"
- " f2(str);\n"
- "}\n");
-
- QTest::newRow("ConvertFromPointer_withInitializer")
- << CppQuickFixFactoryPtr(new ConvertFromAndToPointer)
- << _("void foo() {\n"
- " QString *@str = new QString(QLatin1String(\"schnurz\"));\n"
- " if (!str->isEmpty())\n"
- " str->clear();\n"
- "}\n")
- << _("void foo() {\n"
- " QString str(QLatin1String(\"schnurz\"));\n"
- " if (!str.isEmpty())\n"
- " str.clear();\n"
- "}\n");
-
- QTest::newRow("ConvertFromPointer_withBareInitializer")
- << CppQuickFixFactoryPtr(new ConvertFromAndToPointer)
- << _("void foo() {\n"
- " QString *@str = new QString;\n"
- " if (!str->isEmpty())\n"
- " str->clear();\n"
- "}\n")
- << _("void foo() {\n"
- " QString str;\n"
- " if (!str.isEmpty())\n"
- " str.clear();\n"
- "}\n");
-
- QTest::newRow("ConvertFromPointer_withEmptyInitializer")
- << CppQuickFixFactoryPtr(new ConvertFromAndToPointer)
- << _("void foo() {\n"
- " QString *@str = new QString();\n"
- " if (!str->isEmpty())\n"
- " str->clear();\n"
- "}\n")
- << _("void foo() {\n"
- " QString str;\n"
- " if (!str.isEmpty())\n"
- " str.clear();\n"
- "}\n");
-
- QTest::newRow("ConvertFromPointer_structWithPointer")
- << CppQuickFixFactoryPtr(new ConvertFromAndToPointer)
- << _("struct Bar{ QString *str; };\n"
- "void foo() {\n"
- " Bar *@bar = new Bar;\n"
- " bar->str = new QString;\n"
- " delete bar->str;\n"
- " delete bar;\n"
- "}\n")
- << _("struct Bar{ QString *str; };\n"
- "void foo() {\n"
- " Bar bar;\n"
- " bar.str = new QString;\n"
- " delete bar.str;\n"
- " // delete bar;\n"
- "}\n");
-
- QTest::newRow("ConvertToPointer_withInitializer")
- << CppQuickFixFactoryPtr(new ConvertFromAndToPointer)
- << _("void foo() {\n"
- " QString @str = QLatin1String(\"narf\");\n"
- " if (!str.isEmpty())\n"
- " str.clear();\n"
- "}\n")
- << _("void foo() {\n"
- " QString *str = new QString(QLatin1String(\"narf\"));\n"
- " if (!str->isEmpty())\n"
- " str->clear();\n"
- "}\n");
-
- QTest::newRow("ConvertToPointer_withParenInitializer")
- << CppQuickFixFactoryPtr(new ConvertFromAndToPointer)
- << _("void foo() {\n"
- " QString @str(QLatin1String(\"narf\"));\n"
- " if (!str.isEmpty())\n"
- " str.clear();\n"
- "}\n")
- << _("void foo() {\n"
- " QString *str = new QString(QLatin1String(\"narf\"));\n"
- " if (!str->isEmpty())\n"
- " str->clear();\n"
- "}\n");
-
- QTest::newRow("ConvertToPointer_noTriggerRValueRefs")
- << CppQuickFixFactoryPtr(new ConvertFromAndToPointer)
- << _("void foo(Narf &&@narf) {}\n")
- << _();
-
- QTest::newRow("ConvertToPointer_noTriggerGlobal")
- << CppQuickFixFactoryPtr(new ConvertFromAndToPointer)
- << _("int @global;\n")
- << _();
-
- QTest::newRow("ConvertToPointer_noTriggerClassMember")
- << CppQuickFixFactoryPtr(new ConvertFromAndToPointer)
- << _("struct C { int @member; };\n")
- << _();
-
- QTest::newRow("ConvertToPointer_noTriggerClassMember2")
- << CppQuickFixFactoryPtr(new ConvertFromAndToPointer)
- << _("void f() { struct C { int @member; }; }\n")
- << _();
-
- QTest::newRow("ConvertToPointer_functionOfFunctionLocalClass")
- << CppQuickFixFactoryPtr(new ConvertFromAndToPointer)
- << _("void f() {\n"
- " struct C {\n"
- " void g() { int @member; }\n"
- " };\n"
- "}\n")
- << _("void f() {\n"
- " struct C {\n"
- " void g() { int *member; }\n"
- " };\n"
- "}\n");
-
- QTest::newRow("ConvertToPointer_redeclaredVariable_block")
- << CppQuickFixFactoryPtr(new ConvertFromAndToPointer)
- << _("void foo() {\n"
- " QString @str;\n"
- " str.clear();\n"
- " {\n"
- " QString str;\n"
- " str.clear();\n"
- " }\n"
- " f1(str);\n"
- "}\n")
- << _("void foo() {\n"
- " QString *str = new QString;\n"
- " str->clear();\n"
- " {\n"
- " QString str;\n"
- " str.clear();\n"
- " }\n"
- " f1(*str);\n"
- "}\n");
-
- QTest::newRow("ConvertAutoFromPointer")
- << CppQuickFixFactoryPtr(new ConvertFromAndToPointer)
- << _("void foo() {\n"
- " auto @str = new QString(QLatin1String(\"foo\"));\n"
- " if (!str->isEmpty())\n"
- " str->clear();\n"
- " f1(*str);\n"
- " f2(str);\n"
- "}\n")
- << _("void foo() {\n"
- " auto str = QString(QLatin1String(\"foo\"));\n"
- " if (!str.isEmpty())\n"
- " str.clear();\n"
- " f1(str);\n"
- " f2(&str);\n"
- "}\n");
-
- QTest::newRow("ConvertAutoFromPointer2")
- << CppQuickFixFactoryPtr(new ConvertFromAndToPointer)
- << _("void foo() {\n"
- " auto *@str = new QString;\n"
- " if (!str->isEmpty())\n"
- " str->clear();\n"
- " f1(*str);\n"
- " f2(str);\n"
- "}\n")
- << _("void foo() {\n"
- " auto str = QString();\n"
- " if (!str.isEmpty())\n"
- " str.clear();\n"
- " f1(str);\n"
- " f2(&str);\n"
- "}\n");
-
- QTest::newRow("ConvertAutoToPointer")
- << CppQuickFixFactoryPtr(new ConvertFromAndToPointer)
- << _("void foo() {\n"
- " auto @str = QString(QLatin1String(\"foo\"));\n"
- " if (!str.isEmpty())\n"
- " str.clear();\n"
- " f1(str);\n"
- " f2(&str);\n"
- "}\n")
- << _("void foo() {\n"
- " auto @str = new QString(QLatin1String(\"foo\"));\n"
- " if (!str->isEmpty())\n"
- " str->clear();\n"
- " f1(*str);\n"
- " f2(str);\n"
- "}\n");
-
- QTest::newRow("ConvertToPointerWithMacro")
- << CppQuickFixFactoryPtr(new ConvertFromAndToPointer)
- << _("#define BAR bar\n"
- "void func()\n"
- "{\n"
- " int @foo = 42;\n"
- " int bar;\n"
- " BAR = foo;\n"
- "}\n")
- << _("#define BAR bar\n"
- "void func()\n"
- "{\n"
- " int *foo = 42;\n"
- " int bar;\n"
- " BAR = *foo;\n"
- "}\n");
-
- QString testObjAndFunc = "struct Object\n"
- "{\n"
- " Object(%1){}\n"
- "};\n"
- "void func()\n"
- "{\n"
- " %2\n"
- "}\n";
-
- QTest::newRow("ConvertToStack1_QTCREATORBUG23181")
- << CppQuickFixFactoryPtr(new ConvertFromAndToPointer)
- << _(testObjAndFunc.arg("int").arg("Object *@obj = new Object(0);").toUtf8())
- << _(testObjAndFunc.arg("int").arg("Object obj(0);").toUtf8());
-
- QTest::newRow("ConvertToStack2_QTCREATORBUG23181")
- << CppQuickFixFactoryPtr(new ConvertFromAndToPointer)
- << _(testObjAndFunc.arg("int").arg("Object *@obj = new Object{0};").toUtf8())
- << _(testObjAndFunc.arg("int").arg("Object obj{0};").toUtf8());
-
- QTest::newRow("ConvertToPointer1_QTCREATORBUG23181")
- << CppQuickFixFactoryPtr(new ConvertFromAndToPointer)
- << _(testObjAndFunc.arg("").arg("Object @obj;").toUtf8())
- << _(testObjAndFunc.arg("").arg("Object *obj = new Object;").toUtf8());
-
- QTest::newRow("ConvertToPointer2_QTCREATORBUG23181")
- << CppQuickFixFactoryPtr(new ConvertFromAndToPointer)
- << _(testObjAndFunc.arg("").arg("Object @obj();").toUtf8())
- << _(testObjAndFunc.arg("").arg("Object *obj = new Object();").toUtf8());
-
- QTest::newRow("ConvertToPointer3_QTCREATORBUG23181")
- << CppQuickFixFactoryPtr(new ConvertFromAndToPointer)
- << _(testObjAndFunc.arg("").arg("Object @obj{};").toUtf8())
- << _(testObjAndFunc.arg("").arg("Object *obj = new Object{};").toUtf8());
-
- QTest::newRow("ConvertToPointer4_QTCREATORBUG23181")
- << CppQuickFixFactoryPtr(new ConvertFromAndToPointer)
- << _(testObjAndFunc.arg("int").arg("Object @obj(0);").toUtf8())
- << _(testObjAndFunc.arg("int").arg("Object *obj = new Object(0);").toUtf8());
-
- QTest::newRow("InsertQtPropertyMembers_noTriggerInvalidCode")
- << CppQuickFixFactoryPtr(new InsertQtPropertyMembers)
- << _("class C { @Q_PROPERTY(typeid foo READ foo) };\n")
- << _();
-
- QTest::newRow("convert to camel case: normal")
- << CppQuickFixFactoryPtr(new ConvertToCamelCase(true))
- << _("void @lower_case_function();\n")
- << _("void lowerCaseFunction();\n");
- QTest::newRow("convert to camel case: already camel case")
- << CppQuickFixFactoryPtr(new ConvertToCamelCase(true))
- << _("void @camelCaseFunction();\n")
- << _();
- QTest::newRow("convert to camel case: no underscores (lower case)")
- << CppQuickFixFactoryPtr(new ConvertToCamelCase(true))
- << _("void @lowercasefunction();\n")
- << _();
- QTest::newRow("convert to camel case: no underscores (upper case)")
- << CppQuickFixFactoryPtr(new ConvertToCamelCase(true))
- << _("void @UPPERCASEFUNCTION();\n")
- << _();
- QTest::newRow("convert to camel case: non-applicable underscore")
- << CppQuickFixFactoryPtr(new ConvertToCamelCase(true))
- << _("void @m_a_member;\n")
- << _("void m_aMember;\n");
- QTest::newRow("convert to camel case: upper case")
- << CppQuickFixFactoryPtr(new ConvertToCamelCase(true))
- << _("void @UPPER_CASE_FUNCTION();\n")
- << _("void upperCaseFunction();\n");
- QTest::newRow("convert to camel case: partially camel case already")
- << CppQuickFixFactoryPtr(new ConvertToCamelCase(true))
- << _("void mixed@_andCamelCase();\n")
- << _("void mixedAndCamelCase();\n");
- QTest::newRow("convert to camel case: wild mix")
- << CppQuickFixFactoryPtr(new ConvertToCamelCase(true))
- << _("void @WhAt_TODO_hErE();\n")
- << _("void WhAtTODOHErE();\n");
- QTest::newRow("escape string literal: simple case")
- << CppQuickFixFactoryPtr(new EscapeStringLiteral)
- << _(R"(const char *str = @"àxyz";)")
- << _(R"(const char *str = "\xc3\xa0xyz";)");
- QTest::newRow("escape string literal: simple case reverse")
- << CppQuickFixFactoryPtr(new EscapeStringLiteral)
- << _(R"(const char *str = @"\xc3\xa0xyz";)")
- << _(R"(const char *str = "àxyz";)");
- QTest::newRow("escape string literal: raw string literal")
- << CppQuickFixFactoryPtr(new EscapeStringLiteral)
- << _(R"x(const char *str = @R"(àxyz)";)x")
- << _(R"x(const char *str = R"(\xc3\xa0xyz)";)x");
- QTest::newRow("escape string literal: splitting required")
- << CppQuickFixFactoryPtr(new EscapeStringLiteral)
- << _(R"(const char *str = @"àf23бgб1";)")
- << _(R"(const char *str = "\xc3\xa0""f23\xd0\xb1g\xd0\xb1""1";)");
- QTest::newRow("escape string literal: unescape adjacent literals")
- << CppQuickFixFactoryPtr(new EscapeStringLiteral)
- << _(R"(const char *str = @"\xc3\xa0""f23\xd0\xb1g\xd0\xb1""1";)")
- << _(R"(const char *str = "àf23бgб1";)");
- QTest::newRow("AddLocalDeclaration_QTCREATORBUG-26004")
- << CppQuickFixFactoryPtr(new AddDeclarationForUndeclaredIdentifier)
- << _("void func() {\n"
- " QStringList list;\n"
- " @it = list.cbegin();\n"
- "}\n")
- << _("void func() {\n"
- " QStringList list;\n"
- " auto it = list.cbegin();\n"
- "}\n");
-}
-
-void QuickfixTest::testGeneric()
-{
- QFETCH(CppQuickFixFactoryPtr, factory);
- QFETCH(QByteArray, original);
- QFETCH(QByteArray, expected);
-
- QuickFixOperationTest(singleDocument(original, expected), factory.data());
-}
-
-void QuickfixTest::testGenerateGetterSetterNamespaceHandlingCreate_data()
-{
- QTest::addColumn<QByteArrayList>("headers");
- QTest::addColumn<QByteArrayList>("sources");
-
- QByteArray originalSource;
- QByteArray expectedSource;
-
- const QByteArray originalHeader =
- "namespace N1 {\n"
- "namespace N2 {\n"
- "class Something\n"
- "{\n"
- " int @it;\n"
- "};\n"
- "}\n"
- "}\n";
- const QByteArray expectedHeader =
- "namespace N1 {\n"
- "namespace N2 {\n"
- "class Something\n"
- "{\n"
- " int it;\n"
- "\n"
- "public:\n"
- " int getIt() const;\n"
- " void setIt(int value);\n"
- "};\n"
- "}\n"
- "}\n";
-
- originalSource = "#include \"file.h\"\n";
- expectedSource =
- "#include \"file.h\"\n\n\n"
- "namespace N1 {\n"
- "namespace N2 {\n"
- "int Something::getIt() const\n"
- "{\n"
- " return it;\n"
- "}\n"
- "\n"
- "void Something::setIt(int value)\n"
- "{\n"
- " it = value;\n"
- "}\n\n"
- "}\n"
- "}\n";
- QTest::addRow("insert new namespaces")
- << QByteArrayList{originalHeader, expectedHeader}
- << QByteArrayList{originalSource, expectedSource};
-
- originalSource =
- "#include \"file.h\"\n"
- "namespace N2 {} // decoy\n";
- expectedSource =
- "#include \"file.h\"\n"
- "namespace N2 {} // decoy\n\n\n"
- "namespace N1 {\n"
- "namespace N2 {\n"
- "int Something::getIt() const\n"
- "{\n"
- " return it;\n"
- "}\n"
- "\n"
- "void Something::setIt(int value)\n"
- "{\n"
- " it = value;\n"
- "}\n\n"
- "}\n"
- "}\n";
- QTest::addRow("insert new namespaces (with decoy)")
- << QByteArrayList{originalHeader, expectedHeader}
- << QByteArrayList{originalSource, expectedSource};
-
- originalSource = "#include \"file.h\"\n"
- "namespace N2 {} // decoy\n"
- "namespace {\n"
- "namespace N1 {\n"
- "namespace {\n"
- "}\n"
- "}\n"
- "}\n";
- expectedSource = "#include \"file.h\"\n"
- "namespace N2 {} // decoy\n"
- "namespace {\n"
- "namespace N1 {\n"
- "namespace {\n"
- "}\n"
- "}\n"
- "}\n"
- "\n"
- "\n"
- "namespace N1 {\n"
- "namespace N2 {\n"
- "int Something::getIt() const\n"
- "{\n"
- " return it;\n"
- "}\n"
- "\n"
- "void Something::setIt(int value)\n"
- "{\n"
- " it = value;\n"
- "}\n"
- "\n"
- "}\n"
- "}\n";
- QTest::addRow("insert inner namespace (with decoy and unnamed)")
- << QByteArrayList{originalHeader, expectedHeader}
- << QByteArrayList{originalSource, expectedSource};
-
- const QByteArray unnamedOriginalHeader = "namespace {\n" + originalHeader + "}\n";
- const QByteArray unnamedExpectedHeader = "namespace {\n" + expectedHeader + "}\n";
-
- originalSource = "#include \"file.h\"\n"
- "namespace N2 {} // decoy\n"
- "namespace {\n"
- "namespace N1 {\n"
- "namespace {\n"
- "}\n"
- "}\n"
- "}\n";
- expectedSource = "#include \"file.h\"\n"
- "namespace N2 {} // decoy\n"
- "namespace {\n"
- "namespace N1 {\n"
- "\n"
- "namespace N2 {\n"
- "int Something::getIt() const\n"
- "{\n"
- " return it;\n"
- "}\n"
- "\n"
- "void Something::setIt(int value)\n"
- "{\n"
- " it = value;\n"
- "}\n"
- "\n"
- "}\n"
- "\n"
- "namespace {\n"
- "}\n"
- "}\n"
- "}\n";
- QTest::addRow("insert inner namespace in unnamed (with decoy)")
- << QByteArrayList{unnamedOriginalHeader, unnamedExpectedHeader}
- << QByteArrayList{originalSource, expectedSource};
-
- originalSource =
- "#include \"file.h\"\n"
- "namespace N1 {\n"
- "namespace N2 {\n"
- "namespace N3 {\n"
- "}\n"
- "}\n"
- "}\n";
- expectedSource =
- "#include \"file.h\"\n"
- "namespace N1 {\n"
- "namespace N2 {\n"
- "namespace N3 {\n"
- "}\n\n"
- "int Something::getIt() const\n"
- "{\n"
- " return it;\n"
- "}\n"
- "\n"
- "void Something::setIt(int value)\n"
- "{\n"
- " it = value;\n"
- "}\n\n"
- "}\n"
- "}\n";
- QTest::addRow("all namespaces already present")
- << QByteArrayList{originalHeader, expectedHeader}
- << QByteArrayList{originalSource, expectedSource};
-
- originalSource = "#include \"file.h\"\n"
- "namespace N1 {\n"
- "using namespace N2::N3;\n"
- "using namespace N2;\n"
- "using namespace N2;\n"
- "using namespace N3;\n"
- "}\n";
- expectedSource = "#include \"file.h\"\n"
- "namespace N1 {\n"
- "using namespace N2::N3;\n"
- "using namespace N2;\n"
- "using namespace N2;\n"
- "using namespace N3;\n"
- "\n"
- "int Something::getIt() const\n"
- "{\n"
- " return it;\n"
- "}\n"
- "\n"
- "void Something::setIt(int value)\n"
- "{\n"
- " it = value;\n"
- "}\n\n"
- "}\n";
- QTest::addRow("namespaces present and using namespace")
- << QByteArrayList{originalHeader, expectedHeader}
- << QByteArrayList{originalSource, expectedSource};
-
- originalSource = "#include \"file.h\"\n"
- "using namespace N1::N2::N3;\n"
- "using namespace N1::N2;\n"
- "namespace N1 {\n"
- "using namespace N3;\n"
- "}\n";
- expectedSource = "#include \"file.h\"\n"
- "using namespace N1::N2::N3;\n"
- "using namespace N1::N2;\n"
- "namespace N1 {\n"
- "using namespace N3;\n"
- "\n"
- "int Something::getIt() const\n"
- "{\n"
- " return it;\n"
- "}\n"
- "\n"
- "void Something::setIt(int value)\n"
- "{\n"
- " it = value;\n"
- "}\n"
- "\n"
- "}\n";
- QTest::addRow("namespaces present and outer using namespace")
- << QByteArrayList{originalHeader, expectedHeader}
- << QByteArrayList{originalSource, expectedSource};
-
- originalSource = "#include \"file.h\"\n"
- "using namespace N1;\n"
- "using namespace N2;\n"
- "namespace N3 {\n"
- "}\n";
- expectedSource = "#include \"file.h\"\n"
- "using namespace N1;\n"
- "using namespace N2;\n"
- "namespace N3 {\n"
- "}\n"
- "\n"
- "int Something::getIt() const\n"
- "{\n"
- " return it;\n"
- "}\n"
- "\n"
- "void Something::setIt(int value)\n"
- "{\n"
- " it = value;\n"
- "}\n";
- QTest::addRow("namespaces present and outer using namespace")
- << QByteArrayList{originalHeader, expectedHeader}
- << QByteArrayList{originalSource, expectedSource};
-}
-
-void QuickfixTest::testGenerateGetterSetterNamespaceHandlingCreate()
-{
- QFETCH(QByteArrayList, headers);
- QFETCH(QByteArrayList, sources);
-
- QList<TestDocumentPtr> testDocuments(
- {CppTestDocument::create("file.h", headers.at(0), headers.at(1)),
- CppTestDocument::create("file.cpp", sources.at(0), sources.at(1))});
-
- QuickFixSettings s;
- s->cppFileNamespaceHandling = CppQuickFixSettings::MissingNamespaceHandling::CreateMissing;
- s->setterParameterNameTemplate = "value";
- s->getterNameTemplate = "get<Name>";
- s->setterInCppFileFrom = 1;
- s->getterInCppFileFrom = 1;
- GenerateGetterSetter factory;
- QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 2);
-}
-
-void QuickfixTest::testGenerateGetterSetterNamespaceHandlingAddUsing_data()
-{
- QTest::addColumn<QByteArrayList>("headers");
- QTest::addColumn<QByteArrayList>("sources");
-
- QByteArray originalSource;
- QByteArray expectedSource;
-
- const QByteArray originalHeader = "namespace N1 {\n"
- "namespace N2 {\n"
- "class Something\n"
- "{\n"
- " int @it;\n"
- "};\n"
- "}\n"
- "}\n";
- const QByteArray expectedHeader = "namespace N1 {\n"
- "namespace N2 {\n"
- "class Something\n"
- "{\n"
- " int it;\n"
- "\n"
- "public:\n"
- " void setIt(int value);\n"
- "};\n"
- "}\n"
- "}\n";
-
- originalSource = "#include \"file.h\"\n";
- expectedSource = "#include \"file.h\"\n\n"
- "using namespace N1::N2;\n"
- "void Something::setIt(int value)\n"
- "{\n"
- " it = value;\n"
- "}\n";
- QTest::addRow("add using namespaces") << QByteArrayList{originalHeader, expectedHeader}
- << QByteArrayList{originalSource, expectedSource};
-
- const QByteArray unnamedOriginalHeader = "namespace {\n" + originalHeader + "}\n";
- const QByteArray unnamedExpectedHeader = "namespace {\n" + expectedHeader + "}\n";
-
- originalSource = "#include \"file.h\"\n"
- "namespace N2 {} // decoy\n"
- "namespace {\n"
- "namespace N1 {\n"
- "namespace {\n"
- "}\n"
- "}\n"
- "}\n";
- expectedSource = "#include \"file.h\"\n"
- "namespace N2 {} // decoy\n"
- "namespace {\n"
- "namespace N1 {\n"
- "using namespace N2;\n"
- "void Something::setIt(int value)\n"
- "{\n"
- " it = value;\n"
- "}\n"
- "namespace {\n"
- "}\n"
- "}\n"
- "}\n";
- QTest::addRow("insert using namespace into unnamed nested (with decoy)")
- << QByteArrayList{unnamedOriginalHeader, unnamedExpectedHeader}
- << QByteArrayList{originalSource, expectedSource};
-
- originalSource = "#include \"file.h\"\n";
- expectedSource = "#include \"file.h\"\n\n"
- "using namespace N1::N2;\n"
- "void Something::setIt(int value)\n"
- "{\n"
- " it = value;\n"
- "}\n";
- QTest::addRow("insert using namespace into unnamed")
- << QByteArrayList{unnamedOriginalHeader, unnamedExpectedHeader}
- << QByteArrayList{originalSource, expectedSource};
-
- originalSource = "#include \"file.h\"\n"
- "namespace N2 {} // decoy\n"
- "namespace {\n"
- "namespace N1 {\n"
- "namespace {\n"
- "}\n"
- "}\n"
- "}\n";
- expectedSource = "#include \"file.h\"\n"
- "namespace N2 {} // decoy\n"
- "namespace {\n"
- "namespace N1 {\n"
- "namespace {\n"
- "}\n"
- "}\n"
- "}\n"
- "\n"
- "using namespace N1::N2;\n"
- "void Something::setIt(int value)\n"
- "{\n"
- " it = value;\n"
- "}\n";
- QTest::addRow("insert using namespace (with decoy)")
- << QByteArrayList{originalHeader, expectedHeader}
- << QByteArrayList{originalSource, expectedSource};
-}
-
-void QuickfixTest::testGenerateGetterSetterNamespaceHandlingAddUsing()
-{
- QFETCH(QByteArrayList, headers);
- QFETCH(QByteArrayList, sources);
-
- QList<TestDocumentPtr> testDocuments(
- {CppTestDocument::create("file.h", headers.at(0), headers.at(1)),
- CppTestDocument::create("file.cpp", sources.at(0), sources.at(1))});
-
- QuickFixSettings s;
- s->cppFileNamespaceHandling = CppQuickFixSettings::MissingNamespaceHandling::AddUsingDirective;
- s->setterParameterNameTemplate = "value";
- s->setterInCppFileFrom = 1;
-
- if (std::strstr(QTest::currentDataTag(), "unnamed nested") != nullptr)
- QSKIP("TODO"); // FIXME
- GenerateGetterSetter factory;
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-void QuickfixTest::testGenerateGetterSetterNamespaceHandlingFullyQualify_data()
-{
- QTest::addColumn<QByteArrayList>("headers");
- QTest::addColumn<QByteArrayList>("sources");
-
- QByteArray originalSource;
- QByteArray expectedSource;
-
- const QByteArray originalHeader = "namespace N1 {\n"
- "namespace N2 {\n"
- "class Something\n"
- "{\n"
- " int @it;\n"
- "};\n"
- "}\n"
- "}\n";
- const QByteArray expectedHeader = "namespace N1 {\n"
- "namespace N2 {\n"
- "class Something\n"
- "{\n"
- " int it;\n"
- "\n"
- "public:\n"
- " void setIt(int value);\n"
- "};\n"
- "}\n"
- "}\n";
-
- originalSource = "#include \"file.h\"\n";
- expectedSource = "#include \"file.h\"\n\n"
- "void N1::N2::Something::setIt(int value)\n"
- "{\n"
- " it = value;\n"
- "}\n";
- QTest::addRow("fully qualify") << QByteArrayList{originalHeader, expectedHeader}
- << QByteArrayList{originalSource, expectedSource};
-
- originalSource = "#include \"file.h\"\n"
- "namespace N2 {} // decoy\n";
- expectedSource = "#include \"file.h\"\n"
- "namespace N2 {} // decoy\n"
- "\n"
- "void N1::N2::Something::setIt(int value)\n"
- "{\n"
- " it = value;\n"
- "}\n";
- QTest::addRow("fully qualify (with decoy)") << QByteArrayList{originalHeader, expectedHeader}
- << QByteArrayList{originalSource, expectedSource};
-
- originalSource = "#include \"file.h\"\n"
- "namespace N2 {} // decoy\n"
- "namespace {\n"
- "namespace N1 {\n"
- "namespace {\n"
- "}\n"
- "}\n"
- "}\n";
- expectedSource = "#include \"file.h\"\n"
- "namespace N2 {} // decoy\n"
- "namespace {\n"
- "namespace N1 {\n"
- "namespace {\n"
- "}\n"
- "}\n"
- "}\n"
- "\n"
- "void N1::N2::Something::setIt(int value)\n"
- "{\n"
- " it = value;\n"
- "}\n";
- QTest::addRow("qualify in inner namespace (with decoy)")
- << QByteArrayList{originalHeader, expectedHeader}
- << QByteArrayList{originalSource, expectedSource};
-
- const QByteArray unnamedOriginalHeader = "namespace {\n" + originalHeader + "}\n";
- const QByteArray unnamedExpectedHeader = "namespace {\n" + expectedHeader + "}\n";
-
- originalSource = "#include \"file.h\"\n"
- "namespace N2 {} // decoy\n"
- "namespace {\n"
- "namespace N1 {\n"
- "namespace {\n"
- "}\n"
- "}\n"
- "}\n";
- expectedSource = "#include \"file.h\"\n"
- "namespace N2 {} // decoy\n"
- "namespace {\n"
- "namespace N1 {\n"
- "void N2::Something::setIt(int value)\n"
- "{\n"
- " it = value;\n"
- "}\n"
- "namespace {\n"
- "}\n"
- "}\n"
- "}\n";
- QTest::addRow("qualify in inner namespace unnamed nested (with decoy)")
- << QByteArrayList{unnamedOriginalHeader, unnamedExpectedHeader}
- << QByteArrayList{originalSource, expectedSource};
-
- originalSource = "#include \"file.h\"\n";
- expectedSource = "#include \"file.h\"\n\n"
- "void N1::N2::Something::setIt(int value)\n"
- "{\n"
- " it = value;\n"
- "}\n";
- QTest::addRow("qualify in unnamed namespace")
- << QByteArrayList{unnamedOriginalHeader, unnamedExpectedHeader}
- << QByteArrayList{originalSource, expectedSource};
-}
-
-void QuickfixTest::testGenerateGetterSetterNamespaceHandlingFullyQualify()
-{
- QFETCH(QByteArrayList, headers);
- QFETCH(QByteArrayList, sources);
-
- QList<TestDocumentPtr> testDocuments(
- {CppTestDocument::create("file.h", headers.at(0), headers.at(1)),
- CppTestDocument::create("file.cpp", sources.at(0), sources.at(1))});
-
- QuickFixSettings s;
- s->cppFileNamespaceHandling = CppQuickFixSettings::MissingNamespaceHandling::RewriteType;
- s->setterParameterNameTemplate = "value";
- s->setterInCppFileFrom = 1;
-
- if (std::strstr(QTest::currentDataTag(), "unnamed nested") != nullptr)
- QSKIP("TODO"); // FIXME
- GenerateGetterSetter factory;
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-void QuickfixTest::testGenerateGetterSetterCustomNames_data()
-{
- QTest::addColumn<QByteArrayList>("headers");
- QTest::addColumn<int>("operation");
-
- QByteArray originalSource;
- QByteArray expectedSource;
-
- // Check if right names are created
- originalSource = R"-(
-class Test {
- int m_fooBar_test@;
-};
-)-";
- expectedSource = R"-(
-class Test {
- int m_fooBar_test;
-
-public:
- int give_me_foo_bar_test() const
- {
- return m_fooBar_test;
- }
- void Seet_FooBar_test(int New_Foo_Bar_Test)
- {
- if (m_fooBar_test == New_Foo_Bar_Test)
- return;
- m_fooBar_test = New_Foo_Bar_Test;
- emit newFooBarTestValue();
- }
- void set_fooBarTest_toDefault()
- {
- Seet_FooBar_test({}); // TODO: Adapt to use your actual default value
- }
-
-signals:
- void newFooBarTestValue();
-
-private:
- Q_PROPERTY(int fooBar_test READ give_me_foo_bar_test WRITE Seet_FooBar_test RESET set_fooBarTest_toDefault NOTIFY newFooBarTestValue FINAL)
-};
-)-";
- QTest::addRow("create right names") << QByteArrayList{originalSource, expectedSource} << 4;
-
- // Check if not triggered with custom names
- originalSource = R"-(
-class Test {
- int m_fooBar_test@;
-
-public:
- int give_me_foo_bar_test() const
- {
- return m_fooBar_test;
- }
- void Seet_FooBar_test(int New_Foo_Bar_Test)
- {
- if (m_fooBar_test == New_Foo_Bar_Test)
- return;
- m_fooBar_test = New_Foo_Bar_Test;
- emit newFooBarTestValue();
- }
- void set_fooBarTest_toDefault()
- {
- Seet_FooBar_test({}); // TODO: Adapt to use your actual default value
- }
-
-signals:
- void newFooBarTestValue();
-
-private:
- Q_PROPERTY(int fooBar_test READ give_me_foo_bar_test WRITE Seet_FooBar_test RESET set_fooBarTest_toDefault NOTIFY newFooBarTestValue FINAL)
-};
-)-";
- expectedSource = "";
- QTest::addRow("everything already exists") << QByteArrayList{originalSource, expectedSource} << 4;
-
- // create from Q_PROPERTY with custom names
- originalSource = R"-(
-class Test {
- Q_PROPER@TY(int fooBar_test READ give_me_foo_bar_test WRITE Seet_FooBar_test RESET set_fooBarTest_toDefault NOTIFY newFooBarTestValue FINAL)
-
-public:
- int give_me_foo_bar_test() const
- {
- return mem_fooBar_test;
- }
- void Seet_FooBar_test(int New_Foo_Bar_Test)
- {
- if (mem_fooBar_test == New_Foo_Bar_Test)
- return;
- mem_fooBar_test = New_Foo_Bar_Test;
- emit newFooBarTestValue();
- }
- void set_fooBarTest_toDefault()
- {
- Seet_FooBar_test({}); // TODO: Adapt to use your actual default value
- }
-
-signals:
- void newFooBarTestValue();
-};
-)-";
- expectedSource = R"-(
-class Test {
- Q_PROPERTY(int fooBar_test READ give_me_foo_bar_test WRITE Seet_FooBar_test RESET set_fooBarTest_toDefault NOTIFY newFooBarTestValue FINAL)
-
-public:
- int give_me_foo_bar_test() const
- {
- return mem_fooBar_test;
- }
- void Seet_FooBar_test(int New_Foo_Bar_Test)
- {
- if (mem_fooBar_test == New_Foo_Bar_Test)
- return;
- mem_fooBar_test = New_Foo_Bar_Test;
- emit newFooBarTestValue();
- }
- void set_fooBarTest_toDefault()
- {
- Seet_FooBar_test({}); // TODO: Adapt to use your actual default value
- }
-
-signals:
- void newFooBarTestValue();
-private:
- int mem_fooBar_test;
-};
-)-";
- QTest::addRow("create only member variable")
- << QByteArrayList{originalSource, expectedSource} << 0;
-
- // create from Q_PROPERTY with custom names
- originalSource = R"-(
-class Test {
- Q_PROPE@RTY(int fooBar_test READ give_me_foo_bar_test WRITE Seet_FooBar_test RESET set_fooBarTest_toDefault NOTIFY newFooBarTestValue FINAL)
- int mem_fooBar_test;
-public:
-};
-)-";
- expectedSource = R"-(
-class Test {
- Q_PROPERTY(int fooBar_test READ give_me_foo_bar_test WRITE Seet_FooBar_test RESET set_fooBarTest_toDefault NOTIFY newFooBarTestValue FINAL)
- int mem_fooBar_test;
-public:
- int give_me_foo_bar_test() const
- {
- return mem_fooBar_test;
- }
- void Seet_FooBar_test(int New_Foo_Bar_Test)
- {
- if (mem_fooBar_test == New_Foo_Bar_Test)
- return;
- mem_fooBar_test = New_Foo_Bar_Test;
- emit newFooBarTestValue();
- }
- void set_fooBarTest_toDefault()
- {
- Seet_FooBar_test({}); // TODO: Adapt to use your actual default value
- }
-signals:
- void newFooBarTestValue();
-};
-)-";
- QTest::addRow("create methods with given member variable")
- << QByteArrayList{originalSource, expectedSource} << 0;
-}
-
-void QuickfixTest::testGenerateGetterSetterCustomNames()
-{
- QFETCH(QByteArrayList, headers);
- QFETCH(int, operation);
-
- QList<TestDocumentPtr> testDocuments(
- {CppTestDocument::create("file.h", headers.at(0), headers.at(1))});
-
- QuickFixSettings s;
- s->setterInCppFileFrom = 0;
- s->getterInCppFileFrom = 0;
- s->setterNameTemplate = "Seet_<Name>";
- s->getterNameTemplate = "give_me_<snake>";
- s->signalNameTemplate = "new<Camel>Value";
- s->setterParameterNameTemplate = "New_<Snake>";
- s->resetNameTemplate = "set_<camel>_toDefault";
- s->memberVariableNameTemplate = "mem_<name>";
- if (operation == 0) {
- InsertQtPropertyMembers factory;
- QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), operation);
- } else {
- GenerateGetterSetter factory;
- QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), operation);
- }
-}
-
-void QuickfixTest::testGenerateGetterSetterValueTypes_data()
-{
- QTest::addColumn<QByteArrayList>("headers");
- QTest::addColumn<int>("operation");
-
- QByteArray originalSource;
- QByteArray expectedSource;
-
- // int should be a value type
- originalSource = R"-(
-class Test {
- int i@;
-};
-)-";
- expectedSource = R"-(
-class Test {
- int i;
-
-public:
- int getI() const
- {
- return i;
- }
-};
-)-";
- QTest::addRow("int") << QByteArrayList{originalSource, expectedSource} << 1;
-
- // return type should be only int without const
- originalSource = R"-(
-class Test {
- const int i@;
-};
-)-";
- expectedSource = R"-(
-class Test {
- const int i;
-
-public:
- int getI() const
- {
- return i;
- }
-};
-)-";
- QTest::addRow("const int") << QByteArrayList{originalSource, expectedSource} << 0;
-
- // float should be a value type
- originalSource = R"-(
-class Test {
- float f@;
-};
-)-";
- expectedSource = R"-(
-class Test {
- float f;
-
-public:
- float getF() const
- {
- return f;
- }
-};
-)-";
- QTest::addRow("float") << QByteArrayList{originalSource, expectedSource} << 1;
-
- // pointer should be a value type
- originalSource = R"-(
-class Test {
- void* v@;
-};
-)-";
- expectedSource = R"-(
-class Test {
- void* v;
-
-public:
- void *getV() const
- {
- return v;
- }
-};
-)-";
- QTest::addRow("pointer") << QByteArrayList{originalSource, expectedSource} << 1;
-
- // reference should be a value type (setter is const ref)
- originalSource = R"-(
-class Test {
- int& r@;
-};
-)-";
- expectedSource = R"-(
-class Test {
- int& r;
-
-public:
- int &getR() const
- {
- return r;
- }
- void setR(const int &newR)
- {
- r = newR;
- }
-};
-)-";
- QTest::addRow("reference to value type") << QByteArrayList{originalSource, expectedSource} << 2;
-
- // reference should be a value type
- originalSource = R"-(
-using bar = int;
-class Test {
- bar i@;
-};
-)-";
- expectedSource = R"-(
-using bar = int;
-class Test {
- bar i;
-
-public:
- bar getI() const
- {
- return i;
- }
-};
-)-";
- QTest::addRow("value type through using") << QByteArrayList{originalSource, expectedSource} << 1;
-
- // enum should be a value type
- originalSource = R"-(
-enum Foo{V1, V2};
-class Test {
- Foo e@;
-};
-)-";
- expectedSource = R"-(
-enum Foo{V1, V2};
-class Test {
- Foo e;
-
-public:
- Foo getE() const
- {
- return e;
- }
-};
-)-";
- QTest::addRow("enum") << QByteArrayList{originalSource, expectedSource} << 1;
-
- // class should not be a value type
- originalSource = R"-(
-class NoVal{};
-class Test {
- NoVal n@;
-};
-)-";
- expectedSource = R"-(
-class NoVal{};
-class Test {
- NoVal n;
-
-public:
- const NoVal &getN() const
- {
- return n;
- }
-};
-)-";
- QTest::addRow("class") << QByteArrayList{originalSource, expectedSource} << 1;
-
- // custom classes can be a value type
- originalSource = R"-(
-class Value{};
-class Test {
- Value v@;
-};
-)-";
- expectedSource = R"-(
-class Value{};
-class Test {
- Value v;
-
-public:
- Value getV() const
- {
- return v;
- }
-};
-)-";
- QTest::addRow("value class") << QByteArrayList{originalSource, expectedSource} << 1;
-
- // custom classes (in namespace) can be a value type
- originalSource = R"-(
-namespace N1{
-class Value{};
-}
-class Test {
- N1::Value v@;
-};
-)-";
- expectedSource = R"-(
-namespace N1{
-class Value{};
-}
-class Test {
- N1::Value v;
-
-public:
- N1::Value getV() const
- {
- return v;
- }
-};
-)-";
- QTest::addRow("value class in namespace") << QByteArrayList{originalSource, expectedSource} << 1;
-
- // custom template class can be a value type
- originalSource = R"-(
-template<typename T>
-class Value{};
-class Test {
- Value<int> v@;
-};
-)-";
- expectedSource = R"-(
-template<typename T>
-class Value{};
-class Test {
- Value<int> v;
-
-public:
- Value<int> getV() const
- {
- return v;
- }
-};
-)-";
- QTest::addRow("value template class") << QByteArrayList{originalSource, expectedSource} << 1;
-}
-
-void QuickfixTest::testGenerateGetterSetterValueTypes()
-{
- QFETCH(QByteArrayList, headers);
- QFETCH(int, operation);
-
- QList<TestDocumentPtr> testDocuments(
- {CppTestDocument::create("file.h", headers.at(0), headers.at(1))});
-
- QuickFixSettings s;
- s->setterInCppFileFrom = 0;
- s->getterInCppFileFrom = 0;
- s->getterNameTemplate = "get<Name>";
- s->valueTypes << "Value";
- s->returnByConstRef = true;
-
- GenerateGetterSetter factory;
- QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), operation);
-}
-
-/// Checks: Use template for a custom type
-void QuickfixTest::testGenerateGetterSetterCustomTemplate()
-{
- QList<TestDocumentPtr> testDocuments;
- QByteArray original;
- QByteArray expected;
-
- const _ customTypeDecl = R"--(
-namespace N1 {
-namespace N2 {
-struct test{};
-}
-template<typename T>
-struct custom {
- void assign(const custom<T>&);
- bool equals(const custom<T>&);
- T* get();
-};
-)--";
- // Header File
- original = customTypeDecl + R"--(
-class Foo
-{
-public:
- custom<N2::test> bar@;
-};
-})--";
- expected = customTypeDecl + R"--(
-class Foo
-{
-public:
- custom<N2::test> bar@;
- N2::test *getBar() const;
- void setBar(const custom<N2::test> &newBar);
-signals:
- void barChanged(N2::test *bar);
-private:
- Q_PROPERTY(N2::test *bar READ getBar NOTIFY barChanged FINAL)
-};
-})--";
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- // Source File
- original = "";
- expected = R"-(
-using namespace N1;
-N2::test *Foo::getBar() const
-{
- return bar.get();
-}
-
-void Foo::setBar(const custom<N2::test> &newBar)
-{
- if (bar.equals(newBar))
- return;
- bar.assign(newBar);
- emit barChanged(bar.get());
-}
-)-";
-
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
- QuickFixSettings s;
- s->cppFileNamespaceHandling = CppQuickFixSettings::MissingNamespaceHandling::AddUsingDirective;
- s->getterNameTemplate = "get<Name>";
- s->getterInCppFileFrom = 1;
- s->signalWithNewValue = true;
- CppQuickFixSettings::CustomTemplate t;
- t.types.append("custom");
- t.equalComparison = "<cur>.equals(<new>)";
- t.returnExpression = "<cur>.get()";
- t.returnType = "<T> *";
- t.assignment = "<cur>.assign(<new>)";
- s->customTemplates.push_back(t);
-
- GenerateGetterSetter factory;
- QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 5);
-}
-
-/// Checks: if the setter parameter name is the same as the member variable name, this-> is needed
-void QuickfixTest::testGenerateGetterSetterNeedThis()
-{
- QList<TestDocumentPtr> testDocuments;
-
- // Header File
- const QByteArray original = R"-(
-class Foo {
- int bar@;
-public:
-};
-)-";
- const QByteArray expected = R"-(
-class Foo {
- int bar@;
-public:
- void setBar(int bar)
- {
- this->bar = bar;
- }
-};
-)-";
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- QuickFixSettings s;
- s->setterParameterNameTemplate = "<name>";
- s->setterInCppFileFrom = 0;
-
- GenerateGetterSetter factory;
- QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 0);
-}
-
-void QuickfixTest::testGenerateGetterSetterOfferedFixes_data()
-{
- QTest::addColumn<QByteArray>("header");
- QTest::addColumn<QStringList>("offered");
-
- QByteArray header;
- QStringList offered;
- const QString setter = QStringLiteral("Generate Setter");
- const QString getter = QStringLiteral("Generate Getter");
- const QString getset = QStringLiteral("Generate Getter and Setter");
- const QString constQandMissing = QStringLiteral(
- "Generate Constant Q_PROPERTY and Missing Members");
- const QString qAndResetAndMissing = QStringLiteral(
- "Generate Q_PROPERTY and Missing Members with Reset Function");
- const QString qAndMissing = QStringLiteral("Generate Q_PROPERTY and Missing Members");
- const QStringList all{setter, getter, getset, constQandMissing, qAndResetAndMissing, qAndMissing};
-
- header = R"-(
-class Foo {
- static int bar@;
-};
-)-";
- offered = QStringList{setter, getter, getset, constQandMissing};
- QTest::addRow("static") << header << offered;
-
- header = R"-(
-class Foo {
- static const int bar@;
-};
-)-";
- offered = QStringList{getter, constQandMissing};
- QTest::addRow("const static") << header << offered;
-
- header = R"-(
-class Foo {
- const int bar@;
-};
-)-";
- offered = QStringList{getter, constQandMissing};
- QTest::addRow("const") << header << offered;
-
- header = R"-(
-class Foo {
- const int bar@;
- int getBar() const;
-};
-)-";
- offered = QStringList{constQandMissing};
- QTest::addRow("const + getter") << header << offered;
-
- header = R"-(
-class Foo {
- const int bar@;
- int getBar() const;
- void setBar(int value);
-};
-)-";
- offered = QStringList{};
- QTest::addRow("const + getter + setter") << header << offered;
-
- header = R"-(
-class Foo {
- const int* bar@;
-};
-)-";
- offered = all;
- QTest::addRow("pointer to const") << header << offered;
-
- header = R"-(
-class Foo {
- int bar@;
-public:
- int bar();
-};
-)-";
- offered = QStringList{setter, constQandMissing, qAndResetAndMissing, qAndMissing};
- QTest::addRow("existing getter") << header << offered;
-
- header = R"-(
-class Foo {
- int bar@;
-public:
- set setBar(int);
-};
-)-";
- offered = QStringList{getter};
- QTest::addRow("existing setter") << header << offered;
-
- header = R"-(
-class Foo {
- int bar@;
-signals:
- void barChanged(int);
-};
-)-";
- offered = QStringList{setter, getter, getset, qAndResetAndMissing, qAndMissing};
- QTest::addRow("existing signal (no const Q_PROPERTY)") << header << offered;
-
- header = R"-(
-class Foo {
- int m_bar@;
- Q_PROPERTY(int bar)
-};
-)-";
- offered = QStringList{}; // user should use "InsertQPropertyMembers", no duplicated code
- QTest::addRow("existing Q_PROPERTY") << header << offered;
-}
-
-void QuickfixTest::testGenerateGetterSetterOfferedFixes()
-{
- QFETCH(QByteArray, header);
- QFETCH(QStringList, offered);
-
- QList<TestDocumentPtr> testDocuments(
- {CppTestDocument::create("file.h", header, header)});
-
- GenerateGetterSetter factory;
- QuickFixOfferedOperationsTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), offered);
-}
-
-void QuickfixTest::testGenerateGetterSetterGeneralTests_data()
-{
- QTest::addColumn<int>("operation");
- QTest::addColumn<QByteArray>("original");
- QTest::addColumn<QByteArray>("expected");
-
- QTest::newRow("GenerateGetterSetter_referenceToNonConst")
- << 2
- << _("\n"
- "class Something\n"
- "{\n"
- " int &it@;\n"
- "};\n")
- << _("\n"
- "class Something\n"
- "{\n"
- " int &it;\n"
- "\n"
- "public:\n"
- " int &getIt() const;\n"
- " void setIt(const int &it);\n"
- "};\n"
- "\n"
- "int &Something::getIt() const\n"
- "{\n"
- " return it;\n"
- "}\n"
- "\n"
- "void Something::setIt(const int &it)\n"
- "{\n"
- " this->it = it;\n"
- "}\n");
-
- // Checks: No special treatment for reference to const.
- QTest::newRow("GenerateGetterSetter_referenceToConst")
- << 2
- << _("\n"
- "class Something\n"
- "{\n"
- " const int &it@;\n"
- "};\n")
- << _("\n"
- "class Something\n"
- "{\n"
- " const int &it;\n"
- "\n"
- "public:\n"
- " const int &getIt() const;\n"
- " void setIt(const int &it);\n"
- "};\n"
- "\n"
- "const int &Something::getIt() const\n"
- "{\n"
- " return it;\n"
- "}\n"
- "\n"
- "void Something::setIt(const int &it)\n"
- "{\n"
- " this->it = it;\n"
- "}\n");
-
- // Checks:
- // 1. Setter: Setter is a static function.
- // 2. Getter: Getter is a static, non const function.
- QTest::newRow("GenerateGetterSetter_staticMember")
- << 2
- << _("\n"
- "class Something\n"
- "{\n"
- " static int @m_member;\n"
- "};\n")
- << _("\n"
- "class Something\n"
- "{\n"
- " static int m_member;\n"
- "\n"
- "public:\n"
- " static int member();\n"
- " static void setMember(int member);\n"
- "};\n"
- "\n"
- "int Something::member()\n"
- "{\n"
- " return m_member;\n"
- "}\n"
- "\n"
- "void Something::setMember(int member)\n"
- "{\n"
- " m_member = member;\n"
- "}\n");
-
- // Check: Check if it works on the second declarator
- // clang-format off
- QTest::newRow("GenerateGetterSetter_secondDeclarator") << 2
- << _("\n"
- "class Something\n"
- "{\n"
- " int *foo, @it;\n"
- "};\n")
- << _("\n"
- "class Something\n"
- "{\n"
- " int *foo, it;\n"
- "\n"
- "public:\n"
- " int getIt() const;\n"
- " void setIt(int it);\n"
- "};\n"
- "\n"
- "int Something::getIt() const\n"
- "{\n"
- " return it;\n"
- "}\n"
- "\n"
- "void Something::setIt(int it)\n"
- "{\n"
- " this->it = it;\n"
- "}\n");
- // clang-format on
-
- // Check: Quick fix is offered for "int *@it;" ('@' denotes the text cursor position)
- QTest::newRow("GenerateGetterSetter_triggeringRightAfterPointerSign")
- << 2
- << _("\n"
- "class Something\n"
- "{\n"
- " int *@it;\n"
- "};\n")
- << _("\n"
- "class Something\n"
- "{\n"
- " int *it;\n"
- "\n"
- "public:\n"
- " int *getIt() const;\n"
- " void setIt(int *it);\n"
- "};\n"
- "\n"
- "int *Something::getIt() const\n"
- "{\n"
- " return it;\n"
- "}\n"
- "\n"
- "void Something::setIt(int *it)\n"
- "{\n"
- " this->it = it;\n"
- "}\n");
-
- // Checks if "m_" is recognized as "m" with the postfix "_" and not simply as "m_" prefix.
- QTest::newRow("GenerateGetterSetter_recognizeMasVariableName")
- << 2
- << _("\n"
- "class Something\n"
- "{\n"
- " int @m_;\n"
- "};\n")
- << _("\n"
- "class Something\n"
- "{\n"
- " int m_;\n"
- "\n"
- "public:\n"
- " int m() const;\n"
- " void setM(int m);\n"
- "};\n"
- "\n"
- "int Something::m() const\n"
- "{\n"
- " return m_;\n"
- "}\n"
- "\n"
- "void Something::setM(int m)\n"
- "{\n"
- " m_ = m;\n"
- "}\n");
-
- // Checks if "m" followed by an upper character is recognized as a prefix
- QTest::newRow("GenerateGetterSetter_recognizeMFollowedByCapital")
- << 2
- << _("\n"
- "class Something\n"
- "{\n"
- " int @mFoo;\n"
- "};\n")
- << _("\n"
- "class Something\n"
- "{\n"
- " int mFoo;\n"
- "\n"
- "public:\n"
- " int foo() const;\n"
- " void setFoo(int foo);\n"
- "};\n"
- "\n"
- "int Something::foo() const\n"
- "{\n"
- " return mFoo;\n"
- "}\n"
- "\n"
- "void Something::setFoo(int foo)\n"
- "{\n"
- " mFoo = foo;\n"
- "}\n");
-}
-void QuickfixTest::testGenerateGetterSetterGeneralTests()
-{
- QFETCH(int, operation);
- QFETCH(QByteArray, original);
- QFETCH(QByteArray, expected);
-
- QuickFixSettings s;
- s->setterParameterNameTemplate = "<name>";
- s->getterInCppFileFrom = 1;
- s->setterInCppFileFrom = 1;
-
- GenerateGetterSetter factory;
- QuickFixOperationTest(singleDocument(original, expected),
- &factory,
- ProjectExplorer::HeaderPaths(),
- operation);
-}
-/// Checks: Only generate getter
-void QuickfixTest::testGenerateGetterSetterOnlyGetter()
-{
- QList<TestDocumentPtr> testDocuments;
- QByteArray original;
- QByteArray expected;
-
- // Header File
- original =
- "class Foo\n"
- "{\n"
- "public:\n"
- " int bar@;\n"
- "};\n";
- expected =
- "class Foo\n"
- "{\n"
- "public:\n"
- " int bar@;\n"
- " int getBar() const;\n"
- "};\n";
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- // Source File
- original.resize(0);
- expected =
- "\n"
- "int Foo::getBar() const\n"
- "{\n"
- " return bar;\n"
- "}\n";
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
- QuickFixSettings s;
- s->getterInCppFileFrom = 1;
- s->getterNameTemplate = "get<Name>";
- GenerateGetterSetter factory;
- QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 1);
-}
-
-/// Checks: Only generate setter
-void QuickfixTest::testGenerateGetterSetterOnlySetter()
-{
- QList<TestDocumentPtr> testDocuments;
- QByteArray original;
- QByteArray expected;
- QuickFixSettings s;
- s->setterAsSlot = true; // To be ignored, as we don't have QObjects here.
-
- // Header File
- original =
- "class Foo\n"
- "{\n"
- "public:\n"
- " int bar@;\n"
- "};\n";
- expected =
- "class Foo\n"
- "{\n"
- "public:\n"
- " int bar@;\n"
- " void setBar(int value);\n"
- "};\n";
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- // Source File
- original.resize(0);
- expected =
- "\n"
- "void Foo::setBar(int value)\n"
- "{\n"
- " bar = value;\n"
- "}\n";
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
- s->setterInCppFileFrom = 1;
- s->setterParameterNameTemplate = "value";
-
- GenerateGetterSetter factory;
- QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 0);
-}
-
-void QuickfixTest::testGenerateGetterSetterAnonymousClass()
-{
- QList<TestDocumentPtr> testDocuments;
- QByteArray original;
- QByteArray expected;
- QuickFixSettings s;
- s->setterInCppFileFrom = 1;
- s->setterParameterNameTemplate = "value";
-
- // Header File
- original = R"(
- class {
- int @m_foo;
- } bar;
-)";
- expected = R"(
- class {
- int m_foo;
-
- public:
- int foo() const
- {
- return m_foo;
- }
- void setFoo(int value)
- {
- if (m_foo == value)
- return;
- m_foo = value;
- emit fooChanged();
- }
- void resetFoo()
- {
- setFoo({}); // TODO: Adapt to use your actual default defaultValue
- }
-
- signals:
- void fooChanged();
-
- private:
- Q_PROPERTY(int foo READ foo WRITE setFoo RESET resetFoo NOTIFY fooChanged FINAL)
- } bar;
-)";
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- // Source File
- testDocuments << CppTestDocument::create("file.cpp", {}, {});
-
- GenerateGetterSetter factory;
- QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 4);
-}
-
-void QuickfixTest::testGenerateGetterSetterInlineInHeaderFile()
-{
- QList<TestDocumentPtr> testDocuments;
- const QByteArray original = R"-(
-class Foo {
-public:
- int bar@;
-};
-)-";
- const QByteArray expected = R"-(
-class Foo {
-public:
- int bar;
- int getBar() const;
- void setBar(int value);
- void resetBar();
-signals:
- void barChanged();
-private:
- Q_PROPERTY(int bar READ getBar WRITE setBar RESET resetBar NOTIFY barChanged FINAL)
-};
-
-inline int Foo::getBar() const
-{
- return bar;
-}
-
-inline void Foo::setBar(int value)
-{
- if (bar == value)
- return;
- bar = value;
- emit barChanged();
-}
-
-inline void Foo::resetBar()
-{
- setBar({}); // TODO: Adapt to use your actual default defaultValue
-}
-)-";
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- QuickFixSettings s;
- s->setterOutsideClassFrom = 1;
- s->getterOutsideClassFrom = 1;
- s->setterParameterNameTemplate = "value";
- s->getterNameTemplate = "get<Name>";
-
- GenerateGetterSetter factory;
- QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 4);
-}
-
-void QuickfixTest::testGenerateGetterSetterOnlySetterHeaderFileWithIncludeGuard()
-{
- QList<TestDocumentPtr> testDocuments;
- const QByteArray original =
- "#ifndef FILE__H__DECLARED\n"
- "#define FILE__H__DECLARED\n"
- "class Foo\n"
- "{\n"
- "public:\n"
- " int bar@;\n"
- "};\n"
- "#endif\n";
- const QByteArray expected =
- "#ifndef FILE__H__DECLARED\n"
- "#define FILE__H__DECLARED\n"
- "class Foo\n"
- "{\n"
- "public:\n"
- " int bar@;\n"
- " void setBar(int value);\n"
- "};\n\n"
- "inline void Foo::setBar(int value)\n"
- "{\n"
- " bar = value;\n"
- "}\n"
- "#endif\n";
-
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- QuickFixSettings s;
- s->setterOutsideClassFrom = 1;
- s->setterParameterNameTemplate = "value";
-
- GenerateGetterSetter factory;
- QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 0);
-}
-
-void QuickfixTest::testGenerateGetterFunctionAsTemplateArg()
-{
- QList<TestDocumentPtr> testDocuments;
- const QByteArray original = R"(
-template<typename T> class TS {};
-template<typename T, typename U> class TS<T(U)> {};
-
-class S2 {
- TS<int(int)> @member;
-};
-)";
- const QByteArray expected = R"(
-template<typename T> class TS {};
-template<typename T, typename U> class TS<T(U)> {};
-
-class S2 {
- TS<int(int)> member;
-
-public:
- const TS<int (int)> &getMember() const
- {
- return member;
- }
-};
-)";
-
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- QuickFixSettings s;
- s->getterOutsideClassFrom = 0;
- s->getterInCppFileFrom = 0;
- s->getterNameTemplate = "get<Name>";
- s->returnByConstRef = true;
-
- GenerateGetterSetter factory;
- QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 1);
-}
-
-class CppCodeStyleSettingsChanger {
-public:
- CppCodeStyleSettingsChanger(const CppCodeStyleSettings &settings);
- ~CppCodeStyleSettingsChanger(); // Restore original
-
- static CppCodeStyleSettings currentSettings();
-
-private:
- void setSettings(const CppCodeStyleSettings &settings);
-
- CppCodeStyleSettings m_originalSettings;
-};
-
-CppCodeStyleSettingsChanger::CppCodeStyleSettingsChanger(const CppCodeStyleSettings &settings)
-{
- m_originalSettings = currentSettings();
- setSettings(settings);
-}
-
-CppCodeStyleSettingsChanger::~CppCodeStyleSettingsChanger()
-{
- setSettings(m_originalSettings);
-}
-
-void CppCodeStyleSettingsChanger::setSettings(const CppCodeStyleSettings &settings)
-{
- QVariant variant;
- variant.setValue(settings);
-
- CppToolsSettings::cppCodeStyle()->currentDelegate()->setValue(variant);
-}
-
-CppCodeStyleSettings CppCodeStyleSettingsChanger::currentSettings()
-{
- return CppToolsSettings::cppCodeStyle()->currentDelegate()->value().value<CppCodeStyleSettings>();
-}
-
-void QuickfixTest::testGenerateGettersSetters_data()
-{
- QTest::addColumn<QByteArray>("original");
- QTest::addColumn<QByteArray>("expected");
-
- const QByteArray onlyReset = R"(
-class Foo {
-public:
- int bar() const;
- void setBar(int bar);
-private:
- int m_bar;
-@};)";
-
- const QByteArray onlyResetAfter = R"(
-class @Foo {
-public:
- int bar() const;
- void setBar(int bar);
- void resetBar();
-
-private:
- int m_bar;
-};
-inline void Foo::resetBar()
-{
- setBar({}); // TODO: Adapt to use your actual default defaultValue
-}
-)";
- QTest::addRow("only reset") << onlyReset << onlyResetAfter;
-
- const QByteArray withCandidates = R"(
-class @Foo {
-public:
- int bar() const;
- void setBar(int bar) { m_bar = bar; }
-
- int getBar2() const;
-
- int m_alreadyPublic;
-
-private:
- friend void distraction();
- class AnotherDistraction {};
- enum EvenMoreDistraction { val1, val2 };
-
- int m_bar;
- int bar2_;
- QString bar3;
-};)";
- const QByteArray after = R"(
-class Foo {
-public:
- int bar() const;
- void setBar(int bar) { m_bar = bar; }
-
- int getBar2() const;
-
- int m_alreadyPublic;
-
- void resetBar();
- void setBar2(int value);
- void resetBar2();
- const QString &getBar3() const;
- void setBar3(const QString &value);
- void resetBar3();
-
-signals:
- void bar2Changed();
- void bar3Changed();
-
-private:
- friend void distraction();
- class AnotherDistraction {};
- enum EvenMoreDistraction { val1, val2 };
-
- int m_bar;
- int bar2_;
- QString bar3;
- Q_PROPERTY(int bar2 READ getBar2 WRITE setBar2 RESET resetBar2 NOTIFY bar2Changed FINAL)
- Q_PROPERTY(QString bar3 READ getBar3 WRITE setBar3 RESET resetBar3 NOTIFY bar3Changed FINAL)
-};
-inline void Foo::resetBar()
-{
- setBar({}); // TODO: Adapt to use your actual default defaultValue
-}
-
-inline void Foo::setBar2(int value)
-{
- if (bar2_ == value)
- return;
- bar2_ = value;
- emit bar2Changed();
-}
-
-inline void Foo::resetBar2()
-{
- setBar2({}); // TODO: Adapt to use your actual default defaultValue
-}
-
-inline const QString &Foo::getBar3() const
-{
- return bar3;
-}
-
-inline void Foo::setBar3(const QString &value)
-{
- if (bar3 == value)
- return;
- bar3 = value;
- emit bar3Changed();
-}
-
-inline void Foo::resetBar3()
-{
- setBar3({}); // TODO: Adapt to use your actual default defaultValue
-}
-)";
- QTest::addRow("with candidates") << withCandidates << after;
-}
-
-void QuickfixTest::testGenerateGettersSetters()
-{
- class TestFactory : public GenerateGettersSettersForClass
- {
- public:
- TestFactory() { setTest(); }
- };
-
- QFETCH(QByteArray, original);
- QFETCH(QByteArray, expected);
-
- QuickFixSettings s;
- s->getterNameTemplate = "get<Name>";
- s->setterParameterNameTemplate = "value";
- s->setterOutsideClassFrom = 1;
- s->getterOutsideClassFrom = 1;
- s->returnByConstRef = true;
-
- TestFactory factory;
- QuickFixOperationTest({CppTestDocument::create("file.h", original, expected)}, &factory);
-}
-
-void QuickfixTest::testInsertQtPropertyMembers_data()
-{
- QTest::addColumn<QByteArray>("original");
- QTest::addColumn<QByteArray>("expected");
-
- QTest::newRow("InsertQtPropertyMembers")
- << _("struct QObject { void connect(); }\n"
- "struct XmarksTheSpot : public QObject {\n"
- " @Q_PROPERTY(int it READ getIt WRITE setIt RESET resetIt NOTIFY itChanged)\n"
- "};\n")
- << _("struct QObject { void connect(); }\n"
- "struct XmarksTheSpot : public QObject {\n"
- " Q_PROPERTY(int it READ getIt WRITE setIt RESET resetIt NOTIFY itChanged)\n"
- "\n"
- "public:\n"
- " int getIt() const;\n"
- "\n"
- "public slots:\n"
- " void setIt(int it)\n"
- " {\n"
- " if (m_it == it)\n"
- " return;\n"
- " m_it = it;\n"
- " emit itChanged(m_it);\n"
- " }\n"
- " void resetIt()\n"
- " {\n"
- " setIt({}); // TODO: Adapt to use your actual default value\n"
- " }\n"
- "\n"
- "signals:\n"
- " void itChanged(int it);\n"
- "\n"
- "private:\n"
- " int m_it;\n"
- "};\n"
- "\n"
- "int XmarksTheSpot::getIt() const\n"
- "{\n"
- " return m_it;\n"
- "}\n");
-
- QTest::newRow("InsertQtPropertyMembersResetWithoutSet")
- << _("struct QObject { void connect(); }\n"
- "struct XmarksTheSpot : public QObject {\n"
- " @Q_PROPERTY(int it READ getIt RESET resetIt NOTIFY itChanged)\n"
- "};\n")
- << _("struct QObject { void connect(); }\n"
- "struct XmarksTheSpot : public QObject {\n"
- " Q_PROPERTY(int it READ getIt RESET resetIt NOTIFY itChanged)\n"
- "\n"
- "public:\n"
- " int getIt() const;\n"
- "\n"
- "public slots:\n"
- " void resetIt()\n"
- " {\n"
- " static int defaultValue{}; // TODO: Adapt to use your actual default "
- "value\n"
- " if (m_it == defaultValue)\n"
- " return;\n"
- " m_it = defaultValue;\n"
- " emit itChanged(m_it);\n"
- " }\n"
- "\n"
- "signals:\n"
- " void itChanged(int it);\n"
- "\n"
- "private:\n"
- " int m_it;\n"
- "};\n"
- "\n"
- "int XmarksTheSpot::getIt() const\n"
- "{\n"
- " return m_it;\n"
- "}\n");
-
- QTest::newRow("InsertQtPropertyMembersResetWithoutSetAndNotify")
- << _("struct QObject { void connect(); }\n"
- "struct XmarksTheSpot : public QObject {\n"
- " @Q_PROPERTY(int it READ getIt RESET resetIt)\n"
- "};\n")
- << _("struct QObject { void connect(); }\n"
- "struct XmarksTheSpot : public QObject {\n"
- " Q_PROPERTY(int it READ getIt RESET resetIt)\n"
- "\n"
- "public:\n"
- " int getIt() const;\n"
- "\n"
- "public slots:\n"
- " void resetIt()\n"
- " {\n"
- " static int defaultValue{}; // TODO: Adapt to use your actual default "
- "value\n"
- " m_it = defaultValue;\n"
- " }\n"
- "\n"
- "private:\n"
- " int m_it;\n"
- "};\n"
- "\n"
- "int XmarksTheSpot::getIt() const\n"
- "{\n"
- " return m_it;\n"
- "}\n");
-
- QTest::newRow("InsertQtPropertyMembersPrivateBeforePublic")
- << _("struct QObject { void connect(); }\n"
- "class XmarksTheSpot : public QObject {\n"
- "private:\n"
- " @Q_PROPERTY(int it READ getIt WRITE setIt NOTIFY itChanged)\n"
- "public:\n"
- " void find();\n"
- "};\n")
- << _("struct QObject { void connect(); }\n"
- "class XmarksTheSpot : public QObject {\n"
- "private:\n"
- " Q_PROPERTY(int it READ getIt WRITE setIt NOTIFY itChanged)\n"
- " int m_it;\n"
- "\n"
- "public:\n"
- " void find();\n"
- " int getIt() const;\n"
- "public slots:\n"
- " void setIt(int it)\n"
- " {\n"
- " if (m_it == it)\n"
- " return;\n"
- " m_it = it;\n"
- " emit itChanged(m_it);\n"
- " }\n"
- "signals:\n"
- " void itChanged(int it);\n"
- "};\n"
- "\n"
- "int XmarksTheSpot::getIt() const\n"
- "{\n"
- " return m_it;\n"
- "}\n");
-}
-
-void QuickfixTest::testInsertQtPropertyMembers()
-{
- QFETCH(QByteArray, original);
- QFETCH(QByteArray, expected);
-
- QuickFixSettings s;
- s->setterAsSlot = true;
- s->setterInCppFileFrom = 0;
- s->setterParameterNameTemplate = "<name>";
- s->signalWithNewValue = true;
-
- InsertQtPropertyMembers factory;
- QuickFixOperationTest({CppTestDocument::create("file.cpp", original, expected)}, &factory);
-}
-
-void QuickfixTest::testInsertMemberFromUse_data()
-{
- QTest::addColumn<QByteArray>("original");
- QTest::addColumn<QByteArray>("expected");
-
- QByteArray original;
- QByteArray expected;
-
- original =
- "class C {\n"
- "public:\n"
- " C(int x) : @m_x(x) {}\n"
- "private:\n"
- " int m_y;\n"
- "};\n";
- expected =
- "class C {\n"
- "public:\n"
- " C(int x) : m_x(x) {}\n"
- "private:\n"
- " int m_y;\n"
- " int m_x;\n"
- "};\n";
- QTest::addRow("inline constructor") << original << expected;
-
- original =
- "class C {\n"
- "public:\n"
- " C(int x, double d);\n"
- "private:\n"
- " int m_x;\n"
- "};\n"
- "C::C(int x, double d) : m_x(x), @m_d(d)\n";
- expected =
- "class C {\n"
- "public:\n"
- " C(int x, double d);\n"
- "private:\n"
- " int m_x;\n"
- " double m_d;\n"
- "};\n"
- "C::C(int x, double d) : m_x(x), m_d(d)\n";
- QTest::addRow("out-of-line constructor") << original << expected;
-
- original =
- "class C {\n"
- "public:\n"
- " C(int x) : @m_x(x) {}\n"
- "private:\n"
- " int m_x;\n"
- "};\n";
- expected = "";
- QTest::addRow("member already present") << original << expected;
-
- original =
- "int func() { return 0; }\n"
- "class C {\n"
- "public:\n"
- " C() : @m_x(func()) {}\n"
- "private:\n"
- " int m_y;\n"
- "};\n";
- expected =
- "int func() { return 0; }\n"
- "class C {\n"
- "public:\n"
- " C() : m_x(func()) {}\n"
- "private:\n"
- " int m_y;\n"
- " int m_x;\n"
- "};\n";
- QTest::addRow("initialization via function call") << original << expected;
-
- original =
- "struct S {\n\n};\n"
- "class C {\n"
- "public:\n"
- " void setValue(int v) { m_s.@value = v; }\n"
- "private:\n"
- " S m_s;\n"
- "};\n";
- expected =
- "struct S {\n\n"
- " int value;\n"
- "};\n"
- "class C {\n"
- "public:\n"
- " void setValue(int v) { m_s.value = v; }\n"
- "private:\n"
- " S m_s;\n"
- "};\n";
- QTest::addRow("add member to other struct") << original << expected;
-
- original =
- "struct S {\n\n};\n"
- "class C {\n"
- "public:\n"
- " void setValue(int v) { S::@value = v; }\n"
- "};\n";
- expected =
- "struct S {\n\n"
- " static int value;\n"
- "};\n"
- "class C {\n"
- "public:\n"
- " void setValue(int v) { S::value = v; }\n"
- "};\n";
- QTest::addRow("add static member to other struct (explicit)") << original << expected;
-
- original =
- "struct S {\n\n};\n"
- "class C {\n"
- "public:\n"
- " void setValue(int v) { m_s.@value = v; }\n"
- "private:\n"
- " static S m_s;\n"
- "};\n";
- expected =
- "struct S {\n\n"
- " static int value;\n"
- "};\n"
- "class C {\n"
- "public:\n"
- " void setValue(int v) { m_s.value = v; }\n"
- "private:\n"
- " static S m_s;\n"
- "};\n";
- QTest::addRow("add static member to other struct (implicit)") << original << expected;
-
- original =
- "class C {\n"
- "public:\n"
- " void setValue(int v);\n"
- "};\n"
- "void C::setValue(int v) { this->@m_value = v; }\n";
- expected =
- "class C {\n"
- "public:\n"
- " void setValue(int v);\n"
- "private:\n"
- " int m_value;\n"
- "};\n"
- "void C::setValue(int v) { this->@m_value = v; }\n";
- QTest::addRow("add member to this (explicit)") << original << expected;
-
- original =
- "class C {\n"
- "public:\n"
- " void setValue(int v) { @m_value = v; }\n"
- "};\n";
- expected =
- "class C {\n"
- "public:\n"
- " void setValue(int v) { m_value = v; }\n"
- "private:\n"
- " int m_value;\n"
- "};\n";
- QTest::addRow("add member to this (implicit)") << original << expected;
-
- original =
- "class C {\n"
- "public:\n"
- " static void setValue(int v) { @m_value = v; }\n"
- "};\n";
- expected =
- "class C {\n"
- "public:\n"
- " static void setValue(int v) { m_value = v; }\n"
- "private:\n"
- " static int m_value;\n"
- "};\n";
- QTest::addRow("add static member to this (inline)") << original << expected;
-
- original =
- "class C {\n"
- "public:\n"
- " static void setValue(int v);\n"
- "};\n"
- "void C::setValue(int v) { @m_value = v; }\n";
- expected =
- "class C {\n"
- "public:\n"
- " static void setValue(int v);\n"
- "private:\n"
- " static int m_value;\n"
- "};\n"
- "void C::setValue(int v) { @m_value = v; }\n";
- QTest::addRow("add static member to this (non-inline)") << original << expected;
-
- original =
- "struct S {\n\n};\n"
- "class C {\n"
- "public:\n"
- " void setValue(int v) { m_s.@setValue(v); }\n"
- "private:\n"
- " S m_s;\n"
- "};\n";
- expected =
- "struct S {\n\n"
- " void setValue(int);\n"
- "};\n"
- "class C {\n"
- "public:\n"
- " void setValue(int v) { m_s.setValue(v); }\n"
- "private:\n"
- " S m_s;\n"
- "};\n";
- QTest::addRow("add member function to other struct") << original << expected;
-
- original =
- "struct S {\n\n};\n"
- "class C {\n"
- "public:\n"
- " void setValue(int v) { S::@setValue(v); }\n"
- "};\n";
- expected =
- "struct S {\n\n"
- " static void setValue(int);\n"
- "};\n"
- "class C {\n"
- "public:\n"
- " void setValue(int v) { S::setValue(v); }\n"
- "};\n";
- QTest::addRow("add static member function to other struct (explicit)") << original << expected;
-
- original =
- "struct S {\n\n};\n"
- "class C {\n"
- "public:\n"
- " void setValue(int v) { m_s.@setValue(v); }\n"
- "private:\n"
- " static S m_s;\n"
- "};\n";
- expected =
- "struct S {\n\n"
- " static void setValue(int);\n"
- "};\n"
- "class C {\n"
- "public:\n"
- " void setValue(int v) { m_s.setValue(v); }\n"
- "private:\n"
- " static S m_s;\n"
- "};\n";
- QTest::addRow("add static member function to other struct (implicit)") << original << expected;
-
- original =
- "class C {\n"
- "public:\n"
- " void setValue(int v);\n"
- "};\n"
- "void C::setValue(int v) { this->@setValueInternal(v); }\n";
- expected =
- "class C {\n"
- "public:\n"
- " void setValue(int v);\n"
- "private:\n"
- " void setValueInternal(int);\n"
- "};\n"
- "void C::setValue(int v) { this->setValueInternal(v); }\n";
- QTest::addRow("add member function to this (explicit)") << original << expected;
-
- original =
- "class C {\n"
- "public:\n"
- " void setValue(int v) { @setValueInternal(v); }\n"
- "};\n";
- expected =
- "class C {\n"
- "public:\n"
- " void setValue(int v) { setValueInternal(v); }\n"
- "private:\n"
- " void setValueInternal(int);\n"
- "};\n";
- QTest::addRow("add member function to this (implicit)") << original << expected;
-
- original =
- "class C {\n"
- "public:\n"
- " int value() const { return @valueInternal(); }\n"
- "};\n";
- expected =
- "class C {\n"
- "public:\n"
- " int value() const { return valueInternal(); }\n"
- "private:\n"
- " int valueInternal() const;\n"
- "};\n";
- QTest::addRow("add const member function to this (implicit)") << original << expected;
-
- original =
- "class C {\n"
- "public:\n"
- " static int value() { int i = @valueInternal(); return i; }\n"
- "};\n";
- expected =
- "class C {\n"
- "public:\n"
- " static int value() { int i = @valueInternal(); return i; }\n"
- "private:\n"
- " static int valueInternal();\n"
- "};\n";
- QTest::addRow("add static member function to this (inline)") << original << expected;
-
- original =
- "class C {\n"
- "public:\n"
- " static int value();\n"
- "};\n"
- "int C::value() { return @valueInternal(); }\n";
- expected =
- "class C {\n"
- "public:\n"
- " static int value();\n"
- "private:\n"
- " static int valueInternal();\n"
- "};\n"
- "int C::value() { return valueInternal(); }\n";
- QTest::addRow("add static member function to this (non-inline)") << original << expected;
-}
-
-void QuickfixTest::testInsertMemberFromUse()
-{
- QFETCH(QByteArray, original);
- QFETCH(QByteArray, expected);
-
- QList<TestDocumentPtr> testDocuments({
- CppTestDocument::create("file.h", original, expected)
- });
-
- AddDeclarationForUndeclaredIdentifier factory;
- factory.setMembersOnly();
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-/// Check if definition is inserted right after class for insert definition outside
-void QuickfixTest::testInsertDefFromDeclAfterClass()
-{
- QList<TestDocumentPtr> testDocuments;
- QByteArray original;
- QByteArray expected;
-
- // Header File
- original =
- "class Foo\n"
- "{\n"
- " Foo();\n"
- " void a@();\n"
- "};\n"
- "\n"
- "class Bar {};\n";
- expected =
- "class Foo\n"
- "{\n"
- " Foo();\n"
- " void a();\n"
- "};\n"
- "\n"
- "inline void Foo::a()\n"
- "{\n\n}\n"
- "\n"
- "class Bar {};\n";
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- // Source File
- original =
- "#include \"file.h\"\n"
- "\n"
- "Foo::Foo()\n"
- "{\n\n"
- "}\n";
- expected = original;
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
- InsertDefFromDecl factory;
- QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 1);
-}
-
-/// Check from header file: If there is a source file, insert the definition in the source file.
-/// Case: Source file is empty.
-void QuickfixTest::testInsertDefFromDeclHeaderSourceBasic1()
-{
- QList<TestDocumentPtr> testDocuments;
-
- QByteArray original;
- QByteArray expected;
-
- // Header File
- original =
- "struct Foo\n"
- "{\n"
- " Foo()@;\n"
- "};\n";
- expected = original;
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- // Source File
- original.resize(0);
- expected =
- "\n"
- "Foo::Foo()\n"
- "{\n\n"
- "}\n"
- ;
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
- InsertDefFromDecl factory;
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-/// Check from header file: If there is a source file, insert the definition in the source file.
-/// Case: Source file is not empty.
-void QuickfixTest::testInsertDefFromDeclHeaderSourceBasic2()
-{
- QList<TestDocumentPtr> testDocuments;
-
- QByteArray original;
- QByteArray expected;
-
- // Header File
- original = "void f(const std::vector<int> &v)@;\n";
- expected = original;
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- // Source File
- original =
- "#include \"file.h\"\n"
- "\n"
- "int x;\n"
- ;
- expected =
- "#include \"file.h\"\n"
- "\n"
- "int x;\n"
- "\n"
- "void f(const std::vector<int> &v)\n"
- "{\n"
- "\n"
- "}\n"
- ;
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
- InsertDefFromDecl factory;
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-/// Check from source file: Insert in source file, not header file.
-void QuickfixTest::testInsertDefFromDeclHeaderSourceBasic3()
-{
- QList<TestDocumentPtr> testDocuments;
-
- QByteArray original;
- QByteArray expected;
-
- // Empty Header File
- testDocuments << CppTestDocument::create("file.h", "", "");
-
- // Source File
- original =
- "struct Foo\n"
- "{\n"
- " Foo()@;\n"
- "};\n";
- expected = original +
- "\n"
- "Foo::Foo()\n"
- "{\n\n"
- "}\n"
- ;
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
- InsertDefFromDecl factory;
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-/// Check from header file: If the class is in a namespace, the added function definition
-/// name must be qualified accordingly.
-void QuickfixTest::testInsertDefFromDeclHeaderSourceNamespace1()
-{
- QList<TestDocumentPtr> testDocuments;
-
- QByteArray original;
- QByteArray expected;
-
- // Header File
- original =
- "namespace N {\n"
- "struct Foo\n"
- "{\n"
- " Foo()@;\n"
- "};\n"
- "}\n";
- expected = original;
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- // Source File
- original.resize(0);
- expected =
- "\n"
- "N::Foo::Foo()\n"
- "{\n\n"
- "}\n"
- ;
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
- InsertDefFromDecl factory;
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-/// Check from header file: If the class is in namespace N and the source file has a
-/// "using namespace N" line, the function definition name must be qualified accordingly.
-void QuickfixTest::testInsertDefFromDeclHeaderSourceNamespace2()
-{
- QList<TestDocumentPtr> testDocuments;
-
- QByteArray original;
- QByteArray expected;
-
- // Header File
- original =
- "namespace N {\n"
- "struct Foo\n"
- "{\n"
- " Foo()@;\n"
- "};\n"
- "}\n";
- expected = original;
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- // Source File
- original =
- "#include \"file.h\"\n"
- "using namespace N;\n"
- ;
- expected = original +
- "\n"
- "Foo::Foo()\n"
- "{\n\n"
- "}\n"
- ;
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
- InsertDefFromDecl factory;
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-/// Check definition insert inside class
-void QuickfixTest::testInsertDefFromDeclInsideClass()
-{
- const QByteArray original =
- "class Foo {\n"
- " void b@ar();\n"
- "};";
- const QByteArray expected =
- "class Foo {\n"
- " void bar()\n"
- " {\n\n"
- " }\n"
- "};";
-
- InsertDefFromDecl factory;
- QuickFixOperationTest(singleDocument(original, expected), &factory, ProjectExplorer::HeaderPaths(),
- 1);
-}
-
-/// Check not triggering when definition exists
-void QuickfixTest::testInsertDefFromDeclNotTriggeringWhenDefinitionExists()
-{
- const QByteArray original =
- "class Foo {\n"
- " void b@ar();\n"
- "};\n"
- "void Foo::bar() {}\n";
-
- InsertDefFromDecl factory;
- QuickFixOperationTest(singleDocument(original, ""), &factory, ProjectExplorer::HeaderPaths(), 1);
-}
-
-/// Find right implementation file.
-void QuickfixTest::testInsertDefFromDeclFindRightImplementationFile()
-{
- QList<TestDocumentPtr> testDocuments;
-
- QByteArray original;
- QByteArray expected;
-
- // Header File
- original =
- "struct Foo\n"
- "{\n"
- " Foo();\n"
- " void a();\n"
- " void b@();\n"
- "};\n"
- "}\n";
- expected = original;
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- // Source File #1
- original =
- "#include \"file.h\"\n"
- "\n"
- "Foo::Foo()\n"
- "{\n\n"
- "}\n";
- expected = original;
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
-
- // Source File #2
- original =
- "#include \"file.h\"\n"
- "\n"
- "void Foo::a()\n"
- "{\n\n"
- "}\n";
- expected = original +
- "\n"
- "void Foo::b()\n"
- "{\n\n"
- "}\n";
- testDocuments << CppTestDocument::create("file2.cpp", original, expected);
-
- InsertDefFromDecl factory;
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-/// Ignore generated functions declarations when looking at the surrounding
-/// functions declarations in order to find the right implementation file.
-void QuickfixTest::testInsertDefFromDeclIgnoreSurroundingGeneratedDeclarations()
-{
- QList<TestDocumentPtr> testDocuments;
-
- QByteArray original;
- QByteArray expected;
-
- // Header File
- original =
- "#define DECLARE_HIDDEN_FUNCTION void hidden();\n"
- "struct Foo\n"
- "{\n"
- " void a();\n"
- " DECLARE_HIDDEN_FUNCTION\n"
- " void b@();\n"
- "};\n"
- "}\n";
- expected = original;
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- // Source File #1
- original =
- "#include \"file.h\"\n"
- "\n"
- "void Foo::a()\n"
- "{\n\n"
- "}\n";
- expected =
- "#include \"file.h\"\n"
- "\n"
- "void Foo::a()\n"
- "{\n\n"
- "}\n"
- "\n"
- "void Foo::b()\n"
- "{\n\n"
- "}\n";
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
- // Source File #2
- original =
- "#include \"file.h\"\n"
- "\n"
- "void Foo::hidden()\n"
- "{\n\n"
- "}\n";
- expected = original;
- testDocuments << CppTestDocument::create("file2.cpp", original, expected);
-
- InsertDefFromDecl factory;
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-/// Check if whitespace is respected for operator functions
-void QuickfixTest::testInsertDefFromDeclRespectWsInOperatorNames1()
-{
- QByteArray original =
- "class Foo\n"
- "{\n"
- " Foo &opera@tor =();\n"
- "};\n";
- QByteArray expected =
- "class Foo\n"
- "{\n"
- " Foo &operator =();\n"
- "};\n"
- "\n"
- "Foo &Foo::operator =()\n"
- "{\n"
- "\n"
- "}\n";
-
- InsertDefFromDecl factory;
- QuickFixOperationTest(singleDocument(original, expected), &factory);
-}
-
-/// Check if whitespace is respected for operator functions
-void QuickfixTest::testInsertDefFromDeclRespectWsInOperatorNames2()
-{
- QByteArray original =
- "class Foo\n"
- "{\n"
- " Foo &opera@tor=();\n"
- "};\n";
- QByteArray expected =
- "class Foo\n"
- "{\n"
- " Foo &operator=();\n"
- "};\n"
- "\n"
- "Foo &Foo::operator=()\n"
- "{\n"
- "\n"
- "}\n";
-
- InsertDefFromDecl factory;
- QuickFixOperationTest(singleDocument(original, expected), &factory);
-}
-
-/// Check that the noexcept exception specifier is transferred
-void QuickfixTest::testInsertDefFromDeclNoexceptSpecifier()
-{
- QByteArray original =
- "class Foo\n"
- "{\n"
- " void @foo() noexcept(false);\n"
- "};\n";
- QByteArray expected =
- "class Foo\n"
- "{\n"
- " void foo() noexcept(false);\n"
- "};\n"
- "\n"
- "void Foo::foo() noexcept(false)\n"
- "{\n"
- "\n"
- "}\n";
-
- InsertDefFromDecl factory;
- QuickFixOperationTest(singleDocument(original, expected), &factory);
-}
-
-/// Check if a function like macro use is not separated by the function to insert
-/// Case: Macro preceded by preproceesor directives and declaration.
-void QuickfixTest::testInsertDefFromDeclMacroUsesAtEndOfFile1()
-{
- QList<TestDocumentPtr> testDocuments;
-
- QByteArray original;
- QByteArray expected;
-
- // Header File
- original = "void f()@;\n";
- expected = original;
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- // Source File
- original =
- "#include \"file.h\"\n"
- "#define MACRO(X) X x;\n"
- "int lala;\n"
- "\n"
- "MACRO(int)\n"
- ;
- expected =
- "#include \"file.h\"\n"
- "#define MACRO(X) X x;\n"
- "int lala;\n"
- "\n"
- "\n"
- "\n"
- "void f()\n"
- "{\n"
- "\n"
- "}\n"
- "\n"
- "MACRO(int)\n"
- ;
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
- InsertDefFromDecl factory;
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-/// Check if a function like macro use is not separated by the function to insert
-/// Case: Marco preceded only by preprocessor directives.
-void QuickfixTest::testInsertDefFromDeclMacroUsesAtEndOfFile2()
-{
- QList<TestDocumentPtr> testDocuments;
-
- QByteArray original;
- QByteArray expected;
-
- // Header File
- original = "void f()@;\n";
- expected = original;
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- // Source File
- original =
- "#include \"file.h\"\n"
- "#define MACRO(X) X x;\n"
- "\n"
- "MACRO(int)\n"
- ;
- expected =
- "#include \"file.h\"\n"
- "#define MACRO(X) X x;\n"
- "\n"
- "\n"
- "\n"
- "void f()\n"
- "{\n"
- "\n"
- "}\n"
- "\n"
- "MACRO(int)\n"
- ;
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
- InsertDefFromDecl factory;
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-/// Check if insertion happens before syntactically erroneous statements at end of file.
-void QuickfixTest::testInsertDefFromDeclErroneousStatementAtEndOfFile()
-{
- QList<TestDocumentPtr> testDocuments;
-
- QByteArray original;
- QByteArray expected;
-
- // Header File
- original = "void f()@;\n";
- expected = original;
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- // Source File
- original =
- "#include \"file.h\"\n"
- "\n"
- "MissingSemicolon(int)\n"
- ;
- expected =
- "#include \"file.h\"\n"
- "\n"
- "\n"
- "\n"
- "void f()\n"
- "{\n"
- "\n"
- "}\n"
- "\n"
- "MissingSemicolon(int)\n"
- ;
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
- InsertDefFromDecl factory;
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-/// Check: Respect rvalue references
-void QuickfixTest::testInsertDefFromDeclRvalueReference()
-{
- QList<TestDocumentPtr> testDocuments;
-
- QByteArray original;
- QByteArray expected;
-
- // Header File
- original = "void f(Foo &&)@;\n";
- expected = original;
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- // Source File
- original = "";
- expected =
- "\n"
- "void f(Foo &&)\n"
- "{\n"
- "\n"
- "}\n"
- ;
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
- InsertDefFromDecl factory;
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-void QuickfixTest::testInsertDefFromDeclFunctionTryBlock()
-{
- QList<TestDocumentPtr> testDocuments;
-
- QByteArray original;
- QByteArray expected;
-
- // Header File
- original = R"(
-struct Foo {
- void tryCatchFunc();
- void @otherFunc();
-};
-)";
- expected = original;
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- // Source File
- original = R"(
-#include "file.h"
-
-void Foo::tryCatchFunc() try {} catch (...) {}
-)";
- expected = R"(
-#include "file.h"
-
-void Foo::tryCatchFunc() try {} catch (...) {}
-
-void Foo::otherFunc()
-{
-
-}
-)";
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
- InsertDefFromDecl factory;
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-void QuickfixTest::testInsertDefFromDeclUsingDecl()
-{
- QList<TestDocumentPtr> testDocuments;
-
- QByteArray original;
- QByteArray expected;
-
- // Header File
- original = R"(
-namespace N { struct S; }
-using N::S;
-
-void @func(const S &s);
-)";
- expected = original;
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- // Source File
- original = R"(
-#include "file.h"
-)";
- expected = R"(
-#include "file.h"
-
-void func(const S &s)
-{
-
-}
-)";
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
- InsertDefFromDecl factory;
- QuickFixOperationTest(testDocuments, &factory);
-
- testDocuments.clear();
- original = R"(
-namespace N1 {
-namespace N2 { struct S; }
-using N2::S;
-}
-
-void @func(const N1::S &s);
-)";
- expected = original;
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- // Source File
- original = R"(
-#include "file.h"
-)";
- expected = R"(
-#include "file.h"
-
-void func(const N1::S &s)
-{
-
-}
-)";
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
- QuickFixOperationTest(testDocuments, &factory);
-
- // No using declarations here, but the code model has one. No idea why.
- testDocuments.clear();
- original = R"(
-class B {};
-class D : public B {
- @D();
-};
-)";
- expected = original;
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- // Source File
- original = R"(
-#include "file.h"
-)";
- expected = R"(
-#include "file.h"
-
-D::D()
-{
-
-}
-)";
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
- QuickFixOperationTest(testDocuments, &factory);
-
- testDocuments.clear();
- original = R"(
-namespace ns1 { template<typename T> class span {}; }
-
-namespace ns {
-using ns1::span;
-class foo
-{
- void @bar(ns::span<int>);
-};
-}
-)";
- expected = R"(
-namespace ns1 { template<typename T> class span {}; }
-
-namespace ns {
-using ns1::span;
-class foo
-{
- void bar(ns::span<int>);
-};
-
-void foo::bar(ns::span<int>)
-{
-
-}
-
-}
-)";
- // TODO: Unneeded namespace gets inserted in RewriteName::visit(const QualifiedNameId *)
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-/// Find right implementation file. (QTCREATORBUG-10728)
-void QuickfixTest::testInsertDefFromDeclFindImplementationFile()
-{
- QList<TestDocumentPtr> testDocuments;
-
- QByteArray original;
- QByteArray expected;
-
- // Header File
- original =
- "class Foo {\n"
- " void bar();\n"
- " void ba@z();\n"
- "};\n"
- "\n"
- "void Foo::bar()\n"
- "{}\n";
- expected = original;
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- // Source File
- original =
- "#include \"file.h\"\n"
- ;
- expected =
- "#include \"file.h\"\n"
- "\n"
- "void Foo::baz()\n"
- "{\n"
- "\n"
- "}\n"
- ;
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
- InsertDefFromDecl factory;
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-void QuickfixTest::testInsertDefFromDeclUnicodeIdentifier()
-{
- QList<TestDocumentPtr> testDocuments;
-
- QByteArray original;
- QByteArray expected;
-
- //
- // The following "non-latin1" code points are used in the tests:
- //
- // U+00FC - 2 code units in UTF8, 1 in UTF16 - LATIN SMALL LETTER U WITH DIAERESIS
- // U+4E8C - 3 code units in UTF8, 1 in UTF16 - CJK UNIFIED IDEOGRAPH-4E8C
- // U+10302 - 4 code units in UTF8, 2 in UTF16 - OLD ITALIC LETTER KE
- //
-
-#define UNICODE_U00FC "\xc3\xbc"
-#define UNICODE_U4E8C "\xe4\xba\x8c"
-#define UNICODE_U10302 "\xf0\x90\x8c\x82"
-#define TEST_UNICODE_IDENTIFIER UNICODE_U00FC UNICODE_U4E8C UNICODE_U10302
-
- original =
- "class Foo {\n"
- " void @" TEST_UNICODE_IDENTIFIER "();\n"
- "};\n";
- ;
- expected = original;
- expected +=
- "\n"
- "void Foo::" TEST_UNICODE_IDENTIFIER "()\n"
- "{\n"
- "\n"
- "}\n";
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
-#undef UNICODE_U00FC
-#undef UNICODE_U4E8C
-#undef UNICODE_U10302
-#undef TEST_UNICODE_IDENTIFIER
-
- InsertDefFromDecl factory;
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-void QuickfixTest::testInsertDefFromDeclTemplateClass()
-{
- QByteArray original =
- "template<class T>\n"
- "class Foo\n"
- "{\n"
- " void fun@c1();\n"
- " void func2();\n"
- "};\n\n"
- "template<class T>\n"
- "void Foo<T>::func2() {}\n";
- QByteArray expected =
- "template<class T>\n"
- "class Foo\n"
- "{\n"
- " void func1();\n"
- " void func2();\n"
- "};\n\n"
- "template<class T>\n"
- "void Foo<T>::func1()\n"
- "{\n"
- "\n"
- "}\n\n"
- "template<class T>\n"
- "void Foo<T>::func2() {}\n";
-
- InsertDefFromDecl factory;
- QuickFixOperationTest(singleDocument(original, expected), &factory);
-}
-
-void QuickfixTest::testInsertDefFromDeclTemplateClassWithValueParam()
-{
- QList<TestDocumentPtr> testDocuments;
- QByteArray original =
- "template<typename T, int size> struct MyArray {};\n"
- "MyArray<int, 1> @foo();";
- QByteArray expected = original;
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- original = "#include \"file.h\"\n";
- expected =
- "#include \"file.h\"\n\n"
- "MyArray<int, 1> foo()\n"
- "{\n\n"
- "}\n";
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
- InsertDefFromDecl factory;
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-void QuickfixTest::testInsertDefFromDeclTemplateFunction()
-{
- QByteArray original =
- "class Foo\n"
- "{\n"
- " template<class T>\n"
- " void fun@c();\n"
- "};\n";
- QByteArray expected =
- "class Foo\n"
- "{\n"
- " template<class T>\n"
- " void fun@c();\n"
- "};\n"
- "\n"
- "template<class T>\n"
- "void Foo::func()\n"
- "{\n"
- "\n"
- "}\n";
-
- InsertDefFromDecl factory;
- QuickFixOperationTest(singleDocument(original, expected), &factory);
-}
-
-void QuickfixTest::testInsertDefFromDeclTemplateClassAndTemplateFunction()
-{
- QByteArray original =
- "template<class T>"
- "class Foo\n"
- "{\n"
- " template<class U>\n"
- " T fun@c(U u);\n"
- "};\n";
- QByteArray expected =
- "template<class T>"
- "class Foo\n"
- "{\n"
- " template<class U>\n"
- " T fun@c(U u);\n"
- "};\n"
- "\n"
- "template<class T>\n"
- "template<class U>\n"
- "T Foo<T>::func(U u)\n"
- "{\n"
- "\n"
- "}\n";
-
- InsertDefFromDecl factory;
- QuickFixOperationTest(singleDocument(original, expected), &factory);
-}
-
-void QuickfixTest::testInsertDefFromDeclTemplateClassAndFunctionInsideNamespace()
-{
- QByteArray original =
- "namespace N {\n"
- "template<class T>"
- "class Foo\n"
- "{\n"
- " template<class U>\n"
- " T fun@c(U u);\n"
- "};\n"
- "}\n";
- QByteArray expected =
- "namespace N {\n"
- "template<class T>"
- "class Foo\n"
- "{\n"
- " template<class U>\n"
- " T fun@c(U u);\n"
- "};\n"
- "\n"
- "template<class T>\n"
- "template<class U>\n"
- "T Foo<T>::func(U u)\n"
- "{\n"
- "\n"
- "}\n"
- "\n"
- "}\n";
-
- InsertDefFromDecl factory;
- QuickFixOperationTest(singleDocument(original, expected), &factory);
-}
-
-void QuickfixTest::testInsertDefFromDeclFunctionWithSignedUnsignedArgument()
-{
- QByteArray original;
- QByteArray expected;
- InsertDefFromDecl factory;
-
- original =R"--(
-class myclass
-{
- myc@lass(QVector<signed> g);
- myclass(QVector<unsigned> g);
-}
-)--";
- expected =R"--(
-class myclass
-{
- myclass(QVector<signed> g);
- myclass(QVector<unsigned> g);
-}
-
-myclass::myclass(QVector<signed int> g)
-{
-
-}
-)--";
-
- QuickFixOperationTest(singleDocument(original, expected), &factory);
-
- original =R"--(
-class myclass
-{
- myclass(QVector<signed> g);
- myc@lass(QVector<unsigned> g);
-}
-)--";
- expected =R"--(
-class myclass
-{
- myclass(QVector<signed> g);
- myclass(QVector<unsigned> g);
-}
-
-myclass::myclass(QVector<unsigned int> g)
-{
-
-}
-)--";
-
- QuickFixOperationTest(singleDocument(original, expected), &factory);
-
- original =R"--(
-class myclass
-{
- unsigned f@oo(unsigned);
-}
-)--";
- expected =R"--(
-class myclass
-{
- unsigned foo(unsigned);
-}
-
-unsigned int myclass::foo(unsigned int)
-{
-
-}
-)--";
- QuickFixOperationTest(singleDocument(original, expected), &factory);
-
- original =R"--(
-class myclass
-{
- signed f@oo(signed);
-}
-)--";
- expected =R"--(
-class myclass
-{
- signed foo(signed);
-}
-
-signed int myclass::foo(signed int)
-{
-
-}
-)--";
- QuickFixOperationTest(singleDocument(original, expected), &factory);
-}
-
-void QuickfixTest::testInsertDefFromDeclNotTriggeredForFriendFunc()
-{
- const QByteArray contents =
- "class Foo\n"
- "{\n"
- " friend void f@unc();\n"
- "};\n"
- "\n";
-
- InsertDefFromDecl factory;
- QuickFixOperationTest(singleDocument(contents, ""), &factory);
-}
-
-void QuickfixTest::testInsertDefFromDeclMinimalFunctionParameterType()
-{
- QList<TestDocumentPtr> testDocuments;
-
- QByteArray original;
- QByteArray expected;
-
- // Header File
- original = R"(
-class C {
- typedef int A;
- A @foo(A);
-};
-)";
- expected = original;
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- // Source File
- original = R"(
-#include "file.h"
-)";
- expected = R"(
-#include "file.h"
-
-C::A C::foo(A)
-{
-
-}
-)";
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
- InsertDefFromDecl factory;
- QuickFixOperationTest(testDocuments, &factory);
-
- testDocuments.clear();
- // Header File
- original = R"(
-namespace N {
- struct S;
- S @foo(const S &s);
-};
-)";
- expected = original;
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- // Source File
- original = R"(
-#include "file.h"
-)";
- expected = R"(
-#include "file.h"
-
-N::S N::foo(const S &s)
-{
-
-}
-)";
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-void QuickfixTest::testInsertDefFromDeclAliasTemplateAsReturnType()
-{
- QList<TestDocumentPtr> testDocuments;
-
- QByteArray original;
- QByteArray expected;
-
- // Header File
- original = R"(
-struct foo {
- struct foo2 {
- template <typename T> using MyType = T;
- MyType<int> @bar();
- };
-};
-)";
- expected = original;
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- // Source File
- original = R"(
-#include "file.h"
-)";
- expected = R"(
-#include "file.h"
-
-foo::foo2::MyType<int> foo::foo2::bar()
-{
-
-}
-)";
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
- InsertDefFromDecl factory;
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-void QuickfixTest::testInsertDefsFromDecls_data()
-{
- QTest::addColumn<QByteArrayList>("headers");
- QTest::addColumn<QByteArrayList>("sources");
- QTest::addColumn<int>("mode");
-
- QByteArray origHeader = R"(
-namespace N {
-class @C
-{
-public:
- friend void ignoredFriend();
- void ignoredImplemented() {};
- void ignoredImplemented2(); // Below
- void ignoredImplemented3(); // In cpp file
- void funcNotSelected();
- void funcInline();
- void funcBelow();
- void funcCppFile();
-
-signals:
- void ignoredSignal();
-};
-
-inline void C::ignoredImplemented2() {}
-
-} // namespace N)";
- QByteArray origSource = R"(
-#include "file.h"
-
-namespace N {
-
-void C::ignoredImplemented3() {}
-
-} // namespace N)";
-
- QByteArray expectedHeader = R"(
-namespace N {
-class C
-{
-public:
- friend void ignoredFriend();
- void ignoredImplemented() {};
- void ignoredImplemented2(); // Below
- void ignoredImplemented3(); // In cpp file
- void funcNotSelected();
- void funcInline()
- {
-
- }
- void funcBelow();
- void funcCppFile();
-
-signals:
- void ignoredSignal();
-};
-
-inline void C::ignoredImplemented2() {}
-
-inline void C::funcBelow()
-{
-
-}
-
-} // namespace N)";
- QByteArray expectedSource = R"(
-#include "file.h"
-
-namespace N {
-
-void C::ignoredImplemented3() {}
-
-void C::funcCppFile()
-{
-
-}
-
-} // namespace N)";
- QTest::addRow("normal case")
- << QByteArrayList{origHeader, expectedHeader}
- << QByteArrayList{origSource, expectedSource}
- << int(InsertDefsFromDecls::Mode::Alternating);
- QTest::addRow("aborted dialog")
- << QByteArrayList{origHeader, origHeader}
- << QByteArrayList{origSource, origSource}
- << int(InsertDefsFromDecls::Mode::Off);
-
- origHeader = R"(
- namespace N {
- class @C
- {
- public:
- friend void ignoredFriend();
- void ignoredImplemented() {};
- void ignoredImplemented2(); // Below
- void ignoredImplemented3(); // In cpp file
-
- signals:
- void ignoredSignal();
- };
-
- inline void C::ignoredImplemented2() {}
-
- } // namespace N)";
- QTest::addRow("no candidates")
- << QByteArrayList{origHeader, origHeader}
- << QByteArrayList{origSource, origSource}
- << int(InsertDefsFromDecls::Mode::Alternating);
-
- origHeader = R"(
- namespace N {
- class @C
- {
- public:
- friend void ignoredFriend();
- void ignoredImplemented() {};
-
- signals:
- void ignoredSignal();
- };
- } // namespace N)";
- QTest::addRow("no member functions")
- << QByteArrayList{origHeader, ""}
- << QByteArrayList{origSource, ""}
- << int(InsertDefsFromDecls::Mode::Alternating);
-}
-
-void QuickfixTest::testInsertDefsFromDecls()
-{
- QFETCH(QByteArrayList, headers);
- QFETCH(QByteArrayList, sources);
- QFETCH(int, mode);
-
- QList<TestDocumentPtr> testDocuments({
- CppTestDocument::create("file.h", headers.at(0), headers.at(1)),
- CppTestDocument::create("file.cpp", sources.at(0), sources.at(1))});
- InsertDefsFromDecls factory;
- factory.setMode(static_cast<InsertDefsFromDecls::Mode>(mode));
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-void QuickfixTest::testInsertAndFormatDefsFromDecls()
-{
- if (!isClangFormatPresent())
- QSKIP("This test reqires ClangFormat");
-
- const QByteArray origHeader = R"(
-class @C
-{
-public:
- void func1 (int const &i);
- void func2 (double const d);
-};
-)";
- const QByteArray origSource = R"(
-#include "file.h"
-)";
-
- const QByteArray expectedSource = R"(
-#include "file.h"
-
-void C::func1 (int const &i)
-{
-
-}
-
-void C::func2 (double const d)
-{
-
-}
-)";
-
- const QByteArray clangFormatSettings = R"(
-BreakBeforeBraces: Allman
-QualifierAlignment: Right
-SpaceBeforeParens: Always
-)";
-
- const QList<TestDocumentPtr> testDocuments({
- CppTestDocument::create("file.h", origHeader, origHeader),
- CppTestDocument::create("file.cpp", origSource, expectedSource)});
- InsertDefsFromDecls factory;
- factory.setMode(InsertDefsFromDecls::Mode::Impl);
- CppCodeStylePreferences * const prefs = CppToolsSettings::cppCodeStyle();
- const CppCodeStyleSettings settings = prefs->codeStyleSettings();
- CppCodeStyleSettings tempSettings = settings;
- tempSettings.forceFormatting = true;
- prefs->setCodeStyleSettings(tempSettings);
- QuickFixOperationTest(testDocuments, &factory, {}, {}, {}, clangFormatSettings);
- prefs->setCodeStyleSettings(settings);
-}
-
-QList<TestDocumentPtr> singleHeader(const QByteArray &original, const QByteArray &expected)
-{
- return {CppTestDocument::create("file.h", original, expected)};
-}
-
-void QuickfixTest::testInsertDefOutsideFromDeclTemplateClassAndTemplateFunction()
-{
- QByteArray original =
- "template<class T>"
- "class Foo\n"
- "{\n"
- " template<class U>\n"
- " void fun@c();\n"
- "};\n";
- QByteArray expected =
- "template<class T>"
- "class Foo\n"
- "{\n"
- " template<class U>\n"
- " void fun@c();\n"
- "};\n"
- "\n"
- "template<class T>\n"
- "template<class U>\n"
- "inline void Foo<T>::func()\n"
- "{\n"
- "\n"
- "}\n";
-
- InsertDefFromDecl factory;
- factory.m_defPosOutsideClass = true;
- QuickFixOperationTest(singleHeader(original, expected), &factory);
-}
-
-void QuickfixTest::testInsertDefOutsideFromDeclTemplateClass()
-{
- QByteArray original =
- "template<class T>"
- "class Foo\n"
- "{\n"
- " void fun@c();\n"
- "};\n";
- QByteArray expected =
- "template<class T>"
- "class Foo\n"
- "{\n"
- " void fun@c();\n"
- "};\n"
- "\n"
- "template<class T>\n"
- "inline void Foo<T>::func()\n"
- "{\n"
- "\n"
- "}\n";
-
- InsertDefFromDecl factory;
- factory.m_defPosOutsideClass = true;
- QuickFixOperationTest(singleHeader(original, expected), &factory);
-}
-
-void QuickfixTest::testInsertDefOutsideFromDeclTemplateFunction()
-{
- QByteArray original =
- "class Foo\n"
- "{\n"
- " template<class U>\n"
- " void fun@c();\n"
- "};\n";
- QByteArray expected =
- "class Foo\n"
- "{\n"
- " template<class U>\n"
- " void fun@c();\n"
- "};\n"
- "\n"
- "template<class U>\n"
- "inline void Foo::func()\n"
- "{\n"
- "\n"
- "}\n";
-
- InsertDefFromDecl factory;
- factory.m_defPosOutsideClass = true;
- QuickFixOperationTest(singleHeader(original, expected), &factory);
-}
-
-void QuickfixTest::testInsertDefOutsideFromDeclFunction()
-{
- QByteArray original =
- "class Foo\n"
- "{\n"
- " void fun@c();\n"
- "};\n";
- QByteArray expected =
- "class Foo\n"
- "{\n"
- " void fun@c();\n"
- "};\n"
- "\n"
- "inline void Foo::func()\n"
- "{\n"
- "\n"
- "}\n";
-
- InsertDefFromDecl factory;
- factory.m_defPosOutsideClass = true;
- QuickFixOperationTest(singleHeader(original, expected), &factory);
-}
-
-// Function for one of InsertDeclDef section cases
-void insertToSectionDeclFromDef(const QByteArray &section, int sectionIndex)
-{
- QList<TestDocumentPtr> testDocuments;
-
- QByteArray original;
- QByteArray expected;
- QByteArray sectionString = section + ":\n";
- if (sectionIndex == 4)
- sectionString.clear();
-
- // Header File
- original =
- "class Foo\n"
- "{\n"
- "};\n";
- expected =
- "class Foo\n"
- "{\n"
- + sectionString +
- " Foo();\n"
- "@};\n";
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- // Source File
- original =
- "#include \"file.h\"\n"
- "\n"
- "Foo::Foo@()\n"
- "{\n"
- "}\n"
- ;
- expected = original;
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
- InsertDeclFromDef factory;
- QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), sectionIndex);
-}
-
-/// Check from source file: Insert in header file.
-void QuickfixTest::testInsertDeclFromDef()
-{
- insertToSectionDeclFromDef("public", 0);
- insertToSectionDeclFromDef("public slots", 1);
- insertToSectionDeclFromDef("protected", 2);
- insertToSectionDeclFromDef("protected slots", 3);
- insertToSectionDeclFromDef("private", 4);
- insertToSectionDeclFromDef("private slots", 5);
-}
-
-void QuickfixTest::testInsertDeclFromDefTemplateFuncTypename()
-{
- QByteArray original =
- "class Foo\n"
- "{\n"
- "};\n"
- "\n"
- "template<class T>\n"
- "void Foo::fu@nc() {}\n";
-
- QByteArray expected =
- "class Foo\n"
- "{\n"
- "public:\n"
- " template<class T>\n"
- " void func();\n"
- "};\n"
- "\n"
- "template<class T>\n"
- "void Foo::fu@nc() {}\n";
-
- InsertDeclFromDef factory;
- QuickFixOperationTest(singleDocument(original, expected), &factory, {}, 0);
-}
-
-void QuickfixTest::testInsertDeclFromDefTemplateFuncInt()
-{
- QByteArray original =
- "class Foo\n"
- "{\n"
- "};\n"
- "\n"
- "template<int N>\n"
- "void Foo::fu@nc() {}\n";
-
- QByteArray expected =
- "class Foo\n"
- "{\n"
- "public:\n"
- " template<int N>\n"
- " void func();\n"
- "};\n"
- "\n"
- "template<int N>\n"
- "void Foo::fu@nc() {}\n";
-
- InsertDeclFromDef factory;
- QuickFixOperationTest(singleDocument(original, expected), &factory, {}, 0);
-}
-
-void QuickfixTest::testInsertDeclFromDefTemplateReturnType()
-{
- QByteArray original =
- "class Foo\n"
- "{\n"
- "};\n"
- "\n"
- "std::vector<int> Foo::fu@nc() const {}\n";
-
- QByteArray expected =
- "class Foo\n"
- "{\n"
- "public:\n"
- " std::vector<int> func() const;\n"
- "};\n"
- "\n"
- "std::vector<int> Foo::func() const {}\n";
-
- InsertDeclFromDef factory;
- QuickFixOperationTest(singleDocument(original, expected), &factory, {}, 0);
-}
-
-void QuickfixTest::testInsertDeclFromDefNotTriggeredForTemplateFunc()
-{
- QByteArray contents =
- "class Foo\n"
- "{\n"
- " template<class T>\n"
- " void func();\n"
- "};\n"
- "\n"
- "template<class T>\n"
- "void Foo::fu@nc() {}\n";
-
- InsertDeclFromDef factory;
- QuickFixOperationTest(singleDocument(contents, ""), &factory);
-}
-
-void QuickfixTest::testAddIncludeForUndefinedIdentifier_data()
-{
- QTest::addColumn<QString>("headerPath");
- QTest::addColumn<QuickFixTestDocuments>("testDocuments");
- QTest::addColumn<int>("refactoringOperationIndex");
- QTest::addColumn<QString>("includeForTestFactory");
-
- const int firstRefactoringOperation = 0;
- const int secondRefactoringOperation = 1;
-
- QList<TestDocumentPtr> testDocuments;
- QByteArray original;
- QByteArray expected;
-
- // -------------------------------------------------------------------------------------------
-
- // Header File
- original = "class Foo {};\n";
- expected = original;
- testDocuments << CppTestDocument::create("afile.h", original, expected);
-
- // Source File
- original =
- "#include \"header.h\"\n"
- "\n"
- "void f()\n"
- "{\n"
- " Fo@o foo;\n"
- "}\n"
- ;
- expected =
- "#include \"afile.h\"\n"
- "#include \"header.h\"\n"
- "\n"
- "void f()\n"
- "{\n"
- " Foo foo;\n"
- "}\n"
- ;
- testDocuments << CppTestDocument::create("afile.cpp", original, expected);
- QTest::newRow("onSimpleName")
- << TestIncludePaths::globalIncludePath()
- << testDocuments << firstRefactoringOperation << "";
- testDocuments.clear();
-
- // -------------------------------------------------------------------------------------------
-
- // Header File
- original = "namespace N { class Foo {}; }\n";
- expected = original;
- testDocuments << CppTestDocument::create("afile.h", original, expected);
-
- // Source File
- original =
- "#include \"header.h\"\n"
- "\n"
- "void f()\n"
- "{\n"
- " N::Fo@o foo;\n"
- "}\n"
- ;
- expected =
- "#include \"afile.h\"\n"
- "#include \"header.h\"\n"
- "\n"
- "void f()\n"
- "{\n"
- " N::Foo foo;\n"
- "}\n"
- ;
- testDocuments << CppTestDocument::create("afile.cpp", original, expected);
- QTest::newRow("onNameOfQualifiedName")
- << TestIncludePaths::globalIncludePath()
- << testDocuments << firstRefactoringOperation << "";
- testDocuments.clear();
-
- // -------------------------------------------------------------------------------------------
-
- // Header File
- original = "namespace N { class Foo {}; }\n";
- expected = original;
- testDocuments << CppTestDocument::create("afile.h", original, expected);
-
- // Source File
- original =
- "#include \"header.h\"\n"
- "\n"
- "void f()\n"
- "{\n"
- " @N::Foo foo;\n"
- "}\n"
- ;
- expected =
- "#include \"afile.h\"\n"
- "#include \"header.h\"\n"
- "\n"
- "void f()\n"
- "{\n"
- " N::Foo foo;\n"
- "}\n"
- ;
- testDocuments << CppTestDocument::create("afile.cpp", original, expected);
- QTest::newRow("onBaseOfQualifiedName")
- << TestIncludePaths::globalIncludePath()
- << testDocuments << firstRefactoringOperation << "";
- testDocuments.clear();
-
- // -------------------------------------------------------------------------------------------
-
- // Header File
- original = "class Foo { static void bar() {} };\n";
- expected = original;
- testDocuments << CppTestDocument::create("afile.h", original, expected);
-
- // Source File
- original =
- "#include \"header.h\"\n"
- "\n"
- "void f()\n"
- "{\n"
- " @Foo::bar();\n"
- "}\n"
- ;
- expected =
- "#include \"afile.h\"\n"
- "#include \"header.h\"\n"
- "\n"
- "void f()\n"
- "{\n"
- " Foo::bar();\n"
- "}\n"
- ;
- testDocuments << CppTestDocument::create("afile.cpp", original, expected);
- QTest::newRow("onBaseOfQualifiedClassName")
- << TestIncludePaths::globalIncludePath()
- << testDocuments << firstRefactoringOperation << "";
- testDocuments.clear();
-
- // -------------------------------------------------------------------------------------------
-
- // Header File
- original = "template <typename T> class Foo { static void bar() {} };\n";
- expected = original;
- testDocuments << CppTestDocument::create("afile.h", original, expected);
-
- // Source File
- original =
- "#include \"header.h\"\n"
- "\n"
- "void f()\n"
- "{\n"
- " @Foo<int>::bar();\n"
- "}\n"
- ;
- expected =
- "#include \"afile.h\"\n"
- "#include \"header.h\"\n"
- "\n"
- "void f()\n"
- "{\n"
- " Foo<int>::bar();\n"
- "}\n"
- ;
- testDocuments << CppTestDocument::create("afile.cpp", original, expected);
- QTest::newRow("onBaseOfQualifiedTemplateClassName")
- << TestIncludePaths::globalIncludePath()
- << testDocuments << firstRefactoringOperation << "";
- testDocuments.clear();
-
- // -------------------------------------------------------------------------------------------
-
- // Header File
- original = "namespace N { template <typename T> class Foo {}; }\n";
- expected = original;
- testDocuments << CppTestDocument::create("afile.h", original, expected);
-
- // Source File
- original =
- "#include \"header.h\"\n"
- "\n"
- "void f()\n"
- "{\n"
- " @N::Foo<Bar> foo;\n"
- "}\n"
- ;
- expected =
- "#include \"afile.h\"\n"
- "#include \"header.h\"\n"
- "\n"
- "void f()\n"
- "{\n"
- " N::Foo<Bar> foo;\n"
- "}\n"
- ;
- testDocuments << CppTestDocument::create("afile.cpp", original, expected);
- QTest::newRow("onTemplateName")
- << TestIncludePaths::globalIncludePath()
- << testDocuments << firstRefactoringOperation << "";
- testDocuments.clear();
-
- // -------------------------------------------------------------------------------------------
-
- // Header File
- original = "namespace N { template <typename T> class Foo {}; }\n";
- expected = original;
- testDocuments << CppTestDocument::create("afile.h", original, expected);
-
- // Source File
- original =
- "#include \"header.h\"\n"
- "\n"
- "void f()\n"
- "{\n"
- " N::Bar<@Foo> foo;\n"
- "}\n"
- ;
- expected =
- "#include \"afile.h\"\n"
- "#include \"header.h\"\n"
- "\n"
- "void f()\n"
- "{\n"
- " N::Bar<Foo> foo;\n"
- "}\n"
- ;
- testDocuments << CppTestDocument::create("afile.cpp", original, expected);
- QTest::newRow("onTemplateNameInsideArguments")
- << TestIncludePaths::globalIncludePath()
- << testDocuments << firstRefactoringOperation << "";
- testDocuments.clear();
-
- // -------------------------------------------------------------------------------------------
-
- // Header File
- original = "class Foo {};\n";
- expected = original;
- testDocuments << CppTestDocument::create("afile.h", original, expected);
-
- // Source File
- original =
- "#include \"header.h\"\n"
- "\n"
- "class Foo;\n"
- "\n"
- "void f()\n"
- "{\n"
- " @Foo foo;\n"
- "}\n"
- ;
- expected =
- "#include \"afile.h\"\n"
- "#include \"header.h\"\n"
- "\n"
- "class Foo;\n"
- "\n"
- "void f()\n"
- "{\n"
- " Foo foo;\n"
- "}\n"
- ;
- testDocuments << CppTestDocument::create("afile.cpp", original, expected);
- QTest::newRow("withForwardDeclaration")
- << TestIncludePaths::globalIncludePath()
- << testDocuments << firstRefactoringOperation << "";
- testDocuments.clear();
-
- // -------------------------------------------------------------------------------------------
-
- // Header File
- original = "template<class T> class Foo {};\n";
- expected = original;
- testDocuments << CppTestDocument::create("afile.h", original, expected);
-
- // Source File
- original =
- "#include \"header.h\"\n"
- "\n"
- "template<class T> class Foo;\n"
- "\n"
- "void f()\n"
- "{\n"
- " @Foo foo;\n"
- "}\n"
- ;
- expected =
- "#include \"afile.h\"\n"
- "#include \"header.h\"\n"
- "\n"
- "template<class T> class Foo;\n"
- "\n"
- "void f()\n"
- "{\n"
- " Foo foo;\n"
- "}\n"
- ;
- testDocuments << CppTestDocument::create("afile.cpp", original, expected);
- QTest::newRow("withForwardDeclaration2")
- << TestIncludePaths::globalIncludePath()
- << testDocuments << firstRefactoringOperation << "";
- testDocuments.clear();
-
- // -------------------------------------------------------------------------------------------
-
- // Header File
- original = "template<class T> class QMyClass {};\n";
- expected = original;
- testDocuments << CppTestDocument::create("qmyclass.h", original, expected);
-
- // Forward Header File
- original = "#include \"qmyclass.h\"\n";
- expected = original;
- testDocuments << CppTestDocument::create("QMyClass", original, expected);
-
- // Source File
- original =
- "#include \"header.h\"\n"
- "\n"
- "void f()\n"
- "{\n"
- " @QMyClass c;\n"
- "}\n"
- ;
- expected =
- "#include \"QMyClass\"\n"
- "#include \"header.h\"\n"
- "\n"
- "void f()\n"
- "{\n"
- " QMyClass c;\n"
- "}\n"
- ;
- testDocuments << CppTestDocument::create("afile.cpp", original, expected);
- QTest::newRow("withForwardHeader")
- << TestIncludePaths::globalIncludePath()
- << testDocuments << secondRefactoringOperation << "";
- testDocuments.clear();
-
- // -------------------------------------------------------------------------------------------
-
- original =
- "void @f();\n"
- "#include \"file.moc\";\n"
- ;
- expected =
- "#include \"file.h\"\n"
- "\n"
- "void f();\n"
- "#include \"file.moc\";\n"
- ;
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
- QTest::newRow("insertingIgnoreMoc")
- << TestIncludePaths::globalIncludePath()
- << testDocuments << firstRefactoringOperation << "\"file.h\"";
- testDocuments.clear();
-
- // -------------------------------------------------------------------------------------------
-
- original =
- "#include \"y.h\"\n"
- "#include \"z.h\"\n"
- "\n@"
- ;
- expected =
- "#include \"file.h\"\n"
- "#include \"y.h\"\n"
- "#include \"z.h\"\n"
- "\n"
- ;
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
- QTest::newRow("insertingSortingTop")
- << TestIncludePaths::globalIncludePath()
- << testDocuments << firstRefactoringOperation << "\"file.h\"";
- testDocuments.clear();
-
- // -------------------------------------------------------------------------------------------
-
- original =
- "#include \"a.h\"\n"
- "#include \"z.h\"\n"
- "\n@"
- ;
- expected =
- "#include \"a.h\"\n"
- "#include \"file.h\"\n"
- "#include \"z.h\"\n"
- "\n"
- ;
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
- QTest::newRow("insertingSortingMiddle")
- << TestIncludePaths::globalIncludePath()
- << testDocuments << firstRefactoringOperation << "\"file.h\"";
- testDocuments.clear();
-
- // -------------------------------------------------------------------------------------------
-
- original =
- "#include \"a.h\"\n"
- "#include \"b.h\"\n"
- "\n@"
- ;
- expected =
- "#include \"a.h\"\n"
- "#include \"b.h\"\n"
- "#include \"file.h\"\n"
- "\n"
- ;
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
- QTest::newRow("insertingSortingBottom")
- << TestIncludePaths::globalIncludePath()
- << testDocuments << firstRefactoringOperation << "\"file.h\"";
- testDocuments.clear();
-
- // -------------------------------------------------------------------------------------------
-
- original =
- "#include \"b.h\"\n"
- "#include \"a.h\"\n"
- "\n@"
- ;
- expected =
- "#include \"b.h\"\n"
- "#include \"a.h\"\n"
- "#include \"file.h\"\n"
- "\n"
- ;
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
- QTest::newRow("inserting_appendToUnsorted")
- << TestIncludePaths::globalIncludePath()
- << testDocuments << firstRefactoringOperation << "\"file.h\"";
- testDocuments.clear();
-
- // -------------------------------------------------------------------------------------------
-
- original =
- "#include <a.h>\n"
- "#include <b.h>\n"
- "\n@"
- ;
- expected =
- "#include \"file.h\"\n"
- "\n"
- "#include <a.h>\n"
- "#include <b.h>\n"
- "\n"
- ;
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
- QTest::newRow("inserting_firstLocalIncludeAtFront")
- << TestIncludePaths::globalIncludePath()
- << testDocuments << firstRefactoringOperation << "\"file.h\"";
- testDocuments.clear();
-
- // -------------------------------------------------------------------------------------------
-
- original =
- "#include \"a.h\"\n"
- "#include \"b.h\"\n"
- "\n"
- "void @f();\n"
- ;
- expected =
- "#include \"a.h\"\n"
- "#include \"b.h\"\n"
- "\n"
- "#include <file.h>\n"
- "\n"
- "void f();\n"
- ;
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
- QTest::newRow("firstGlobalIncludeAtBack")
- << TestIncludePaths::globalIncludePath()
- << testDocuments << firstRefactoringOperation << "<file.h>";
- testDocuments.clear();
-
- // -------------------------------------------------------------------------------------------
-
- original =
- "#include \"prefixa.h\"\n"
- "#include \"prefixb.h\"\n"
- "\n"
- "#include \"foo.h\"\n"
- "\n@"
- ;
- expected =
- "#include \"prefixa.h\"\n"
- "#include \"prefixb.h\"\n"
- "#include \"prefixc.h\"\n"
- "\n"
- "#include \"foo.h\"\n"
- "\n"
- ;
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
- QTest::newRow("inserting_preferGroupWithLongerMatchingPrefix")
- << TestIncludePaths::globalIncludePath()
- << testDocuments << firstRefactoringOperation << "\"prefixc.h\"";
- testDocuments.clear();
-
- // -------------------------------------------------------------------------------------------
-
- original =
- "#include \"lib/file.h\"\n"
- "#include \"lib/fileother.h\"\n"
- "\n@"
- ;
- expected =
- "#include \"lib/file.h\"\n"
- "#include \"lib/fileother.h\"\n"
- "\n"
- "#include \"file.h\"\n"
- "\n"
- ;
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
- QTest::newRow("inserting_newGroupIfOnlyDifferentIncludeDirs")
- << TestIncludePaths::globalIncludePath()
- << testDocuments << firstRefactoringOperation << "\"file.h\"";
- testDocuments.clear();
-
- // -------------------------------------------------------------------------------------------
-
- original =
- "#include <lib/file.h>\n"
- "#include <otherlib/file.h>\n"
- "#include <utils/file.h>\n"
- "\n@"
- ;
- expected =
- "#include <firstlib/file.h>\n"
- "#include <lib/file.h>\n"
- "#include <otherlib/file.h>\n"
- "#include <utils/file.h>\n"
- "\n"
- ;
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
- QTest::newRow("inserting_mixedDirsSorted")
- << TestIncludePaths::globalIncludePath()
- << testDocuments << firstRefactoringOperation << "<firstlib/file.h>";
- testDocuments.clear();
-
- // -------------------------------------------------------------------------------------------
-
- original =
- "#include <otherlib/file.h>\n"
- "#include <lib/file.h>\n"
- "#include <utils/file.h>\n"
- "\n@"
- ;
- expected =
- "#include <otherlib/file.h>\n"
- "#include <lib/file.h>\n"
- "#include <utils/file.h>\n"
- "#include <lastlib/file.h>\n"
- "\n"
- ;
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
- QTest::newRow("inserting_mixedDirsUnsorted")
- << TestIncludePaths::globalIncludePath()
- << testDocuments << firstRefactoringOperation << "<lastlib/file.h>";
- testDocuments.clear();
-
- // -------------------------------------------------------------------------------------------
-
- original =
- "#include \"a.h\"\n"
- "#include <global.h>\n"
- "\n@"
- ;
- expected =
- "#include \"a.h\"\n"
- "#include \"z.h\"\n"
- "#include <global.h>\n"
- "\n"
- ;
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
- QTest::newRow("inserting_mixedIncludeTypes1")
- << TestIncludePaths::globalIncludePath()
- << testDocuments << firstRefactoringOperation << "\"z.h\"";
- testDocuments.clear();
-
- // -------------------------------------------------------------------------------------------
-
- original =
- "#include \"z.h\"\n"
- "#include <global.h>\n"
- "\n@"
- ;
- expected =
- "#include \"a.h\"\n"
- "#include \"z.h\"\n"
- "#include <global.h>\n"
- "\n"
- ;
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
- QTest::newRow("inserting_mixedIncludeTypes2")
- << TestIncludePaths::globalIncludePath()
- << testDocuments << firstRefactoringOperation << "\"a.h\"";
- testDocuments.clear();
-
- // -------------------------------------------------------------------------------------------
-
- original =
- "#include \"z.h\"\n"
- "#include <global.h>\n"
- "\n@"
- ;
- expected =
- "#include \"z.h\"\n"
- "#include \"lib/file.h\"\n"
- "#include <global.h>\n"
- "\n"
- ;
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
- QTest::newRow("inserting_mixedIncludeTypes3")
- << TestIncludePaths::globalIncludePath()
- << testDocuments << firstRefactoringOperation << "\"lib/file.h\"";
- testDocuments.clear();
-
- // -------------------------------------------------------------------------------------------
-
- original =
- "#include \"z.h\"\n"
- "#include <global.h>\n"
- "\n@"
- ;
- expected =
- "#include \"z.h\"\n"
- "#include <global.h>\n"
- "#include <lib/file.h>\n"
- "\n"
- ;
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
- QTest::newRow("inserting_mixedIncludeTypes4")
- << TestIncludePaths::globalIncludePath()
- << testDocuments << firstRefactoringOperation << "<lib/file.h>";
- testDocuments.clear();
-
- // -------------------------------------------------------------------------------------------
-
- original =
- "void @f();\n"
- ;
- expected =
- "#include \"file.h\"\n"
- "\n"
- "void f();\n"
- ;
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
- QTest::newRow("inserting_noinclude")
- << TestIncludePaths::globalIncludePath()
- << testDocuments << firstRefactoringOperation << "\"file.h\"";
- testDocuments.clear();
-
- // -------------------------------------------------------------------------------------------
-
- original =
- "#ifndef FOO_H\n"
- "#define FOO_H\n"
- "void @f();\n"
- "#endif\n"
- ;
- expected =
- "#ifndef FOO_H\n"
- "#define FOO_H\n"
- "\n"
- "#include \"file.h\"\n"
- "\n"
- "void f();\n"
- "#endif\n"
- ;
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
- QTest::newRow("inserting_onlyIncludeGuard")
- << TestIncludePaths::globalIncludePath()
- << testDocuments << firstRefactoringOperation << "\"file.h\"";
- testDocuments.clear();
-
- // -------------------------------------------------------------------------------------------
-
- original =
- "\n"
- "// comment\n"
- "\n"
- "void @f();\n"
- ;
- expected =
- "\n"
- "// comment\n"
- "\n"
- "#include \"file.h\"\n"
- "\n"
- "void @f();\n"
- ;
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
- QTest::newRow("inserting_veryFirstIncludeCppStyleCommentOnTop")
- << TestIncludePaths::globalIncludePath()
- << testDocuments << firstRefactoringOperation << "\"file.h\"";
- testDocuments.clear();
-
- // -------------------------------------------------------------------------------------------
-
- original =
- "\n"
- "/*\n"
- " comment\n"
- " */\n"
- "\n"
- "void @f();\n"
- ;
- expected =
- "\n"
- "/*\n"
- " comment\n"
- " */\n"
- "\n"
- "#include \"file.h\"\n"
- "\n"
- "void @f();\n"
- ;
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
- QTest::newRow("inserting_veryFirstIncludeCStyleCommentOnTop")
- << TestIncludePaths::globalIncludePath()
- << testDocuments << firstRefactoringOperation << "\"file.h\"";
- testDocuments.clear();
-
- // -------------------------------------------------------------------------------------------
-
- original =
- "@QDir dir;\n"
- ;
- expected =
- "#include <QDir>\n"
- "\n"
- "QDir dir;\n"
- ;
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
- QTest::newRow("inserting_checkQSomethingInQtIncludePaths")
- << TestIncludePaths::globalQtCoreIncludePath()
- << testDocuments << firstRefactoringOperation << "";
- testDocuments.clear();
-
- original =
- "std::s@tring s;\n"
- ;
- expected =
- "#include <string>\n"
- "\n"
- "std::string s;\n"
- ;
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
- QTest::newRow("inserting_std::string")
- << TestIncludePaths::globalIncludePath()
- << testDocuments << firstRefactoringOperation << "";
- testDocuments.clear();
-
- original = "class A{};";
- testDocuments << CppTestDocument::create("a.h", original, original);
- original = "class B{};";
- testDocuments << CppTestDocument::create("b.h", original, original);
- original =
- "#include \"b.h\"\n"
- "@A a;\n"
- "B b;";
- expected =
- "#include \"b.h\"\n\n"
- "#include \"a.h\"\n"
- "A a;\n"
- "B b;";
- testDocuments << CppTestDocument::create("b.cpp", original, expected);
- QTest::newRow("preserve first header")
- << TestIncludePaths::globalIncludePath()
- << testDocuments << firstRefactoringOperation << "";
- testDocuments.clear();
-
- original = "class C{};";
- testDocuments << CppTestDocument::create("c.h", original, original);
- original = "class B{};";
- testDocuments << CppTestDocument::create("b.h", original, original);
- original =
- "#include \"c.h\"\n"
- "C c;\n"
- "@B b;";
- expected =
- "#include \"b.h\"\n"
- "#include \"c.h\"\n"
- "C c;\n"
- "B b;";
- testDocuments << CppTestDocument::create("x.cpp", original, expected);
- QTest::newRow("do not preserve first header")
- << TestIncludePaths::globalIncludePath()
- << testDocuments << firstRefactoringOperation << "";
- testDocuments.clear();
-}
-
-void QuickfixTest::testAddIncludeForUndefinedIdentifier()
-{
- QFETCH(QString, headerPath);
- QFETCH(QuickFixTestDocuments, testDocuments);
- QFETCH(int, refactoringOperationIndex);
- QFETCH(QString, includeForTestFactory);
-
- TemporaryDir temporaryDir;
- QVERIFY(temporaryDir.isValid());
- for (const TestDocumentPtr &testDocument : std::as_const(testDocuments))
- testDocument->setBaseDirectory(temporaryDir.path());
-
- QScopedPointer<CppQuickFixFactory> factory;
- if (includeForTestFactory.isEmpty())
- factory.reset(new AddIncludeForUndefinedIdentifier);
- else
- factory.reset(new AddIncludeForUndefinedIdentifierTestFactory(includeForTestFactory));
-
- QuickFixOperationTest::run(testDocuments, factory.data(), headerPath,
- refactoringOperationIndex);
-}
-
-void QuickfixTest::testAddIncludeForUndefinedIdentifierNoDoubleQtHeaderInclude()
-{
- TemporaryDir temporaryDir;
- QVERIFY(temporaryDir.isValid());
-
- QList<TestDocumentPtr> testDocuments;
- QByteArray original;
- QByteArray expected;
-
- const QByteArray base = temporaryDir.path().toUtf8();
-
- // This file makes the QDir definition available so that locator finds it.
- original = expected = "#include <QDir>\n"
- "void avoidBeingRecognizedAsForwardingHeader();";
- testDocuments << CppTestDocument::create(base + "/fileUsingQDir.cpp", original, expected);
-
- original = expected = "@QDir dir;\n";
- testDocuments << CppTestDocument::create(base + "/fileWantsToUseQDir.cpp", original, expected);
-
- AddIncludeForUndefinedIdentifier factory;
- const QStringList expectedOperations = QStringList("Add #include <QDir>");
- QuickFixOfferedOperationsTest(testDocuments, &factory, ProjectExplorer::toUserHeaderPaths(
- QStringList{TestIncludePaths::globalQtCoreIncludePath()}), expectedOperations);
-}
-
-void QuickfixTest::testAddForwardDeclForUndefinedIdentifier_data()
-{
- QTest::addColumn<QuickFixTestDocuments>("testDocuments");
- QTest::addColumn<QString>("symbol");
- QTest::addColumn<int>("symbolPos");
-
- QByteArray original;
- QByteArray expected;
-
- original =
- "#pragma once\n"
- "\n"
- "void f(const Blu@bb &b)\n"
- "{\n"
- "}\n"
- ;
- expected =
- "#pragma once\n"
- "\n"
- "\n"
- "class Blubb;\n"
- "void f(const Blubb &b)\n"
- "{\n"
- "}\n"
- ;
- QTest::newRow("unqualified symbol")
- << QuickFixTestDocuments{CppTestDocument::create("theheader.h", original, expected)}
- << "Blubb" << original.indexOf('@');
-
- original =
- "#pragma once\n"
- "\n"
- "namespace NS {\n"
- "class C;\n"
- "}\n"
- "void f(const NS::Blu@bb &b)\n"
- "{\n"
- "}\n"
- ;
- expected =
- "#pragma once\n"
- "\n"
- "namespace NS {\n"
- "\n"
- "class Blubb;\n"
- "class C;\n"
- "}\n"
- "void f(const NS::Blubb &b)\n"
- "{\n"
- "}\n"
- ;
- QTest::newRow("qualified symbol, full namespace present")
- << QuickFixTestDocuments{CppTestDocument::create("theheader.h", original, expected)}
- << "NS::Blubb" << original.indexOf('@');
-
- original =
- "#pragma once\n"
- "\n"
- "namespace NS {\n"
- "class C;\n"
- "}\n"
- "void f(const NS::NS2::Blu@bb &b)\n"
- "{\n"
- "}\n"
- ;
- expected =
- "#pragma once\n"
- "\n"
- "namespace NS {\n"
- "\n"
- "namespace NS2 { class Blubb; }\n"
- "class C;\n"
- "}\n"
- "void f(const NS::NS2::Blubb &b)\n"
- "{\n"
- "}\n"
- ;
- QTest::newRow("qualified symbol, partial namespace present")
- << QuickFixTestDocuments{CppTestDocument::create("theheader.h", original, expected)}
- << "NS::NS2::Blubb" << original.indexOf('@');
-
- original =
- "#pragma once\n"
- "\n"
- "namespace NS {\n"
- "class C;\n"
- "}\n"
- "void f(const NS2::Blu@bb &b)\n"
- "{\n"
- "}\n"
- ;
- expected =
- "#pragma once\n"
- "\n"
- "\n"
- "namespace NS2 { class Blubb; }\n"
- "namespace NS {\n"
- "class C;\n"
- "}\n"
- "void f(const NS2::Blubb &b)\n"
- "{\n"
- "}\n"
- ;
- QTest::newRow("qualified symbol, other namespace present")
- << QuickFixTestDocuments{CppTestDocument::create("theheader.h", original, expected)}
- << "NS2::Blubb" << original.indexOf('@');
-
- original =
- "#pragma once\n"
- "\n"
- "void f(const NS2::Blu@bb &b)\n"
- "{\n"
- "}\n"
- ;
- expected =
- "#pragma once\n"
- "\n"
- "\n"
- "namespace NS2 { class Blubb; }\n"
- "void f(const NS2::Blubb &b)\n"
- "{\n"
- "}\n"
- ;
- QTest::newRow("qualified symbol, no namespace present")
- << QuickFixTestDocuments{CppTestDocument::create("theheader.h", original, expected)}
- << "NS2::Blubb" << original.indexOf('@');
-
- original =
- "#pragma once\n"
- "\n"
- "void f(const NS2::Blu@bb &b)\n"
- "{\n"
- "}\n"
- "namespace NS2 {}\n"
- ;
- expected =
- "#pragma once\n"
- "\n"
- "\n"
- "namespace NS2 { class Blubb; }\n"
- "void f(const NS2::Blubb &b)\n"
- "{\n"
- "}\n"
- "namespace NS2 {}\n"
- ;
- QTest::newRow("qualified symbol, existing namespace after symbol")
- << QuickFixTestDocuments{CppTestDocument::create("theheader.h", original, expected)}
- << "NS2::Blubb" << original.indexOf('@');
-}
-
-void QuickfixTest::testAddForwardDeclForUndefinedIdentifier()
-{
- QFETCH(QuickFixTestDocuments, testDocuments);
- QFETCH(QString, symbol);
- QFETCH(int, symbolPos);
-
- TemporaryDir temporaryDir;
- QVERIFY(temporaryDir.isValid());
- testDocuments.first()->setBaseDirectory(temporaryDir.path());
-
- QScopedPointer<CppQuickFixFactory> factory(
- new AddForwardDeclForUndefinedIdentifierTestFactory(symbol, symbolPos));
- QuickFixOperationTest::run({testDocuments}, factory.data(), ".", 0);
-}
-
-/// Check: Move definition from header to cpp.
-void QuickfixTest::testMoveFuncDefOutsideMemberFuncToCpp()
-{
- QList<TestDocumentPtr> testDocuments;
- QByteArray original;
- QByteArray expected;
-
- // Header File
- original =
- "class Foo {\n"
- " inline int numbe@r() const\n"
- " {\n"
- " return 5;\n"
- " }\n"
- "\n"
- " void bar();\n"
- "};\n";
- expected =
- "class Foo {\n"
- " int number() const;\n"
- "\n"
- " void bar();\n"
- "};\n";
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- // Source File
- original =
- "#include \"file.h\"\n";
- expected =
- "#include \"file.h\"\n"
- "\n"
- "int Foo::number() const\n"
- "{\n"
- " return 5;\n"
- "}\n"
- ;
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
- MoveFuncDefOutside factory;
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-void QuickfixTest::testMoveFuncDefOutsideMemberFuncToCppStatic()
-{
- QList<TestDocumentPtr> testDocuments;
- QByteArray original;
- QByteArray expected;
-
- // Header File
- original =
- "class Foo {\n"
- " static inline int numbe@r() const\n"
- " {\n"
- " return 5;\n"
- " }\n"
- "\n"
- " void bar();\n"
- "};\n";
- expected =
- "class Foo {\n"
- " static int number() const;\n"
- "\n"
- " void bar();\n"
- "};\n";
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- // Source File
- original =
- "#include \"file.h\"\n";
- expected =
- "#include \"file.h\"\n"
- "\n"
- "int Foo::number() const\n"
- "{\n"
- " return 5;\n"
- "}\n";
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
- MoveFuncDefOutside factory;
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-void QuickfixTest::testMoveFuncDefOutsideMemberFuncToCppWithInlinePartOfName()
-{
- QList<TestDocumentPtr> testDocuments;
- QByteArray original;
- QByteArray expected;
-
- // Header File
- original =
- "class Foo {\n"
- " static inline int numbe@r_inline () const\n"
- " {\n"
- " return 5;\n"
- " }\n"
- "\n"
- " void bar();\n"
- "};\n";
- expected =
- "class Foo {\n"
- " static int number_inline () const;\n"
- "\n"
- " void bar();\n"
- "};\n";
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- // Source File
- original =
- "#include \"file.h\"\n";
- expected =
- "#include \"file.h\"\n"
- "\n"
- "int Foo::number_inline() const\n"
- "{\n"
- " return 5;\n"
- "}\n";
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
- MoveFuncDefOutside factory;
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-void QuickfixTest::testMoveFuncDefOutsideMixedQualifiers()
-{
- QList<TestDocumentPtr> testDocuments;
- QByteArray original;
- QByteArray expected;
-
- // Header File
- original = R"(
-struct Base {
- virtual auto func() const && noexcept -> void = 0;
-};
-struct Derived : public Base {
- auto @func() const && noexcept -> void override {}
-};)";
- expected = R"(
-struct Base {
- virtual auto func() const && noexcept -> void = 0;
-};
-struct Derived : public Base {
- auto func() const && noexcept -> void override;
-};)";
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- // Source File
- original = "#include \"file.h\"\n";
- expected = R"DELIM(#include "file.h"
-
-auto Derived::func() const && noexcept -> void {}
-)DELIM";
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
- MoveFuncDefOutside factory;
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-void QuickfixTest::testMoveFuncDefOutsideMemberFuncToCppInsideNS()
-{
- QList<TestDocumentPtr> testDocuments;
- QByteArray original;
- QByteArray expected;
-
- // Header File
- original =
- "namespace SomeNamespace {\n"
- "class Foo {\n"
- " int ba@r()\n"
- " {\n"
- " return 5;\n"
- " }\n"
- "};\n"
- "}\n";
- expected =
- "namespace SomeNamespace {\n"
- "class Foo {\n"
- " int ba@r();\n"
- "};\n"
- "}\n";
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- // Source File
- original =
- "#include \"file.h\"\n"
- "namespace SomeNamespace {\n"
- "\n"
- "}\n";
- expected =
- "#include \"file.h\"\n"
- "namespace SomeNamespace {\n"
- "\n"
- "int Foo::bar()\n"
- "{\n"
- " return 5;\n"
- "}\n"
- "\n"
- "}\n";
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
- MoveFuncDefOutside factory;
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-/// Check: Move definition outside class
-void QuickfixTest::testMoveFuncDefOutsideMemberFuncOutside1()
-{
- QByteArray original =
- "class Foo {\n"
- " void f1();\n"
- " inline int f2@() const\n"
- " {\n"
- " return 1;\n"
- " }\n"
- " void f3();\n"
- " void f4();\n"
- "};\n"
- "\n"
- "void Foo::f4() {}\n";
- QByteArray expected =
- "class Foo {\n"
- " void f1();\n"
- " int f2@() const;\n"
- " void f3();\n"
- " void f4();\n"
- "};\n"
- "\n"
- "int Foo::f2() const\n"
- "{\n"
- " return 1;\n"
- "}\n"
- "\n"
- "void Foo::f4() {}\n";
-
- MoveFuncDefOutside factory;
- QuickFixOperationTest(singleDocument(original, expected), &factory);
-}
-
-/// Check: Move definition outside class
-void QuickfixTest::testMoveFuncDefOutsideMemberFuncOutside2()
-{
- QList<TestDocumentPtr> testDocuments;
- QByteArray original;
- QByteArray expected;
-
- // Header File
- original =
- "class Foo {\n"
- " void f1();\n"
- " int f2@()\n"
- " {\n"
- " return 1;\n"
- " }\n"
- " void f3();\n"
- "};\n";
- expected =
- "class Foo {\n"
- " void f1();\n"
- " int f2();\n"
- " void f3();\n"
- "};\n"
- "\n"
- "inline int Foo::f2()\n"
- "{\n"
- " return 1;\n"
- "}\n";
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- // Source File
- original =
- "#include \"file.h\"\n"
- "void Foo::f1() {}\n"
- "void Foo::f3() {}\n";
- expected = original;
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
- MoveFuncDefOutside factory;
- QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 1);
-}
-
-/// Check: Move definition from header to cpp (with namespace).
-void QuickfixTest::testMoveFuncDefOutsideMemberFuncToCppNS()
-{
- QList<TestDocumentPtr> testDocuments;
- QByteArray original;
- QByteArray expected;
-
- // Header File
- original =
- "namespace MyNs {\n"
- "class Foo {\n"
- " inline int numbe@r() const\n"
- " {\n"
- " return 5;\n"
- " }\n"
- "};\n"
- "}\n";
- expected =
- "namespace MyNs {\n"
- "class Foo {\n"
- " int number() const;\n"
- "};\n"
- "}\n";
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- // Source File
- original =
- "#include \"file.h\"\n";
- expected =
- "#include \"file.h\"\n"
- "\n"
- "int MyNs::Foo::number() const\n"
- "{\n"
- " return 5;\n"
- "}\n";
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
- MoveFuncDefOutside factory;
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-/// Check: Move definition from header to cpp (with namespace + using).
-void QuickfixTest::testMoveFuncDefOutsideMemberFuncToCppNSUsing()
-{
- QList<TestDocumentPtr> testDocuments;
- QByteArray original;
- QByteArray expected;
-
- // Header File
- original =
- "namespace MyNs {\n"
- "class Foo {\n"
- " inline int numbe@r() const\n"
- " {\n"
- " return 5;\n"
- " }\n"
- "};\n"
- "}\n";
- expected =
- "namespace MyNs {\n"
- "class Foo {\n"
- " int number() const;\n"
- "};\n"
- "}\n";
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- // Source File
- original =
- "#include \"file.h\"\n"
- "using namespace MyNs;\n";
- expected =
- "#include \"file.h\"\n"
- "using namespace MyNs;\n"
- "\n"
- "int Foo::number() const\n"
- "{\n"
- " return 5;\n"
- "}\n";
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
- MoveFuncDefOutside factory;
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-/// Check: Move definition outside class with Namespace
-void QuickfixTest::testMoveFuncDefOutsideMemberFuncOutsideWithNs()
-{
- QByteArray original =
- "namespace MyNs {\n"
- "class Foo {\n"
- " inline int numbe@r() const\n"
- " {\n"
- " return 5;\n"
- " }\n"
- "};}\n";
- QByteArray expected =
- "namespace MyNs {\n"
- "class Foo {\n"
- " int number() const;\n"
- "};\n"
- "\n"
- "int Foo::number() const\n"
- "{\n"
- " return 5;\n"
- "}\n"
- "\n}\n";
-
- MoveFuncDefOutside factory;
- QuickFixOperationTest(singleDocument(original, expected), &factory);
-}
-
-/// Check: Move free function from header to cpp.
-void QuickfixTest::testMoveFuncDefOutsideFreeFuncToCpp()
-{
- QList<TestDocumentPtr> testDocuments;
- QByteArray original;
- QByteArray expected;
-
- // Header File
- original =
- "int numbe@r() const\n"
- "{\n"
- " return 5;\n"
- "}\n";
- expected =
- "int number() const;\n"
- ;
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- // Source File
- original =
- "#include \"file.h\"\n";
- expected =
- "#include \"file.h\"\n"
- "\n"
- "int number() const\n"
- "{\n"
- " return 5;\n"
- "}\n";
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
- MoveFuncDefOutside factory;
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-/// Check: Move free function from header to cpp (with namespace).
-void QuickfixTest::testMoveFuncDefOutsideFreeFuncToCppNS()
-{
- QList<TestDocumentPtr> testDocuments;
- QByteArray original;
- QByteArray expected;
-
- // Header File
- original =
- "namespace MyNamespace {\n"
- "int numbe@r() const\n"
- "{\n"
- " return 5;\n"
- "}\n"
- "}\n";
- expected =
- "namespace MyNamespace {\n"
- "int number() const;\n"
- "}\n";
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- // Source File
- original =
- "#include \"file.h\"\n";
- expected =
- "#include \"file.h\"\n"
- "\n"
- "int MyNamespace::number() const\n"
- "{\n"
- " return 5;\n"
- "}\n";
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
- MoveFuncDefOutside factory;
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-/// Check: Move Ctor with member initialization list (QTCREATORBUG-9157).
-void QuickfixTest::testMoveFuncDefOutsideCtorWithInitialization1()
-{
- QList<TestDocumentPtr> testDocuments;
- QByteArray original;
- QByteArray expected;
-
- // Header File
- original =
- "class Foo {\n"
- "public:\n"
- " Fo@o() : a(42), b(3.141) {}\n"
- "private:\n"
- " int a;\n"
- " float b;\n"
- "};\n";
- expected =
- "class Foo {\n"
- "public:\n"
- " Foo();\n"
- "private:\n"
- " int a;\n"
- " float b;\n"
- "};\n";
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- // Source File
- original ="#include \"file.h\"\n";
- expected =
- "#include \"file.h\"\n"
- "\n"
- "Foo::Foo() : a(42), b(3.141) {}\n"
- ;
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
- MoveFuncDefOutside factory;
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-/// Check: Move Ctor with member initialization list (QTCREATORBUG-9462).
-void QuickfixTest::testMoveFuncDefOutsideCtorWithInitialization2()
-{
- QList<TestDocumentPtr> testDocuments;
- QByteArray original;
- QByteArray expected;
-
- // Header File
- original =
- "class Foo\n"
- "{\n"
- "public:\n"
- " Fo@o() : member(2)\n"
- " {\n"
- " }\n"
- "\n"
- " int member;\n"
- "};\n";
-
- expected =
- "class Foo\n"
- "{\n"
- "public:\n"
- " Foo();\n"
- "\n"
- " int member;\n"
- "};\n";
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- // Source File
- original ="#include \"file.h\"\n";
- expected =
- "#include \"file.h\"\n"
- "\n"
- "Foo::Foo() : member(2)\n"
- "{\n"
- "}\n"
- ;
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
- MoveFuncDefOutside factory;
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-/// Check if definition is inserted right after class for move definition outside
-void QuickfixTest::testMoveFuncDefOutsideAfterClass()
-{
- QList<TestDocumentPtr> testDocuments;
- QByteArray original;
- QByteArray expected;
-
- // Header File
- original =
- "class Foo\n"
- "{\n"
- " Foo();\n"
- " void a@() {}\n"
- "};\n"
- "\n"
- "class Bar {};\n";
- expected =
- "class Foo\n"
- "{\n"
- " Foo();\n"
- " void a();\n"
- "};\n"
- "\n"
- "inline void Foo::a() {}\n"
- "\n"
- "class Bar {};\n";
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- // Source File
- original =
- "#include \"file.h\"\n"
- "\n"
- "Foo::Foo()\n"
- "{\n\n"
- "}\n";
- expected = original;
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
- MoveFuncDefOutside factory;
- QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 1);
-}
-
-/// Check if whitespace is respected for operator functions
-void QuickfixTest::testMoveFuncDefOutsideRespectWsInOperatorNames1()
-{
- QByteArray original =
- "class Foo\n"
- "{\n"
- " Foo &opera@tor =() {}\n"
- "};\n";
- QByteArray expected =
- "class Foo\n"
- "{\n"
- " Foo &operator =();\n"
- "};\n"
- "\n"
- "Foo &Foo::operator =() {}\n"
- ;
-
- MoveFuncDefOutside factory;
- QuickFixOperationTest(singleDocument(original, expected), &factory);
-}
-
-/// Check if whitespace is respected for operator functions
-void QuickfixTest::testMoveFuncDefOutsideRespectWsInOperatorNames2()
-{
- QByteArray original =
- "class Foo\n"
- "{\n"
- " Foo &opera@tor=() {}\n"
- "};\n";
- QByteArray expected =
- "class Foo\n"
- "{\n"
- " Foo &operator=();\n"
- "};\n"
- "\n"
- "Foo &Foo::operator=() {}\n"
- ;
-
- MoveFuncDefOutside factory;
- QuickFixOperationTest(singleDocument(original, expected), &factory);
-}
-
-void QuickfixTest::testMoveFuncDefOutsideMacroUses()
-{
- QByteArray original =
- "#define CONST const\n"
- "#define VOLATILE volatile\n"
- "class Foo\n"
- "{\n"
- " int fu@nc(int a, int b) CONST VOLATILE\n"
- " {\n"
- " return 42;\n"
- " }\n"
- "};\n";
- QByteArray expected =
- "#define CONST const\n"
- "#define VOLATILE volatile\n"
- "class Foo\n"
- "{\n"
- " int func(int a, int b) CONST VOLATILE;\n"
- "};\n"
- "\n"
- "\n"
- // const volatile become lowercase: QTCREATORBUG-12620
- "int Foo::func(int a, int b) const volatile\n"
- "{\n"
- " return 42;\n"
- "}\n"
- ;
-
- MoveFuncDefOutside factory;
- QuickFixOperationTest(singleDocument(original, expected), &factory,
- ProjectExplorer::HeaderPaths(), 0, "QTCREATORBUG-12314");
-}
-
-void QuickfixTest::testMoveFuncDefOutsideTemplate()
-{
- QByteArray original =
- "template<class T>\n"
- "class Foo { void fu@nc() {} };\n";
- QByteArray expected =
- "template<class T>\n"
- "class Foo { void fu@nc(); };\n"
- "\n"
- "template<class T>\n"
- "void Foo<T>::func() {}\n";
- ;
-
- MoveFuncDefOutside factory;
- QuickFixOperationTest(singleDocument(original, expected), &factory);
-}
-
-void QuickfixTest::testMoveFuncDefOutsideMemberFunctionTemplate()
-{
- const QByteArray original = R"(
-struct S {
- template<typename In>
- void @foo(In in) { (void)in; }
-};
-)";
- const QByteArray expected = R"(
-struct S {
- template<typename In>
- void foo(In in);
-};
-
-template<typename In>
-void S::foo(In in) { (void)in; }
-)";
-
- MoveFuncDefOutside factory;
- QuickFixOperationTest(singleDocument(original, expected), &factory);
-}
-
-void QuickfixTest::testMoveFuncDefOutsideTemplateSpecializedClass()
-{
- QByteArray original = R"(
-template<typename T> class base {};
-template<>
-class base<int>
-{
-public:
- void @bar() {}
-};
-)";
- QByteArray expected = R"(
-template<typename T> class base {};
-template<>
-class base<int>
-{
-public:
- void bar();
-};
-
-void base<int>::bar() {}
-)";
-
- MoveFuncDefOutside factory;
- QuickFixOperationTest(singleDocument(original, expected), &factory);
-}
-
-void QuickfixTest::testMoveFuncDefOutsideUnnamedTemplate()
-{
- QByteArray original =
- "template<typename T, typename>\n"
- "class Foo { void fu@nc() {} };\n";
- QByteArray expected =
- "template<typename T, typename>\n"
- "class Foo { void fu@nc(); };\n"
- "\n"
- "template<typename T, typename T2>\n"
- "void Foo<T, T2>::func() {}\n";
- ;
-
- MoveFuncDefOutside factory;
- QuickFixOperationTest(singleDocument(original, expected), &factory);
-}
-
-void QuickfixTest::testMoveFuncDefToDecl_data()
-{
- QTest::addColumn<QByteArrayList>("headers");
- QTest::addColumn<QByteArrayList>("sources");
-
- QByteArray originalHeader;
- QByteArray expectedHeader;
- QByteArray originalSource;
- QByteArray expectedSource;
-
- originalHeader =
- "class Foo {\n"
- " inline int @number() const;\n"
- "};\n";
- expectedHeader =
- "class Foo {\n"
- " inline int number() const {return 5;}\n"
- "};\n";
- originalSource =
- "#include \"file.h\"\n"
- "\n"
- "int Foo::num@ber() const {return 5;}\n";
- expectedSource =
- "#include \"file.h\"\n"
- "\n\n";
- QTest::newRow("member function, two files") << QByteArrayList{originalHeader, expectedHeader}
- << QByteArrayList{originalSource, expectedSource};
-
- originalSource =
- "class Foo {\n"
- " inline int @number() const;\n"
- "};\n"
- "\n"
- "int Foo::num@ber() const\n"
- "{\n"
- " return 5;\n"
- "}\n";
-
- expectedSource =
- "class Foo {\n"
- " inline int number() const\n"
- " {\n"
- " return 5;\n"
- " }\n"
- "};\n\n\n";
- QTest::newRow("member function, one file") << QByteArrayList()
- << QByteArrayList{originalSource, expectedSource};
-
- originalHeader =
- "namespace MyNs {\n"
- "class Foo {\n"
- " inline int @number() const;\n"
- "};\n"
- "}\n";
- expectedHeader =
- "namespace MyNs {\n"
- "class Foo {\n"
- " inline int number() const\n"
- " {\n"
- " return 5;\n"
- " }\n"
- "};\n"
- "}\n";
- originalSource =
- "#include \"file.h\"\n"
- "\n"
- "int MyNs::Foo::num@ber() const\n"
- "{\n"
- " return 5;\n"
- "}\n";
- expectedSource = "#include \"file.h\"\n\n\n";
- QTest::newRow("member function, two files, namespace")
- << QByteArrayList{originalHeader, expectedHeader}
- << QByteArrayList{originalSource, expectedSource};
-
- originalHeader =
- "namespace MyNs {\n"
- "class Foo {\n"
- " inline int numbe@r() const;\n"
- "};\n"
- "}\n";
- expectedHeader =
- "namespace MyNs {\n"
- "class Foo {\n"
- " inline int number() const\n"
- " {\n"
- " return 5;\n"
- " }\n"
- "};\n"
- "}\n";
- originalSource =
- "#include \"file.h\"\n"
- "using namespace MyNs;\n"
- "\n"
- "int Foo::num@ber() const\n"
- "{\n"
- " return 5;\n"
- "}\n";
- expectedSource =
- "#include \"file.h\"\n"
- "using namespace MyNs;\n"
- "\n\n";
- QTest::newRow("member function, two files, namespace with using-directive")
- << QByteArrayList{originalHeader, expectedHeader}
- << QByteArrayList{originalSource, expectedSource};
-
- originalSource =
- "namespace MyNs {\n"
- "class Foo {\n"
- " inline int @number() const;\n"
- "};\n"
- "\n"
- "int Foo::numb@er() const\n"
- "{\n"
- " return 5;\n"
- "}"
- "\n}\n";
- expectedSource =
- "namespace MyNs {\n"
- "class Foo {\n"
- " inline int number() const\n"
- " {\n"
- " return 5;\n"
- " }\n"
- "};\n\n\n}\n";
-
- QTest::newRow("member function, one file, namespace")
- << QByteArrayList() << QByteArrayList{originalSource, expectedSource};
-
- originalHeader = "int nu@mber() const;\n";
- expectedHeader =
- "inline int number() const\n"
- "{\n"
- " return 5;\n"
- "}\n";
- originalSource =
- "#include \"file.h\"\n"
- "\n"
- "\n"
- "int numb@er() const\n"
- "{\n"
- " return 5;\n"
- "}\n";
- expectedSource = "#include \"file.h\"\n\n\n\n";
- QTest::newRow("free function") << QByteArrayList{originalHeader, expectedHeader}
- << QByteArrayList{originalSource, expectedSource};
-
- originalHeader =
- "namespace MyNamespace {\n"
- "int n@umber() const;\n"
- "}\n";
- expectedHeader =
- "namespace MyNamespace {\n"
- "inline int number() const\n"
- "{\n"
- " return 5;\n"
- "}\n"
- "}\n";
- originalSource =
- "#include \"file.h\"\n"
- "\n"
- "int MyNamespace::nu@mber() const\n"
- "{\n"
- " return 5;\n"
- "}\n";
- expectedSource =
- "#include \"file.h\"\n"
- "\n\n";
- QTest::newRow("free function, namespace") << QByteArrayList{originalHeader, expectedHeader}
- << QByteArrayList{originalSource, expectedSource};
-
- originalHeader =
- "class Foo {\n"
- "public:\n"
- " Fo@o();\n"
- "private:\n"
- " int a;\n"
- " float b;\n"
- "};\n";
- expectedHeader =
- "class Foo {\n"
- "public:\n"
- " Foo() : a(42), b(3.141) {}\n"
- "private:\n"
- " int a;\n"
- " float b;\n"
- "};\n";
- originalSource =
- "#include \"file.h\"\n"
- "\n"
- "Foo::F@oo() : a(42), b(3.141) {}"
- ;
- expectedSource ="#include \"file.h\"\n\n";
- QTest::newRow("constructor") << QByteArrayList{originalHeader, expectedHeader}
- << QByteArrayList{originalSource, expectedSource};
-
- originalSource =
- "struct Foo\n"
- "{\n"
- " void f@oo();\n"
- "} bar;\n"
- "void Foo::fo@o()\n"
- "{\n"
- " return;\n"
- "}";
- expectedSource =
- "struct Foo\n"
- "{\n"
- " void foo()\n"
- " {\n"
- " return;\n"
- " }\n"
- "} bar;\n";
- QTest::newRow("QTCREATORBUG-10303") << QByteArrayList()
- << QByteArrayList{originalSource, expectedSource};
-
- originalSource =
- "struct Base {\n"
- " virtual int foo() = 0;\n"
- "};\n"
- "struct Derived : Base {\n"
- " int @foo() override;\n"
- "};\n"
- "\n"
- "int Derived::fo@o()\n"
- "{\n"
- " return 5;\n"
- "}\n";
- expectedSource =
- "struct Base {\n"
- " virtual int foo() = 0;\n"
- "};\n"
- "struct Derived : Base {\n"
- " int foo() override\n"
- " {\n"
- " return 5;\n"
- " }\n"
- "};\n\n\n";
- QTest::newRow("overridden virtual") << QByteArrayList()
- << QByteArrayList{originalSource, expectedSource};
-
- originalSource =
- "template<class T>\n"
- "class Foo { void @func(); };\n"
- "\n"
- "template<class T>\n"
- "void Foo<T>::fu@nc() {}\n";
- expectedSource =
- "template<class T>\n"
- "class Foo { void fu@nc() {} };\n\n\n";
- QTest::newRow("class template") << QByteArrayList()
- << QByteArrayList{originalSource, expectedSource};
-
- originalSource =
- "class Foo\n"
- "{\n"
- " template<class T>\n"
- " void @func();\n"
- "};\n"
- "\n"
- "template<class T>\n"
- "void Foo::fu@nc() {}\n";
- expectedSource =
- "class Foo\n"
- "{\n"
- " template<class T>\n"
- " void func() {}\n"
- "};\n\n\n";
- QTest::newRow("function template") << QByteArrayList()
- << QByteArrayList{originalSource, expectedSource};
-}
-
-void QuickfixTest::testMoveFuncDefToDecl()
-{
- QFETCH(QByteArrayList, headers);
- QFETCH(QByteArrayList, sources);
-
- QVERIFY(headers.isEmpty() || headers.size() == 2);
- QVERIFY(sources.size() == 2);
-
- QByteArray &declDoc = !headers.empty() ? headers.first() : sources.first();
- const int declCursorPos = declDoc.indexOf('@');
- QVERIFY(declCursorPos != -1);
- const int defCursorPos = sources.first().lastIndexOf('@');
- QVERIFY(defCursorPos != -1);
- QVERIFY(declCursorPos != defCursorPos);
-
- declDoc.remove(declCursorPos, 1);
- QList<TestDocumentPtr> testDocuments;
- if (!headers.isEmpty())
- testDocuments << CppTestDocument::create("file.h", headers.first(), headers.last());
- testDocuments << CppTestDocument::create("file.cpp", sources.first(), sources.last());
-
- MoveFuncDefToDeclPush pushFactory;
- QuickFixOperationTest(testDocuments, &pushFactory);
-
- declDoc.insert(declCursorPos, '@');
- sources.first().remove(defCursorPos, 1);
- testDocuments.clear();
- if (!headers.isEmpty())
- testDocuments << CppTestDocument::create("file.h", headers.first(), headers.last());
- testDocuments << CppTestDocument::create("file.cpp", sources.first(), sources.last());
-
- MoveFuncDefToDeclPull pullFactory;
- QuickFixOperationTest(testDocuments, &pullFactory);
-}
-
-void QuickfixTest::testMoveFuncDefToDeclMacroUses()
-{
- QByteArray original =
- "#define CONST const\n"
- "#define VOLATILE volatile\n"
- "class Foo\n"
- "{\n"
- " int func(int a, int b) CONST VOLATILE;\n"
- "};\n"
- "\n"
- "\n"
- "int Foo::fu@nc(int a, int b) CONST VOLATILE"
- "{\n"
- " return 42;\n"
- "}\n";
- QByteArray expected =
- "#define CONST const\n"
- "#define VOLATILE volatile\n"
- "class Foo\n"
- "{\n"
- " int func(int a, int b) CONST VOLATILE\n"
- " {\n"
- " return 42;\n"
- " }\n"
- "};\n\n\n\n";
-
- MoveFuncDefToDeclPush factory;
- QuickFixOperationTest(singleDocument(original, expected), &factory,
- ProjectExplorer::HeaderPaths(), 0, "QTCREATORBUG-12314");
-}
-
-/// Check: Move all definitions from header to cpp.
-void QuickfixTest::testMoveAllFuncDefOutsideMemberFuncToCpp()
-{
- QList<TestDocumentPtr> testDocuments;
- QByteArray original;
- QByteArray expected;
-
- // Header File
- original =
- "class Foo {@\n"
- " int numberA() const\n"
- " {\n"
- " return 5;\n"
- " }\n"
- " int numberB() const\n"
- " {\n"
- " return 5;\n"
- " }\n"
- "};\n";
- expected =
- "class Foo {\n"
- " int numberA() const;\n"
- " int numberB() const;\n"
- "};\n";
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- // Source File
- original =
- "#include \"file.h\"\n";
- expected =
- "#include \"file.h\"\n"
- "\n"
- "int Foo::numberA() const\n"
- "{\n"
- " return 5;\n"
- "}\n"
- "\n"
- "int Foo::numberB() const\n"
- "{\n"
- " return 5;\n"
- "}\n"
- ;
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
- MoveAllFuncDefOutside factory;
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-/// Check: Move all definition outside class
-void QuickfixTest::testMoveAllFuncDefOutsideMemberFuncOutside()
-{
- QByteArray original =
- "class F@oo {\n"
- " int f1()\n"
- " {\n"
- " return 1;\n"
- " }\n"
- " int f2() const\n"
- " {\n"
- " return 2;\n"
- " }\n"
- "};\n";
- QByteArray expected =
- "class Foo {\n"
- " int f1();\n"
- " int f2() const;\n"
- "};\n"
- "\n"
- "int Foo::f1()\n"
- "{\n"
- " return 1;\n"
- "}\n"
- "\n"
- "int Foo::f2() const\n"
- "{\n"
- " return 2;\n"
- "}\n";
-
- MoveAllFuncDefOutside factory;
- QuickFixOperationTest(singleDocument(original, expected), &factory);
-}
-
-/// Check: Move all definition outside class
-void QuickfixTest::testMoveAllFuncDefOutsideDoNotTriggerOnBaseClass()
-{
- QByteArray original =
- "class Bar;\n"
- "class Foo : public Ba@r {\n"
- " int f1()\n"
- " {\n"
- " return 1;\n"
- " }\n"
- "};\n";
-
- MoveAllFuncDefOutside factory;
- QuickFixOperationTest(singleDocument(original, ""), &factory);
-}
-
-/// Check: Move all definition outside class
-void QuickfixTest::testMoveAllFuncDefOutsideClassWithBaseClass()
-{
- QByteArray original =
- "class Bar;\n"
- "class Fo@o : public Bar {\n"
- " int f1()\n"
- " {\n"
- " return 1;\n"
- " }\n"
- "};\n";
- QByteArray expected =
- "class Bar;\n"
- "class Foo : public Bar {\n"
- " int f1();\n"
- "};\n"
- "\n"
- "int Foo::f1()\n"
- "{\n"
- " return 1;\n"
- "}\n";
-
- MoveAllFuncDefOutside factory;
- QuickFixOperationTest(singleDocument(original, expected), &factory);
-}
-
-/// Check: Do not take macro expanded code into account (QTCREATORBUG-13900)
-void QuickfixTest::testMoveAllFuncDefOutsideIgnoreMacroCode()
-{
- QByteArray original =
- "#define FAKE_Q_OBJECT int bar() {return 5;}\n"
- "class Fo@o {\n"
- " FAKE_Q_OBJECT\n"
- " int f1()\n"
- " {\n"
- " return 1;\n"
- " }\n"
- "};\n";
- QByteArray expected =
- "#define FAKE_Q_OBJECT int bar() {return 5;}\n"
- "class Foo {\n"
- " FAKE_Q_OBJECT\n"
- " int f1();\n"
- "};\n"
- "\n"
- "int Foo::f1()\n"
- "{\n"
- " return 1;\n"
- "}\n";
-
- MoveAllFuncDefOutside factory;
- QuickFixOperationTest(singleDocument(original, expected), &factory);
-}
-
-void QuickfixTest::testAssignToLocalVariableTemplates()
-{
-
- QList<TestDocumentPtr> testDocuments;
- QByteArray original;
- QByteArray expected;
-
- // Header File
- original =
- "template <typename T>\n"
- "class List {\n"
- "public:\n"
- " T first();"
- "};\n"
- ;
- expected = original;
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- // Source File
- original =
- "#include \"file.h\"\n"
- "void foo() {\n"
- " List<int> list;\n"
- " li@st.first();\n"
- "}\n";
- expected =
- "#include \"file.h\"\n"
- "void foo() {\n"
- " List<int> list;\n"
- " auto localFirst = list.first();\n"
- "}\n";
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
- AssignToLocalVariable factory;
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-void QuickfixTest::testExtractFunction_data()
-{
- QTest::addColumn<QByteArray>("original");
- QTest::addColumn<QByteArray>("expected");
-
- QTest::newRow("basic")
- << _("// Documentation for f\n"
- "void f()\n"
- "{\n"
- " @{start}g();@{end}\n"
- "}\n")
- << _("inline void extracted()\n"
- "{\n"
- " g();\n"
- "}\n"
- "\n"
- "// Documentation for f\n"
- "void f()\n"
- "{\n"
- " extracted();\n"
- "}\n");
-
- QTest::newRow("class function")
- << _("class Foo\n"
- "{\n"
- "private:\n"
- " void bar();\n"
- "};\n\n"
- "void Foo::bar()\n"
- "{\n"
- " @{start}g();@{end}\n"
- "}\n")
- << _("class Foo\n"
- "{\n"
- "public:\n"
- " void extracted();\n\n"
- "private:\n"
- " void bar();\n"
- "};\n\n"
- "inline void Foo::extracted()\n"
- "{\n"
- " g();\n"
- "}\n\n"
- "void Foo::bar()\n"
- "{\n"
- " extracted();\n"
- "}\n");
-
- QTest::newRow("class in namespace")
- << _("namespace NS {\n"
- "class C {\n"
- " void f(C &c);\n"
- "};\n"
- "}\n"
- "void NS::C::f(NS::C &c)\n"
- "{\n"
- " @{start}C *c2 = &c;@{end}\n"
- "}\n")
- << _("namespace NS {\n"
- "class C {\n"
- " void f(C &c);\n"
- "\n"
- "public:\n"
- " void extracted(NS::C &c);\n" // TODO: Remove non-required qualification
- "};\n"
- "}\n"
- "inline void NS::C::extracted(NS::C &c)\n"
- "{\n"
- " C *c2 = &c;\n"
- "}\n"
- "\n"
- "void NS::C::f(NS::C &c)\n"
- "{\n"
- " extracted(c);\n"
- "}\n");
-
- QTest::newRow("if-block")
- << _("inline void func()\n"
- "{\n"
- " int dummy = 0;\n"
- " @{start}if@{end} (dummy < 10) {\n"
- " ++dummy;\n"
- " }\n"
- "}\n")
- << _("inline void extracted(int dummy)\n"
- "{\n"
- " if (dummy < 10) {\n"
- " ++dummy;\n"
- " }\n"
- "}\n\n"
- "inline void func()\n"
- "{\n"
- " int dummy = 0;\n"
- " extracted(dummy);\n"
- "}\n");
-}
-
-void QuickfixTest::testExtractFunction()
-{
- QFETCH(QByteArray, original);
- QFETCH(QByteArray, expected);
-
- QList<TestDocumentPtr> testDocuments;
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- ExtractFunction factory([]() { return QLatin1String("extracted"); });
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-void QuickfixTest::testExtractLiteralAsParameterTypeDeduction_data()
-{
- QTest::addColumn<QByteArray>("typeString");
- QTest::addColumn<QByteArray>("literal");
- QTest::newRow("int")
- << QByteArray("int ") << QByteArray("156");
- QTest::newRow("unsigned int")
- << QByteArray("unsigned int ") << QByteArray("156u");
- QTest::newRow("long")
- << QByteArray("long ") << QByteArray("156l");
- QTest::newRow("unsigned long")
- << QByteArray("unsigned long ") << QByteArray("156ul");
- QTest::newRow("long long")
- << QByteArray("long long ") << QByteArray("156ll");
- QTest::newRow("unsigned long long")
- << QByteArray("unsigned long long ") << QByteArray("156ull");
- QTest::newRow("float")
- << QByteArray("float ") << QByteArray("3.14159f");
- QTest::newRow("double")
- << QByteArray("double ") << QByteArray("3.14159");
- QTest::newRow("long double")
- << QByteArray("long double ") << QByteArray("3.14159L");
- QTest::newRow("bool")
- << QByteArray("bool ") << QByteArray("true");
- QTest::newRow("bool")
- << QByteArray("bool ") << QByteArray("false");
- QTest::newRow("char")
- << QByteArray("char ") << QByteArray("'X'");
- QTest::newRow("wchar_t")
- << QByteArray("wchar_t ") << QByteArray("L'X'");
- QTest::newRow("char16_t")
- << QByteArray("char16_t ") << QByteArray("u'X'");
- QTest::newRow("char32_t")
- << QByteArray("char32_t ") << QByteArray("U'X'");
- QTest::newRow("const char *")
- << QByteArray("const char *") << QByteArray("\"narf\"");
- QTest::newRow("const wchar_t *")
- << QByteArray("const wchar_t *") << QByteArray("L\"narf\"");
- QTest::newRow("const char16_t *")
- << QByteArray("const char16_t *") << QByteArray("u\"narf\"");
- QTest::newRow("const char32_t *")
- << QByteArray("const char32_t *") << QByteArray("U\"narf\"");
-}
-
-void QuickfixTest::testExtractLiteralAsParameterTypeDeduction()
-{
- QFETCH(QByteArray, typeString);
- QFETCH(QByteArray, literal);
- const QByteArray original = QByteArray("void foo() {return @") + literal + QByteArray(";}\n");
- const QByteArray expected = QByteArray("void foo(") + typeString + QByteArray("newParameter = ")
- + literal + QByteArray(") {return newParameter;}\n");
-
- if (literal == "3.14159") {
- qWarning("Literal 3.14159 is wrongly reported as int. Skipping.");
- return;
- } else if (literal == "3.14159L") {
- qWarning("Literal 3.14159L is wrongly reported as long. Skipping.");
- return;
- }
-
- ExtractLiteralAsParameter factory;
- QuickFixOperationTest(singleDocument(original, expected), &factory);
-}
-
-void QuickfixTest::testExtractLiteralAsParameterFreeFunctionSeparateFiles()
-{
- QList<TestDocumentPtr> testDocuments;
- QByteArray original;
- QByteArray expected;
-
- // Header File
- original =
- "void foo(const char *a, long b = 1);\n";
- expected =
- "void foo(const char *a, long b = 1, int newParameter = 156);\n";
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- // Source File
- original =
- "void foo(const char *a, long b)\n"
- "{return 1@56 + 123 + 156;}\n";
- expected =
- "void foo(const char *a, long b, int newParameter)\n"
- "{return newParameter + 123 + newParameter;}\n";
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
- ExtractLiteralAsParameter factory;
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-void QuickfixTest::testExtractLiteralAsParameterMemberFunctionSeparateFiles()
-{
- QList<TestDocumentPtr> testDocuments;
- QByteArray original;
- QByteArray expected;
-
- // Header File
- original =
- "class Narf {\n"
- "public:\n"
- " int zort();\n"
- "};\n";
- expected =
- "class Narf {\n"
- "public:\n"
- " int zort(int newParameter = 155);\n"
- "};\n";
- testDocuments << CppTestDocument::create("file.h", original, expected);
-
- // Source File
- original =
- "#include \"file.h\"\n\n"
- "int Narf::zort()\n"
- "{ return 15@5 + 1; }\n";
- expected =
- "#include \"file.h\"\n\n"
- "int Narf::zort(int newParameter)\n"
- "{ return newParameter + 1; }\n";
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
- ExtractLiteralAsParameter factory;
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-void QuickfixTest::testExtractLiteralAsParameterNotTriggeringForInvalidCode()
-{
- QList<TestDocumentPtr> testDocuments;
- QByteArray original;
- original =
- "T(\"test\")\n"
- "{\n"
- " const int i = @14;\n"
- "}\n";
- testDocuments << CppTestDocument::create("file.cpp", original, "");
-
- ExtractLiteralAsParameter factory;
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-void QuickfixTest::testAddCurlyBraces_data()
-{
- QTest::addColumn<QByteArray>("original");
- QTest::addColumn<QByteArray>("expected");
-
- QByteArray original = R"delim(
-void MyObject::f()
-{
- @if (true)
- emit mySig();
-})delim";
- QByteArray expected = R"delim(
-void MyObject::f()
-{
- if (true) {
- emit mySig();
- }
-})delim";
- QTest::newRow("if") << original << expected;
-
- original = R"delim(
-void MyObject::f()
-{
- @if (true)
- emit mySig();
- else
- emit otherSig();
-})delim";
- expected = R"delim(
-void MyObject::f()
-{
- @if (true) {
- emit mySig();
- } else {
- emit otherSig();
- }
-})delim";
- QTest::newRow("if with one else, unbraced") << original << expected;
-
- original = R"delim(
-void MyObject::f()
-{
- @if (true) {
- emit mySig();
- } else
- emit otherSig();
-})delim";
- expected = R"delim(
-void MyObject::f()
-{
- @if (true) {
- emit mySig();
- } else {
- emit otherSig();
- }
-})delim";
- QTest::newRow("if with one else, if braced") << original << expected;
-
- original = R"delim(
-void MyObject::f()
-{
- @if (true)
- emit mySig();
- else {
- emit otherSig();
- }
-})delim";
- expected = R"delim(
-void MyObject::f()
-{
- @if (true) {
- emit mySig();
- } else {
- emit otherSig();
- }
-})delim";
- QTest::newRow("if with one else, else braced") << original << expected;
-
- original = R"delim(
-void MyObject::f()
-{
- @if (true) {
- emit mySig();
- } else {
- emit otherSig();
- }
-})delim";
- expected.clear();
- QTest::newRow("if with one else, both braced") << original << expected;
-
- original = R"delim(
-void MyObject::f()
-{
- @if (x == 1)
- emit sig1();
- else if (x == 2)
- emit sig2();
-})delim";
- expected = R"delim(
-void MyObject::f()
-{
- if (x == 1) {
- emit sig1();
- } else if (x == 2) {
- emit sig2();
- }
-})delim";
- QTest::newRow("if-else chain without final else, unbraced") << original << expected;
-
- original = R"delim(
-void MyObject::f()
-{
- @if (x == 1) {
- emit sig1();
- } else if (x == 2)
- emit sig2();
-})delim";
- expected = R"delim(
-void MyObject::f()
-{
- if (x == 1) {
- emit sig1();
- } else if (x == 2) {
- emit sig2();
- }
-})delim";
- QTest::newRow("if-else chain without final else, partially braced 1") << original << expected;
-
- original = R"delim(
-void MyObject::f()
-{
- @if (x == 1)
- emit sig1();
- else if (x == 2) {
- emit sig2();
- }
-})delim";
- expected = R"delim(
-void MyObject::f()
-{
- if (x == 1) {
- emit sig1();
- } else if (x == 2) {
- emit sig2();
- }
-})delim";
- QTest::newRow("if-else chain without final else, partially braced 2") << original << expected;
-
- original = R"delim(
-void MyObject::f()
-{
- @if (x == 1) {
- emit sig1();
- } else if (x == 2) {
- emit sig2();
- }
-})delim";
- expected.clear();
- QTest::newRow("if-else chain without final else, fully braced") << original << expected;
-
- original = R"delim(
-void MyObject::f()
-{
- @if (x == 1)
- emit sig1();
- else if (x == 2)
- emit sig2();
- else if (x == 3)
- emit sig3();
- else
- emit otherSig();
-})delim";
- expected = R"delim(
-void MyObject::f()
-{
- if (x == 1) {
- emit sig1();
- } else if (x == 2) {
- emit sig2();
- } else if (x == 3) {
- emit sig3();
- } else {
- emit otherSig();
- }
-})delim";
- QTest::newRow("if-else chain, unbraced") << original << expected;
-
- original = R"delim(
-void MyObject::f()
-{
- @if (x == 1) {
- emit sig1();
- } else if (x == 2)
- emit sig2();
- else if (x == 3)
- emit sig3();
- else
- emit otherSig();
-})delim";
- expected = R"delim(
-void MyObject::f()
-{
- if (x == 1) {
- emit sig1();
- } else if (x == 2) {
- emit sig2();
- } else if (x == 3) {
- emit sig3();
- } else {
- emit otherSig();
- }
-})delim";
- QTest::newRow("if-else chain, partially braced 1") << original << expected;
-
- original = R"delim(
-void MyObject::f()
-{
- @if (x == 1)
- emit sig1();
- else if (x == 2) {
- emit sig2();
- } else if (x == 3)
- emit sig3();
- else
- emit otherSig();
-})delim";
- expected = R"delim(
-void MyObject::f()
-{
- if (x == 1) {
- emit sig1();
- } else if (x == 2) {
- emit sig2();
- } else if (x == 3) {
- emit sig3();
- } else {
- emit otherSig();
- }
-})delim";
- QTest::newRow("if-else chain, partially braced 2") << original << expected;
-
- original = R"delim(
-void MyObject::f()
-{
- @if (x == 1)
- emit sig1();
- else if (x == 2)
- emit sig2();
- else if (x == 3) {
- emit sig3();
- } else
- emit otherSig();
-})delim";
- expected = R"delim(
-void MyObject::f()
-{
- if (x == 1) {
- emit sig1();
- } else if (x == 2) {
- emit sig2();
- } else if (x == 3) {
- emit sig3();
- } else {
- emit otherSig();
- }
-})delim";
- QTest::newRow("if-else chain, partially braced 3") << original << expected;
-
- original = R"delim(
-void MyObject::f()
-{
- @if (x == 1)
- emit sig1();
- else if (x == 2)
- emit sig2();
- else if (x == 3)
- emit sig3();
- else {
- emit otherSig();
- }
-})delim";
- expected = R"delim(
-void MyObject::f()
-{
- if (x == 1) {
- emit sig1();
- } else if (x == 2) {
- emit sig2();
- } else if (x == 3) {
- emit sig3();
- } else {
- emit otherSig();
- }
-})delim";
- QTest::newRow("if-else chain, partially braced 4") << original << expected;
-
- original = R"delim(
-void MyObject::f()
-{
- @if (x == 1) {
- emit sig1();
- } else if (x == 2) {
- emit sig2();
- } else if (x == 3) {
- emit sig3();
- } else {
- emit otherSig();
- }
-})delim";
- expected.clear();
- QTest::newRow("if-else chain, fully braced") << original << expected;
-
- original = R"delim(
-void MyObject::f()
-{
- @while (true)
- emit mySig();
-})delim";
- expected = R"delim(
-void MyObject::f()
-{
- while (true) {
- emit mySig();
- }
-})delim";
- QTest::newRow("while") << original << expected;
-
- original = R"delim(
-void MyObject::f()
-{
- @for (int i = 0; i < 10; ++i)
- emit mySig();
-})delim";
- expected = R"delim(
-void MyObject::f()
-{
- for (int i = 0; i < 10; ++i) {
- emit mySig();
- }
-})delim";
- QTest::newRow("for") << original << expected;
-
- original = R"delim(
-void MyObject::f()
-{
- @for (int i : list)
- emit mySig();
-})delim";
- expected = R"delim(
-void MyObject::f()
-{
- for (int i : list) {
- emit mySig();
- }
-})delim";
- QTest::newRow("range-based for") << original << expected;
-
- original = R"delim(
-void MyObject::f()
-{
- @do
- emit mySig();
- while (true);
-})delim";
- expected = R"delim(
-void MyObject::f()
-{
- do {
- emit mySig();
- } while (true);
-})delim";
- QTest::newRow("do") << original << expected;
-
- original = R"delim(
-void MyObject::f()
-{
- @do {
- emit mySig();
- } while (true);
-})delim";
- expected.clear();
- QTest::newRow("already has braces") << original << expected;
-}
-
-void QuickfixTest::testAddCurlyBraces()
-{
- QFETCH(QByteArray, original);
- QFETCH(QByteArray, expected);
-
- AddBracesToControlStatement factory;
- QuickFixOperationTest({CppTestDocument::create("file.cpp", original, expected)}, &factory);
-}
-
-void QuickfixTest::testConvertQt4ConnectConnectOutOfClass()
-{
- QByteArray prefix =
- "class QObject {};\n"
- "class TestClass : public QObject\n"
- "{\n"
- "public:\n"
- " void setProp(int) {}\n"
- " void sigFoo(int) {}\n"
- "};\n"
- "\n"
- "int foo()\n"
- "{\n";
-
- QByteArray suffix = "\n}\n";
-
- QByteArray original = prefix
- + " TestClass obj;\n"
- " conne@ct(&obj, SIGNAL(sigFoo(int)), &obj, SLOT(setProp(int)));"
- + suffix;
-
- QByteArray expected = prefix
- + " TestClass obj;\n"
- " connect(&obj, &TestClass::sigFoo, &obj, &TestClass::setProp);"
- + suffix;
-
- QList<TestDocumentPtr> testDocuments;
- testDocuments << CppTestDocument::create("file.cpp", original, expected);
-
- ConvertQt4Connect factory;
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-void QuickfixTest::testConvertQt4ConnectConnectWithinClass_data()
-{
- QTest::addColumn<QByteArray>("original");
- QTest::addColumn<QByteArray>("expected");
-
- QTest::newRow("four-args-connect")
- << QByteArray("conne@ct(this, SIGNAL(sigFoo(int)), this, SLOT(setProp(int)));")
- << QByteArray("connect(this, &TestClass::sigFoo, this, &TestClass::setProp);");
-
- QTest::newRow("four-args-disconnect")
- << QByteArray("disconne@ct(this, SIGNAL(sigFoo(int)), this, SLOT(setProp(int)));")
- << QByteArray("disconnect(this, &TestClass::sigFoo, this, &TestClass::setProp);");
-
- QTest::newRow("three-args-connect")
- << QByteArray("conne@ct(this, SIGNAL(sigFoo(int)), SLOT(setProp(int)));")
- << QByteArray("connect(this, &TestClass::sigFoo, this, &TestClass::setProp);");
-
- QTest::newRow("template-value")
- << QByteArray("Pointer<TestClass> p;\n"
- "conne@ct(p.t, SIGNAL(sigFoo(int)), p.t, SLOT(setProp(int)));")
- << QByteArray("Pointer<TestClass> p;\n"
- "connect(p.t, &TestClass::sigFoo, p.t, &TestClass::setProp);");
-
- QTest::newRow("implicit-pointer")
- << QByteArray("Pointer<TestClass> p;\n"
- "conne@ct(p, SIGNAL(sigFoo(int)), p, SLOT(setProp(int)));")
- << QByteArray("Pointer<TestClass> p;\n"
- "connect(p.data(), &TestClass::sigFoo, p.data(), &TestClass::setProp);");
-}
-
-void QuickfixTest::testConvertQt4ConnectConnectWithinClass()
-{
- QFETCH(QByteArray, original);
- QFETCH(QByteArray, expected);
-
- QByteArray prefix =
- "template<class T>\n"
- "struct Pointer\n"
- "{\n"
- " T *t;\n"
- " operator T*() const { return t; }\n"
- " T *data() const { return t; }\n"
- "};\n"
- "class QObject {};\n"
- "class TestClass : public QObject\n"
- "{\n"
- "public:\n"
- " void setProp(int) {}\n"
- " void sigFoo(int) {}\n"
- " void setupSignals();\n"
- "};\n"
- "\n"
- "int TestClass::setupSignals()\n"
- "{\n";
-
- QByteArray suffix = "\n}\n";
-
- QList<TestDocumentPtr> testDocuments;
- testDocuments << CppTestDocument::create("file.cpp",
- prefix + original + suffix,
- prefix + expected + suffix);
-
- ConvertQt4Connect factory;
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-void QuickfixTest::testConvertQt4ConnectDifferentNamespace()
-{
- const QByteArray prefix =
- "namespace NsA {\n"
- "class ClassA : public QObject\n"
- "{\n"
- " static ClassA *instance();\n"
- "signals:\n"
- " void sig();\n"
- "};\n"
- "}\n"
- "\n"
- "namespace NsB {\n"
- "class ClassB : public QObject\n"
- "{\n"
- " void slot();\n"
- " void connector() {\n";
-
- const QByteArray suffix = " }\n};\n}";
-
- const QByteArray original = "co@nnect(NsA::ClassA::instance(), SIGNAL(sig()),\n"
- " this, SLOT(slot()));\n";
- const QByteArray expected = "connect(NsA::ClassA::instance(), &NsA::ClassA::sig,\n"
- " this, &ClassB::slot);\n";
- QList<TestDocumentPtr> testDocuments;
- testDocuments << CppTestDocument::create("file.cpp",
- prefix + original + suffix,
- prefix + expected + suffix);
-
- ConvertQt4Connect factory;
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-void QuickfixTest::testRemoveUsingNamespace_data()
-{
- QTest::addColumn<QByteArray>("header1");
- QTest::addColumn<QByteArray>("header2");
- QTest::addColumn<QByteArray>("header3");
- QTest::addColumn<QByteArray>("expected1");
- QTest::addColumn<QByteArray>("expected2");
- QTest::addColumn<QByteArray>("expected3");
- QTest::addColumn<int>("operation");
-
- const QByteArray header1 = "namespace std{\n"
- " template<typename T>\n"
- " class vector{};\n"
- " namespace chrono{\n"
- " using seconds = int;\n"
- " }\n"
- "}\n"
- "using namespace std;\n"
- "namespace test{\n"
- " class vector{\n"
- " std::vector<int> ints;\n"
- " };\n"
- "}\n";
- const QByteArray header2 = "#include \"header1.h\"\n"
- "using foo = test::vector;\n"
- "using namespace std;\n"
- "using namespace test;\n"
- "vector<int> others;\n";
-
- const QByteArray header3 = "#include \"header2.h\"\n"
- "using namespace std;\n"
- "using namespace chrono;\n"
- "namespace test{\n"
- " vector vec;\n"
- " seconds t;\n"
- "}\n"
- "void scope(){\n"
- " for (;;) {\n"
- " using namespace std;\n"
- " vector<int> fori;\n"
- " }\n"
- " vector<int> no;\n"
- " using namespace std;\n"
- " vector<int> _no_change;\n"
- "}\n"
- "foo foos;\n";
-
- QByteArray h3 = "#include \"header2.h\"\n"
- "using namespace s@td;\n"
- "using namespace chrono;\n"
- "namespace test{\n"
- " vector vec;\n"
- " seconds t;\n"
- "}\n"
- "void scope(){\n"
- " for (;;) {\n"
- " using namespace std;\n"
- " vector<int> fori;\n"
- " }\n"
- " vector<int> no;\n"
- " using namespace std;\n"
- " vector<int> _no_change;\n"
- "}\n"
- "foo foos;\n";
-
- // like header1 but without "using namespace std;\n"
- QByteArray expected1 = "namespace std{\n"
- " template<typename T>\n"
- " class vector{};\n"
- " namespace chrono{\n"
- " using seconds = int;\n"
- " }\n"
- "}\n"
- "namespace test{\n"
- " class vector{\n"
- " std::vector<int> ints;\n"
- " };\n"
- "}\n";
-
- // like header2 but without "using namespace std;\n" and with std::vector
- QByteArray expected2 = "#include \"header1.h\"\n"
- "using foo = test::vector;\n"
- "using namespace test;\n"
- "std::vector<int> others;\n";
-
- QByteArray expected3 = "#include \"header2.h\"\n"
- "using namespace std::chrono;\n"
- "namespace test{\n"
- " vector vec;\n"
- " seconds t;\n"
- "}\n"
- "void scope(){\n"
- " for (;;) {\n"
- " using namespace std;\n"
- " vector<int> fori;\n"
- " }\n"
- " std::vector<int> no;\n"
- " using namespace std;\n"
- " vector<int> _no_change;\n"
- "}\n"
- "foo foos;\n";
-
- QTest::newRow("remove only in one file local")
- << header1 << header2 << h3 << header1 << header2 << expected3 << 0;
- QTest::newRow("remove only in one file globally")
- << header1 << header2 << h3 << expected1 << expected2 << expected3 << 1;
-
- QByteArray h2 = "#include \"header1.h\"\n"
- "using foo = test::vector;\n"
- "using namespace s@td;\n"
- "using namespace test;\n"
- "vector<int> others;\n";
-
- QTest::newRow("remove across two files only this")
- << header1 << h2 << header3 << header1 << expected2 << header3 << 0;
- QTest::newRow("remove across two files globally1")
- << header1 << h2 << header3 << expected1 << expected2 << expected3 << 1;
-
- QByteArray h1 = "namespace std{\n"
- " template<typename T>\n"
- " class vector{};\n"
- " namespace chrono{\n"
- " using seconds = int;\n"
- " }\n"
- "}\n"
- "using namespace s@td;\n"
- "namespace test{\n"
- " class vector{\n"
- " std::vector<int> ints;\n"
- " };\n"
- "}\n";
-
- QTest::newRow("remove across tree files only this")
- << h1 << header2 << header3 << expected1 << header2 << header3 << 0;
- QTest::newRow("remove across tree files globally")
- << h1 << header2 << header3 << expected1 << expected2 << expected3 << 1;
-
- expected3 = "#include \"header2.h\"\n"
- "using namespace std::chrono;\n"
- "namespace test{\n"
- " vector vec;\n"
- " seconds t;\n"
- "}\n"
- "void scope(){\n"
- " for (;;) {\n"
- " using namespace s@td;\n"
- " vector<int> fori;\n"
- " }\n"
- " std::vector<int> no;\n"
- " using namespace std;\n"
- " vector<int> _no_change;\n"
- "}\n"
- "foo foos;\n";
-
- QByteArray expected3_new = "#include \"header2.h\"\n"
- "using namespace std::chrono;\n"
- "namespace test{\n"
- " vector vec;\n"
- " seconds t;\n"
- "}\n"
- "void scope(){\n"
- " for (;;) {\n"
- " std::vector<int> fori;\n"
- " }\n"
- " std::vector<int> no;\n"
- " using namespace std;\n"
- " vector<int> _no_change;\n"
- "}\n"
- "foo foos;\n";
-
- QTest::newRow("scoped remove")
- << expected1 << expected2 << expected3 << expected1 << expected2 << expected3_new << 0;
-
- h2 = "#include \"header1.h\"\n"
- "using foo = test::vector;\n"
- "using namespace std;\n"
- "using namespace t@est;\n"
- "vector<int> others;\n";
- expected2 = "#include \"header1.h\"\n"
- "using foo = test::vector;\n"
- "using namespace std;\n"
- "vector<int> others;\n";
-
- QTest::newRow("existing namespace")
- << header1 << h2 << header3 << header1 << expected2 << header3 << 1;
-
- // test: remove using directive at global scope in every file
- h1 = "using namespace tes@t;";
- h2 = "using namespace test;";
- h3 = "using namespace test;";
-
- expected1 = expected2 = expected3 = "";
- QTest::newRow("global scope remove in every file")
- << h1 << h2 << h3 << expected1 << expected2 << expected3 << 1;
-
- // test: dont print inline namespaces
- h1 = R"--(
-namespace test {
- inline namespace test {
- class Foo{
- void foo1();
- void foo2();
- };
- inline int TEST = 42;
- }
-}
-)--";
- h2 = R"--(
-#include "header1.h"
-using namespace tes@t;
-)--";
- h3 = R"--(
-#include "header2.h"
-Foo f1;
-test::Foo f2;
-using T1 = Foo;
-using T2 = test::Foo;
-int i1 = TEST;
-int i2 = test::TEST;
-void Foo::foo1(){};
-void test::Foo::foo2(){};
-)--";
-
- expected1 = h1;
- expected2 = R"--(
-#include "header1.h"
-)--";
- expected3 = R"--(
-#include "header2.h"
-test::Foo f1;
-test::Foo f2;
-using T1 = test::Foo;
-using T2 = test::Foo;
-int i1 = test::TEST;
-int i2 = test::TEST;
-void test::Foo::foo1(){};
-void test::Foo::foo2(){};
-)--";
- QTest::newRow("don't insert inline namespaces")
- << h1 << h2 << h3 << expected1 << expected2 << expected3 << 0;
-}
-
-void QuickfixTest::testRemoveUsingNamespace()
-{
- QFETCH(QByteArray, header1);
- QFETCH(QByteArray, header2);
- QFETCH(QByteArray, header3);
- QFETCH(QByteArray, expected1);
- QFETCH(QByteArray, expected2);
- QFETCH(QByteArray, expected3);
- QFETCH(int, operation);
-
- QList<TestDocumentPtr> testDocuments;
- testDocuments << CppTestDocument::create("header1.h", header1, expected1);
- testDocuments << CppTestDocument::create("header2.h", header2, expected2);
- testDocuments << CppTestDocument::create("header3.h", header3, expected3);
-
- RemoveUsingNamespace factory;
- QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), operation);
-}
-
-void QuickfixTest::testRemoveUsingNamespaceSimple_data()
-{
- QTest::addColumn<QByteArray>("header");
- QTest::addColumn<QByteArray>("expected");
-
- const QByteArray common = R"--(
-namespace N{
- template<typename T>
- struct vector{
- using iterator = T*;
- };
- using int_vector = vector<int>;
-}
-)--";
- const QByteArray header = common + R"--(
-using namespace N@;
-int_vector ints;
-int_vector::iterator intIter;
-using vec = vector<int>;
-vec::iterator it;
-)--";
- const QByteArray expected = common + R"--(
-N::int_vector ints;
-N::int_vector::iterator intIter;
-using vec = N::vector<int>;
-vec::iterator it;
-)--";
-
- QTest::newRow("nested typedefs with Namespace") << header << expected;
-}
-
-void QuickfixTest::testRemoveUsingNamespaceSimple()
-{
- QFETCH(QByteArray, header);
- QFETCH(QByteArray, expected);
-
- QList<TestDocumentPtr> testDocuments;
- testDocuments << CppTestDocument::create("header.h", header, expected);
-
- RemoveUsingNamespace factory;
- QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths());
-}
-
-void QuickfixTest::testRemoveUsingNamespaceDifferentSymbols()
-{
- QByteArray header = "namespace test{\n"
- " struct foo{\n"
- " foo();\n"
- " void bar();\n"
- " };\n"
- " void func();\n"
- " enum E {E1, E2};\n"
- " int bar;\n"
- "}\n"
- "using namespace t@est;\n"
- "foo::foo(){}\n"
- "void foo::bar(){}\n"
- "void test(){\n"
- " int i = bar * 4;\n"
- " E val = E1;\n"
- " auto p = &foo::bar;\n"
- " func()\n"
- "}\n";
- QByteArray expected = "namespace test{\n"
- " struct foo{\n"
- " foo();\n"
- " void bar();\n"
- " };\n"
- " void func();\n"
- " enum E {E1, E2};\n"
- " int bar;\n"
- "}\n"
- "test::foo::foo(){}\n"
- "void test::foo::bar(){}\n"
- "void test(){\n"
- " int i = test::bar * 4;\n"
- " test::E val = test::E1;\n"
- " auto p = &test::foo::bar;\n"
- " test::func()\n"
- "}\n";
-
- QList<TestDocumentPtr> testDocuments;
- testDocuments << CppTestDocument::create("file.h", header, expected);
- RemoveUsingNamespace factory;
- QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 0);
-}
-
-enum ConstructorLocation { Inside, Outside, CppGenNamespace, CppGenUsingDirective, CppRewriteType };
-
-void QuickfixTest::testGenerateConstructor_data()
-{
- QTest::addColumn<QByteArray>("original_header");
- QTest::addColumn<QByteArray>("expected_header");
- QTest::addColumn<QByteArray>("original_source");
- QTest::addColumn<QByteArray>("expected_source");
- QTest::addColumn<int>("location");
- const int Inside = ConstructorLocation::Inside;
- const int Outside = ConstructorLocation::Outside;
- const int CppGenNamespace = ConstructorLocation::CppGenNamespace;
- const int CppGenUsingDirective = ConstructorLocation::CppGenUsingDirective;
- const int CppRewriteType = ConstructorLocation::CppRewriteType;
-
- QByteArray header = R"--(
-class@ Foo{
- int test;
- static int s;
-};
-)--";
- QByteArray expected = R"--(
-class Foo{
- int test;
- static int s;
-public:
- Foo(int test) : test(test)
- {}
-};
-)--";
- QTest::newRow("ignore static") << header << expected << QByteArray() << QByteArray() << Inside;
-
- header = R"--(
-class@ Foo{
- CustomType test;
-};
-)--";
- expected = R"--(
-class Foo{
- CustomType test;
-public:
- Foo(CustomType test) : test(std::move(test))
- {}
-};
-)--";
- QTest::newRow("Move custom value types")
- << header << expected << QByteArray() << QByteArray() << Inside;
-
- header = R"--(
-class@ Foo{
- int test;
-protected:
- Foo() = default;
-};
-)--";
- expected = R"--(
-class Foo{
- int test;
-public:
- Foo(int test) : test(test)
- {}
-
-protected:
- Foo() = default;
-};
-)--";
-
- QTest::newRow("new section before existing")
- << header << expected << QByteArray() << QByteArray() << Inside;
-
- header = R"--(
-class@ Foo{
- int test;
-};
-)--";
- expected = R"--(
-class Foo{
- int test;
-public:
- Foo(int test) : test(test)
- {}
-};
-)--";
- QTest::newRow("new section at end")
- << header << expected << QByteArray() << QByteArray() << Inside;
-
- header = R"--(
-class@ Foo{
- int test;
-public:
- /**
- * Random comment
- */
- Foo(int i, int i2);
-};
-)--";
- expected = R"--(
-class Foo{
- int test;
-public:
- Foo(int test) : test(test)
- {}
- /**
- * Random comment
- */
- Foo(int i, int i2);
-};
-)--";
- QTest::newRow("in section before")
- << header << expected << QByteArray() << QByteArray() << Inside;
-
- header = R"--(
-class@ Foo{
- int test;
-public:
- Foo() = default;
-};
-)--";
- expected = R"--(
-class Foo{
- int test;
-public:
- Foo() = default;
- Foo(int test) : test(test)
- {}
-};
-)--";
- QTest::newRow("in section after")
- << header << expected << QByteArray() << QByteArray() << Inside;
-
- header = R"--(
-class@ Foo{
- int test1;
- int test2;
- int test3;
-public:
-};
-)--";
- expected = R"--(
-class Foo{
- int test1;
- int test2;
- int test3;
-public:
- Foo(int test2, int test3, int test1) : test1(test1),
- test2(test2),
- test3(test3)
- {}
-};
-)--";
- // No worry, that is not the default behavior.
- // Move first member to the back when testing with 3 or more members
- QTest::newRow("changed parameter order")
- << header << expected << QByteArray() << QByteArray() << Inside;
-
- header = R"--(
-class@ Foo{
- int test;
- int di_test;
-public:
-};
-)--";
- expected = R"--(
-class Foo{
- int test;
- int di_test;
-public:
- Foo(int test, int di_test = 42) : test(test),
- di_test(di_test)
- {}
-};
-)--";
- QTest::newRow("default parameters")
- << header << expected << QByteArray() << QByteArray() << Inside;
-
- header = R"--(
-struct Bar{
- Bar(int i);
-};
-class@ Foo : public Bar{
- int test;
-public:
-};
-)--";
- expected = R"--(
-struct Bar{
- Bar(int i);
-};
-class Foo : public Bar{
- int test;
-public:
- Foo(int test, int i) : Bar(i),
- test(test)
- {}
-};
-)--";
- QTest::newRow("parent constructor")
- << header << expected << QByteArray() << QByteArray() << Inside;
-
- header = R"--(
-struct Bar{
- Bar(int use_i = 6);
-};
-class@ Foo : public Bar{
- int test;
-public:
-};
-)--";
- expected = R"--(
-struct Bar{
- Bar(int use_i = 6);
-};
-class Foo : public Bar{
- int test;
-public:
- Foo(int test, int use_i = 6) : Bar(use_i),
- test(test)
- {}
-};
-)--";
- QTest::newRow("parent constructor with default")
- << header << expected << QByteArray() << QByteArray() << Inside;
-
- header = R"--(
-struct Bar{
- Bar(int use_i = L'A', int use_i2 = u8"B");
-};
-class@ Foo : public Bar{
-public:
-};
-)--";
- expected = R"--(
-struct Bar{
- Bar(int use_i = L'A', int use_i2 = u8"B");
-};
-class Foo : public Bar{
-public:
- Foo(int use_i = L'A', int use_i2 = u8"B") : Bar(use_i, use_i2)
- {}
-};
-)--";
- QTest::newRow("parent constructor with char/string default value")
- << header << expected << QByteArray() << QByteArray() << Inside;
-
- const QByteArray common = R"--(
-namespace N{
- template<typename T>
- struct vector{
- };
-}
-)--";
- header = common + R"--(
-namespace M{
-enum G{g};
-class@ Foo{
- N::vector<G> g;
- enum E{e}e;
-public:
-};
-}
-)--";
-
- expected = common + R"--(
-namespace M{
-enum G{g};
-class@ Foo{
- N::vector<G> g;
- enum E{e}e;
-public:
- Foo(const N::vector<G> &g, E e);
-};
-
-Foo::Foo(const N::vector<G> &g, Foo::E e) : g(g),
- e(e)
-{}
-
-}
-)--";
- QTest::newRow("source: right type outside class ")
- << QByteArray() << QByteArray() << header << expected << Outside;
- expected = common + R"--(
-namespace M{
-enum G{g};
-class@ Foo{
- N::vector<G> g;
- enum E{e}e;
-public:
- Foo(const N::vector<G> &g, E e);
-};
-}
-
-
-inline M::Foo::Foo(const N::vector<M::G> &g, M::Foo::E e) : g(g),
- e(e)
-{}
-
-)--";
- QTest::newRow("header: right type outside class ")
- << header << expected << QByteArray() << QByteArray() << Outside;
-
- expected = common + R"--(
-namespace M{
-enum G{g};
-class@ Foo{
- N::vector<G> g;
- enum E{e}e;
-public:
- Foo(const N::vector<G> &g, E e);
-};
-}
-)--";
- const QByteArray source = R"--(
-#include "file.h"
-)--";
- QByteArray expected_source = R"--(
-#include "file.h"
-
-
-namespace M {
-Foo::Foo(const N::vector<G> &g, Foo::E e) : g(g),
- e(e)
-{}
-
-}
-)--";
- QTest::newRow("source: right type inside namespace")
- << header << expected << source << expected_source << CppGenNamespace;
-
- expected_source = R"--(
-#include "file.h"
-
-using namespace M;
-Foo::Foo(const N::vector<G> &g, Foo::E e) : g(g),
- e(e)
-{}
-)--";
- QTest::newRow("source: right type with using directive")
- << header << expected << source << expected_source << CppGenUsingDirective;
-
- expected_source = R"--(
-#include "file.h"
-
-M::Foo::Foo(const N::vector<M::G> &g, M::Foo::E e) : g(g),
- e(e)
-{}
-)--";
- QTest::newRow("source: right type while rewritung types")
- << header << expected << source << expected_source << CppRewriteType;
-}
-
-void QuickfixTest::testGenerateConstructor()
-{
- class TestFactory : public GenerateConstructor
- {
- public:
- TestFactory() { setTest(); }
- };
-
- QFETCH(QByteArray, original_header);
- QFETCH(QByteArray, expected_header);
- QFETCH(QByteArray, original_source);
- QFETCH(QByteArray, expected_source);
- QFETCH(int, location);
-
- QuickFixSettings s;
- s->valueTypes << "CustomType";
- using L = ConstructorLocation;
- if (location == L::Inside) {
- s->setterInCppFileFrom = -1;
- s->setterOutsideClassFrom = -1;
- } else if (location == L::Outside) {
- s->setterInCppFileFrom = -1;
- s->setterOutsideClassFrom = 1;
- } else if (location >= L::CppGenNamespace && location <= L::CppRewriteType) {
- s->setterInCppFileFrom = 1;
- s->setterOutsideClassFrom = -1;
- using Handling = CppQuickFixSettings::MissingNamespaceHandling;
- if (location == L::CppGenNamespace)
- s->cppFileNamespaceHandling = Handling::CreateMissing;
- else if (location == L::CppGenUsingDirective)
- s->cppFileNamespaceHandling = Handling::AddUsingDirective;
- else if (location == L::CppRewriteType)
- s->cppFileNamespaceHandling = Handling::RewriteType;
- } else {
- QFAIL("location is none of the values of the ConstructorLocation enum");
- }
-
- QList<TestDocumentPtr> testDocuments;
- testDocuments << CppTestDocument::create("file.h", original_header, expected_header);
- testDocuments << CppTestDocument::create("file.cpp", original_source, expected_source);
- TestFactory factory;
- QuickFixOperationTest(testDocuments, &factory);
-}
-
-void QuickfixTest::testChangeCommentType_data()
-{
- QTest::addColumn<QString>("input");
- QTest::addColumn<QString>("expectedOutput");
-
- QTest::newRow("C -> C++ / no selection / single line") << R"(
-int var1;
-/* Other comment, unaffected */
-/* Our @comment */
-/* Another unaffected comment */
-int var2;)" << R"(
-int var1;
-/* Other comment, unaffected */
-// Our comment
-/* Another unaffected comment */
-int var2;)";
-
- QTest::newRow("C -> C++ / no selection / multi-line / preserved header and footer") << R"(
-/****************************************************
- * some info
- * more @info
- ***************************************************/)" << R"(
-/////////////////////////////////////////////////////
-// some info
-// more info
-/////////////////////////////////////////////////////)";
-
- QTest::newRow("C -> C++ / no selection / multi-line / non-preserved header and footer") << R"(
-/*
- * some info
- * more @info
- */)" << R"(
-// some info
-// more info
-)";
-
- QTest::newRow("C -> C++ / no selection / qdoc") << R"(
-/*!
- \qmlproperty string Type::element.name
- \qmlproperty int Type::element.id
-
- \brief Holds the @element name and id.
-*/)" << R"(
-//! \qmlproperty string Type::element.name
-//! \qmlproperty @int Type::element.id
-//!
-//! \brief Holds the element name and id.
-)";
-
- QTest::newRow("C -> C++ / no selection / doxygen") << R"(
-/*! \class Test
- \brief A test class.
-
- A more detailed @class description.
-*/)" << R"(
-//! \class Test
-//! \brief A test class.
-//!
-//! A more detailed class description.
-)";
-
- QTest::newRow("C -> C++ / selection / single line") << R"(
-int var1;
-/* Other comment, unaffected */
-@{start}/* Our comment */@{end}
-/* Another unaffected comment */
-int var2;)" << R"(
-int var1;
-/* Other comment, unaffected */
-// Our comment
-/* Another unaffected comment */
-int var2;)";
-
- QTest::newRow("C -> C++ / selection / multi-line / preserved header and footer") << R"(
-/****************************************************
- * @{start}some info
- * more info@{end}
- ***************************************************/)" << R"(
-/////////////////////////////////////////////////////
-// some info
-// more info
-/////////////////////////////////////////////////////)";
-
- QTest::newRow("C -> C++ / selection / multi-line / non-preserved header and footer") << R"(
-/*@{start}
- * some in@{end}fo
- * more info
- */)" << R"(
-// some info
-// more info
-)";
-
- QTest::newRow("C -> C++ / selection / qdoc") << R"(
-/*!@{start}
- \qmlproperty string Type::element.name
- \qmlproperty int Type::element.id
-
- \brief Holds the element name and id.
-*/@{end})" << R"(
-//! \qmlproperty string Type::element.name
-//! \qmlproperty int Type::element.id
-//!
-//! \brief Holds the element name and id.
-)";
-
- QTest::newRow("C -> C++ / selection / doxygen") << R"(
-/** Expand envi@{start}ronment variables in a string.
- *
- * Environment variables are accepted in the @{end}following forms:
- * $SOMEVAR, ${SOMEVAR} on Unix and %SOMEVAR% on Windows.
- * No escapes and quoting are supported.
- * If a variable is not found, it is not substituted.
- */)" << R"(
-//! Expand environment variables in a string.
-//!
-//! Environment variables are accepted in the following forms:
-//! $SOMEVAR, ${SOMEVAR} on Unix and %SOMEVAR% on Windows.
-//! No escapes and quoting are supported.
-//! If a variable is not found, it is not substituted.
-)";
-
- QTest::newRow("C -> C++ / selection / multiple comments") << R"(
-@{start}/* Affected comment */
-/* Another affected comment */
-/* A third affected comment */@{end}
-/* An unaffected comment */)" << R"(
-// Affected comment
-// Another affected comment
-// A third affected comment
-/* An unaffected comment */)";
-
- // FIXME: Remove adjacent newline along with last block
- // FIXME: Use CppRefactoringFile to auto-indent continuation lines?
- QTest::newRow("C -> C++, indented") << R"(
-struct S {
- /*
- * @This is an
- * indented comment.
- */
- void func();
-)" << R"(
-struct S {
- // This is an
-// indented comment.
-
- void func();
-)";
-
- QTest::newRow("C++ -> C / no selection / single line") << R"(
-// Other comment, unaffected
-// Our @comment
-// Another unaffected comment)" << R"(
-// Other comment, unaffected
-/* Our comment */
-// Another unaffected comment)";
-
- QTest::newRow("C++ -> C / selection / single line") << R"(
-// Other comment, unaffected
-@{start}// Our comment@{end}
-// Another unaffected comment)" << R"(
-// Other comment, unaffected
-/* Our comment */
-// Another unaffected comment)";
-
- QTest::newRow("C++ -> C / selection / multi-line / preserved header and footer") << R"(
-@{start}/////////////////////////////////////////////////////
-// some info
-// more info
-/////////////////////////////////////////////////////@{end})" << R"(
-/****************************************************/
-/* some info */
-/* more info */
-/****************************************************/)";
-
- QTest::newRow("C++ -> C / selection / qdoc") << R"(
-@{start}//! \qmlproperty string Type::element.name
-//! \qmlproperty int Type::element.id
-//!
-//! \brief Holds the element name and id.@{end}
-)" << R"(
-/*!
- \qmlproperty string Type::element.name
- \qmlproperty int Type::element.id
-
- \brief Holds the element name and id.
-*/
-)";
-
- QTest::newRow("C++ -> C / selection / doxygen") << R"(
-@{start}//! \class Test
-//! \brief A test class.
-//!
-//! A more detailed class description.@{end}
-)" << R"(
-/*!
- \class Test
- \brief A test class.
-
- A more detailed class description.
-*/
-)";
-}
-
-void QuickfixTest::testChangeCommentType()
-{
- QFETCH(QString, input);
- QFETCH(QString, expectedOutput);
-
- ConvertCommentStyle factory;
- QuickFixOperationTest(
- {CppTestDocument::create("file.h", input.toUtf8(), expectedOutput.toUtf8())},
- &factory);
-}
-
-void QuickfixTest::testMoveComments_data()
-{
- QTest::addColumn<QByteArrayList>("headers");
- QTest::addColumn<QByteArrayList>("sources");
-
- const QByteArrayList headersFuncDecl2Def{R"(
-// Function comment
-void @aFunction();
-)", R"(
-void aFunction();
-)"};
- const QByteArrayList sourcesFuncDecl2Def{R"(
-#include "file.h"
-
-void aFunction() {}
-)", R"(
-#include "file.h"
-
-// Function comment
-void aFunction() {}
-)"};
- QTest::newRow("function: from decl to def") << headersFuncDecl2Def << sourcesFuncDecl2Def;
-
- const QByteArrayList headersFuncDef2Decl{R"(
-void aFunction();
-)", R"(
-/* function */
-/* comment */
-void aFunction();
-)"};
- const QByteArrayList sourcesFuncDef2Decl{R"(
-#include "file.h"
-
-/* function */
-/* comment */
-void a@Function() {}
-)", R"(
-#include "file.h"
-
-void aFunction() {}
-)"};
- QTest::newRow("function: from def to decl") << headersFuncDef2Decl << sourcesFuncDef2Decl;
-
- const QByteArrayList headersFuncNoDef{R"(
-// Function comment
-void @aFunction();
-)", R"(
-// Function comment
-void aFunction();
-)"};
- QTest::newRow("function: no def") << headersFuncNoDef << QByteArrayList();
-
- const QByteArrayList headersFuncNoDecl{R"(
-// Function comment
-inline void @aFunction() {}
-)", R"(
-// Function comment
-inline void aFunction() {}
-)"};
- QTest::newRow("function: no decl") << headersFuncNoDecl << QByteArrayList();
-
- const QByteArrayList headersFuncTemplateDecl2Def{R"(
-// Function comment
-template<typename T> T @aFunction();
-
-template<typename T> inline T aFunction() { return T(); }
-)", R"(
-template<typename T> T aFunction();
-
-// Function comment
-template<typename T> inline T aFunction() { return T(); }
-)"};
- QTest::newRow("function template: from decl to def") << headersFuncTemplateDecl2Def
- << QByteArrayList();
-
- const QByteArrayList headersFuncTemplateDef2Decl{R"(
-template<typename T> T aFunction();
-
-// Function comment
-template<typename T> inline T @aFunction() { return T(); }
-)", R"(
-// Function comment
-template<typename T> T aFunction();
-
-template<typename T> inline T aFunction() { return T(); }
-)"};
- QTest::newRow("function template: from def to decl") << headersFuncTemplateDef2Decl
- << QByteArrayList();
-
- const QByteArrayList headersMemberDecl2Def{R"(
-class C {
- /**
- * \brief Foo::aMember
- */
- void @aMember();
-)", R"(
-class C {
- void aMember();
-)"};
- const QByteArrayList sourcesMemberDecl2Def{R"(
-#include "file.h"
-
-void C::aMember() {}
-)", R"(
-#include "file.h"
-
-/**
- * \brief Foo::aMember
- */
-void C::aMember() {}
-)"};
- QTest::newRow("member function: from decl to def") << headersMemberDecl2Def
- << sourcesMemberDecl2Def;
-
- const QByteArrayList headersMemberDef2Decl{R"(
-class C {
- void aMember();
-)", R"(
-class C {
- /**
- * \brief Foo::aMember
- */
- void aMember();
-)"};
- const QByteArrayList sourcesMemberDef2Decl{R"(
-#include "file.h"
-
-/**
- * \brief Foo::aMember
- */
-void C::aMember() {@}
-)", R"(
-#include "file.h"
-
-void C::aMember() {}
-)"};
- QTest::newRow("member function: from def to decl") << headersMemberDef2Decl
- << sourcesMemberDef2Decl;
-}
-
-void QuickfixTest::testMoveComments()
-{
- QFETCH(QByteArrayList, headers);
- QFETCH(QByteArrayList, sources);
-
- QList<TestDocumentPtr> documents;
- QCOMPARE(headers.size(), 2);
- documents << CppTestDocument::create("file.h", headers.at(0), headers.at(1));
- if (!sources.isEmpty()) {
- QCOMPARE(sources.size(), 2);
- documents << CppTestDocument::create("file.cpp", sources.at(0), sources.at(1));
- }
- MoveFunctionComments factory;
- QByteArray failMessage;
- if (QByteArray(QTest::currentDataTag()) == "function template: from def to decl")
- failMessage = "decl/def switch doesn't work for templates";
- QuickFixOperationTest(documents, &factory, {}, {}, failMessage);
-}
-
-void QuickfixTest::testConvertToMetaMethodInvocation_data()
-{
- QTest::addColumn<QByteArray>("input");
- QTest::addColumn<QByteArray>("expected");
-
- // ^ marks the cursor locations.
- // $ marks the replacement regions.
- // The quoted string in the comment is the data tag.
- // The rest of the comment is the replacement string.
- const QByteArray allCases = R"(
-class C {
-public:
- C() {
- $this->^aSignal()$; // "signal from region on pointer to object" QMetaObject::invokeMethod(this, "aSignal")
- C c;
- $c.^aSignal()$; // "signal from region on object value" QMetaObject::invokeMethod(&c, "aSignal")
- $(new C)->^aSignal()$; // "signal from region on expression" QMetaObject::invokeMethod((new C), "aSignal")
- $emit this->^aSignal()$; // "signal from region, with emit" QMetaObject::invokeMethod(this, "aSignal")
- $Q_EMIT this->^aSignal()$; // "signal from region, with Q_EMIT" QMetaObject::invokeMethod(this, "aSignal")
- $this->^aSlot()$; // "slot from region" QMetaObject::invokeMethod(this, "aSlot")
- $this->^noArgs()$; // "Q_SIGNAL, no arguments" QMetaObject::invokeMethod(this, "noArgs")
- $this->^oneArg(0)$; // "Q_SLOT, one argument" QMetaObject::invokeMethod(this, "oneArg", Q_ARG(int, 0))
- $this->^twoArgs(0, c)$; // "Q_INVOKABLE, two arguments" QMetaObject::invokeMethod(this, "twoArgs", Q_ARG(int, 0), Q_ARG(C, c))
- this->^notInvokable(); // "not invokable"
- }
-
-signals:
- void aSignal();
-
-private slots:
- void aSlot();
-
-private:
- Q_SIGNAL void noArgs();
- Q_SLOT void oneArg(int index);
- Q_INVOKABLE void twoArgs(int index, const C &value);
- void notInvokable();
-};
-)";
-
- qsizetype nextCursor = allCases.indexOf('^');
- while (nextCursor != -1) {
- const int commentStart = allCases.indexOf("//", nextCursor);
- QVERIFY(commentStart != -1);
- const int tagStart = allCases.indexOf('"', commentStart + 2);
- QVERIFY(tagStart != -1);
- const int tagEnd = allCases.indexOf('"', tagStart + 1);
- QVERIFY(tagEnd != -1);
- QByteArray input = allCases;
- QByteArray output = allCases;
- input.replace(nextCursor, 1, "@");
- const QByteArray tag = allCases.mid(tagStart + 1, tagEnd - tagStart - 1);
- const int prevNewline = allCases.lastIndexOf('\n', nextCursor);
- const int regionStart = allCases.lastIndexOf('$', nextCursor);
- bool hasReplacement = false;
- if (regionStart != -1 && regionStart > prevNewline) {
- const int regionEnd = allCases.indexOf('$', regionStart + 1);
- QVERIFY(regionEnd != -1);
- const int nextNewline = allCases.indexOf('\n', tagEnd);
- QVERIFY(nextNewline != -1);
- const QByteArray replacement
- = allCases.mid(tagEnd + 1, nextNewline - tagEnd - 1).trimmed();
- output.replace(regionStart, regionEnd - regionStart, replacement);
- hasReplacement = true;
- }
- static const auto matcher = [](char c) { return c == '^' || c == '$'; };
- input.removeIf(matcher);
- if (hasReplacement) {
- output.removeIf(matcher);
- output.prepend("#include <QMetaObject>\n\n");
- } else {
- output.clear();
- }
- QTest::newRow(tag.data()) << input << output;
- nextCursor = allCases.indexOf('^', nextCursor + 1);
- }
-}
-
-void QuickfixTest::testConvertToMetaMethodInvocation()
-{
- QFETCH(QByteArray, input);
- QFETCH(QByteArray, expected);
-
- ConvertToMetaMethodCall factory;
- QuickFixOperationTest({CppTestDocument::create("file.cpp", input, expected)}, &factory);
-}
-
-} // namespace CppEditor::Internal::Tests
diff --git a/src/plugins/cppeditor/cppquickfix_test.h b/src/plugins/cppeditor/cppquickfix_test.h
deleted file mode 100644
index 917abe19c7..0000000000
--- a/src/plugins/cppeditor/cppquickfix_test.h
+++ /dev/null
@@ -1,236 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#pragma once
-
-#include "cppquickfix.h"
-#include "cpptoolstestcase.h"
-
-#include <projectexplorer/headerpath.h>
-
-#include <QByteArray>
-#include <QList>
-#include <QObject>
-#include <QSharedPointer>
-#include <QStringList>
-
-namespace TextEditor { class QuickFixOperation; }
-
-namespace CppEditor {
-class CppCodeStylePreferences;
-
-namespace Internal {
-namespace Tests {
-
-class BaseQuickFixTestCase : public CppEditor::Tests::TestCase
-{
-public:
- /// Exactly one QuickFixTestDocument must contain the cursor position marker '@'
- /// or "@{start}" and "@{end}"
- BaseQuickFixTestCase(const QList<TestDocumentPtr> &testDocuments,
- const ProjectExplorer::HeaderPaths &headerPaths,
- const QByteArray &clangFormatSettings = {});
-
- ~BaseQuickFixTestCase();
-
-protected:
- TestDocumentPtr m_documentWithMarker;
- QList<TestDocumentPtr> m_testDocuments;
-
-private:
- QScopedPointer<CppEditor::Tests::TemporaryDir> m_temporaryDirectory;
-
- CppCodeStylePreferences *m_cppCodeStylePreferences;
- QByteArray m_cppCodeStylePreferencesOriginalDelegateId;
-
- ProjectExplorer::HeaderPaths m_headerPathsToRestore;
- bool m_restoreHeaderPaths;
-};
-
-/// Tests a concrete QuickFixOperation of a given CppQuickFixFactory
-class QuickFixOperationTest : public BaseQuickFixTestCase
-{
-public:
- QuickFixOperationTest(const QList<TestDocumentPtr> &testDocuments,
- CppQuickFixFactory *factory,
- const ProjectExplorer::HeaderPaths &headerPaths
- = ProjectExplorer::HeaderPaths(),
- int operationIndex = 0,
- const QByteArray &expectedFailMessage = {},
- const QByteArray &clangFormatSettings = {});
-
- static void run(const QList<TestDocumentPtr> &testDocuments,
- CppQuickFixFactory *factory,
- const QString &headerPath,
- int operationIndex = 0);
-};
-
-QList<TestDocumentPtr> singleDocument(const QByteArray &original,
- const QByteArray &expected);
-
-class QuickfixTest : public QObject
-{
- Q_OBJECT
-
-private slots:
- void testGeneric_data();
- void testGeneric();
-
- void testGenerateGetterSetterNamespaceHandlingCreate_data();
- void testGenerateGetterSetterNamespaceHandlingCreate();
- void testGenerateGetterSetterNamespaceHandlingAddUsing_data();
- void testGenerateGetterSetterNamespaceHandlingAddUsing();
- void testGenerateGetterSetterNamespaceHandlingFullyQualify_data();
- void testGenerateGetterSetterNamespaceHandlingFullyQualify();
- void testGenerateGetterSetterCustomNames_data();
- void testGenerateGetterSetterCustomNames();
- void testGenerateGetterSetterValueTypes_data();
- void testGenerateGetterSetterValueTypes();
- void testGenerateGetterSetterCustomTemplate();
- void testGenerateGetterSetterNeedThis();
- void testGenerateGetterSetterOfferedFixes_data();
- void testGenerateGetterSetterOfferedFixes();
- void testGenerateGetterSetterGeneralTests_data();
- void testGenerateGetterSetterGeneralTests();
- void testGenerateGetterSetterOnlyGetter();
- void testGenerateGetterSetterOnlySetter();
- void testGenerateGetterSetterAnonymousClass();
- void testGenerateGetterSetterInlineInHeaderFile();
- void testGenerateGetterSetterOnlySetterHeaderFileWithIncludeGuard();
- void testGenerateGetterFunctionAsTemplateArg();
- void testGenerateGettersSetters_data();
- void testGenerateGettersSetters();
-
- void testInsertQtPropertyMembers_data();
- void testInsertQtPropertyMembers();
-
- void testInsertMemberFromUse_data();
- void testInsertMemberFromUse();
-
- void testConvertQt4ConnectConnectOutOfClass();
- void testConvertQt4ConnectConnectWithinClass_data();
- void testConvertQt4ConnectConnectWithinClass();
- void testConvertQt4ConnectDifferentNamespace();
-
- void testInsertDefFromDeclAfterClass();
- void testInsertDefFromDeclHeaderSourceBasic1();
- void testInsertDefFromDeclHeaderSourceBasic2();
- void testInsertDefFromDeclHeaderSourceBasic3();
- void testInsertDefFromDeclHeaderSourceNamespace1();
- void testInsertDefFromDeclHeaderSourceNamespace2();
- void testInsertDefFromDeclInsideClass();
- void testInsertDefFromDeclNotTriggeringWhenDefinitionExists();
- void testInsertDefFromDeclFindRightImplementationFile();
- void testInsertDefFromDeclIgnoreSurroundingGeneratedDeclarations();
- void testInsertDefFromDeclRespectWsInOperatorNames1();
- void testInsertDefFromDeclRespectWsInOperatorNames2();
- void testInsertDefFromDeclNoexceptSpecifier();
- void testInsertDefFromDeclMacroUsesAtEndOfFile1();
- void testInsertDefFromDeclMacroUsesAtEndOfFile2();
- void testInsertDefFromDeclErroneousStatementAtEndOfFile();
- void testInsertDefFromDeclRvalueReference();
- void testInsertDefFromDeclFunctionTryBlock();
- void testInsertDefFromDeclUsingDecl();
- void testInsertDefFromDeclFindImplementationFile();
- void testInsertDefFromDeclUnicodeIdentifier();
- void testInsertDefFromDeclTemplateClass();
- void testInsertDefFromDeclTemplateClassWithValueParam();
- void testInsertDefFromDeclTemplateFunction();
- void testInsertDefFromDeclTemplateClassAndTemplateFunction();
- void testInsertDefFromDeclTemplateClassAndFunctionInsideNamespace();
- void testInsertDefFromDeclFunctionWithSignedUnsignedArgument();
- void testInsertDefFromDeclNotTriggeredForFriendFunc();
- void testInsertDefFromDeclMinimalFunctionParameterType();
- void testInsertDefFromDeclAliasTemplateAsReturnType();
- void testInsertDefsFromDecls_data();
- void testInsertDefsFromDecls();
- void testInsertAndFormatDefsFromDecls();
-
- void testInsertDefOutsideFromDeclTemplateClassAndTemplateFunction();
- void testInsertDefOutsideFromDeclTemplateClass();
- void testInsertDefOutsideFromDeclTemplateFunction();
- void testInsertDefOutsideFromDeclFunction();
-
- void testInsertDeclFromDef();
- void testInsertDeclFromDefTemplateFuncTypename();
- void testInsertDeclFromDefTemplateFuncInt();
- void testInsertDeclFromDefTemplateReturnType();
- void testInsertDeclFromDefNotTriggeredForTemplateFunc();
-
- void testAddIncludeForUndefinedIdentifier_data();
- void testAddIncludeForUndefinedIdentifier();
- void testAddIncludeForUndefinedIdentifierNoDoubleQtHeaderInclude();
-
- void testAddForwardDeclForUndefinedIdentifier_data();
- void testAddForwardDeclForUndefinedIdentifier();
-
- void testMoveFuncDefOutsideMemberFuncToCpp();
- void testMoveFuncDefOutsideMemberFuncToCppInsideNS();
- void testMoveFuncDefOutsideMemberFuncOutside1();
- void testMoveFuncDefOutsideMemberFuncOutside2();
- void testMoveFuncDefOutsideMemberFuncToCppNS();
- void testMoveFuncDefOutsideMemberFuncToCppNSUsing();
- void testMoveFuncDefOutsideMemberFuncOutsideWithNs();
- void testMoveFuncDefOutsideFreeFuncToCpp();
- void testMoveFuncDefOutsideFreeFuncToCppNS();
- void testMoveFuncDefOutsideCtorWithInitialization1();
- void testMoveFuncDefOutsideCtorWithInitialization2();
- void testMoveFuncDefOutsideAfterClass();
- void testMoveFuncDefOutsideRespectWsInOperatorNames1();
- void testMoveFuncDefOutsideRespectWsInOperatorNames2();
- void testMoveFuncDefOutsideMacroUses();
- void testMoveFuncDefOutsideTemplate();
- void testMoveFuncDefOutsideMemberFunctionTemplate();
- void testMoveFuncDefOutsideTemplateSpecializedClass();
- void testMoveFuncDefOutsideUnnamedTemplate();
- void testMoveFuncDefOutsideMemberFuncToCppStatic();
- void testMoveFuncDefOutsideMemberFuncToCppWithInlinePartOfName();
- void testMoveFuncDefOutsideMixedQualifiers();
-
- void testMoveAllFuncDefOutsideMemberFuncToCpp();
- void testMoveAllFuncDefOutsideMemberFuncOutside();
- void testMoveAllFuncDefOutsideDoNotTriggerOnBaseClass();
- void testMoveAllFuncDefOutsideClassWithBaseClass();
- void testMoveAllFuncDefOutsideIgnoreMacroCode();
-
- void testMoveFuncDefToDecl_data();
- void testMoveFuncDefToDecl();
-
- void testMoveFuncDefToDeclMacroUses();
-
- void testAssignToLocalVariableTemplates();
-
- void testExtractFunction_data();
- void testExtractFunction();
-
- void testExtractLiteralAsParameterTypeDeduction_data();
- void testExtractLiteralAsParameterTypeDeduction();
- void testExtractLiteralAsParameterFreeFunctionSeparateFiles();
- void testExtractLiteralAsParameterMemberFunctionSeparateFiles();
- void testExtractLiteralAsParameterNotTriggeringForInvalidCode();
-
- void testAddCurlyBraces_data();
- void testAddCurlyBraces();
-
- void testRemoveUsingNamespace_data();
- void testRemoveUsingNamespace();
- void testRemoveUsingNamespaceSimple_data();
- void testRemoveUsingNamespaceSimple();
- void testRemoveUsingNamespaceDifferentSymbols();
-
- void testGenerateConstructor_data();
- void testGenerateConstructor();
-
- void testChangeCommentType_data();
- void testChangeCommentType();
-
- void testMoveComments_data();
- void testMoveComments();
-
- void testConvertToMetaMethodInvocation_data();
- void testConvertToMetaMethodInvocation();
-};
-
-} // namespace Tests
-} // namespace Internal
-} // namespace CppEditor
diff --git a/src/plugins/cppeditor/cppquickfixes.cpp b/src/plugins/cppeditor/cppquickfixes.cpp
deleted file mode 100644
index 9ac67e1e29..0000000000
--- a/src/plugins/cppeditor/cppquickfixes.cpp
+++ /dev/null
@@ -1,10104 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#include "cppquickfixes.h"
-
-#include "baseeditordocumentprocessor.h"
-#include "cppcodestylesettings.h"
-#include "cppeditordocument.h"
-#include "cppeditortr.h"
-#include "cppeditorwidget.h"
-#include "cppfunctiondecldeflink.h"
-#include "cppinsertvirtualmethods.h"
-#include "cpplocatordata.h"
-#include "cpppointerdeclarationformatter.h"
-#include "cppquickfixassistant.h"
-#include "cppquickfixprojectsettings.h"
-#include "cpprefactoringchanges.h"
-#include "cpptoolsreuse.h"
-#include "includeutils.h"
-#include "indexitem.h"
-#include "insertionpointlocator.h"
-#include "symbolfinder.h"
-
-#include <coreplugin/icore.h>
-#include <coreplugin/messagebox.h>
-
-#include <cplusplus/ASTPath.h>
-#include <cplusplus/CPlusPlusForwardDeclarations.h>
-#include <cplusplus/CppRewriter.h>
-#include <cplusplus/declarationcomments.h>
-#include <cplusplus/NamePrettyPrinter.h>
-#include <cplusplus/TypeOfExpression.h>
-#include <cplusplus/TypePrettyPrinter.h>
-
-#include <extensionsystem/pluginmanager.h>
-
-#include <projectexplorer/editorconfiguration.h>
-#include <projectexplorer/projectnodes.h>
-#include <projectexplorer/projecttree.h>
-#include <projectexplorer/projectmanager.h>
-
-#include <texteditor/tabsettings.h>
-
-#include <utils/algorithm.h>
-#include <utils/basetreeview.h>
-#include <utils/layoutbuilder.h>
-#include <utils/fancylineedit.h>
-#include <utils/fileutils.h>
-#include <utils/qtcassert.h>
-#include <utils/treemodel.h>
-
-#ifdef WITH_TESTS
-#include <QAbstractItemModelTester>
-#endif
-#include <QApplication>
-#include <QCheckBox>
-#include <QComboBox>
-#include <QDialog>
-#include <QDialogButtonBox>
-#include <QDir>
-#include <QFileInfo>
-#include <QFormLayout>
-#include <QGridLayout>
-#include <QHash>
-#include <QHeaderView>
-#include <QInputDialog>
-#include <QMimeData>
-#include <QPair>
-#include <QProxyStyle>
-#include <QPushButton>
-#include <QRegularExpression>
-#include <QSharedPointer>
-#include <QStack>
-#include <QStyledItemDelegate>
-#include <QTableView>
-#include <QTextCodec>
-#include <QTextCursor>
-#include <QVBoxLayout>
-
-#include <bitset>
-#include <cctype>
-#include <functional>
-#include <limits>
-#include <vector>
-
-using namespace CPlusPlus;
-using namespace TextEditor;
-using namespace Utils;
-
-namespace CppEditor {
-
-static QList<CppQuickFixFactory *> g_cppQuickFixFactories;
-
-CppQuickFixFactory::CppQuickFixFactory()
-{
- g_cppQuickFixFactories.append(this);
-}
-
-CppQuickFixFactory::~CppQuickFixFactory()
-{
- g_cppQuickFixFactories.removeOne(this);
-}
-
-void CppQuickFixFactory::match(const Internal::CppQuickFixInterface &interface,
- QuickFixOperations &result)
-{
- if (m_clangdReplacement) {
- if (const auto clangdVersion = CppModelManager::usesClangd(
- interface.currentFile()->editor()->textDocument());
- clangdVersion && clangdVersion >= m_clangdReplacement) {
- return;
- }
- }
-
- doMatch(interface, result);
-}
-
-const QList<CppQuickFixFactory *> &CppQuickFixFactory::cppQuickFixFactories()
-{
- return g_cppQuickFixFactories;
-}
-
-namespace Internal {
-
-QString inlinePrefix(const FilePath &targetFile, const std::function<bool()> &extraCondition = {})
-{
- if (ProjectFile::isHeader(ProjectFile::classify(targetFile.path()))
- && (!extraCondition || extraCondition())) {
- return "inline ";
- }
- return {};
-}
-
-// In the following anonymous namespace all functions are collected, which could be of interest for
-// different quick fixes.
-namespace {
-
-enum DefPos {
- DefPosInsideClass,
- DefPosOutsideClass,
- DefPosImplementationFile
-};
-
-
-inline bool isQtStringLiteral(const QByteArray &id)
-{
- return id == "QLatin1String" || id == "QLatin1Literal" || id == "QStringLiteral"
- || id == "QByteArrayLiteral";
-}
-
-inline bool isQtStringTranslation(const QByteArray &id)
-{
- return id == "tr" || id == "trUtf8" || id == "translate" || id == "QT_TRANSLATE_NOOP";
-}
-
-Class *isMemberFunction(const LookupContext &context, Function *function)
-{
- QTC_ASSERT(function, return nullptr);
-
- Scope *enclosingScope = function->enclosingScope();
- while (!(enclosingScope->asNamespace() || enclosingScope->asClass()))
- enclosingScope = enclosingScope->enclosingScope();
- QTC_ASSERT(enclosingScope != nullptr, return nullptr);
-
- const Name *functionName = function->name();
- if (!functionName)
- return nullptr;
-
- if (!functionName->asQualifiedNameId())
- return nullptr; // trying to add a declaration for a global function
-
- const QualifiedNameId *q = functionName->asQualifiedNameId();
- if (!q->base())
- return nullptr;
-
- if (ClassOrNamespace *binding = context.lookupType(q->base(), enclosingScope)) {
- const QList<Symbol *> symbols = binding->symbols();
- for (Symbol *s : symbols) {
- if (Class *matchingClass = s->asClass())
- return matchingClass;
- }
- }
-
- return nullptr;
-}
-
-Namespace *isNamespaceFunction(const LookupContext &context, Function *function)
-{
- QTC_ASSERT(function, return nullptr);
- if (isMemberFunction(context, function))
- return nullptr;
-
- Scope *enclosingScope = function->enclosingScope();
- while (!(enclosingScope->asNamespace() || enclosingScope->asClass()))
- enclosingScope = enclosingScope->enclosingScope();
- QTC_ASSERT(enclosingScope != nullptr, return nullptr);
-
- const Name *functionName = function->name();
- if (!functionName)
- return nullptr;
-
- // global namespace
- if (!functionName->asQualifiedNameId()) {
- const QList<Symbol *> symbols = context.globalNamespace()->symbols();
- for (Symbol *s : symbols) {
- if (Namespace *matchingNamespace = s->asNamespace())
- return matchingNamespace;
- }
- return nullptr;
- }
-
- const QualifiedNameId *q = functionName->asQualifiedNameId();
- if (!q->base())
- return nullptr;
-
- if (ClassOrNamespace *binding = context.lookupType(q->base(), enclosingScope)) {
- const QList<Symbol *> symbols = binding->symbols();
- for (Symbol *s : symbols) {
- if (Namespace *matchingNamespace = s->asNamespace())
- return matchingNamespace;
- }
- }
-
- return nullptr;
-}
-
-// Given include is e.g. "afile.h" or <afile.h> (quotes/angle brackets included!).
-static void insertNewIncludeDirective(const QString &include,
- CppRefactoringFilePtr file,
- const Document::Ptr &cppDocument,
- ChangeSet &changes)
-{
- // Find optimal position
- unsigned newLinesToPrepend = 0;
- unsigned newLinesToAppend = 0;
- const int insertLine = lineForNewIncludeDirective(file->filePath(), file->document(),
- cppDocument, IgnoreMocIncludes, AutoDetect,
- include,
- &newLinesToPrepend, &newLinesToAppend);
- QTC_ASSERT(insertLine >= 1, return);
- const int insertPosition = file->position(insertLine, 1);
- QTC_ASSERT(insertPosition >= 0, return);
-
- // Construct text to insert
- const QString includeLine = QLatin1String("#include ") + include + QLatin1Char('\n');
- QString prependedNewLines, appendedNewLines;
- while (newLinesToAppend--)
- appendedNewLines += QLatin1String("\n");
- while (newLinesToPrepend--)
- prependedNewLines += QLatin1String("\n");
- const QString textToInsert = prependedNewLines + includeLine + appendedNewLines;
-
- // Insert
- changes.insert(insertPosition, textToInsert);
-}
-
-bool nameIncludesOperatorName(const Name *name)
-{
- return name->asOperatorNameId()
- || (name->asQualifiedNameId() && name->asQualifiedNameId()->name()->asOperatorNameId());
-}
-
-QString memberBaseName(const QString &name)
-{
- const auto validName = [](const QString &name) {
- return !name.isEmpty() && !name.at(0).isDigit();
- };
- QString baseName = name;
-
- CppQuickFixSettings *settings = CppQuickFixProjectsSettings::getQuickFixSettings(
- ProjectExplorer::ProjectTree::currentProject());
- const QString &nameTemplate = settings->memberVariableNameTemplate;
- const QString prefix = nameTemplate.left(nameTemplate.indexOf('<'));
- const QString postfix = nameTemplate.mid(nameTemplate.lastIndexOf('>') + 1);
- if (name.startsWith(prefix) && name.endsWith(postfix)) {
- const QString base = name.mid(prefix.length(), name.length() - postfix.length());
- if (validName(base))
- return base;
- }
-
- // Remove leading and trailing "_"
- while (baseName.startsWith(QLatin1Char('_')))
- baseName.remove(0, 1);
- while (baseName.endsWith(QLatin1Char('_')))
- baseName.chop(1);
- if (baseName != name && validName(baseName))
- return baseName;
-
- // If no leading/trailing "_": remove "m_" and "m" prefix
- if (baseName.startsWith(QLatin1String("m_"))) {
- baseName.remove(0, 2);
- } else if (baseName.startsWith(QLatin1Char('m')) && baseName.length() > 1
- && baseName.at(1).isUpper()) {
- baseName.remove(0, 1);
- baseName[0] = baseName.at(0).toLower();
- }
-
- return validName(baseName) ? baseName : name;
-}
-
-// Returns a non-null value if and only if the cursor is on the name of a (proper) class
-// declaration or at some place inside the body of a class declaration that does not
-// correspond to an AST of its own, i.e. on "empty space".
-ClassSpecifierAST *astForClassOperations(const CppQuickFixInterface &interface)
-{
- const QList<AST *> &path = interface.path();
- if (path.isEmpty())
- return nullptr;
- if (const auto classSpec = path.last()->asClassSpecifier()) // Cursor inside class decl?
- return classSpec;
-
- // Cursor on a class name?
- if (path.size() < 2)
- return nullptr;
- const SimpleNameAST * const nameAST = path.at(path.size() - 1)->asSimpleName();
- if (!nameAST || !interface.isCursorOn(nameAST))
- return nullptr;
- if (const auto classSpec = path.at(path.size() - 2)->asClassSpecifier())
- return classSpec;
- return nullptr;
-}
-
-QString nameString(const NameAST *name)
-{
- return CppCodeStyleSettings::currentProjectCodeStyleOverview().prettyName(name->name);
-}
-
-static FullySpecifiedType typeOfExpr(const ExpressionAST *expr,
- const CppRefactoringFilePtr &file,
- const Snapshot &snapshot,
- const LookupContext &context)
-{
- TypeOfExpression typeOfExpression;
- typeOfExpression.init(file->cppDocument(), snapshot, context.bindings());
- Scope *scope = file->scopeAt(expr->firstToken());
- const QList<LookupItem> result = typeOfExpression(
- file->textOf(expr).toUtf8(), scope, TypeOfExpression::Preprocess);
- if (result.isEmpty())
- return {};
-
- SubstitutionEnvironment env;
- env.setContext(context);
- env.switchScope(result.first().scope());
- ClassOrNamespace *con = typeOfExpression.context().lookupType(scope);
- if (!con)
- con = typeOfExpression.context().globalNamespace();
- UseMinimalNames q(con);
- env.enter(&q);
-
- Control *control = context.bindings()->control().get();
- return rewriteType(result.first().type(), &env, control);
-}
-
-// FIXME: Needs to consider the scope at the insertion site.
-QString declFromExpr(const TypeOrExpr &typeOrExpr, const CallAST *call, const NameAST *varName,
- const Snapshot &snapshot, const LookupContext &context,
- const CppRefactoringFilePtr &file, bool makeConst)
-{
- const auto getTypeFromUser = [varName, call]() -> QString {
- if (call)
- return {};
- const QString typeFromUser = QInputDialog::getText(Core::ICore::dialogParent(),
- Tr::tr("Provide the type"),
- Tr::tr("Data type:"), QLineEdit::Normal);
- if (!typeFromUser.isEmpty())
- return typeFromUser + ' ' + nameString(varName);
- return {};
- };
- const auto getTypeOfExpr = [&](const ExpressionAST *expr) -> FullySpecifiedType {
- return typeOfExpr(expr, file, snapshot, context);
- };
-
- const Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview();
- const FullySpecifiedType type = std::holds_alternative<FullySpecifiedType>(typeOrExpr)
- ? std::get<FullySpecifiedType>(typeOrExpr)
- : getTypeOfExpr(std::get<const ExpressionAST *>(typeOrExpr));
- if (!call)
- return type.isValid() ? oo.prettyType(type, varName->name) : getTypeFromUser();
-
- Function func(file->cppDocument()->translationUnit(), 0, varName->name);
- func.setConst(makeConst);
- for (ExpressionListAST *it = call->expression_list; it; it = it->next) {
- Argument * const arg = new Argument(nullptr, 0, nullptr);
- arg->setType(getTypeOfExpr(it->value));
- func.addMember(arg);
- }
- return oo.prettyType(type) + ' ' + oo.prettyType(func.type(), varName->name);
-}
-
-} // anonymous namespace
-
-namespace {
-
-class InverseLogicalComparisonOp: public CppQuickFixOperation
-{
-public:
- InverseLogicalComparisonOp(const CppQuickFixInterface &interface,
- int priority,
- BinaryExpressionAST *binary,
- Kind invertToken)
- : CppQuickFixOperation(interface, priority)
- , binary(binary)
- {
- Token tok;
- tok.f.kind = invertToken;
- replacement = QLatin1String(tok.spell());
-
- // check for enclosing nested expression
- if (priority - 1 >= 0)
- nested = interface.path()[priority - 1]->asNestedExpression();
-
- // check for ! before parentheses
- if (nested && priority - 2 >= 0) {
- negation = interface.path()[priority - 2]->asUnaryExpression();
- if (negation && !interface.currentFile()->tokenAt(negation->unary_op_token).is(T_EXCLAIM))
- negation = nullptr;
- }
- }
-
- QString description() const override
- {
- return Tr::tr("Rewrite Using %1").arg(replacement);
- }
-
- void perform() override
- {
- CppRefactoringChanges refactoring(snapshot());
- CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath());
-
- ChangeSet changes;
- if (negation) {
- // can't remove parentheses since that might break precedence
- changes.remove(currentFile->range(negation->unary_op_token));
- } else if (nested) {
- changes.insert(currentFile->startOf(nested), QLatin1String("!"));
- } else {
- changes.insert(currentFile->startOf(binary), QLatin1String("!("));
- changes.insert(currentFile->endOf(binary), QLatin1String(")"));
- }
- changes.replace(currentFile->range(binary->binary_op_token), replacement);
- currentFile->setChangeSet(changes);
- currentFile->apply();
- }
-
-private:
- BinaryExpressionAST *binary = nullptr;
- NestedExpressionAST *nested = nullptr;
- UnaryExpressionAST *negation = nullptr;
-
- QString replacement;
-};
-
-} // anonymous namespace
-
-void InverseLogicalComparison::doMatch(const CppQuickFixInterface &interface,
- QuickFixOperations &result)
-{
- CppRefactoringFilePtr file = interface.currentFile();
-
- const QList<AST *> &path = interface.path();
- if (path.isEmpty())
- return;
- int index = path.size() - 1;
- BinaryExpressionAST *binary = path.at(index)->asBinaryExpression();
- if (!binary)
- return;
- if (!interface.isCursorOn(binary->binary_op_token))
- return;
-
- Kind invertToken;
- switch (file->tokenAt(binary->binary_op_token).kind()) {
- case T_LESS_EQUAL:
- invertToken = T_GREATER;
- break;
- case T_LESS:
- invertToken = T_GREATER_EQUAL;
- break;
- case T_GREATER:
- invertToken = T_LESS_EQUAL;
- break;
- case T_GREATER_EQUAL:
- invertToken = T_LESS;
- break;
- case T_EQUAL_EQUAL:
- invertToken = T_EXCLAIM_EQUAL;
- break;
- case T_EXCLAIM_EQUAL:
- invertToken = T_EQUAL_EQUAL;
- break;
- default:
- return;
- }
-
- result << new InverseLogicalComparisonOp(interface, index, binary, invertToken);
-}
-
-namespace {
-
-class FlipLogicalOperandsOp: public CppQuickFixOperation
-{
-public:
- FlipLogicalOperandsOp(const CppQuickFixInterface &interface, int priority,
- BinaryExpressionAST *binary, QString replacement)
- : CppQuickFixOperation(interface)
- , binary(binary)
- , replacement(replacement)
- {
- setPriority(priority);
- }
-
- QString description() const override
- {
- if (replacement.isEmpty())
- return Tr::tr("Swap Operands");
- else
- return Tr::tr("Rewrite Using %1").arg(replacement);
- }
-
- void perform() override
- {
- CppRefactoringChanges refactoring(snapshot());
- CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath());
-
- ChangeSet changes;
- changes.flip(currentFile->range(binary->left_expression),
- currentFile->range(binary->right_expression));
- if (!replacement.isEmpty())
- changes.replace(currentFile->range(binary->binary_op_token), replacement);
-
- currentFile->setChangeSet(changes);
- currentFile->apply();
- }
-
-private:
- BinaryExpressionAST *binary;
- QString replacement;
-};
-
-} // anonymous namespace
-
-void FlipLogicalOperands::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result)
-{
- const QList<AST *> &path = interface.path();
- if (path.isEmpty())
- return;
- CppRefactoringFilePtr file = interface.currentFile();
-
- int index = path.size() - 1;
- BinaryExpressionAST *binary = path.at(index)->asBinaryExpression();
- if (!binary)
- return;
- if (!interface.isCursorOn(binary->binary_op_token))
- return;
-
- Kind flipToken;
- switch (file->tokenAt(binary->binary_op_token).kind()) {
- case T_LESS_EQUAL:
- flipToken = T_GREATER_EQUAL;
- break;
- case T_LESS:
- flipToken = T_GREATER;
- break;
- case T_GREATER:
- flipToken = T_LESS;
- break;
- case T_GREATER_EQUAL:
- flipToken = T_LESS_EQUAL;
- break;
- case T_EQUAL_EQUAL:
- case T_EXCLAIM_EQUAL:
- case T_AMPER_AMPER:
- case T_PIPE_PIPE:
- flipToken = T_EOF_SYMBOL;
- break;
- default:
- return;
- }
-
- QString replacement;
- if (flipToken != T_EOF_SYMBOL) {
- Token tok;
- tok.f.kind = flipToken;
- replacement = QLatin1String(tok.spell());
- }
-
- result << new FlipLogicalOperandsOp(interface, index, binary, replacement);
-}
-
-namespace {
-
-class RewriteLogicalAndOp: public CppQuickFixOperation
-{
-public:
- std::shared_ptr<ASTPatternBuilder> mk;
- UnaryExpressionAST *left;
- UnaryExpressionAST *right;
- BinaryExpressionAST *pattern;
-
- RewriteLogicalAndOp(const CppQuickFixInterface &interface)
- : CppQuickFixOperation(interface)
- , mk(new ASTPatternBuilder)
- {
- left = mk->UnaryExpression();
- right = mk->UnaryExpression();
- pattern = mk->BinaryExpression(left, right);
- }
-
- void perform() override
- {
- CppRefactoringChanges refactoring(snapshot());
- CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath());
-
- ChangeSet changes;
- changes.replace(currentFile->range(pattern->binary_op_token), QLatin1String("||"));
- changes.remove(currentFile->range(left->unary_op_token));
- changes.remove(currentFile->range(right->unary_op_token));
- const int start = currentFile->startOf(pattern);
- const int end = currentFile->endOf(pattern);
- changes.insert(start, QLatin1String("!("));
- changes.insert(end, QLatin1String(")"));
-
- currentFile->setChangeSet(changes);
- currentFile->apply();
- }
-};
-
-} // anonymous namespace
-
-void RewriteLogicalAnd::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result)
-{
- BinaryExpressionAST *expression = nullptr;
- const QList<AST *> &path = interface.path();
- CppRefactoringFilePtr file = interface.currentFile();
-
- int index = path.size() - 1;
- for (; index != -1; --index) {
- expression = path.at(index)->asBinaryExpression();
- if (expression)
- break;
- }
-
- if (!expression)
- return;
-
- if (!interface.isCursorOn(expression->binary_op_token))
- return;
-
- QSharedPointer<RewriteLogicalAndOp> op(new RewriteLogicalAndOp(interface));
-
- ASTMatcher matcher;
-
- if (expression->match(op->pattern, &matcher) &&
- file->tokenAt(op->pattern->binary_op_token).is(T_AMPER_AMPER) &&
- file->tokenAt(op->left->unary_op_token).is(T_EXCLAIM) &&
- file->tokenAt(op->right->unary_op_token).is(T_EXCLAIM)) {
- op->setDescription(Tr::tr("Rewrite Condition Using ||"));
- op->setPriority(index);
- result.append(op);
- }
-}
-
-static bool checkDeclarationForSplit(SimpleDeclarationAST *declaration)
-{
- if (!declaration->semicolon_token)
- return false;
-
- if (!declaration->decl_specifier_list)
- return false;
-
- for (SpecifierListAST *it = declaration->decl_specifier_list; it; it = it->next) {
- SpecifierAST *specifier = it->value;
- if (specifier->asEnumSpecifier() || specifier->asClassSpecifier())
- return false;
- }
-
- return declaration->declarator_list && declaration->declarator_list->next;
-}
-
-namespace {
-
-class SplitSimpleDeclarationOp: public CppQuickFixOperation
-{
-public:
- SplitSimpleDeclarationOp(const CppQuickFixInterface &interface, int priority,
- SimpleDeclarationAST *decl)
- : CppQuickFixOperation(interface, priority)
- , declaration(decl)
- {
- setDescription(Tr::tr("Split Declaration"));
- }
-
- void perform() override
- {
- CppRefactoringChanges refactoring(snapshot());
- CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath());
-
- ChangeSet changes;
-
- SpecifierListAST *specifiers = declaration->decl_specifier_list;
- int declSpecifiersStart = currentFile->startOf(specifiers->firstToken());
- int declSpecifiersEnd = currentFile->endOf(specifiers->lastToken() - 1);
- int insertPos = currentFile->endOf(declaration->semicolon_token);
-
- DeclaratorAST *prevDeclarator = declaration->declarator_list->value;
-
- for (DeclaratorListAST *it = declaration->declarator_list->next; it; it = it->next) {
- DeclaratorAST *declarator = it->value;
-
- changes.insert(insertPos, QLatin1String("\n"));
- changes.copy(declSpecifiersStart, declSpecifiersEnd, insertPos);
- changes.insert(insertPos, QLatin1String(" "));
- changes.move(currentFile->range(declarator), insertPos);
- changes.insert(insertPos, QLatin1String(";"));
-
- const int prevDeclEnd = currentFile->endOf(prevDeclarator);
- changes.remove(prevDeclEnd, currentFile->startOf(declarator));
-
- prevDeclarator = declarator;
- }
-
- currentFile->setChangeSet(changes);
- currentFile->apply();
- }
-
-private:
- SimpleDeclarationAST *declaration;
-};
-
-} // anonymous namespace
-
-void SplitSimpleDeclaration::doMatch(const CppQuickFixInterface &interface,
- QuickFixOperations &result)
-{
- CoreDeclaratorAST *core_declarator = nullptr;
- const QList<AST *> &path = interface.path();
- CppRefactoringFilePtr file = interface.currentFile();
- const int cursorPosition = file->cursor().selectionStart();
-
- for (int index = path.size() - 1; index != -1; --index) {
- AST *node = path.at(index);
-
- if (CoreDeclaratorAST *coreDecl = node->asCoreDeclarator()) {
- core_declarator = coreDecl;
- } else if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) {
- if (checkDeclarationForSplit(simpleDecl)) {
- SimpleDeclarationAST *declaration = simpleDecl;
-
- const int startOfDeclSpecifier = file->startOf(declaration->decl_specifier_list->firstToken());
- const int endOfDeclSpecifier = file->endOf(declaration->decl_specifier_list->lastToken() - 1);
-
- if (cursorPosition >= startOfDeclSpecifier && cursorPosition <= endOfDeclSpecifier) {
- // the AST node under cursor is a specifier.
- result << new SplitSimpleDeclarationOp(interface, index, declaration);
- return;
- }
-
- if (core_declarator && interface.isCursorOn(core_declarator)) {
- // got a core-declarator under the text cursor.
- result << new SplitSimpleDeclarationOp(interface, index, declaration);
- return;
- }
- }
-
- return;
- }
- }
-}
-
-namespace {
-template<typename Statement> Statement *asControlStatement(AST *node)
-{
- if constexpr (std::is_same_v<Statement, IfStatementAST>)
- return node->asIfStatement();
- if constexpr (std::is_same_v<Statement, WhileStatementAST>)
- return node->asWhileStatement();
- if constexpr (std::is_same_v<Statement, ForStatementAST>)
- return node->asForStatement();
- if constexpr (std::is_same_v<Statement, RangeBasedForStatementAST>)
- return node->asRangeBasedForStatement();
- if constexpr (std::is_same_v<Statement, DoStatementAST>)
- return node->asDoStatement();
- return nullptr;
-}
-
-template<typename Statement>
-int triggerToken(const Statement *statement)
-{
- if constexpr (std::is_same_v<Statement, IfStatementAST>)
- return statement->if_token;
- if constexpr (std::is_same_v<Statement, WhileStatementAST>)
- return statement->while_token;
- if constexpr (std::is_same_v<Statement, DoStatementAST>)
- return statement->do_token;
- if constexpr (std::is_same_v<Statement, ForStatementAST>
- || std::is_same_v<Statement, RangeBasedForStatementAST>) {
- return statement->for_token;
- }
-}
-
-template<typename Statement>
-int tokenToInsertOpeningBraceAfter(const Statement *statement)
-{
- if constexpr (std::is_same_v<Statement, DoStatementAST>)
- return statement->do_token;
- return statement->rparen_token;
-}
-
-template<typename Statement> class AddBracesToControlStatementOp : public CppQuickFixOperation
-{
-public:
- AddBracesToControlStatementOp(const CppQuickFixInterface &interface,
- const QList<Statement *> &statements,
- StatementAST *elseStatement,
- int elseToken)
- : CppQuickFixOperation(interface, 0)
- , m_statements(statements), m_elseStatement(elseStatement), m_elseToken(elseToken)
- {
- setDescription(Tr::tr("Add Curly Braces"));
- }
-
- void perform() override
- {
- CppRefactoringChanges refactoring(snapshot());
- CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath());
-
- ChangeSet changes;
- for (Statement * const statement : m_statements) {
- const int start = currentFile->endOf(tokenToInsertOpeningBraceAfter(statement));
- changes.insert(start, QLatin1String(" {"));
- if constexpr (std::is_same_v<Statement, DoStatementAST>) {
- const int end = currentFile->startOf(statement->while_token);
- changes.insert(end, QLatin1String("} "));
- } else if constexpr (std::is_same_v<Statement, IfStatementAST>) {
- if (statement->else_statement) {
- changes.insert(currentFile->startOf(statement->else_token), "} ");
- } else {
- changes.insert(currentFile->endOf(statement->statement->lastToken() - 1),
- "\n}");
- }
-
- } else {
- const int end = currentFile->endOf(statement->statement->lastToken() - 1);
- changes.insert(end, QLatin1String("\n}"));
- }
- }
- if (m_elseStatement) {
- changes.insert(currentFile->endOf(m_elseToken), " {");
- changes.insert(currentFile->endOf(m_elseStatement->lastToken() - 1), "\n}");
- }
-
- currentFile->setChangeSet(changes);
- currentFile->apply();
- }
-
-private:
- const QList<Statement *> m_statements;
- StatementAST * const m_elseStatement;
- const int m_elseToken;
-};
-
-} // anonymous namespace
-
-template<typename Statement>
-bool checkControlStatementsHelper(const CppQuickFixInterface &interface, QuickFixOperations &result)
-{
- Statement * const statement = asControlStatement<Statement>(interface.path().last());
- if (!statement)
- return false;
-
- QList<Statement *> statements;
- if (interface.isCursorOn(triggerToken(statement)) && statement->statement
- && !statement->statement->asCompoundStatement()) {
- statements << statement;
- }
-
- StatementAST *elseStmt = nullptr;
- int elseToken = 0;
- if constexpr (std::is_same_v<Statement, IfStatementAST>) {
- IfStatementAST *currentIfStmt = statement;
- for (elseStmt = currentIfStmt->else_statement, elseToken = currentIfStmt->else_token;
- elseStmt && (currentIfStmt = elseStmt->asIfStatement());
- elseStmt = currentIfStmt->else_statement, elseToken = currentIfStmt->else_token) {
- if (currentIfStmt->statement && !currentIfStmt->statement->asCompoundStatement())
- statements << currentIfStmt;
- }
- if (elseStmt && (elseStmt->asIfStatement() || elseStmt->asCompoundStatement())) {
- elseStmt = nullptr;
- elseToken = 0;
- }
- }
-
- if (!statements.isEmpty() || elseStmt)
- result << new AddBracesToControlStatementOp(interface, statements, elseStmt, elseToken);
- return true;
-}
-
-template<typename ...Statements>
-void checkControlStatements(const CppQuickFixInterface &interface, QuickFixOperations &result)
-{
- (... || checkControlStatementsHelper<Statements>(interface, result));
-}
-
-void AddBracesToControlStatement::doMatch(const CppQuickFixInterface &interface,
- QuickFixOperations &result)
-{
- if (interface.path().isEmpty())
- return;
- checkControlStatements<IfStatementAST,
- WhileStatementAST,
- ForStatementAST,
- RangeBasedForStatementAST,
- DoStatementAST>(interface, result);
-}
-
-namespace {
-
-class MoveDeclarationOutOfIfOp: public CppQuickFixOperation
-{
-public:
- MoveDeclarationOutOfIfOp(const CppQuickFixInterface &interface)
- : CppQuickFixOperation(interface)
- {
- setDescription(Tr::tr("Move Declaration out of Condition"));
-
- reset();
- }
-
- void reset()
- {
- condition = mk.Condition();
- pattern = mk.IfStatement(condition);
- }
-
- void perform() override
- {
- CppRefactoringChanges refactoring(snapshot());
- CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath());
-
- ChangeSet changes;
-
- changes.copy(currentFile->range(core), currentFile->startOf(condition));
-
- int insertPos = currentFile->startOf(pattern);
- changes.move(currentFile->range(condition), insertPos);
- changes.insert(insertPos, QLatin1String(";\n"));
-
- currentFile->setChangeSet(changes);
- currentFile->apply();
- }
-
- ASTMatcher matcher;
- ASTPatternBuilder mk;
- ConditionAST *condition = nullptr;
- IfStatementAST *pattern = nullptr;
- CoreDeclaratorAST *core = nullptr;
-};
-
-} // anonymous namespace
-
-void MoveDeclarationOutOfIf::doMatch(const CppQuickFixInterface &interface,
- QuickFixOperations &result)
-{
- const QList<AST *> &path = interface.path();
- using Ptr = QSharedPointer<MoveDeclarationOutOfIfOp>;
- Ptr op(new MoveDeclarationOutOfIfOp(interface));
-
- int index = path.size() - 1;
- for (; index != -1; --index) {
- if (IfStatementAST *statement = path.at(index)->asIfStatement()) {
- if (statement->match(op->pattern, &op->matcher) && op->condition->declarator) {
- DeclaratorAST *declarator = op->condition->declarator;
- op->core = declarator->core_declarator;
- if (!op->core)
- return;
-
- if (interface.isCursorOn(op->core)) {
- op->setPriority(index);
- result.append(op);
- return;
- }
-
- op->reset();
- }
- }
- }
-}
-
-namespace {
-
-class MoveDeclarationOutOfWhileOp: public CppQuickFixOperation
-{
-public:
- MoveDeclarationOutOfWhileOp(const CppQuickFixInterface &interface)
- : CppQuickFixOperation(interface)
- {
- setDescription(Tr::tr("Move Declaration out of Condition"));
- reset();
- }
-
- void reset()
- {
- condition = mk.Condition();
- pattern = mk.WhileStatement(condition);
- }
-
- void perform() override
- {
- CppRefactoringChanges refactoring(snapshot());
- CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath());
-
- ChangeSet changes;
-
- changes.insert(currentFile->startOf(condition), QLatin1String("("));
- changes.insert(currentFile->endOf(condition), QLatin1String(") != 0"));
-
- int insertPos = currentFile->startOf(pattern);
- const int conditionStart = currentFile->startOf(condition);
- changes.move(conditionStart, currentFile->startOf(core), insertPos);
- changes.copy(currentFile->range(core), insertPos);
- changes.insert(insertPos, QLatin1String(";\n"));
-
- currentFile->setChangeSet(changes);
- currentFile->apply();
- }
-
- ASTMatcher matcher;
- ASTPatternBuilder mk;
- ConditionAST *condition = nullptr;
- WhileStatementAST *pattern = nullptr;
- CoreDeclaratorAST *core = nullptr;
-};
-
-} // anonymous namespace
-
-void MoveDeclarationOutOfWhile::doMatch(const CppQuickFixInterface &interface,
- QuickFixOperations &result)
-{
- const QList<AST *> &path = interface.path();
- QSharedPointer<MoveDeclarationOutOfWhileOp> op(new MoveDeclarationOutOfWhileOp(interface));
-
- int index = path.size() - 1;
- for (; index != -1; --index) {
- if (WhileStatementAST *statement = path.at(index)->asWhileStatement()) {
- if (statement->match(op->pattern, &op->matcher) && op->condition->declarator) {
- DeclaratorAST *declarator = op->condition->declarator;
- op->core = declarator->core_declarator;
-
- if (!op->core)
- return;
-
- if (!declarator->equal_token)
- return;
-
- if (!declarator->initializer)
- return;
-
- if (interface.isCursorOn(op->core)) {
- op->setPriority(index);
- result.append(op);
- return;
- }
-
- op->reset();
- }
- }
- }
-}
-
-namespace {
-
-class SplitIfStatementOp: public CppQuickFixOperation
-{
-public:
- SplitIfStatementOp(const CppQuickFixInterface &interface, int priority,
- IfStatementAST *pattern, BinaryExpressionAST *condition)
- : CppQuickFixOperation(interface, priority)
- , pattern(pattern)
- , condition(condition)
- {
- setDescription(Tr::tr("Split if Statement"));
- }
-
- void perform() override
- {
- CppRefactoringChanges refactoring(snapshot());
- CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath());
-
- const Token binaryToken = currentFile->tokenAt(condition->binary_op_token);
-
- if (binaryToken.is(T_AMPER_AMPER))
- splitAndCondition(currentFile);
- else
- splitOrCondition(currentFile);
- }
-
- void splitAndCondition(CppRefactoringFilePtr currentFile) const
- {
- ChangeSet changes;
-
- int startPos = currentFile->startOf(pattern);
- changes.insert(startPos, QLatin1String("if ("));
- changes.move(currentFile->range(condition->left_expression), startPos);
- changes.insert(startPos, QLatin1String(") {\n"));
-
- const int lExprEnd = currentFile->endOf(condition->left_expression);
- changes.remove(lExprEnd, currentFile->startOf(condition->right_expression));
- changes.insert(currentFile->endOf(pattern), QLatin1String("\n}"));
-
- currentFile->setChangeSet(changes);
- currentFile->apply();
- }
-
- void splitOrCondition(CppRefactoringFilePtr currentFile) const
- {
- ChangeSet changes;
-
- StatementAST *ifTrueStatement = pattern->statement;
- CompoundStatementAST *compoundStatement = ifTrueStatement->asCompoundStatement();
-
- int insertPos = currentFile->endOf(ifTrueStatement);
- if (compoundStatement)
- changes.insert(insertPos, QLatin1String(" "));
- else
- changes.insert(insertPos, QLatin1String("\n"));
- changes.insert(insertPos, QLatin1String("else if ("));
-
- const int rExprStart = currentFile->startOf(condition->right_expression);
- changes.move(rExprStart, currentFile->startOf(pattern->rparen_token), insertPos);
- changes.insert(insertPos, QLatin1String(")"));
-
- const int rParenEnd = currentFile->endOf(pattern->rparen_token);
- changes.copy(rParenEnd, currentFile->endOf(pattern->statement), insertPos);
-
- const int lExprEnd = currentFile->endOf(condition->left_expression);
- changes.remove(lExprEnd, currentFile->startOf(condition->right_expression));
-
- currentFile->setChangeSet(changes);
- currentFile->apply();
- }
-
-private:
- IfStatementAST *pattern;
- BinaryExpressionAST *condition;
-};
-
-} // anonymous namespace
-
-void SplitIfStatement::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result)
-{
- IfStatementAST *pattern = nullptr;
- const QList<AST *> &path = interface.path();
-
- int index = path.size() - 1;
- for (; index != -1; --index) {
- AST *node = path.at(index);
- if (IfStatementAST *stmt = node->asIfStatement()) {
- pattern = stmt;
- break;
- }
- }
-
- if (!pattern || !pattern->statement)
- return;
-
- unsigned splitKind = 0;
- for (++index; index < path.size(); ++index) {
- AST *node = path.at(index);
- BinaryExpressionAST *condition = node->asBinaryExpression();
- if (!condition)
- return;
-
- Token binaryToken = interface.currentFile()->tokenAt(condition->binary_op_token);
-
- // only accept a chain of ||s or &&s - no mixing
- if (!splitKind) {
- splitKind = binaryToken.kind();
- if (splitKind != T_AMPER_AMPER && splitKind != T_PIPE_PIPE)
- return;
- // we can't reliably split &&s in ifs with an else branch
- if (splitKind == T_AMPER_AMPER && pattern->else_statement)
- return;
- } else if (splitKind != binaryToken.kind()) {
- return;
- }
-
- if (interface.isCursorOn(condition->binary_op_token)) {
- result << new SplitIfStatementOp(interface, index, pattern, condition);
- return;
- }
- }
-}
-
-/* Analze a string/character literal like "x", QLatin1String("x") and return the literal
- * (StringLiteral or NumericLiteral for characters) and its type
- * and the enclosing function (QLatin1String, tr...) */
-
-enum StringLiteralType { TypeString, TypeObjCString, TypeChar, TypeNone };
-
-enum ActionFlags {
- EncloseInQLatin1CharAction = 0x1,
- EncloseInQLatin1StringAction = 0x2,
- EncloseInQStringLiteralAction = 0x4,
- EncloseInQByteArrayLiteralAction = 0x8,
- EncloseActionMask = EncloseInQLatin1CharAction | EncloseInQLatin1StringAction
- | EncloseInQStringLiteralAction | EncloseInQByteArrayLiteralAction,
- TranslateTrAction = 0x10,
- TranslateQCoreApplicationAction = 0x20,
- TranslateNoopAction = 0x40,
- TranslationMask = TranslateTrAction | TranslateQCoreApplicationAction | TranslateNoopAction,
- RemoveObjectiveCAction = 0x100,
- ConvertEscapeSequencesToCharAction = 0x200,
- ConvertEscapeSequencesToStringAction = 0x400,
- SingleQuoteAction = 0x800,
- DoubleQuoteAction = 0x1000
-};
-
-/* Convert single-character string literals into character literals with some
- * special cases "a" --> 'a', "'" --> '\'', "\n" --> '\n', "\"" --> '"'. */
-static QByteArray stringToCharEscapeSequences(const QByteArray &content)
-{
- if (content.size() == 1)
- return content.at(0) == '\'' ? QByteArray("\\'") : content;
- if (content.size() == 2 && content.at(0) == '\\')
- return content == "\\\"" ? QByteArray(1, '"') : content;
- return QByteArray();
-}
-
-/* Convert character literal into a string literal with some special cases
- * 'a' -> "a", '\n' -> "\n", '\'' --> "'", '"' --> "\"". */
-static QByteArray charToStringEscapeSequences(const QByteArray &content)
-{
- if (content.size() == 1)
- return content.at(0) == '"' ? QByteArray("\\\"") : content;
- if (content.size() == 2)
- return content == "\\'" ? QByteArray("'") : content;
- return QByteArray();
-}
-
-static QString msgQtStringLiteralDescription(const QString &replacement)
-{
- return Tr::tr("Enclose in %1(...)").arg(replacement);
-}
-
-static QString stringLiteralReplacement(unsigned actions)
-{
- if (actions & EncloseInQLatin1CharAction)
- return QLatin1String("QLatin1Char");
- if (actions & EncloseInQLatin1StringAction)
- return QLatin1String("QLatin1String");
- if (actions & EncloseInQStringLiteralAction)
- return QLatin1String("QStringLiteral");
- if (actions & EncloseInQByteArrayLiteralAction)
- return QLatin1String("QByteArrayLiteral");
- if (actions & TranslateTrAction)
- return QLatin1String("tr");
- if (actions & TranslateQCoreApplicationAction)
- return QLatin1String("QCoreApplication::translate");
- if (actions & TranslateNoopAction)
- return QLatin1String("QT_TRANSLATE_NOOP");
- return QString();
-}
-
-static ExpressionAST *analyzeStringLiteral(const QList<AST *> &path,
- const CppRefactoringFilePtr &file, StringLiteralType *type,
- QByteArray *enclosingFunction = nullptr,
- CallAST **enclosingFunctionCall = nullptr)
-{
- *type = TypeNone;
- if (enclosingFunction)
- enclosingFunction->clear();
- if (enclosingFunctionCall)
- *enclosingFunctionCall = nullptr;
-
- if (path.isEmpty())
- return nullptr;
-
- ExpressionAST *literal = path.last()->asExpression();
- if (literal) {
- if (literal->asStringLiteral()) {
- // Check for Objective C string (@"bla")
- const QChar firstChar = file->charAt(file->startOf(literal));
- *type = firstChar == QLatin1Char('@') ? TypeObjCString : TypeString;
- } else if (NumericLiteralAST *numericLiteral = literal->asNumericLiteral()) {
- // character ('c') constants are numeric.
- if (file->tokenAt(numericLiteral->literal_token).is(T_CHAR_LITERAL))
- *type = TypeChar;
- }
- }
-
- if (*type != TypeNone && enclosingFunction && path.size() > 1) {
- if (CallAST *call = path.at(path.size() - 2)->asCall()) {
- if (call->base_expression) {
- if (IdExpressionAST *idExpr = call->base_expression->asIdExpression()) {
- if (SimpleNameAST *functionName = idExpr->name->asSimpleName()) {
- *enclosingFunction = file->tokenAt(functionName->identifier_token).identifier->chars();
- if (enclosingFunctionCall)
- *enclosingFunctionCall = call;
- }
- }
- }
- }
- }
- return literal;
-}
-
-namespace {
-
-/// Operation performs the operations of type ActionFlags passed in as actions.
-class WrapStringLiteralOp : public CppQuickFixOperation
-{
-public:
- WrapStringLiteralOp(const CppQuickFixInterface &interface, int priority,
- unsigned actions, const QString &description, ExpressionAST *literal,
- const QString &translationContext = QString())
- : CppQuickFixOperation(interface, priority), m_actions(actions), m_literal(literal),
- m_translationContext(translationContext)
- {
- setDescription(description);
- }
-
- void perform() override
- {
- CppRefactoringChanges refactoring(snapshot());
- CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath());
-
- ChangeSet changes;
-
- const int startPos = currentFile->startOf(m_literal);
- const int endPos = currentFile->endOf(m_literal);
-
- // kill leading '@'. No need to adapt endPos, that is done by ChangeSet
- if (m_actions & RemoveObjectiveCAction)
- changes.remove(startPos, startPos + 1);
-
- // Fix quotes
- if (m_actions & (SingleQuoteAction | DoubleQuoteAction)) {
- const QString newQuote((m_actions & SingleQuoteAction)
- ? QLatin1Char('\'') : QLatin1Char('"'));
- changes.replace(startPos, startPos + 1, newQuote);
- changes.replace(endPos - 1, endPos, newQuote);
- }
-
- // Convert single character strings into character constants
- if (m_actions & ConvertEscapeSequencesToCharAction) {
- StringLiteralAST *stringLiteral = m_literal->asStringLiteral();
- QTC_ASSERT(stringLiteral, return ;);
- const QByteArray oldContents(currentFile->tokenAt(stringLiteral->literal_token).identifier->chars());
- const QByteArray newContents = stringToCharEscapeSequences(oldContents);
- QTC_ASSERT(!newContents.isEmpty(), return ;);
- if (oldContents != newContents)
- changes.replace(startPos + 1, endPos -1, QString::fromLatin1(newContents));
- }
-
- // Convert character constants into strings constants
- if (m_actions & ConvertEscapeSequencesToStringAction) {
- NumericLiteralAST *charLiteral = m_literal->asNumericLiteral(); // char 'c' constants are numerical.
- QTC_ASSERT(charLiteral, return ;);
- const QByteArray oldContents(currentFile->tokenAt(charLiteral->literal_token).identifier->chars());
- const QByteArray newContents = charToStringEscapeSequences(oldContents);
- QTC_ASSERT(!newContents.isEmpty(), return ;);
- if (oldContents != newContents)
- changes.replace(startPos + 1, endPos -1, QString::fromLatin1(newContents));
- }
-
- // Enclose in literal or translation function, macro.
- if (m_actions & (EncloseActionMask | TranslationMask)) {
- changes.insert(endPos, QString(QLatin1Char(')')));
- QString leading = stringLiteralReplacement(m_actions);
- leading += QLatin1Char('(');
- if (m_actions
- & (TranslateQCoreApplicationAction | TranslateNoopAction)) {
- leading += QLatin1Char('"');
- leading += m_translationContext;
- leading += QLatin1String("\", ");
- }
- changes.insert(startPos, leading);
- }
-
- currentFile->setChangeSet(changes);
- currentFile->apply();
- }
-
-private:
- const unsigned m_actions;
- ExpressionAST *m_literal;
- const QString m_translationContext;
-};
-
-} // anonymous namespace
-
-void WrapStringLiteral::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result)
-{
- StringLiteralType type = TypeNone;
- QByteArray enclosingFunction;
- const QList<AST *> &path = interface.path();
- CppRefactoringFilePtr file = interface.currentFile();
- ExpressionAST *literal = analyzeStringLiteral(path, file, &type, &enclosingFunction);
- if (!literal || type == TypeNone)
- return;
- if ((type == TypeChar && enclosingFunction == "QLatin1Char")
- || isQtStringLiteral(enclosingFunction)
- || isQtStringTranslation(enclosingFunction))
- return;
-
- const int priority = path.size() - 1; // very high priority
- if (type == TypeChar) {
- unsigned actions = EncloseInQLatin1CharAction;
- QString description = msgQtStringLiteralDescription(stringLiteralReplacement(actions));
- result << new WrapStringLiteralOp(interface, priority, actions, description, literal);
- if (NumericLiteralAST *charLiteral = literal->asNumericLiteral()) {
- const QByteArray contents(file->tokenAt(charLiteral->literal_token).identifier->chars());
- if (!charToStringEscapeSequences(contents).isEmpty()) {
- actions = DoubleQuoteAction | ConvertEscapeSequencesToStringAction;
- description = Tr::tr("Convert to String Literal");
- result << new WrapStringLiteralOp(interface, priority, actions,
- description, literal);
- }
- }
- } else {
- const unsigned objectiveCActions = type == TypeObjCString ?
- unsigned(RemoveObjectiveCAction) : 0u;
- unsigned actions = 0;
- if (StringLiteralAST *stringLiteral = literal->asStringLiteral()) {
- const QByteArray contents(file->tokenAt(stringLiteral->literal_token).identifier->chars());
- if (!stringToCharEscapeSequences(contents).isEmpty()) {
- actions = EncloseInQLatin1CharAction | SingleQuoteAction
- | ConvertEscapeSequencesToCharAction | objectiveCActions;
- QString description =
- Tr::tr("Convert to Character Literal and Enclose in QLatin1Char(...)");
- result << new WrapStringLiteralOp(interface, priority, actions,
- description, literal);
- actions &= ~EncloseInQLatin1CharAction;
- description = Tr::tr("Convert to Character Literal");
- result << new WrapStringLiteralOp(interface, priority, actions,
- description, literal);
- }
- }
- actions = EncloseInQLatin1StringAction | objectiveCActions;
- result << new WrapStringLiteralOp(interface, priority, actions,
- msgQtStringLiteralDescription(stringLiteralReplacement(actions)), literal);
- actions = EncloseInQStringLiteralAction | objectiveCActions;
- result << new WrapStringLiteralOp(interface, priority, actions,
- msgQtStringLiteralDescription(stringLiteralReplacement(actions)), literal);
- actions = EncloseInQByteArrayLiteralAction | objectiveCActions;
- result << new WrapStringLiteralOp(interface, priority, actions,
- msgQtStringLiteralDescription(stringLiteralReplacement(actions)), literal);
- }
-}
-
-void TranslateStringLiteral::doMatch(const CppQuickFixInterface &interface,
- QuickFixOperations &result)
-{
- // Initialize
- StringLiteralType type = TypeNone;
- QByteArray enclosingFunction;
- const QList<AST *> &path = interface.path();
- CppRefactoringFilePtr file = interface.currentFile();
- ExpressionAST *literal = analyzeStringLiteral(path, file, &type, &enclosingFunction);
- if (!literal || type != TypeString
- || isQtStringLiteral(enclosingFunction) || isQtStringTranslation(enclosingFunction))
- return;
-
- QString trContext;
-
- std::shared_ptr<Control> control = interface.context().bindings()->control();
- const Name *trName = control->identifier("tr");
-
- // Check whether we are in a function:
- const QString description = Tr::tr("Mark as Translatable");
- for (int i = path.size() - 1; i >= 0; --i) {
- if (FunctionDefinitionAST *definition = path.at(i)->asFunctionDefinition()) {
- Function *function = definition->symbol;
- ClassOrNamespace *b = interface.context().lookupType(function);
- if (b) {
- // Do we have a tr function?
- const QList<LookupItem> items = b->find(trName);
- for (const LookupItem &r : items) {
- Symbol *s = r.declaration();
- if (s->type()->asFunctionType()) {
- // no context required for tr
- result << new WrapStringLiteralOp(interface, path.size() - 1,
- TranslateTrAction,
- description, literal);
- return;
- }
- }
- }
- // We need to do a QCA::translate, so we need a context.
- // Use fully qualified class name:
- Overview oo;
- const QList<const Name *> names = LookupContext::path(function);
- for (const Name *n : names) {
- if (!trContext.isEmpty())
- trContext.append(QLatin1String("::"));
- trContext.append(oo.prettyName(n));
- }
- // ... or global if none available!
- if (trContext.isEmpty())
- trContext = QLatin1String("GLOBAL");
- result << new WrapStringLiteralOp(interface, path.size() - 1,
- TranslateQCoreApplicationAction,
- description, literal, trContext);
- return;
- }
- }
-
- // We need to use Q_TRANSLATE_NOOP
- result << new WrapStringLiteralOp(interface, path.size() - 1,
- TranslateNoopAction,
- description, literal, trContext);
-}
-
-namespace {
-
-class ConvertCStringToNSStringOp: public CppQuickFixOperation
-{
-public:
- ConvertCStringToNSStringOp(const CppQuickFixInterface &interface, int priority,
- StringLiteralAST *stringLiteral, CallAST *qlatin1Call)
- : CppQuickFixOperation(interface, priority)
- , stringLiteral(stringLiteral)
- , qlatin1Call(qlatin1Call)
- {
- setDescription(Tr::tr("Convert to Objective-C String Literal"));
- }
-
- void perform() override
- {
- CppRefactoringChanges refactoring(snapshot());
- CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath());
-
- ChangeSet changes;
-
- if (qlatin1Call) {
- changes.replace(currentFile->startOf(qlatin1Call), currentFile->startOf(stringLiteral),
- QLatin1String("@"));
- changes.remove(currentFile->endOf(stringLiteral), currentFile->endOf(qlatin1Call));
- } else {
- changes.insert(currentFile->startOf(stringLiteral), QLatin1String("@"));
- }
-
- currentFile->setChangeSet(changes);
- currentFile->apply();
- }
-
-private:
- StringLiteralAST *stringLiteral;
- CallAST *qlatin1Call;
-};
-
-} // anonymous namespace
-
-void ConvertCStringToNSString::doMatch(const CppQuickFixInterface &interface,
- QuickFixOperations &result)
-{
- CppRefactoringFilePtr file = interface.currentFile();
-
- if (!interface.editor()->cppEditorDocument()->isObjCEnabled())
- return;
-
- StringLiteralType type = TypeNone;
- QByteArray enclosingFunction;
- CallAST *qlatin1Call;
- const QList<AST *> &path = interface.path();
- ExpressionAST *literal = analyzeStringLiteral(path, file, &type, &enclosingFunction,
- &qlatin1Call);
- if (!literal || type != TypeString)
- return;
- if (!isQtStringLiteral(enclosingFunction))
- qlatin1Call = nullptr;
-
- result << new ConvertCStringToNSStringOp(interface, path.size() - 1, literal->asStringLiteral(),
- qlatin1Call);
-}
-
-namespace {
-
-class ConvertNumericLiteralOp: public CppQuickFixOperation
-{
-public:
- ConvertNumericLiteralOp(const CppQuickFixInterface &interface, int start, int end,
- const QString &replacement)
- : CppQuickFixOperation(interface)
- , start(start)
- , end(end)
- , replacement(replacement)
- {}
-
- void perform() override
- {
- CppRefactoringChanges refactoring(snapshot());
- CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath());
-
- ChangeSet changes;
- changes.replace(start, end, replacement);
- currentFile->setChangeSet(changes);
- currentFile->apply();
- }
-
-private:
- int start, end;
- QString replacement;
-};
-
-} // anonymous namespace
-
-void ConvertNumericLiteral::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result)
-{
- const QList<AST *> &path = interface.path();
- CppRefactoringFilePtr file = interface.currentFile();
-
- if (path.isEmpty())
- return;
-
- NumericLiteralAST *literal = path.last()->asNumericLiteral();
-
- if (!literal)
- return;
-
- Token token = file->tokenAt(literal->asNumericLiteral()->literal_token);
- if (!token.is(T_NUMERIC_LITERAL))
- return;
- const NumericLiteral *numeric = token.number;
- if (numeric->isDouble() || numeric->isFloat())
- return;
-
- // remove trailing L or U and stuff
- const char * const spell = numeric->chars();
- int numberLength = numeric->size();
- while (numberLength > 0 && !std::isxdigit(spell[numberLength - 1]))
- --numberLength;
- if (numberLength < 1)
- return;
-
- // convert to number
- bool valid;
- ulong value = 0;
- const QString x = QString::fromUtf8(spell).left(numberLength);
- if (x.startsWith("0b", Qt::CaseInsensitive))
- value = x.mid(2).toULong(&valid, 2);
- else
- value = x.toULong(&valid, 0);
-
- if (!valid)
- return;
-
- const int priority = path.size() - 1; // very high priority
- const int start = file->startOf(literal);
- const char * const str = numeric->chars();
-
- const bool isBinary = numberLength > 2 && str[0] == '0' && (str[1] == 'b' || str[1] == 'B');
- const bool isOctal = numberLength >= 2 && str[0] == '0' && str[1] >= '0' && str[1] <= '7';
- const bool isDecimal = !(isBinary || isOctal || numeric->isHex());
-
- if (!numeric->isHex()) {
- /*
- Convert integer literal to hex representation.
- Replace
- 0b100000
- 32
- 040
- With
- 0x20
-
- */
- const QString replacement = QString::asprintf("0x%lX", value);
- auto op = new ConvertNumericLiteralOp(interface, start, start + numberLength, replacement);
- op->setDescription(Tr::tr("Convert to Hexadecimal"));
- op->setPriority(priority);
- result << op;
- }
-
- if (!isOctal) {
- /*
- Convert integer literal to octal representation.
- Replace
- 0b100000
- 32
- 0x20
- With
- 040
- */
- const QString replacement = QString::asprintf("0%lo", value);
- auto op = new ConvertNumericLiteralOp(interface, start, start + numberLength, replacement);
- op->setDescription(Tr::tr("Convert to Octal"));
- op->setPriority(priority);
- result << op;
- }
-
- if (!isDecimal) {
- /*
- Convert integer literal to decimal representation.
- Replace
- 0b100000
- 0x20
- 040
- With
- 32
- */
- const QString replacement = QString::asprintf("%lu", value);
- auto op = new ConvertNumericLiteralOp(interface, start, start + numberLength, replacement);
- op->setDescription(Tr::tr("Convert to Decimal"));
- op->setPriority(priority);
- result << op;
- }
-
- if (!isBinary) {
- /*
- Convert integer literal to binary representation.
- Replace
- 32
- 0x20
- 040
- With
- 0b100000
- */
- QString replacement = "0b";
- if (value == 0) {
- replacement.append('0');
- } else {
- std::bitset<std::numeric_limits<decltype (value)>::digits> b(value);
- QRegularExpression re("^[0]*");
- replacement.append(QString::fromStdString(b.to_string()).remove(re));
- }
- auto op = new ConvertNumericLiteralOp(interface, start, start + numberLength, replacement);
- op->setDescription(Tr::tr("Convert to Binary"));
- op->setPriority(priority);
- result << op;
- }
-}
-
-namespace {
-
-class AddLocalDeclarationOp: public CppQuickFixOperation
-{
-public:
- AddLocalDeclarationOp(const CppQuickFixInterface &interface,
- int priority,
- const BinaryExpressionAST *binaryAST,
- const SimpleNameAST *simpleNameAST)
- : CppQuickFixOperation(interface, priority)
- , binaryAST(binaryAST)
- , simpleNameAST(simpleNameAST)
- {
- setDescription(Tr::tr("Add Local Declaration"));
- }
-
- void perform() override
- {
- CppRefactoringChanges refactoring(snapshot());
- CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath());
- QString declaration = getDeclaration();
-
- if (!declaration.isEmpty()) {
- ChangeSet changes;
- changes.replace(currentFile->startOf(binaryAST),
- currentFile->endOf(simpleNameAST),
- declaration);
- currentFile->setChangeSet(changes);
- currentFile->apply();
- }
- }
-
-private:
- QString getDeclaration()
- {
- CppRefactoringChanges refactoring(snapshot());
- CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath());
- Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview();
- const auto settings = CppQuickFixProjectsSettings::getQuickFixSettings(
- ProjectExplorer::ProjectTree::currentProject());
-
- if (currentFile->cppDocument()->languageFeatures().cxx11Enabled && settings->useAuto)
- return "auto " + oo.prettyName(simpleNameAST->name);
- return declFromExpr(binaryAST->right_expression, nullptr, simpleNameAST, snapshot(),
- context(), currentFile, false);
- }
-
- const BinaryExpressionAST *binaryAST;
- const SimpleNameAST *simpleNameAST;
-};
-
-} // anonymous namespace
-
-namespace {
-
-class ConvertToCamelCaseOp: public CppQuickFixOperation
-{
-public:
- ConvertToCamelCaseOp(const CppQuickFixInterface &interface, const QString &name,
- const AST *nameAst, bool test)
- : CppQuickFixOperation(interface, -1)
- , m_name(name)
- , m_nameAst(nameAst)
- , m_isAllUpper(name.isUpper())
- , m_test(test)
- {
- setDescription(Tr::tr("Convert to Camel Case"));
- }
-
- void perform() override
- {
- CppRefactoringChanges refactoring(snapshot());
- CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath());
-
- QString newName = m_isAllUpper ? m_name.toLower() : m_name;
- for (int i = 1; i < newName.length(); ++i) {
- const QChar c = newName.at(i);
- if (c.isUpper() && m_isAllUpper) {
- newName[i] = c.toLower();
- } else if (i < newName.length() - 1 && isConvertibleUnderscore(newName, i)) {
- newName.remove(i, 1);
- newName[i] = newName.at(i).toUpper();
- }
- }
- if (m_test) {
- ChangeSet changeSet;
- changeSet.replace(currentFile->range(m_nameAst), newName);
- currentFile->setChangeSet(changeSet);
- currentFile->apply();
- } else {
- editor()->renameUsages(newName);
- }
- }
-
- static bool isConvertibleUnderscore(const QString &name, int pos)
- {
- return name.at(pos) == QLatin1Char('_') && name.at(pos+1).isLetter()
- && !(pos == 1 && name.at(0) == QLatin1Char('m'));
- }
-
-private:
- const QString m_name;
- const AST * const m_nameAst;
- const bool m_isAllUpper;
- const bool m_test;
-};
-
-} // anonymous namespace
-
-void ConvertToCamelCase::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result)
-{
- const QList<AST *> &path = interface.path();
-
- if (path.isEmpty())
- return;
-
- AST * const ast = path.last();
- const Name *name = nullptr;
- const AST *astForName = nullptr;
- if (const NameAST * const nameAst = ast->asName()) {
- if (nameAst->name && nameAst->name->asNameId()) {
- astForName = nameAst;
- name = nameAst->name;
- }
- } else if (const NamespaceAST * const namespaceAst = ast->asNamespace()) {
- astForName = namespaceAst;
- name = namespaceAst->symbol->name();
- }
-
- if (!name)
- return;
-
- QString nameString = QString::fromUtf8(name->identifier()->chars());
- if (nameString.length() < 3)
- return;
- for (int i = 1; i < nameString.length() - 1; ++i) {
- if (ConvertToCamelCaseOp::isConvertibleUnderscore(nameString, i)) {
- result << new ConvertToCamelCaseOp(interface, nameString, astForName, m_test);
- return;
- }
- }
-}
-
-AddIncludeForUndefinedIdentifierOp::AddIncludeForUndefinedIdentifierOp(
- const CppQuickFixInterface &interface, int priority, const QString &include)
- : CppQuickFixOperation(interface, priority)
- , m_include(include)
-{
- setDescription(Tr::tr("Add #include %1").arg(m_include));
-}
-
-void AddIncludeForUndefinedIdentifierOp::perform()
-{
- CppRefactoringChanges refactoring(snapshot());
- CppRefactoringFilePtr file = refactoring.cppFile(filePath());
-
- ChangeSet changes;
- insertNewIncludeDirective(m_include, file, semanticInfo().doc, changes);
- file->setChangeSet(changes);
- file->apply();
-}
-
-AddForwardDeclForUndefinedIdentifierOp::AddForwardDeclForUndefinedIdentifierOp(
- const CppQuickFixInterface &interface,
- int priority,
- const QString &fqClassName,
- int symbolPos)
- : CppQuickFixOperation(interface, priority), m_className(fqClassName), m_symbolPos(symbolPos)
-{
- setDescription(Tr::tr("Add forward declaration for %1").arg(m_className));
-}
-
-void AddForwardDeclForUndefinedIdentifierOp::perform()
-{
- const QStringList parts = m_className.split("::");
- QTC_ASSERT(!parts.isEmpty(), return);
- const QStringList namespaces = parts.mid(0, parts.length() - 1);
-
- CppRefactoringChanges refactoring(snapshot());
- CppRefactoringFilePtr file = refactoring.cppFile(filePath());
-
- NSVisitor visitor(file.data(), namespaces, m_symbolPos);
- visitor.accept(file->cppDocument()->translationUnit()->ast());
- const auto stringToInsert = [&visitor, symbol = parts.last()] {
- QString s = "\n";
- for (const QString &ns : visitor.remainingNamespaces())
- s += "namespace " + ns + " { ";
- s += "class " + symbol + ';';
- for (int i = 0; i < visitor.remainingNamespaces().size(); ++i)
- s += " }";
- return s;
- };
-
- int insertPos = 0;
-
- // Find the position to insert:
- // If we have a matching namespace, we do the insertion there.
- // If we don't have a matching namespace, but there is another namespace in the file,
- // we assume that to be a good position for our insertion.
- // Otherwise, do the insertion after the last include that comes before the use of the symbol.
- // If there is no such include, do the insertion before the first token.
- if (visitor.enclosingNamespace()) {
- insertPos = file->startOf(visitor.enclosingNamespace()->linkage_body) + 1;
- } else if (visitor.firstNamespace()) {
- insertPos = file->startOf(visitor.firstNamespace());
- } else {
- const QTextCursor tc = file->document()->find(
- QRegularExpression("^\\s*#include .*$"),
- m_symbolPos,
- QTextDocument::FindBackward | QTextDocument::FindCaseSensitively);
- if (!tc.isNull())
- insertPos = tc.position() + 1;
- else if (visitor.firstToken())
- insertPos = file->startOf(visitor.firstToken());
- }
-
- QString insertion = stringToInsert();
- if (file->charAt(insertPos - 1) != QChar::ParagraphSeparator)
- insertion.prepend('\n');
- if (file->charAt(insertPos) != QChar::ParagraphSeparator)
- insertion.append('\n');
- ChangeSet s;
- s.insert(insertPos, insertion);
- file->setChangeSet(s);
- file->apply();
-}
-
-namespace {
-
-QString findShortestInclude(const QString currentDocumentFilePath,
- const QString candidateFilePath,
- const ProjectExplorer::HeaderPaths &headerPaths)
-{
- QString result;
-
- const QFileInfo fileInfo(candidateFilePath);
-
- if (fileInfo.path() == QFileInfo(currentDocumentFilePath).path()) {
- result = QLatin1Char('"') + fileInfo.fileName() + QLatin1Char('"');
- } else {
- for (const ProjectExplorer::HeaderPath &headerPath : headerPaths) {
- if (!candidateFilePath.startsWith(headerPath.path))
- continue;
- QString relativePath = candidateFilePath.mid(headerPath.path.size());
- if (!relativePath.isEmpty() && relativePath.at(0) == QLatin1Char('/'))
- relativePath = relativePath.mid(1);
- if (result.isEmpty() || relativePath.size() + 2 < result.size())
- result = QLatin1Char('<') + relativePath + QLatin1Char('>');
- }
- }
-
- return result;
-}
-
-QString findMatchingInclude(const QString &className,
- const ProjectExplorer::HeaderPaths &headerPaths)
-{
- const QStringList candidateFileNames{className, className + ".h", className + ".hpp",
- className.toLower(), className.toLower() + ".h", className.toLower() + ".hpp"};
- for (const QString &fileName : candidateFileNames) {
- for (const ProjectExplorer::HeaderPath &headerPath : headerPaths) {
- const QString headerPathCandidate = headerPath.path + QLatin1Char('/') + fileName;
- const QFileInfo fileInfo(headerPathCandidate);
- if (fileInfo.exists() && fileInfo.isFile())
- return '<' + fileName + '>';
- }
- }
- return {};
-}
-
-ProjectExplorer::HeaderPaths relevantHeaderPaths(const QString &filePath)
-{
- ProjectExplorer::HeaderPaths headerPaths;
-
- const QList<ProjectPart::ConstPtr> projectParts = CppModelManager::projectPart(filePath);
- if (projectParts.isEmpty()) { // Not part of any project, better use all include paths than none
- headerPaths += CppModelManager::headerPaths();
- } else {
- for (const ProjectPart::ConstPtr &part : projectParts)
- headerPaths += part->headerPaths;
- }
-
- return headerPaths;
-}
-
-NameAST *nameUnderCursor(const QList<AST *> &path)
-{
- if (path.isEmpty())
- return nullptr;
-
- NameAST *nameAst = nullptr;
- for (int i = path.size() - 1; i >= 0; --i) {
- AST * const ast = path.at(i);
- if (SimpleNameAST *simpleName = ast->asSimpleName()) {
- nameAst = simpleName;
- } else if (TemplateIdAST *templateId = ast->asTemplateId()) {
- nameAst = templateId;
- } else if (nameAst && ast->asNamedTypeSpecifier()) {
- break; // Stop at "Foo" for "N::Bar<@Foo>"
- } else if (QualifiedNameAST *qualifiedName = ast->asQualifiedName()) {
- nameAst = qualifiedName;
- break;
- }
- }
-
- return nameAst;
-}
-
-enum class LookupResult { Declared, ForwardDeclared, NotDeclared };
-LookupResult lookUpDefinition(const CppQuickFixInterface &interface, const NameAST *nameAst)
-{
- QTC_ASSERT(nameAst && nameAst->name, return LookupResult::NotDeclared);
-
- // Find the enclosing scope
- int line, column;
- const Document::Ptr doc = interface.semanticInfo().doc;
- doc->translationUnit()->getTokenPosition(nameAst->firstToken(), &line, &column);
- Scope *scope = doc->scopeAt(line, column);
- if (!scope)
- return LookupResult::NotDeclared;
-
- // Try to find the class/template definition
- const Name *name = nameAst->name;
- const QList<LookupItem> results = interface.context().lookup(name, scope);
- LookupResult best = LookupResult::NotDeclared;
- for (const LookupItem &item : results) {
- if (Symbol *declaration = item.declaration()) {
- if (declaration->asClass())
- return LookupResult::Declared;
- if (declaration->asForwardClassDeclaration()) {
- best = LookupResult::ForwardDeclared;
- continue;
- }
- if (Template *templ = declaration->asTemplate()) {
- if (Symbol *declaration = templ->declaration()) {
- if (declaration->asClass())
- return LookupResult::Declared;
- if (declaration->asForwardClassDeclaration()) {
- best = LookupResult::ForwardDeclared;
- continue;
- }
- }
- }
- return LookupResult::Declared;
- }
- }
-
- return best;
-}
-
-QString templateNameAsString(const TemplateNameId *templateName)
-{
- const Identifier *id = templateName->identifier();
- return QString::fromUtf8(id->chars(), id->size());
-}
-
-Snapshot forwardingHeaders(const CppQuickFixInterface &interface)
-{
- Snapshot result;
-
- const Snapshot docs = interface.snapshot();
- for (Document::Ptr doc : docs) {
- if (doc->globalSymbolCount() == 0 && doc->resolvedIncludes().size() == 1)
- result.insert(doc);
- }
-
- return result;
-}
-
-QList<IndexItem::Ptr> matchName(const Name *name, QString *className)
-{
- if (!name)
- return {};
-
- QString simpleName;
- QList<IndexItem::Ptr> matches;
- CppLocatorData *locatorData = CppModelManager::locatorData();
- const Overview oo;
- if (const QualifiedNameId *qualifiedName = name->asQualifiedNameId()) {
- const Name *name = qualifiedName->name();
- if (const TemplateNameId *templateName = name->asTemplateNameId()) {
- *className = templateNameAsString(templateName);
- } else {
- simpleName = oo.prettyName(name);
- *className = simpleName;
- matches = locatorData->findSymbols(IndexItem::Class, *className);
- if (matches.isEmpty()) {
- if (const Name *name = qualifiedName->base()) {
- if (const TemplateNameId *templateName = name->asTemplateNameId())
- *className = templateNameAsString(templateName);
- else
- *className = oo.prettyName(name);
- }
- }
- }
- } else if (const TemplateNameId *templateName = name->asTemplateNameId()) {
- *className = templateNameAsString(templateName);
- } else {
- *className = oo.prettyName(name);
- }
-
- if (matches.isEmpty())
- matches = locatorData->findSymbols(IndexItem::Class, *className);
-
- if (matches.isEmpty() && !simpleName.isEmpty())
- *className = simpleName;
-
- return matches;
-}
-
-} // anonymous namespace
-
-void AddIncludeForUndefinedIdentifier::doMatch(const CppQuickFixInterface &interface,
- QuickFixOperations &result)
-{
- const NameAST *nameAst = nameUnderCursor(interface.path());
- if (!nameAst || !nameAst->name)
- return;
-
- const LookupResult lookupResult = lookUpDefinition(interface, nameAst);
- if (lookupResult == LookupResult::Declared)
- return;
-
- QString className;
- const QString currentDocumentFilePath = interface.semanticInfo().doc->filePath().toString();
- const ProjectExplorer::HeaderPaths headerPaths = relevantHeaderPaths(currentDocumentFilePath);
- FilePaths headers;
-
- const QList<IndexItem::Ptr> matches = matchName(nameAst->name, &className);
- // Find an include file through the locator
- if (!matches.isEmpty()) {
- QList<IndexItem::Ptr> indexItems;
- const Snapshot forwardHeaders = forwardingHeaders(interface);
- for (const IndexItem::Ptr &info : matches) {
- if (!info || info->symbolName() != className)
- continue;
- indexItems << info;
-
- Snapshot localForwardHeaders = forwardHeaders;
- localForwardHeaders.insert(interface.snapshot().document(info->filePath()));
- FilePaths headerAndItsForwardingHeaders;
- headerAndItsForwardingHeaders << info->filePath();
- headerAndItsForwardingHeaders += localForwardHeaders.filesDependingOn(info->filePath());
-
- for (const FilePath &header : std::as_const(headerAndItsForwardingHeaders)) {
- const QString include = findShortestInclude(currentDocumentFilePath,
- header.toString(),
- headerPaths);
- if (include.size() > 2) {
- const QString headerFileName = info->filePath().fileName();
- QTC_ASSERT(!headerFileName.isEmpty(), break);
-
- int priority = 0;
- if (headerFileName == className)
- priority = 2;
- else if (headerFileName.at(1).isUpper())
- priority = 1;
-
- result << new AddIncludeForUndefinedIdentifierOp(interface, priority,
- include);
- headers << header;
- }
- }
- }
-
- if (lookupResult == LookupResult::NotDeclared && indexItems.size() == 1) {
- QString qualifiedName = Overview().prettyName(nameAst->name);
- if (qualifiedName.startsWith("::"))
- qualifiedName.remove(0, 2);
- if (indexItems.first()->scopedSymbolName().endsWith(qualifiedName)) {
- const ProjectExplorer::Node * const node = ProjectExplorer::ProjectTree
- ::nodeForFile(interface.filePath());
- ProjectExplorer::FileType fileType = node && node->asFileNode()
- ? node->asFileNode()->fileType() : ProjectExplorer::FileType::Unknown;
- if (fileType == ProjectExplorer::FileType::Unknown
- && ProjectFile::isHeader(ProjectFile::classify(interface.filePath().toString()))) {
- fileType = ProjectExplorer::FileType::Header;
- }
- if (fileType == ProjectExplorer::FileType::Header) {
- result << new AddForwardDeclForUndefinedIdentifierOp(
- interface, 0, indexItems.first()->scopedSymbolName(),
- interface.currentFile()->startOf(nameAst));
- }
- }
- }
- }
-
- if (className.isEmpty())
- return;
-
- // Fallback: Check the include paths for files that look like candidates
- // for the given name.
- if (!Utils::contains(headers,
- [&className](const Utils::FilePath &fp) { return fp.fileName() == className; })) {
- const QString include = findMatchingInclude(className, headerPaths);
- const auto matcher = [&include](const QuickFixOperation::Ptr &o) {
- const auto includeOp = o.dynamicCast<AddIncludeForUndefinedIdentifierOp>();
- return includeOp && includeOp->include() == include;
- };
- if (!include.isEmpty() && !Utils::contains(result, matcher))
- result << new AddIncludeForUndefinedIdentifierOp(interface, 1, include);
- }
-}
-
-namespace {
-
-class RearrangeParamDeclarationListOp: public CppQuickFixOperation
-{
-public:
- enum Target { TargetPrevious, TargetNext };
-
- RearrangeParamDeclarationListOp(const CppQuickFixInterface &interface, AST *currentParam,
- AST *targetParam, Target target)
- : CppQuickFixOperation(interface)
- , m_currentParam(currentParam)
- , m_targetParam(targetParam)
- {
- QString targetString;
- if (target == TargetPrevious)
- targetString = Tr::tr("Switch with Previous Parameter");
- else
- targetString = Tr::tr("Switch with Next Parameter");
- setDescription(targetString);
- }
-
- void perform() override
- {
- CppRefactoringChanges refactoring(snapshot());
- CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath());
-
- int targetEndPos = currentFile->endOf(m_targetParam);
- ChangeSet changes;
- changes.flip(currentFile->startOf(m_currentParam), currentFile->endOf(m_currentParam),
- currentFile->startOf(m_targetParam), targetEndPos);
- currentFile->setChangeSet(changes);
- currentFile->setOpenEditor(false, targetEndPos);
- currentFile->apply();
- }
-
-private:
- AST *m_currentParam;
- AST *m_targetParam;
-};
-
-} // anonymous namespace
-
-void RearrangeParamDeclarationList::doMatch(const CppQuickFixInterface &interface,
- QuickFixOperations &result)
-{
- const QList<AST *> path = interface.path();
-
- ParameterDeclarationAST *paramDecl = nullptr;
- int index = path.size() - 1;
- for (; index != -1; --index) {
- paramDecl = path.at(index)->asParameterDeclaration();
- if (paramDecl)
- break;
- }
-
- if (index < 1)
- return;
-
- ParameterDeclarationClauseAST *paramDeclClause = path.at(index-1)->asParameterDeclarationClause();
- QTC_ASSERT(paramDeclClause && paramDeclClause->parameter_declaration_list, return);
-
- ParameterDeclarationListAST *paramListNode = paramDeclClause->parameter_declaration_list;
- ParameterDeclarationListAST *prevParamListNode = nullptr;
- while (paramListNode) {
- if (paramDecl == paramListNode->value)
- break;
- prevParamListNode = paramListNode;
- paramListNode = paramListNode->next;
- }
-
- if (!paramListNode)
- return;
-
- if (prevParamListNode)
- result << new RearrangeParamDeclarationListOp(interface, paramListNode->value,
- prevParamListNode->value, RearrangeParamDeclarationListOp::TargetPrevious);
- if (paramListNode->next)
- result << new RearrangeParamDeclarationListOp(interface, paramListNode->value,
- paramListNode->next->value, RearrangeParamDeclarationListOp::TargetNext);
-}
-
-namespace {
-
-class ReformatPointerDeclarationOp: public CppQuickFixOperation
-{
-public:
- ReformatPointerDeclarationOp(const CppQuickFixInterface &interface, const ChangeSet change)
- : CppQuickFixOperation(interface)
- , m_change(change)
- {
- QString description;
- if (m_change.operationList().size() == 1) {
- description = Tr::tr(
- "Reformat to \"%1\"").arg(m_change.operationList().constFirst().text());
- } else { // > 1
- description = Tr::tr("Reformat Pointers or References");
- }
- setDescription(description);
- }
-
- void perform() override
- {
- CppRefactoringChanges refactoring(snapshot());
- CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath());
- currentFile->setChangeSet(m_change);
- currentFile->apply();
- }
-
-private:
- ChangeSet m_change;
-};
-
-/// Filter the results of ASTPath.
-/// The resulting list contains the supported AST types only once.
-/// For this, the results of ASTPath are iterated in reverse order.
-class ReformatPointerDeclarationASTPathResultsFilter
-{
-public:
- QList<AST*> filter(const QList<AST*> &astPathList)
- {
- QList<AST*> filtered;
-
- for (int i = astPathList.size() - 1; i >= 0; --i) {
- AST *ast = astPathList.at(i);
-
- if (!m_hasSimpleDeclaration && ast->asSimpleDeclaration()) {
- m_hasSimpleDeclaration = true;
- filtered.append(ast);
- } else if (!m_hasFunctionDefinition && ast->asFunctionDefinition()) {
- m_hasFunctionDefinition = true;
- filtered.append(ast);
- } else if (!m_hasParameterDeclaration && ast->asParameterDeclaration()) {
- m_hasParameterDeclaration = true;
- filtered.append(ast);
- } else if (!m_hasIfStatement && ast->asIfStatement()) {
- m_hasIfStatement = true;
- filtered.append(ast);
- } else if (!m_hasWhileStatement && ast->asWhileStatement()) {
- m_hasWhileStatement = true;
- filtered.append(ast);
- } else if (!m_hasForStatement && ast->asForStatement()) {
- m_hasForStatement = true;
- filtered.append(ast);
- } else if (!m_hasForeachStatement && ast->asForeachStatement()) {
- m_hasForeachStatement = true;
- filtered.append(ast);
- }
- }
-
- return filtered;
- }
-
-private:
- bool m_hasSimpleDeclaration = false;
- bool m_hasFunctionDefinition = false;
- bool m_hasParameterDeclaration = false;
- bool m_hasIfStatement = false;
- bool m_hasWhileStatement = false;
- bool m_hasForStatement = false;
- bool m_hasForeachStatement = false;
-};
-
-} // anonymous namespace
-
-void ReformatPointerDeclaration::doMatch(const CppQuickFixInterface &interface,
- QuickFixOperations &result)
-{
- const QList<AST *> &path = interface.path();
- CppRefactoringFilePtr file = interface.currentFile();
-
- Overview overview = CppCodeStyleSettings::currentProjectCodeStyleOverview();
- overview.showArgumentNames = true;
- overview.showReturnTypes = true;
-
- const QTextCursor cursor = file->cursor();
- ChangeSet change;
- PointerDeclarationFormatter formatter(file, overview,
- PointerDeclarationFormatter::RespectCursor);
-
- if (cursor.hasSelection()) {
- // This will no work always as expected since this function is only called if
- // interface-path() is not empty. If the user selects the whole document via
- // ctrl-a and there is an empty line in the end, then the cursor is not on
- // any AST and therefore no quick fix will be triggered.
- change = formatter.format(file->cppDocument()->translationUnit()->ast());
- if (!change.isEmpty())
- result << new ReformatPointerDeclarationOp(interface, change);
- } else {
- const QList<AST *> suitableASTs
- = ReformatPointerDeclarationASTPathResultsFilter().filter(path);
- for (AST *ast : suitableASTs) {
- change = formatter.format(ast);
- if (!change.isEmpty()) {
- result << new ReformatPointerDeclarationOp(interface, change);
- return;
- }
- }
- }
-}
-
-namespace {
-
-class CaseStatementCollector : public ASTVisitor
-{
-public:
- CaseStatementCollector(Document::Ptr document, const Snapshot &snapshot,
- Scope *scope)
- : ASTVisitor(document->translationUnit()),
- document(document),
- scope(scope)
- {
- typeOfExpression.init(document, snapshot);
- }
-
- QStringList operator ()(AST *ast)
- {
- values.clear();
- foundCaseStatementLevel = false;
- accept(ast);
- return values;
- }
-
- bool preVisit(AST *ast) override {
- if (CaseStatementAST *cs = ast->asCaseStatement()) {
- foundCaseStatementLevel = true;
- if (ExpressionAST *csExpression = cs->expression) {
- if (ExpressionAST *expression = csExpression->asIdExpression()) {
- QList<LookupItem> candidates = typeOfExpression(expression, document, scope);
- if (!candidates.isEmpty() && candidates.first().declaration()) {
- Symbol *decl = candidates.first().declaration();
- values << prettyPrint.prettyName(LookupContext::fullyQualifiedName(decl));
- }
- }
- }
- return true;
- } else if (foundCaseStatementLevel) {
- return false;
- }
- return true;
- }
-
- Overview prettyPrint;
- bool foundCaseStatementLevel = false;
- QStringList values;
- TypeOfExpression typeOfExpression;
- Document::Ptr document;
- Scope *scope;
-};
-
-class CompleteSwitchCaseStatementOp: public CppQuickFixOperation
-{
-public:
- CompleteSwitchCaseStatementOp(const CppQuickFixInterface &interface,
- int priority, CompoundStatementAST *compoundStatement, const QStringList &values)
- : CppQuickFixOperation(interface, priority)
- , compoundStatement(compoundStatement)
- , values(values)
- {
- setDescription(Tr::tr("Complete Switch Statement"));
- }
-
- void perform() override
- {
- CppRefactoringChanges refactoring(snapshot());
- CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath());
-
- ChangeSet changes;
- int start = currentFile->endOf(compoundStatement->lbrace_token);
- changes.insert(start, QLatin1String("\ncase ")
- + values.join(QLatin1String(":\nbreak;\ncase "))
- + QLatin1String(":\nbreak;"));
- currentFile->setChangeSet(changes);
- currentFile->apply();
- }
-
- CompoundStatementAST *compoundStatement;
- QStringList values;
-};
-
-static Enum *findEnum(const QList<LookupItem> &results, const LookupContext &ctxt)
-{
- for (const LookupItem &result : results) {
- const FullySpecifiedType fst = result.type();
-
- Type *type = result.declaration() ? result.declaration()->type().type()
- : fst.type();
-
- if (!type)
- continue;
- if (Enum *e = type->asEnumType())
- return e;
- if (const NamedType *namedType = type->asNamedType()) {
- if (ClassOrNamespace *con = ctxt.lookupType(namedType->name(), result.scope())) {
- QList<Enum *> enums = con->unscopedEnums();
- const QList<Symbol *> symbols = con->symbols();
- for (Symbol * const s : symbols) {
- if (const auto e = s->asEnum())
- enums << e;
- }
- const Name *referenceName = namedType->name();
- if (const QualifiedNameId *qualifiedName = referenceName->asQualifiedNameId())
- referenceName = qualifiedName->name();
- for (Enum *e : std::as_const(enums)) {
- if (const Name *candidateName = e->name()) {
- if (candidateName->match(referenceName))
- return e;
- }
- }
- }
- }
- }
-
- return nullptr;
-}
-
-Enum *conditionEnum(const CppQuickFixInterface &interface, SwitchStatementAST *statement)
-{
- Block *block = statement->symbol;
- Scope *scope = interface.semanticInfo().doc->scopeAt(block->line(), block->column());
- TypeOfExpression typeOfExpression;
- typeOfExpression.setExpandTemplates(true);
- typeOfExpression.init(interface.semanticInfo().doc, interface.snapshot());
- const QList<LookupItem> results = typeOfExpression(statement->condition,
- interface.semanticInfo().doc,
- scope);
-
- return findEnum(results, typeOfExpression.context());
-}
-
-} // anonymous namespace
-
-void CompleteSwitchCaseStatement::doMatch(const CppQuickFixInterface &interface,
- QuickFixOperations &result)
-{
- const QList<AST *> &path = interface.path();
-
- if (path.isEmpty())
- return;
-
- // look for switch statement
- for (int depth = path.size() - 1; depth >= 0; --depth) {
- AST *ast = path.at(depth);
- SwitchStatementAST *switchStatement = ast->asSwitchStatement();
- if (switchStatement) {
- if (!switchStatement->statement || !switchStatement->symbol)
- return;
- CompoundStatementAST *compoundStatement = switchStatement->statement->asCompoundStatement();
- if (!compoundStatement) // we ignore pathologic case "switch (t) case A: ;"
- return;
- // look if the condition's type is an enum
- if (Enum *e = conditionEnum(interface, switchStatement)) {
- // check the possible enum values
- QStringList values;
- Overview prettyPrint;
- for (int i = 0; i < e->memberCount(); ++i) {
- if (Declaration *decl = e->memberAt(i)->asDeclaration())
- values << prettyPrint.prettyName(LookupContext::fullyQualifiedName(decl));
- }
- // Get the used values
- Block *block = switchStatement->symbol;
- CaseStatementCollector caseValues(interface.semanticInfo().doc, interface.snapshot(),
- interface.semanticInfo().doc->scopeAt(block->line(), block->column()));
- const QStringList usedValues = caseValues(switchStatement);
- // save the values that would be added
- for (const QString &usedValue : usedValues)
- values.removeAll(usedValue);
- if (!values.isEmpty())
- result << new CompleteSwitchCaseStatementOp(interface, depth,
- compoundStatement, values);
- return;
- }
-
- return;
- }
- }
-}
-
-namespace {
-
-class InsertDeclOperation: public CppQuickFixOperation
-{
-public:
- InsertDeclOperation(const CppQuickFixInterface &interface,
- const FilePath &targetFilePath, const Class *targetSymbol,
- InsertionPointLocator::AccessSpec xsSpec, const QString &decl, int priority)
- : CppQuickFixOperation(interface, priority)
- , m_targetFilePath(targetFilePath)
- , m_targetSymbol(targetSymbol)
- , m_xsSpec(xsSpec)
- , m_decl(decl)
- {
- setDescription(Tr::tr("Add %1 Declaration")
- .arg(InsertionPointLocator::accessSpecToString(xsSpec)));
- }
-
- void perform() override
- {
- CppRefactoringChanges refactoring(snapshot());
-
- InsertionPointLocator locator(refactoring);
- const InsertionLocation loc = locator.methodDeclarationInClass(
- m_targetFilePath, m_targetSymbol, m_xsSpec);
- QTC_ASSERT(loc.isValid(), return);
-
- CppRefactoringFilePtr targetFile = refactoring.cppFile(m_targetFilePath);
- int targetPosition = targetFile->position(loc.line(), loc.column());
-
- ChangeSet target;
- target.insert(targetPosition, loc.prefix() + m_decl);
- targetFile->setChangeSet(target);
- targetFile->setOpenEditor(true, targetPosition);
- targetFile->apply();
- }
-
- static QString generateDeclaration(const Function *function);
-
-private:
- FilePath m_targetFilePath;
- const Class *m_targetSymbol;
- InsertionPointLocator::AccessSpec m_xsSpec;
- QString m_decl;
-};
-
-class DeclOperationFactory
-{
-public:
- DeclOperationFactory(const CppQuickFixInterface &interface, const FilePath &filePath,
- const Class *matchingClass, const QString &decl)
- : m_interface(interface)
- , m_filePath(filePath)
- , m_matchingClass(matchingClass)
- , m_decl(decl)
- {}
-
- QuickFixOperation *operator()(InsertionPointLocator::AccessSpec xsSpec, int priority)
- {
- return new InsertDeclOperation(m_interface, m_filePath, m_matchingClass, xsSpec, m_decl, priority);
- }
-
-private:
- const CppQuickFixInterface &m_interface;
- const FilePath &m_filePath;
- const Class *m_matchingClass;
- const QString &m_decl;
-};
-
-} // anonymous namespace
-
-void InsertDeclFromDef::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result)
-{
- const QList<AST *> &path = interface.path();
- CppRefactoringFilePtr file = interface.currentFile();
-
- FunctionDefinitionAST *funDef = nullptr;
- int idx = 0;
- for (; idx < path.size(); ++idx) {
- AST *node = path.at(idx);
- if (idx > 1) {
- if (DeclaratorIdAST *declId = node->asDeclaratorId()) {
- if (file->isCursorOn(declId)) {
- if (FunctionDefinitionAST *candidate = path.at(idx - 2)->asFunctionDefinition()) {
- funDef = candidate;
- break;
- }
- }
- }
- }
-
- if (node->asClassSpecifier())
- return;
- }
-
- if (!funDef || !funDef->symbol)
- return;
-
- Function *fun = funDef->symbol;
- if (Class *matchingClass = isMemberFunction(interface.context(), fun)) {
- const QualifiedNameId *qName = fun->name()->asQualifiedNameId();
- for (Symbol *symbol = matchingClass->find(qName->identifier());
- symbol; symbol = symbol->next()) {
- Symbol *s = symbol;
- if (fun->enclosingScope()->asTemplate()) {
- if (const Template *templ = s->type()->asTemplateType()) {
- if (Symbol *decl = templ->declaration()) {
- if (decl->type()->asFunctionType())
- s = decl;
- }
- }
- }
- if (!s->name()
- || !qName->identifier()->match(s->identifier())
- || !s->type()->asFunctionType())
- continue;
-
- if (s->type().match(fun->type())) {
- // Declaration exists.
- return;
- }
- }
- const FilePath fileName = matchingClass->filePath();
- const QString decl = InsertDeclOperation::generateDeclaration(fun);
-
- // Add several possible insertion locations for declaration
- DeclOperationFactory operation(interface, fileName, matchingClass, decl);
-
- result << operation(InsertionPointLocator::Public, 5)
- << operation(InsertionPointLocator::PublicSlot, 4)
- << operation(InsertionPointLocator::Protected, 3)
- << operation(InsertionPointLocator::ProtectedSlot, 2)
- << operation(InsertionPointLocator::Private, 1)
- << operation(InsertionPointLocator::PrivateSlot, 0);
- }
-}
-
-QString InsertDeclOperation::generateDeclaration(const Function *function)
-{
- Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview();
- oo.showFunctionSignatures = true;
- oo.showReturnTypes = true;
- oo.showArgumentNames = true;
- oo.showEnclosingTemplate = true;
-
- QString decl;
- decl += oo.prettyType(function->type(), function->unqualifiedName());
- decl += QLatin1String(";\n");
-
- return decl;
-}
-
-namespace {
-
-class InsertDefOperation: public CppQuickFixOperation
-{
-public:
- // Make sure that either loc is valid or targetFileName is not empty.
- InsertDefOperation(const CppQuickFixInterface &interface,
- Declaration *decl, DeclaratorAST *declAST, const InsertionLocation &loc,
- const DefPos defpos, const FilePath &targetFileName = {},
- bool freeFunction = false)
- : CppQuickFixOperation(interface, 0)
- , m_decl(decl)
- , m_declAST(declAST)
- , m_loc(loc)
- , m_defpos(defpos)
- , m_targetFilePath(targetFileName)
- {
- if (m_defpos == DefPosImplementationFile) {
- const FilePath declFile = decl->filePath();
- const FilePath targetFile = m_loc.isValid() ? m_loc.filePath() : m_targetFilePath;
- const FilePath resolved = targetFile.relativePathFrom(declFile.parentDir());
- setPriority(2);
- setDescription(Tr::tr("Add Definition in %1").arg(resolved.displayName()));
- } else if (freeFunction) {
- setDescription(Tr::tr("Add Definition Here"));
- } else if (m_defpos == DefPosInsideClass) {
- setDescription(Tr::tr("Add Definition Inside Class"));
- } else if (m_defpos == DefPosOutsideClass) {
- setPriority(1);
- setDescription(Tr::tr("Add Definition Outside Class"));
- }
- }
-
- static void insertDefinition(
- const CppQuickFixOperation *op,
- InsertionLocation loc,
- DefPos defPos,
- DeclaratorAST *declAST,
- Declaration *decl,
- const FilePath &targetFilePath,
- ChangeSet *changeSet = nullptr)
- {
- CppRefactoringChanges refactoring(op->snapshot());
- if (!loc.isValid())
- loc = insertLocationForMethodDefinition(decl, true, NamespaceHandling::Ignore,
- refactoring, targetFilePath);
- QTC_ASSERT(loc.isValid(), return);
-
- CppRefactoringFilePtr targetFile = refactoring.cppFile(loc.filePath());
- Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview();
- oo.showFunctionSignatures = true;
- oo.showReturnTypes = true;
- oo.showArgumentNames = true;
- oo.showEnclosingTemplate = true;
-
- // What we really want is to show template parameters for the class, but not for the
- // function, but we cannot express that. This is an approximation that will work
- // as long as either the surrounding class or the function is not a template.
- oo.showTemplateParameters = decl->enclosingClass()
- && decl->enclosingClass()->enclosingTemplate();
-
- if (defPos == DefPosInsideClass) {
- const int targetPos = targetFile->position(loc.line(), loc.column());
- ChangeSet localChangeSet;
- ChangeSet * const target = changeSet ? changeSet : &localChangeSet;
- target->replace(targetPos - 1, targetPos, QLatin1String("\n {\n\n}")); // replace ';'
-
- if (!changeSet) {
- targetFile->setChangeSet(*target);
- targetFile->setOpenEditor(true, targetPos);
- targetFile->apply();
-
- // Move cursor inside definition
- QTextCursor c = targetFile->cursor();
- c.setPosition(targetPos);
- c.movePosition(QTextCursor::Down);
- c.movePosition(QTextCursor::EndOfLine);
- op->editor()->setTextCursor(c);
- }
- } else {
- // make target lookup context
- Document::Ptr targetDoc = targetFile->cppDocument();
- Scope *targetScope = targetDoc->scopeAt(loc.line(), loc.column());
-
- // Correct scope in case of a function try-block. See QTCREATORBUG-14661.
- if (targetScope && targetScope->asBlock()) {
- if (Class * const enclosingClass = targetScope->enclosingClass())
- targetScope = enclosingClass;
- else
- targetScope = targetScope->enclosingNamespace();
- }
-
- LookupContext targetContext(targetDoc, op->snapshot());
- ClassOrNamespace *targetCoN = targetContext.lookupType(targetScope);
- if (!targetCoN)
- targetCoN = targetContext.globalNamespace();
-
- // setup rewriting to get minimally qualified names
- SubstitutionEnvironment env;
- env.setContext(op->context());
- env.switchScope(decl->enclosingScope());
- UseMinimalNames q(targetCoN);
- env.enter(&q);
- Control *control = op->context().bindings()->control().get();
-
- // rewrite the function type
- const FullySpecifiedType tn = rewriteType(decl->type(), &env, control);
-
- // rewrite the function name
- if (nameIncludesOperatorName(decl->name())) {
- CppRefactoringFilePtr file = refactoring.cppFile(op->filePath());
- const QString operatorNameText = file->textOf(declAST->core_declarator);
- oo.includeWhiteSpaceInOperatorName = operatorNameText.contains(QLatin1Char(' '));
- }
- const QString name = oo.prettyName(LookupContext::minimalName(decl, targetCoN,
- control));
-
- const QString inlinePref = inlinePrefix(targetFilePath, [defPos] {
- return defPos == DefPosOutsideClass;
- });
-
- const QString prettyType = oo.prettyType(tn, name);
-
- QString input = prettyType;
- int index = 0;
- while (input.startsWith("template")) {
- QRegularExpression templateRegex("template\\s*<[^>]*>");
- QRegularExpressionMatch match = templateRegex.match(input);
- if (match.hasMatch()) {
- index += match.captured().size() + 1;
- input = input.mid(match.captured().size() + 1);
- }
- }
-
- QString defText = prettyType;
- defText.insert(index, inlinePref);
- defText += QLatin1String("\n{\n\n}");
-
- ChangeSet localChangeSet;
- ChangeSet * const target = changeSet ? changeSet : &localChangeSet;
- const int targetPos = targetFile->position(loc.line(), loc.column());
- target->insert(targetPos, loc.prefix() + defText + loc.suffix());
-
- if (!changeSet) {
- targetFile->setChangeSet(*target);
- targetFile->setOpenEditor(true, targetPos);
- targetFile->apply();
-
- // Move cursor inside definition
- QTextCursor c = targetFile->cursor();
- c.setPosition(targetPos);
- c.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor,
- loc.prefix().count(QLatin1String("\n")) + 2);
- c.movePosition(QTextCursor::EndOfLine);
- if (defPos == DefPosImplementationFile) {
- if (targetFile->editor())
- targetFile->editor()->setTextCursor(c);
- } else {
- op->editor()->setTextCursor(c);
- }
- }
- }
- }
-
-private:
- void perform() override
- {
- insertDefinition(this, m_loc, m_defpos, m_declAST, m_decl, m_targetFilePath);
- }
-
- Declaration *m_decl;
- DeclaratorAST *m_declAST;
- InsertionLocation m_loc;
- const DefPos m_defpos;
- const FilePath m_targetFilePath;
-};
-
-} // anonymous namespace
-
-void InsertDefFromDecl::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result)
-{
- const QList<AST *> &path = interface.path();
-
- int idx = path.size() - 1;
- for (; idx >= 0; --idx) {
- AST *node = path.at(idx);
- if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) {
- if (idx > 0 && path.at(idx - 1)->asStatement())
- return;
- if (simpleDecl->symbols && !simpleDecl->symbols->next) {
- if (Symbol *symbol = simpleDecl->symbols->value) {
- if (Declaration *decl = symbol->asDeclaration()) {
- if (Function *func = decl->type()->asFunctionType()) {
- if (func->isSignal() || func->isPureVirtual() || func->isFriend())
- return;
-
- // Check if there is already a definition
- SymbolFinder symbolFinder;
- if (symbolFinder.findMatchingDefinition(decl, interface.snapshot(),
- true)) {
- return;
- }
-
- // Insert Position: Implementation File
- DeclaratorAST *declAST = simpleDecl->declarator_list->value;
- InsertDefOperation *op = nullptr;
- ProjectFile::Kind kind = ProjectFile::classify(interface.filePath().toString());
- const bool isHeaderFile = ProjectFile::isHeader(kind);
- if (isHeaderFile) {
- CppRefactoringChanges refactoring(interface.snapshot());
- InsertionPointLocator locator(refactoring);
- // find appropriate implementation file, but do not use this
- // location, because insertLocationForMethodDefinition() should
- // be used in perform() to get consistent insert positions.
- for (const InsertionLocation &location :
- locator.methodDefinition(decl, false, {})) {
- if (!location.isValid())
- continue;
-
- const FilePath filePath = location.filePath();
- if (ProjectFile::isHeader(ProjectFile::classify(filePath.path()))) {
- const FilePath source = correspondingHeaderOrSource(filePath);
- if (!source.isEmpty()) {
- op = new InsertDefOperation(interface, decl, declAST,
- InsertionLocation(),
- DefPosImplementationFile,
- source);
- }
- } else {
- op = new InsertDefOperation(interface, decl, declAST,
- InsertionLocation(),
- DefPosImplementationFile,
- filePath);
- }
-
- if (op)
- result << op;
- break;
- }
- }
-
- // Determine if we are dealing with a free function
- const bool isFreeFunction = func->enclosingClass() == nullptr;
-
- // Insert Position: Outside Class
- if (!isFreeFunction || m_defPosOutsideClass) {
- result << new InsertDefOperation(interface, decl, declAST,
- InsertionLocation(),
- DefPosOutsideClass,
- interface.filePath());
- }
-
- // Insert Position: Inside Class
- // Determine insert location direct after the declaration.
- int line, column;
- const CppRefactoringFilePtr file = interface.currentFile();
- file->lineAndColumn(file->endOf(simpleDecl), &line, &column);
- const InsertionLocation loc
- = InsertionLocation(interface.filePath(), QString(),
- QString(), line, column);
- result << new InsertDefOperation(interface, decl, declAST, loc,
- DefPosInsideClass, FilePath(),
- isFreeFunction);
-
- return;
- }
- }
- }
- }
- break;
- }
- }
-}
-
-class InsertMemberFromInitializationOp : public CppQuickFixOperation
-{
-public:
- InsertMemberFromInitializationOp(
- const CppQuickFixInterface &interface,
- const Class *theClass,
- const NameAST *memberName,
- const TypeOrExpr &typeOrExpr,
- const CallAST *call,
- InsertionPointLocator::AccessSpec accessSpec,
- bool makeStatic,
- bool makeConst)
- : CppQuickFixOperation(interface),
- m_class(theClass), m_memberName(memberName), m_typeOrExpr(typeOrExpr), m_call(call),
- m_accessSpec(accessSpec), m_makeStatic(makeStatic), m_makeConst(makeConst)
- {
- if (call)
- setDescription(Tr::tr("Add Member Function \"%1\"").arg(nameString(memberName)));
- else
- setDescription(Tr::tr("Add Class Member \"%1\"").arg(nameString(memberName)));
- }
-
-private:
- void perform() override
- {
- QString decl = declFromExpr(m_typeOrExpr, m_call, m_memberName, snapshot(), context(),
- currentFile(), m_makeConst);
- if (decl.isEmpty())
- return;
- if (m_makeStatic)
- decl.prepend("static ");
-
- const CppRefactoringChanges refactoring(snapshot());
- const InsertionPointLocator locator(refactoring);
- const FilePath filePath = FilePath::fromUtf8(m_class->fileName());
- const InsertionLocation loc = locator.methodDeclarationInClass(
- filePath, m_class, m_accessSpec);
- QTC_ASSERT(loc.isValid(), return);
-
- CppRefactoringFilePtr targetFile = refactoring.cppFile(filePath);
- const int targetPosition = targetFile->position(loc.line(), loc.column());
- ChangeSet target;
- target.insert(targetPosition, loc.prefix() + decl + ";\n");
- targetFile->setChangeSet(target);
- targetFile->apply();
- }
-
- const Class * const m_class;
- const NameAST * const m_memberName;
- const TypeOrExpr m_typeOrExpr;
- const CallAST * m_call;
- const InsertionPointLocator::AccessSpec m_accessSpec;
- const bool m_makeStatic;
- const bool m_makeConst;
-};
-
-void AddDeclarationForUndeclaredIdentifier::doMatch(const CppQuickFixInterface &interface,
- QuickFixOperations &result)
-{
- // Are we on a name?
- const QList<AST *> &path = interface.path();
- if (path.isEmpty())
- return;
- if (!path.last()->asSimpleName())
- return;
-
- // Special case: Member initializer.
- if (!checkForMemberInitializer(interface, result))
- return;
-
- // Are we inside a function?
- const FunctionDefinitionAST *func = nullptr;
- for (auto it = path.rbegin(); !func && it != path.rend(); ++it)
- func = (*it)->asFunctionDefinition();
- if (!func)
- return;
-
- // Is this name declared somewhere already?
- const CursorInEditor cursorInEditor(interface.cursor(), interface.filePath(),
- interface.editor(), interface.editor()->textDocument());
- const auto followSymbolFallback = [&](const Link &link) {
- if (!link.hasValidTarget())
- collectOperations(interface, result);
- };
- CppModelManager::followSymbol(cursorInEditor, followSymbolFallback, false, false,
- FollowSymbolMode::Exact,
- CppModelManager::Backend::Builtin);
-}
-
-void AddDeclarationForUndeclaredIdentifier::collectOperations(
- const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result)
-{
- const QList<AST *> &path = interface.path();
- const CppRefactoringFilePtr &file = interface.currentFile();
- for (int index = path.size() - 1; index != -1; --index) {
- if (const auto call = path.at(index)->asCall())
- return handleCall(call, interface, result);
-
- // We only trigger if the identifier appears on the left-hand side of an
- // assignment expression.
- const auto binExpr = path.at(index)->asBinaryExpression();
- if (!binExpr)
- continue;
- if (!binExpr->left_expression || !binExpr->right_expression
- || file->tokenAt(binExpr->binary_op_token).kind() != T_EQUAL
- || !interface.isCursorOn(binExpr->left_expression)) {
- return;
- }
-
- // In the case of "a.|b = c", find out the type of a, locate the class declaration
- // and add a member b there.
- if (const auto memberAccess = binExpr->left_expression->asMemberAccess()) {
- if (interface.isCursorOn(memberAccess->member_name)
- && memberAccess->member_name == path.last()) {
- maybeAddMember(interface, file->scopeAt(memberAccess->firstToken()),
- file->textOf(memberAccess->base_expression).toUtf8(),
- binExpr->right_expression, nullptr, result);
- }
- return;
- }
-
- const auto idExpr = binExpr->left_expression->asIdExpression();
- if (!idExpr || !idExpr->name)
- return;
-
- // In the case of "A::|b = c", add a static member b to A.
- if (const auto qualName = idExpr->name->asQualifiedName()) {
- return maybeAddStaticMember(interface, qualName, binExpr->right_expression, nullptr,
- result);
- }
-
- // For an unqualified access, offer a local declaration and, if we are
- // in a member function, a member declaration.
- if (const auto simpleName = idExpr->name->asSimpleName()) {
- if (!m_membersOnly)
- result << new AddLocalDeclarationOp(interface, index, binExpr, simpleName);
- maybeAddMember(interface, file->scopeAt(idExpr->firstToken()), "this",
- binExpr->right_expression, nullptr, result);
- return;
- }
- }
-}
-
-void AddDeclarationForUndeclaredIdentifier::handleCall(
- const CallAST *call, const CppQuickFixInterface &interface,
- TextEditor::QuickFixOperations &result)
-{
- if (!call->base_expression)
- return;
-
- // In order to find out the return type, we need to check the context of the call.
- // If it is a statement expression, the type is void, if it's a binary expression,
- // we assume the type of the other side of the expression, if it's a return statement,
- // we use the return type of the surrounding function, and if it's a declaration,
- // we use the type of the variable. Other cases are not supported.
- const QList<AST *> &path = interface.path();
- const CppRefactoringFilePtr &file = interface.currentFile();
- TypeOrExpr returnTypeOrExpr;
- for (auto it = path.rbegin(); it != path.rend(); ++it) {
- if ((*it)->asCompoundStatement())
- return;
- if ((*it)->asExpressionStatement()) {
- returnTypeOrExpr = FullySpecifiedType(new VoidType);
- break;
- }
- if (const auto binExpr = (*it)->asBinaryExpression()) {
- returnTypeOrExpr = interface.isCursorOn(binExpr->left_expression)
- ? binExpr->right_expression : binExpr->left_expression;
- break;
- }
- if (const auto returnExpr = (*it)->asReturnStatement()) {
- for (auto it2 = std::next(it); it2 != path.rend(); ++it2) {
- if (const auto func = (*it2)->asFunctionDefinition()) {
- if (!func->symbol)
- return;
- returnTypeOrExpr = func->symbol->returnType();
- break;
- }
- }
- break;
- }
- if (const auto declarator = (*it)->asDeclarator()) {
- if (!interface.isCursorOn(declarator->initializer))
- return;
- const auto decl = (*std::next(it))->asSimpleDeclaration();
- if (!decl || !decl->symbols)
- return;
- if (!decl->symbols->value->type().isValid())
- return;
- returnTypeOrExpr = decl->symbols->value->type();
- break;
- }
- }
-
- if (std::holds_alternative<const ExpressionAST *>(returnTypeOrExpr)
- && !std::get<const ExpressionAST *>(returnTypeOrExpr)) {
- return;
- }
-
- // a.f()
- if (const auto memberAccess = call->base_expression->asMemberAccess()) {
- if (!interface.isCursorOn(memberAccess->member_name))
- return;
- maybeAddMember(
- interface, file->scopeAt(call->firstToken()),
- file->textOf(memberAccess->base_expression).toUtf8(), returnTypeOrExpr, call, result);
- }
-
- const auto idExpr = call->base_expression->asIdExpression();
- if (!idExpr || !idExpr->name)
- return;
-
- // A::f()
- if (const auto qualName = idExpr->name->asQualifiedName())
- return maybeAddStaticMember(interface, qualName, returnTypeOrExpr, call, result);
-
- // f()
- if (const auto simpleName = idExpr->name->asSimpleName()) {
- maybeAddMember(interface, file->scopeAt(idExpr->firstToken()), "this",
- returnTypeOrExpr, call, result);
- }
-}
-
-bool AddDeclarationForUndeclaredIdentifier::checkForMemberInitializer(
- const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result)
-{
- const QList<AST *> &path = interface.path();
- const int size = path.size();
- if (size < 4)
- return true;
- const MemInitializerAST * const memInitializer = path.at(size - 2)->asMemInitializer();
- if (!memInitializer)
- return true;
- if (!path.at(size - 3)->asCtorInitializer())
- return true;
- const FunctionDefinitionAST * ctor = path.at(size - 4)->asFunctionDefinition();
- if (!ctor)
- return false;
-
- // Now find the class.
- const Class *theClass = nullptr;
- if (size > 4) {
- const ClassSpecifierAST * const classSpec = path.at(size - 5)->asClassSpecifier();
- if (classSpec) // Inline constructor. We get the class directly.
- theClass = classSpec->symbol;
- }
- if (!theClass) {
- // Out-of-line constructor. We need to find the class.
- SymbolFinder finder;
- const QList<Declaration *> matches = finder.findMatchingDeclaration(
- LookupContext(interface.currentFile()->cppDocument(), interface.snapshot()),
- ctor->symbol);
- if (!matches.isEmpty())
- theClass = matches.first()->enclosingClass();
- }
-
- if (!theClass)
- return false;
-
- const SimpleNameAST * const name = path.at(size - 1)->asSimpleName();
- QTC_ASSERT(name, return false);
-
- // Check whether the member exists already.
- if (theClass->find(interface.currentFile()->cppDocument()->translationUnit()->identifier(
- name->identifier_token))) {
- return false;
- }
-
- result << new InsertMemberFromInitializationOp(
- interface, theClass, memInitializer->name->asSimpleName(), memInitializer->expression,
- nullptr, InsertionPointLocator::Private, false, false);
- return false;
-}
-
-void AddDeclarationForUndeclaredIdentifier::maybeAddMember(
- const CppQuickFixInterface &interface, Scope *scope, const QByteArray &classTypeExpr,
- const TypeOrExpr &typeOrExpr, const CallAST *call, TextEditor::QuickFixOperations &result)
-{
- const QList<AST *> &path = interface.path();
-
- TypeOfExpression typeOfExpression;
- typeOfExpression.init(interface.semanticInfo().doc, interface.snapshot(),
- interface.context().bindings());
- const QList<LookupItem> lhsTypes = typeOfExpression(
- classTypeExpr, scope,
- TypeOfExpression::Preprocess);
- if (lhsTypes.isEmpty())
- return;
-
- const Type *type = lhsTypes.first().type().type();
- if (!type)
- return;
- if (type->asPointerType()) {
- type = type->asPointerType()->elementType().type();
- if (!type)
- return;
- }
- const auto namedType = type->asNamedType();
- if (!namedType)
- return;
- const ClassOrNamespace * const classOrNamespace
- = interface.context().lookupType(namedType->name(), scope);
- if (!classOrNamespace || !classOrNamespace->rootClass())
- return;
-
- const Class * const theClass = classOrNamespace->rootClass();
- bool needsStatic = lhsTypes.first().type().isStatic();
-
- // If the base expression refers to the same class that the member function is in,
- // then we want to insert a private member, otherwise a public one.
- const FunctionDefinitionAST *func = nullptr;
- for (auto it = path.rbegin(); !func && it != path.rend(); ++it)
- func = (*it)->asFunctionDefinition();
- QTC_ASSERT(func, return);
- InsertionPointLocator::AccessSpec accessSpec = InsertionPointLocator::Public;
- for (int i = 0; i < theClass->memberCount(); ++i) {
- if (theClass->memberAt(i) == func->symbol) {
- accessSpec = InsertionPointLocator::Private;
- needsStatic = func->symbol->isStatic();
- break;
- }
- }
- if (accessSpec == InsertionPointLocator::Public) {
- QList<Declaration *> decls;
- QList<Declaration *> dummy;
- SymbolFinder().findMatchingDeclaration(interface.context(), func->symbol, &decls,
- &dummy, &dummy);
- for (const Declaration * const decl : std::as_const(decls)) {
- for (int i = 0; i < theClass->memberCount(); ++i) {
- if (theClass->memberAt(i) == decl) {
- accessSpec = InsertionPointLocator::Private;
- needsStatic = decl->isStatic();
- break;
- }
- }
- if (accessSpec == InsertionPointLocator::Private)
- break;
- }
- }
- result << new InsertMemberFromInitializationOp(interface, theClass, path.last()->asName(),
- typeOrExpr, call, accessSpec, needsStatic,
- func->symbol->isConst());
-}
-
-void AddDeclarationForUndeclaredIdentifier::maybeAddStaticMember(
- const CppQuickFixInterface &interface, const QualifiedNameAST *qualName,
- const TypeOrExpr &typeOrExpr, const CallAST *call, TextEditor::QuickFixOperations &result)
-{
- const QList<AST *> &path = interface.path();
-
- if (!interface.isCursorOn(qualName->unqualified_name))
- return;
- if (qualName->unqualified_name != path.last())
- return;
- if (!qualName->nested_name_specifier_list)
- return;
-
- const NameAST * const topLevelName
- = qualName->nested_name_specifier_list->value->class_or_namespace_name;
- if (!topLevelName)
- return;
- ClassOrNamespace * const classOrNamespace = interface.context().lookupType(
- topLevelName->name, interface.currentFile()->scopeAt(qualName->firstToken()));
- if (!classOrNamespace)
- return;
- QList<const Name *> otherNames;
- for (auto it = qualName->nested_name_specifier_list->next; it; it = it->next) {
- if (!it->value || !it->value->class_or_namespace_name)
- return;
- otherNames << it->value->class_or_namespace_name->name;
- }
-
- const Class *theClass = nullptr;
- if (!otherNames.isEmpty()) {
- const Symbol * const symbol = classOrNamespace->lookupInScope(otherNames);
- if (!symbol)
- return;
- theClass = symbol->asClass();
- } else {
- theClass = classOrNamespace->rootClass();
- }
- if (theClass) {
- result << new InsertMemberFromInitializationOp(
- interface, theClass, path.last()->asName(), typeOrExpr, call,
- InsertionPointLocator::Public, true, false);
- }
-}
-
-class MemberFunctionImplSetting
-{
-public:
- Symbol *func = nullptr;
- DefPos defPos = DefPosImplementationFile;
-};
-using MemberFunctionImplSettings = QList<MemberFunctionImplSetting>;
-
-class AddImplementationsDialog : public QDialog
-{
-public:
- AddImplementationsDialog(const QList<Symbol *> &candidates, const FilePath &implFile)
- : QDialog(Core::ICore::dialogParent()), m_candidates(candidates)
- {
- setWindowTitle(Tr::tr("Member Function Implementations"));
-
- const auto defaultImplTargetComboBox = new QComboBox;
- QStringList implTargetStrings{Tr::tr("None"), Tr::tr("Inline"), Tr::tr("Outside Class")};
- if (!implFile.isEmpty())
- implTargetStrings.append(implFile.fileName());
- defaultImplTargetComboBox->insertItems(0, implTargetStrings);
- connect(defaultImplTargetComboBox, &QComboBox::currentIndexChanged, this,
- [this](int index) {
- for (int i = 0; i < m_implTargetBoxes.size(); ++i) {
- if (!m_candidates.at(i)->type()->asFunctionType()->isPureVirtual())
- static_cast<QComboBox *>(m_implTargetBoxes.at(i))->setCurrentIndex(index);
- }
- });
- const auto defaultImplTargetLayout = new QHBoxLayout;
- defaultImplTargetLayout->addWidget(new QLabel(Tr::tr("Default implementation location:")));
- defaultImplTargetLayout->addWidget(defaultImplTargetComboBox);
-
- const auto candidatesLayout = new QGridLayout;
- Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview();
- oo.showFunctionSignatures = true;
- oo.showReturnTypes = true;
- for (int i = 0; i < m_candidates.size(); ++i) {
- const Function * const func = m_candidates.at(i)->type()->asFunctionType();
- QTC_ASSERT(func, continue);
- const auto implTargetComboBox = new QComboBox;
- m_implTargetBoxes.append(implTargetComboBox);
- implTargetComboBox->insertItems(0, implTargetStrings);
- if (func->isPureVirtual())
- implTargetComboBox->setCurrentIndex(0);
- candidatesLayout->addWidget(new QLabel(oo.prettyType(func->type(), func->name())),
- i, 0);
- candidatesLayout->addWidget(implTargetComboBox, i, 1);
- }
-
- const auto buttonBox
- = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
- connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
- connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
-
- defaultImplTargetComboBox->setCurrentIndex(implTargetStrings.size() - 1);
- const auto mainLayout = new QVBoxLayout(this);
- mainLayout->addLayout(defaultImplTargetLayout);
- mainLayout->addWidget(Layouting::createHr(this));
- mainLayout->addLayout(candidatesLayout);
- mainLayout->addWidget(buttonBox);
- }
-
- MemberFunctionImplSettings settings() const
- {
- QTC_ASSERT(m_candidates.size() == m_implTargetBoxes.size(), return {});
- MemberFunctionImplSettings settings;
- for (int i = 0; i < m_candidates.size(); ++i) {
- MemberFunctionImplSetting setting;
- const int index = m_implTargetBoxes.at(i)->currentIndex();
- const bool addImplementation = index != 0;
- if (!addImplementation)
- continue;
- setting.func = m_candidates.at(i);
- setting.defPos = static_cast<DefPos>(index - 1);
- settings << setting;
- }
- return settings;
- }
-
-private:
- const QList<Symbol *> m_candidates;
- QList<QComboBox *> m_implTargetBoxes;
-};
-
-class InsertDefsOperation: public CppQuickFixOperation
-{
-public:
- InsertDefsOperation(const CppQuickFixInterface &interface)
- : CppQuickFixOperation(interface)
- {
- setDescription(Tr::tr("Create Implementations for Member Functions"));
-
- m_classAST = astForClassOperations(interface);
- if (!m_classAST)
- return;
- const Class * const theClass = m_classAST->symbol;
- if (!theClass)
- return;
-
- // Collect all member functions.
- for (auto it = theClass->memberBegin(); it != theClass->memberEnd(); ++it) {
- Symbol * const s = *it;
- if (!s->identifier() || !s->type() || !s->asDeclaration() || s->asFunction())
- continue;
- Function * const func = s->type()->asFunctionType();
- if (!func || func->isSignal() || func->isFriend())
- continue;
- Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview();
- oo.showFunctionSignatures = true;
- if (magicQObjectFunctions().contains(oo.prettyName(func->name())))
- continue;
- m_declarations << s;
- }
- }
-
- bool isApplicable() const { return !m_declarations.isEmpty(); }
- void setMode(InsertDefsFromDecls::Mode mode) { m_mode = mode; }
-
-private:
- void perform() override
- {
- QList<Symbol *> unimplemented;
- SymbolFinder symbolFinder;
- for (Symbol * const s : std::as_const(m_declarations)) {
- if (!symbolFinder.findMatchingDefinition(s, snapshot()))
- unimplemented << s;
- }
- if (unimplemented.isEmpty())
- return;
-
- CppRefactoringChanges refactoring(snapshot());
- const bool isHeaderFile = ProjectFile::isHeader(ProjectFile::classify(filePath().toString()));
- FilePath cppFile; // Only set if the class is defined in a header file.
- if (isHeaderFile) {
- InsertionPointLocator locator(refactoring);
- for (const InsertionLocation &location
- : locator.methodDefinition(unimplemented.first(), false, {})) {
- if (!location.isValid())
- continue;
- const FilePath filePath = location.filePath();
- if (ProjectFile::isHeader(ProjectFile::classify(filePath.path()))) {
- const FilePath source = correspondingHeaderOrSource(filePath);
- if (!source.isEmpty())
- cppFile = source;
- } else {
- cppFile = filePath;
- }
- break;
- }
- }
-
- MemberFunctionImplSettings settings;
- switch (m_mode) {
- case InsertDefsFromDecls::Mode::User: {
- AddImplementationsDialog dlg(unimplemented, cppFile);
- if (dlg.exec() == QDialog::Accepted)
- settings = dlg.settings();
- break;
- }
- case InsertDefsFromDecls::Mode::Impl: {
- for (Symbol * const func : std::as_const(unimplemented)) {
- MemberFunctionImplSetting setting;
- setting.func = func;
- setting.defPos = DefPosImplementationFile;
- settings << setting;
- }
- break;
- }
- case InsertDefsFromDecls::Mode::Alternating: {
- int defPos = DefPosImplementationFile;
- const auto incDefPos = [&defPos] {
- defPos = (defPos + 1) % (DefPosImplementationFile + 2);
- };
- for (Symbol * const func : std::as_const(unimplemented)) {
- incDefPos();
- if (defPos > DefPosImplementationFile)
- continue;
- MemberFunctionImplSetting setting;
- setting.func = func;
- setting.defPos = static_cast<DefPos>(defPos);
- settings << setting;
- }
- break;
- }
- case InsertDefsFromDecls::Mode::Off:
- break;
- }
-
- if (settings.isEmpty())
- return;
-
- class DeclFinder : public ASTVisitor
- {
- public:
- DeclFinder(const CppRefactoringFile *file, const Symbol *func)
- : ASTVisitor(file->cppDocument()->translationUnit()), m_func(func) {}
-
- SimpleDeclarationAST *decl() const { return m_decl; }
-
- private:
- bool visit(SimpleDeclarationAST *decl) override
- {
- if (m_decl)
- return false;
- if (decl->symbols && decl->symbols->value == m_func)
- m_decl = decl;
- return !m_decl;
- }
-
- const Symbol * const m_func;
- SimpleDeclarationAST *m_decl = nullptr;
- };
-
- QHash<FilePath, ChangeSet> changeSets;
- for (const MemberFunctionImplSetting &setting : std::as_const(settings)) {
- DeclFinder finder(currentFile().data(), setting.func);
- finder.accept(m_classAST);
- QTC_ASSERT(finder.decl(), continue);
- InsertionLocation loc;
- const FilePath targetFilePath = setting.defPos == DefPosImplementationFile
- ? cppFile : filePath();
- QTC_ASSERT(!targetFilePath.isEmpty(), continue);
- if (setting.defPos == DefPosInsideClass) {
- int line, column;
- currentFile()->lineAndColumn(currentFile()->endOf(finder.decl()), &line, &column);
- loc = InsertionLocation(filePath(), QString(), QString(), line, column);
- }
- ChangeSet &changeSet = changeSets[targetFilePath];
- InsertDefOperation::insertDefinition(
- this, loc, setting.defPos, finder.decl()->declarator_list->value,
- setting.func->asDeclaration(),targetFilePath, &changeSet);
- }
- for (auto it = changeSets.cbegin(); it != changeSets.cend(); ++it) {
- const CppRefactoringFilePtr file = refactoring.cppFile(it.key());
- file->setChangeSet(it.value());
- file->apply();
- }
- }
-
- ClassSpecifierAST *m_classAST = nullptr;
- InsertDefsFromDecls::Mode m_mode;
- QList<Symbol *> m_declarations;
-};
-
-
-void InsertDefsFromDecls::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result)
-{
- const auto op = QSharedPointer<InsertDefsOperation>::create(interface);
- op->setMode(m_mode);
- if (op->isApplicable())
- result << op;
-}
-
-namespace {
-
-std::optional<FullySpecifiedType> getFirstTemplateParameter(const Name *name)
-{
- if (const QualifiedNameId *qualifiedName = name->asQualifiedNameId())
- return getFirstTemplateParameter(qualifiedName->name());
-
- if (const TemplateNameId *templateName = name->asTemplateNameId()) {
- if (templateName->templateArgumentCount() > 0)
- return templateName->templateArgumentAt(0).type();
- }
- return {};
-}
-
-std::optional<FullySpecifiedType> getFirstTemplateParameter(Type *type)
-{
- if (NamedType *namedType = type->asNamedType())
- return getFirstTemplateParameter(namedType->name());
-
- return {};
-}
-
-std::optional<FullySpecifiedType> getFirstTemplateParameter(FullySpecifiedType type)
-{
- return getFirstTemplateParameter(type.type());
-}
-
-QString symbolAtDifferentLocation(const CppQuickFixInterface &interface,
- Symbol *symbol,
- const CppRefactoringFilePtr &targetFile,
- InsertionLocation targetLocation)
-{
- QTC_ASSERT(symbol, return QString());
- Scope *scopeAtInsertPos = targetFile->cppDocument()->scopeAt(targetLocation.line(),
- targetLocation.column());
-
- LookupContext cppContext(targetFile->cppDocument(), interface.snapshot());
- ClassOrNamespace *cppCoN = cppContext.lookupType(scopeAtInsertPos);
- if (!cppCoN)
- cppCoN = cppContext.globalNamespace();
- SubstitutionEnvironment env;
- env.setContext(interface.context());
- env.switchScope(symbol->enclosingScope());
- UseMinimalNames q(cppCoN);
- env.enter(&q);
- Control *control = interface.context().bindings()->control().get();
- Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview();
- return oo.prettyName(LookupContext::minimalName(symbol, cppCoN, control));
-}
-
-FullySpecifiedType typeAtDifferentLocation(const CppQuickFixInterface &interface,
- FullySpecifiedType type,
- Scope *originalScope,
- const CppRefactoringFilePtr &targetFile,
- InsertionLocation targetLocation,
- const QStringList &newNamespaceNamesAtLoc = {})
-{
- Scope *scopeAtInsertPos = targetFile->cppDocument()->scopeAt(targetLocation.line(),
- targetLocation.column());
- for (const QString &nsName : newNamespaceNamesAtLoc) {
- const QByteArray utf8Name = nsName.toUtf8();
- Control *control = targetFile->cppDocument()->control();
- const Name *name = control->identifier(utf8Name.data(), utf8Name.size());
- Namespace *ns = control->newNamespace(0, name);
- ns->setEnclosingScope(scopeAtInsertPos);
- scopeAtInsertPos = ns;
- }
- LookupContext cppContext(targetFile->cppDocument(), interface.snapshot());
- ClassOrNamespace *cppCoN = cppContext.lookupType(scopeAtInsertPos);
- if (!cppCoN)
- cppCoN = cppContext.globalNamespace();
- SubstitutionEnvironment env;
- env.setContext(interface.context());
- env.switchScope(originalScope);
- UseMinimalNames q(cppCoN);
- env.enter(&q);
- Control *control = interface.context().bindings()->control().get();
- return rewriteType(type, &env, control);
-}
-
-struct ExistingGetterSetterData
-{
- Class *clazz = nullptr;
- Declaration *declarationSymbol = nullptr;
- QString getterName;
- QString setterName;
- QString resetName;
- QString signalName;
- QString qPropertyName;
- QString memberVariableName;
- Document::Ptr doc;
-
- int computePossibleFlags() const;
-};
-
-class GetterSetterRefactoringHelper
-{
-public:
- GetterSetterRefactoringHelper(CppQuickFixOperation *operation,
- const FilePath &filePath,
- Class *clazz)
- : m_operation(operation)
- , m_changes(m_operation->snapshot())
- , m_locator(m_changes)
- , m_headerFile(m_changes.cppFile(filePath))
- , m_sourceFile([&] {
- FilePath cppFilePath = correspondingHeaderOrSource(filePath, &m_isHeaderHeaderFile);
- if (!m_isHeaderHeaderFile || !cppFilePath.exists()) {
- // there is no "source" file
- return m_headerFile;
- } else {
- return m_changes.cppFile(cppFilePath);
- }
- }())
- , m_class(clazz)
- {}
-
- void performGeneration(ExistingGetterSetterData data, int generationFlags);
-
- void applyChanges()
- {
- const auto classLayout = {
- InsertionPointLocator::Public,
- InsertionPointLocator::PublicSlot,
- InsertionPointLocator::Signals,
- InsertionPointLocator::Protected,
- InsertionPointLocator::ProtectedSlot,
- InsertionPointLocator::PrivateSlot,
- InsertionPointLocator::Private,
- };
- for (auto spec : classLayout) {
- const auto iter = m_headerFileCode.find(spec);
- if (iter != m_headerFileCode.end()) {
- const InsertionLocation loc = headerLocationFor(spec);
- m_headerFile->setOpenEditor(true, m_headerFile->position(loc.line(), loc.column()));
- insertAndIndent(m_headerFile, loc, *iter);
- }
- }
- if (!m_sourceFileCode.isEmpty() && m_sourceFileInsertionPoint.isValid()) {
- m_sourceFile->setOpenEditor(true, m_sourceFile->position(
- m_sourceFileInsertionPoint.line(),
- m_sourceFileInsertionPoint.column()));
- insertAndIndent(m_sourceFile, m_sourceFileInsertionPoint, m_sourceFileCode);
- }
-
- if (!m_headerFileChangeSet.isEmpty()) {
- m_headerFile->setChangeSet(m_headerFileChangeSet);
- m_headerFile->apply();
- }
- if (!m_sourceFileChangeSet.isEmpty()) {
- m_sourceFile->setChangeSet(m_sourceFileChangeSet);
- m_sourceFile->apply();
- }
- }
-
- bool hasSourceFile() const { return m_headerFile != m_sourceFile; }
-
-protected:
- void insertAndIndent(const RefactoringFilePtr &file,
- const InsertionLocation &loc,
- const QString &text)
- {
- int targetPosition = file->position(loc.line(), loc.column());
- ChangeSet &changeSet = file == m_headerFile ? m_headerFileChangeSet : m_sourceFileChangeSet;
- changeSet.insert(targetPosition, loc.prefix() + text + loc.suffix());
- }
-
- FullySpecifiedType makeConstRef(FullySpecifiedType type)
- {
- type.setConst(true);
- return m_operation->currentFile()->cppDocument()->control()->referenceType(type, false);
- }
-
- FullySpecifiedType addConstToReference(FullySpecifiedType type)
- {
- if (ReferenceType *ref = type.type()->asReferenceType()) {
- FullySpecifiedType elemType = ref->elementType();
- if (elemType.isConst())
- return type;
- elemType.setConst(true);
- return m_operation->currentFile()->cppDocument()->control()->referenceType(elemType,
- false);
- }
- return type;
- }
-
- QString symbolAt(Symbol *symbol,
- const CppRefactoringFilePtr &targetFile,
- InsertionLocation targetLocation)
- {
- return symbolAtDifferentLocation(*m_operation, symbol, targetFile, targetLocation);
- }
-
- FullySpecifiedType typeAt(FullySpecifiedType type,
- Scope *originalScope,
- const CppRefactoringFilePtr &targetFile,
- InsertionLocation targetLocation,
- const QStringList &newNamespaceNamesAtLoc = {})
- {
- return typeAtDifferentLocation(*m_operation,
- type,
- originalScope,
- targetFile,
- targetLocation,
- newNamespaceNamesAtLoc);
- }
-
- /**
- * @brief checks if the type in the enclosing scope in the header is a value type
- * @param type a type in the m_headerFile
- * @param enclosingScope the enclosing scope
- * @param customValueType if not nullptr set to true when value type comes
- * from CppQuickFixSettings::isValueType
- * @return true if it is a pointer, enum, integer, floating point, reference, custom value type
- */
- bool isValueType(FullySpecifiedType type, Scope *enclosingScope, bool *customValueType = nullptr)
- {
- if (customValueType)
- *customValueType = false;
- // a type is a value type if it is one of the following
- const auto isTypeValueType = [](const FullySpecifiedType &t) {
- return t->asPointerType() || t->asEnumType() || t->asIntegerType() || t->asFloatType()
- || t->asReferenceType();
- };
- if (type->asNamedType()) {
- // we need a recursive search and a lookup context
- LookupContext context(m_headerFile->cppDocument(), m_changes.snapshot());
- auto isValueType = [settings = m_settings,
- &customValueType,
- &context,
- &isTypeValueType](const Name *name,
- Scope *scope,
- auto &isValueType) {
- // maybe the type is a custom value type by name
- if (const Identifier *id = name->identifier()) {
- if (settings->isValueType(QString::fromUtf8(id->chars(), id->size()))) {
- if (customValueType)
- *customValueType = true;
- return true;
- }
- }
- // search for the type declaration
- QList<LookupItem> localLookup = context.lookup(name, scope);
- for (auto &&i : localLookup) {
- if (isTypeValueType(i.type()))
- return true;
- if (i.type()->asNamedType()) { // check if we have to search recursively
- const Name *newName = i.type()->asNamedType()->name();
- Scope *newScope = i.declaration()->enclosingScope();
- if (Matcher::match(newName, name)
- && Matcher::match(newScope->name(), scope->name())) {
- continue; // we have found the start location of the search
- }
- return isValueType(newName, newScope, isValueType);
- }
- return false;
- }
- return false;
- };
- // start recursion
- return isValueType(type->asNamedType()->name(), enclosingScope, isValueType);
- }
- return isTypeValueType(type);
- }
-
- bool isValueType(Symbol *symbol, bool *customValueType = nullptr)
- {
- return isValueType(symbol->type(), symbol->enclosingScope(), customValueType);
- }
-
- void addHeaderCode(InsertionPointLocator::AccessSpec spec, QString code)
- {
- QString &existing = m_headerFileCode[spec];
- existing += code;
- if (!existing.endsWith('\n'))
- existing += '\n';
- }
-
- InsertionLocation headerLocationFor(InsertionPointLocator::AccessSpec spec)
- {
- const auto insertionPoint = m_headerInsertionPoints.find(spec);
- if (insertionPoint != m_headerInsertionPoints.end())
- return *insertionPoint;
- const InsertionLocation loc = m_locator.methodDeclarationInClass(
- m_headerFile->filePath(), m_class, spec,
- InsertionPointLocator::ForceAccessSpec::Yes);
- m_headerInsertionPoints.insert(spec, loc);
- return loc;
- }
-
- InsertionLocation sourceLocationFor(Symbol *symbol, QStringList *insertedNamespaces = nullptr)
- {
- if (m_sourceFileInsertionPoint.isValid())
- return m_sourceFileInsertionPoint;
- m_sourceFileInsertionPoint
- = insertLocationForMethodDefinition(symbol,
- false,
- m_settings->createMissingNamespacesinCppFile()
- ? NamespaceHandling::CreateMissing
- : NamespaceHandling::Ignore,
- m_changes,
- m_sourceFile->filePath(),
- insertedNamespaces);
- if (m_settings->addUsingNamespaceinCppFile()) {
- // check if we have to insert a using namespace ...
- auto requiredNamespaces = getNamespaceNames(
- symbol->asClass() ? symbol : symbol->enclosingClass());
- NSCheckerVisitor visitor(m_sourceFile.get(),
- requiredNamespaces,
- m_sourceFile->position(m_sourceFileInsertionPoint.line(),
- m_sourceFileInsertionPoint.column()));
- visitor.accept(m_sourceFile->cppDocument()->translationUnit()->ast());
- if (insertedNamespaces)
- insertedNamespaces->clear();
- if (auto rns = visitor.remainingNamespaces(); !rns.empty()) {
- QString ns = "using namespace ";
- for (auto &n : rns) {
- if (!n.isEmpty()) { // we have to ignore unnamed namespaces
- ns += n;
- ns += "::";
- if (insertedNamespaces)
- insertedNamespaces->append(n);
- }
- }
- ns.resize(ns.size() - 2); // remove last '::'
- ns += ";\n";
- const auto &loc = m_sourceFileInsertionPoint;
- m_sourceFileInsertionPoint = InsertionLocation(loc.filePath(),
- loc.prefix() + ns,
- loc.suffix(),
- loc.line(),
- loc.column());
- }
- }
- return m_sourceFileInsertionPoint;
- }
-
- void addSourceFileCode(QString code)
- {
- while (!m_sourceFileCode.isEmpty() && !m_sourceFileCode.endsWith("\n\n"))
- m_sourceFileCode += '\n';
- m_sourceFileCode += code;
- }
-
-protected:
- CppQuickFixOperation *const m_operation;
- const CppRefactoringChanges m_changes;
- const InsertionPointLocator m_locator;
- const CppRefactoringFilePtr m_headerFile;
- bool m_isHeaderHeaderFile = false; // the "header" (where the class is defined) can be a source file
- const CppRefactoringFilePtr m_sourceFile;
- CppQuickFixSettings *const m_settings = CppQuickFixProjectsSettings::getQuickFixSettings(
- ProjectExplorer::ProjectTree::currentProject());
- Class *const m_class;
-
-private:
- ChangeSet m_headerFileChangeSet;
- ChangeSet m_sourceFileChangeSet;
- QMap<InsertionPointLocator::AccessSpec, InsertionLocation> m_headerInsertionPoints;
- InsertionLocation m_sourceFileInsertionPoint;
- QString m_sourceFileCode;
- QMap<InsertionPointLocator::AccessSpec, QString> m_headerFileCode;
-};
-
-class GenerateGetterSetterOp : public CppQuickFixOperation
-{
-public:
- enum GenerateFlag {
- GenerateGetter = 1 << 0,
- GenerateSetter = 1 << 1,
- GenerateSignal = 1 << 2,
- GenerateMemberVariable = 1 << 3,
- GenerateReset = 1 << 4,
- GenerateProperty = 1 << 5,
- GenerateConstantProperty = 1 << 6,
- HaveExistingQProperty = 1 << 7,
- Invalid = -1,
- };
-
- GenerateGetterSetterOp(const CppQuickFixInterface &interface,
- ExistingGetterSetterData data,
- int generateFlags,
- int priority,
- const QString &description)
- : CppQuickFixOperation(interface)
- , m_generateFlags(generateFlags)
- , m_data(data)
- {
- setDescription(description);
- setPriority(priority);
- }
-
- static void generateQuickFixes(QuickFixOperations &results,
- const CppQuickFixInterface &interface,
- const ExistingGetterSetterData &data,
- const int possibleFlags)
- {
- // flags can have the value HaveExistingQProperty or a combination of all other values
- // of the enum 'GenerateFlag'
- int p = 0;
- if (possibleFlags & HaveExistingQProperty) {
- const QString desc = Tr::tr("Generate Missing Q_PROPERTY Members");
- results << new GenerateGetterSetterOp(interface, data, possibleFlags, ++p, desc);
- } else {
- if (possibleFlags & GenerateSetter) {
- const QString desc = Tr::tr("Generate Setter");
- results << new GenerateGetterSetterOp(interface, data, GenerateSetter, ++p, desc);
- }
- if (possibleFlags & GenerateGetter) {
- const QString desc = Tr::tr("Generate Getter");
- results << new GenerateGetterSetterOp(interface, data, GenerateGetter, ++p, desc);
- }
- if (possibleFlags & GenerateGetter && possibleFlags & GenerateSetter) {
- const QString desc = Tr::tr("Generate Getter and Setter");
- const int flags = GenerateGetter | GenerateSetter;
- results << new GenerateGetterSetterOp(interface, data, flags, ++p, desc);
- }
-
- if (possibleFlags & GenerateConstantProperty) {
- const QString desc = Tr::tr("Generate Constant Q_PROPERTY and Missing Members");
- const int flags = possibleFlags & ~(GenerateSetter | GenerateSignal | GenerateReset);
- results << new GenerateGetterSetterOp(interface, data, flags, ++p, desc);
- }
- if (possibleFlags & GenerateProperty) {
- if (possibleFlags & GenerateReset) {
- const QString desc = Tr::tr(
- "Generate Q_PROPERTY and Missing Members with Reset Function");
- const int flags = possibleFlags & ~GenerateConstantProperty;
- results << new GenerateGetterSetterOp(interface, data, flags, ++p, desc);
- }
- const QString desc = Tr::tr("Generate Q_PROPERTY and Missing Members");
- const int flags = possibleFlags & ~GenerateConstantProperty & ~GenerateReset;
- results << new GenerateGetterSetterOp(interface, data, flags, ++p, desc);
- }
- }
- }
-
- void perform() override
- {
- GetterSetterRefactoringHelper helper(this, currentFile()->filePath(), m_data.clazz);
- helper.performGeneration(m_data, m_generateFlags);
- helper.applyChanges();
- }
-
-private:
- int m_generateFlags;
- ExistingGetterSetterData m_data;
-};
-
-int ExistingGetterSetterData::computePossibleFlags() const
-{
- const bool isConst = declarationSymbol->type().isConst();
- const bool isStatic = declarationSymbol->type().isStatic();
- using Flag = GenerateGetterSetterOp::GenerateFlag;
- int generateFlags = 0;
- if (getterName.isEmpty())
- generateFlags |= Flag::GenerateGetter;
- if (!isConst) {
- if (resetName.isEmpty())
- generateFlags |= Flag::GenerateReset;
- if (!isStatic && signalName.isEmpty() && setterName.isEmpty())
- generateFlags |= Flag::GenerateSignal;
- if (setterName.isEmpty())
- generateFlags |= Flag::GenerateSetter;
- }
- if (!isStatic) {
- const bool hasSignal = !signalName.isEmpty() || generateFlags & Flag::GenerateSignal;
- if (!isConst && hasSignal)
- generateFlags |= Flag::GenerateProperty;
- }
- if (setterName.isEmpty() && signalName.isEmpty())
- generateFlags |= Flag::GenerateConstantProperty;
- return generateFlags;
-}
-
-void GetterSetterRefactoringHelper::performGeneration(ExistingGetterSetterData data, int generateFlags)
-{
- using Flag = GenerateGetterSetterOp::GenerateFlag;
-
- if (generateFlags & Flag::GenerateGetter && data.getterName.isEmpty()) {
- data.getterName = m_settings->getGetterName(data.qPropertyName);
- if (data.getterName == data.memberVariableName) {
- data.getterName = "get" + data.memberVariableName.left(1).toUpper()
- + data.memberVariableName.mid(1);
- }
- }
- if (generateFlags & Flag::GenerateSetter && data.setterName.isEmpty())
- data.setterName = m_settings->getSetterName(data.qPropertyName);
- if (generateFlags & Flag::GenerateSignal && data.signalName.isEmpty())
- data.signalName = m_settings->getSignalName(data.qPropertyName);
- if (generateFlags & Flag::GenerateReset && data.resetName.isEmpty())
- data.resetName = m_settings->getResetName(data.qPropertyName);
-
- FullySpecifiedType memberVariableType = data.declarationSymbol->type();
- memberVariableType.setConst(false);
- const bool isMemberVariableStatic = memberVariableType.isStatic();
- memberVariableType.setStatic(false);
- Overview overview = CppCodeStyleSettings::currentProjectCodeStyleOverview();
- overview.showTemplateParameters = false;
- // TODO does not work with using. e.g. 'using foo = std::unique_ptr<int>'
- // TODO must be fully qualified
- auto getSetTemplate = m_settings->findGetterSetterTemplate(overview.prettyType(memberVariableType));
- overview.showTemplateParameters = true;
-
- // Ok... - If a type is a Named type we have to search recusive for the real type
- const bool isValueType = this->isValueType(memberVariableType,
- data.declarationSymbol->enclosingScope());
- const FullySpecifiedType parameterType = isValueType ? memberVariableType
- : makeConstRef(memberVariableType);
-
- QString baseName = memberBaseName(data.memberVariableName);
- if (baseName.isEmpty())
- baseName = data.memberVariableName;
-
- const QString parameterName = m_settings->getSetterParameterName(baseName);
- if (parameterName == data.memberVariableName)
- data.memberVariableName = "this->" + data.memberVariableName;
-
- getSetTemplate.replacePlaceholders(data.memberVariableName, parameterName);
-
- using Pattern = CppQuickFixSettings::GetterSetterTemplate;
- std::optional<FullySpecifiedType> returnTypeTemplateParameter;
- if (getSetTemplate.returnTypeTemplate.has_value()) {
- QString returnTypeTemplate = getSetTemplate.returnTypeTemplate.value();
- if (returnTypeTemplate.contains(Pattern::TEMPLATE_PARAMETER_PATTERN)) {
- returnTypeTemplateParameter = getFirstTemplateParameter(data.declarationSymbol->type());
- if (!returnTypeTemplateParameter.has_value())
- return; // Maybe report error to the user
- }
- }
- const FullySpecifiedType returnTypeHeader = [&] {
- if (!getSetTemplate.returnTypeTemplate.has_value())
- return m_settings->returnByConstRef ? parameterType : memberVariableType;
- QString typeTemplate = getSetTemplate.returnTypeTemplate.value();
- if (returnTypeTemplateParameter.has_value())
- typeTemplate.replace(Pattern::TEMPLATE_PARAMETER_PATTERN,
- overview.prettyType(returnTypeTemplateParameter.value()));
- if (typeTemplate.contains(Pattern::TYPE_PATTERN))
- typeTemplate.replace(Pattern::TYPE_PATTERN,
- overview.prettyType(data.declarationSymbol->type()));
- Control *control = m_operation->currentFile()->cppDocument()->control();
- std::string utf8TypeName = typeTemplate.toUtf8().toStdString();
- return FullySpecifiedType(control->namedType(control->identifier(utf8TypeName.c_str())));
- }();
-
- // getter declaration
- if (generateFlags & Flag::GenerateGetter) {
- // maybe we added 'this->' to memberVariableName because of a collision with parameterName
- // but here the 'this->' is not needed
- const QString returnExpression = QString{getSetTemplate.returnExpression}.replace("this->",
- "");
- QString getterInClassDeclaration = overview.prettyType(returnTypeHeader, data.getterName)
- + QLatin1String("()");
- if (isMemberVariableStatic)
- getterInClassDeclaration.prepend(QLatin1String("static "));
- else
- getterInClassDeclaration += QLatin1String(" const");
- getterInClassDeclaration.prepend(m_settings->getterAttributes + QLatin1Char(' '));
-
- auto getterLocation = m_settings->determineGetterLocation(1);
- // if we have an anonymous class we must add code inside the class
- if (data.clazz->name()->asAnonymousNameId())
- getterLocation = CppQuickFixSettings::FunctionLocation::InsideClass;
-
- if (getterLocation == CppQuickFixSettings::FunctionLocation::InsideClass) {
- getterInClassDeclaration += QLatin1String("\n{\nreturn ") + returnExpression
- + QLatin1String(";\n}\n");
- } else {
- getterInClassDeclaration += QLatin1String(";\n");
- }
- addHeaderCode(InsertionPointLocator::Public, getterInClassDeclaration);
- if (getterLocation == CppQuickFixSettings::FunctionLocation::CppFile && !hasSourceFile())
- getterLocation = CppQuickFixSettings::FunctionLocation::OutsideClass;
-
- if (getterLocation != CppQuickFixSettings::FunctionLocation::InsideClass) {
- const auto getReturnTypeAt = [&](CppRefactoringFilePtr targetFile,
- InsertionLocation targetLoc) {
- if (getSetTemplate.returnTypeTemplate.has_value()) {
- QString returnType = getSetTemplate.returnTypeTemplate.value();
- if (returnTypeTemplateParameter.has_value()) {
- const QString templateTypeName = overview.prettyType(typeAt(
- returnTypeTemplateParameter.value(), data.clazz, targetFile, targetLoc));
- returnType.replace(Pattern::TEMPLATE_PARAMETER_PATTERN, templateTypeName);
- }
- if (returnType.contains(Pattern::TYPE_PATTERN)) {
- const QString declarationType = overview.prettyType(
- typeAt(memberVariableType, data.clazz, targetFile, targetLoc));
- returnType.replace(Pattern::TYPE_PATTERN, declarationType);
- }
- Control *control = m_operation->currentFile()->cppDocument()->control();
- std::string utf8String = returnType.toUtf8().toStdString();
- return FullySpecifiedType(
- control->namedType(control->identifier(utf8String.c_str())));
- } else {
- FullySpecifiedType returnType = typeAt(memberVariableType,
- data.clazz,
- targetFile,
- targetLoc);
- if (m_settings->returnByConstRef && !isValueType)
- return makeConstRef(returnType);
- return returnType;
- }
- };
- const QString constSpec = isMemberVariableStatic ? QLatin1String("")
- : QLatin1String(" const");
- if (getterLocation == CppQuickFixSettings::FunctionLocation::CppFile) {
- InsertionLocation loc = sourceLocationFor(data.declarationSymbol);
- FullySpecifiedType returnType;
- QString clazz;
- if (m_settings->rewriteTypesinCppFile()) {
- returnType = getReturnTypeAt(m_sourceFile, loc);
- clazz = symbolAt(data.clazz, m_sourceFile, loc);
- } else {
- returnType = returnTypeHeader;
- const Identifier *identifier = data.clazz->name()->identifier();
- clazz = QString::fromUtf8(identifier->chars(), identifier->size());
- }
- const QString code = overview.prettyType(returnType, clazz + "::" + data.getterName)
- + "()" + constSpec + "\n{\nreturn " + returnExpression + ";\n}";
- addSourceFileCode(code);
- } else if (getterLocation == CppQuickFixSettings::FunctionLocation::OutsideClass) {
- InsertionLocation loc
- = insertLocationForMethodDefinition(data.declarationSymbol,
- false,
- NamespaceHandling::Ignore,
- m_changes,
- m_headerFile->filePath());
- const FullySpecifiedType returnType = getReturnTypeAt(m_headerFile, loc);
- const QString clazz = symbolAt(data.clazz, m_headerFile, loc);
- QString code = overview.prettyType(returnType, clazz + "::" + data.getterName)
- + "()" + constSpec + "\n{\nreturn " + returnExpression + ";\n}";
- if (m_isHeaderHeaderFile)
- code.prepend("inline ");
- insertAndIndent(m_headerFile, loc, code);
- }
- }
- }
-
- // setter declaration
- InsertionPointLocator::AccessSpec setterAccessSpec = InsertionPointLocator::Public;
- if (m_settings->setterAsSlot) {
- const QByteArray connectName = "connect";
- const Identifier connectId(connectName.data(), connectName.size());
- const QList<LookupItem> items = m_operation->context().lookup(&connectId, data.clazz);
- for (const LookupItem &item : items) {
- if (item.declaration() && item.declaration()->enclosingClass()
- && overview.prettyName(item.declaration()->enclosingClass()->name())
- == "QObject") {
- setterAccessSpec = InsertionPointLocator::PublicSlot;
- break;
- }
- }
- }
- const auto createSetterBodyWithSignal = [this, &getSetTemplate, &data] {
- QString body;
- QTextStream setter(&body);
- setter << "if (" << getSetTemplate.equalComparison << ")\nreturn;\n";
-
- setter << getSetTemplate.assignment << ";\n";
- if (m_settings->signalWithNewValue)
- setter << "emit " << data.signalName << "(" << getSetTemplate.returnExpression << ");\n";
- else
- setter << "emit " << data.signalName << "();\n";
-
- return body;
- };
- if (generateFlags & Flag::GenerateSetter) {
- QString headerDeclaration = "void " + data.setterName + '('
- + overview.prettyType(addConstToReference(parameterType),
- parameterName)
- + ")";
- if (isMemberVariableStatic)
- headerDeclaration.prepend("static ");
- QString body = "\n{\n";
- if (data.signalName.isEmpty())
- body += getSetTemplate.assignment + ";\n";
- else
- body += createSetterBodyWithSignal();
-
- body += "}";
-
- auto setterLocation = m_settings->determineSetterLocation(body.count('\n') - 2);
- // if we have an anonymous class we must add code inside the class
- if (data.clazz->name()->asAnonymousNameId())
- setterLocation = CppQuickFixSettings::FunctionLocation::InsideClass;
-
- if (setterLocation == CppQuickFixSettings::FunctionLocation::CppFile && !hasSourceFile())
- setterLocation = CppQuickFixSettings::FunctionLocation::OutsideClass;
-
- if (setterLocation == CppQuickFixSettings::FunctionLocation::InsideClass) {
- headerDeclaration += body;
- } else {
- headerDeclaration += ";\n";
- if (setterLocation == CppQuickFixSettings::FunctionLocation::CppFile) {
- InsertionLocation loc = sourceLocationFor(data.declarationSymbol);
- QString clazz;
- FullySpecifiedType newParameterType = parameterType;
- if (m_settings->rewriteTypesinCppFile()) {
- newParameterType = typeAt(memberVariableType, data.clazz, m_sourceFile, loc);
- if (!isValueType)
- newParameterType = makeConstRef(newParameterType);
- clazz = symbolAt(data.clazz, m_sourceFile, loc);
- } else {
- const Identifier *identifier = data.clazz->name()->identifier();
- clazz = QString::fromUtf8(identifier->chars(), identifier->size());
- }
- newParameterType = addConstToReference(newParameterType);
- const QString code = "void " + clazz + "::" + data.setterName + '('
- + overview.prettyType(newParameterType, parameterName) + ')'
- + body;
- addSourceFileCode(code);
- } else if (setterLocation == CppQuickFixSettings::FunctionLocation::OutsideClass) {
- InsertionLocation loc
- = insertLocationForMethodDefinition(data.declarationSymbol,
- false,
- NamespaceHandling::Ignore,
- m_changes,
- m_headerFile->filePath());
-
- FullySpecifiedType newParameterType = typeAt(data.declarationSymbol->type(),
- data.clazz,
- m_headerFile,
- loc);
- if (!isValueType)
- newParameterType = makeConstRef(newParameterType);
- newParameterType = addConstToReference(newParameterType);
- QString clazz = symbolAt(data.clazz, m_headerFile, loc);
-
- QString code = "void " + clazz + "::" + data.setterName + '('
- + overview.prettyType(newParameterType, parameterName) + ')' + body;
- if (m_isHeaderHeaderFile)
- code.prepend("inline ");
- insertAndIndent(m_headerFile, loc, code);
- }
- }
- addHeaderCode(setterAccessSpec, headerDeclaration);
- }
-
- // reset declaration
- if (generateFlags & Flag::GenerateReset) {
- QString headerDeclaration = "void " + data.resetName + "()";
- if (isMemberVariableStatic)
- headerDeclaration.prepend("static ");
- QString body = "\n{\n";
- if (!data.setterName.isEmpty()) {
- body += data.setterName + "({}); // TODO: Adapt to use your actual default value\n";
- } else {
- body += "static $TYPE defaultValue{}; "
- "// TODO: Adapt to use your actual default value\n";
- if (data.signalName.isEmpty())
- body += getSetTemplate.assignment + ";\n";
- else
- body += createSetterBodyWithSignal();
- }
- body += "}";
-
- // the template use <parameterName> as new value name, but we want to use 'defaultValue'
- body.replace(QRegularExpression("\\b" + parameterName + "\\b"), "defaultValue");
- // body.count('\n') - 2 : do not count the 2 at start
- auto resetLocation = m_settings->determineSetterLocation(body.count('\n') - 2);
- // if we have an anonymous class we must add code inside the class
- if (data.clazz->name()->asAnonymousNameId())
- resetLocation = CppQuickFixSettings::FunctionLocation::InsideClass;
-
- if (resetLocation == CppQuickFixSettings::FunctionLocation::CppFile && !hasSourceFile())
- resetLocation = CppQuickFixSettings::FunctionLocation::OutsideClass;
-
- if (resetLocation == CppQuickFixSettings::FunctionLocation::InsideClass) {
- headerDeclaration += body.replace("$TYPE", overview.prettyType(memberVariableType));
- } else {
- headerDeclaration += ";\n";
- if (resetLocation == CppQuickFixSettings::FunctionLocation::CppFile) {
- const InsertionLocation loc = sourceLocationFor(data.declarationSymbol);
- QString clazz;
- FullySpecifiedType type = memberVariableType;
- if (m_settings->rewriteTypesinCppFile()) {
- type = typeAt(memberVariableType, data.clazz, m_sourceFile, loc);
- clazz = symbolAt(data.clazz, m_sourceFile, loc);
- } else {
- const Identifier *identifier = data.clazz->name()->identifier();
- clazz = QString::fromUtf8(identifier->chars(), identifier->size());
- }
- const QString code = "void " + clazz + "::" + data.resetName + "()"
- + body.replace("$TYPE", overview.prettyType(type));
- addSourceFileCode(code);
- } else if (resetLocation == CppQuickFixSettings::FunctionLocation::OutsideClass) {
- const InsertionLocation loc = insertLocationForMethodDefinition(
- data.declarationSymbol,
- false,
- NamespaceHandling::Ignore,
- m_changes,
- m_headerFile->filePath());
- const FullySpecifiedType type = typeAt(data.declarationSymbol->type(),
- data.clazz,
- m_headerFile,
- loc);
- const QString clazz = symbolAt(data.clazz, m_headerFile, loc);
- QString code = "void " + clazz + "::" + data.resetName + "()"
- + body.replace("$TYPE", overview.prettyType(type));
- if (m_isHeaderHeaderFile)
- code.prepend("inline ");
- insertAndIndent(m_headerFile, loc, code);
- }
- }
- addHeaderCode(setterAccessSpec, headerDeclaration);
- }
-
- // signal declaration
- if (generateFlags & Flag::GenerateSignal) {
- const auto &parameter = overview.prettyType(returnTypeHeader, data.qPropertyName);
- const QString newValue = m_settings->signalWithNewValue ? parameter : QString();
- const QString declaration = QString("void %1(%2);\n").arg(data.signalName, newValue);
- addHeaderCode(InsertionPointLocator::Signals, declaration);
- }
-
- // member variable
- if (generateFlags & Flag::GenerateMemberVariable) {
- QString storageDeclaration = overview.prettyType(memberVariableType, data.memberVariableName);
- if (memberVariableType->asPointerType()
- && m_operation->semanticInfo().doc->translationUnit()->languageFeatures().cxx11Enabled) {
- storageDeclaration.append(" = nullptr");
- }
- storageDeclaration.append(";\n");
- addHeaderCode(InsertionPointLocator::Private, storageDeclaration);
- }
-
- // Q_PROPERTY
- if (generateFlags & Flag::GenerateProperty || generateFlags & Flag::GenerateConstantProperty) {
- // Use the returnTypeHeader as base because of custom types in getSetTemplates.
- // Remove const reference from type.
- FullySpecifiedType type = returnTypeHeader;
- if (ReferenceType *ref = type.type()->asReferenceType())
- type = ref->elementType();
- type.setConst(false);
-
- QString propertyDeclaration = QLatin1String("Q_PROPERTY(")
- + overview.prettyType(type,
- memberBaseName(data.memberVariableName));
- bool needMember = false;
- if (data.getterName.isEmpty())
- needMember = true;
- else
- propertyDeclaration += QLatin1String(" READ ") + data.getterName;
- if (generateFlags & Flag::GenerateConstantProperty) {
- if (needMember)
- propertyDeclaration += QLatin1String(" MEMBER ") + data.memberVariableName;
- propertyDeclaration.append(QLatin1String(" CONSTANT"));
- } else {
- if (data.setterName.isEmpty()) {
- needMember = true;
- } else if (!getSetTemplate.returnTypeTemplate.has_value()) {
- // if the return type of the getter and then Q_PROPERTY is different than
- // the setter type, we should not add WRITE to the Q_PROPERTY
- propertyDeclaration.append(QLatin1String(" WRITE ")).append(data.setterName);
- }
- if (needMember)
- propertyDeclaration += QLatin1String(" MEMBER ") + data.memberVariableName;
- if (!data.resetName.isEmpty())
- propertyDeclaration += QLatin1String(" RESET ") + data.resetName;
- propertyDeclaration.append(QLatin1String(" NOTIFY ")).append(data.signalName);
- }
-
- propertyDeclaration.append(QLatin1String(" FINAL)\n"));
- addHeaderCode(InsertionPointLocator::Private, propertyDeclaration);
- }
-}
-
-QStringList toStringList(const QList<Symbol *> names)
-{
- QStringList list;
- list.reserve(names.size());
- for (const auto symbol : names) {
- const Identifier *const id = symbol->identifier();
- list << QString::fromUtf8(id->chars(), id->size());
- }
- return list;
-}
-
-void findExistingFunctions(ExistingGetterSetterData &existing, QStringList memberFunctionNames)
-{
- const CppQuickFixSettings *settings = CppQuickFixProjectsSettings::getQuickFixSettings(
- ProjectExplorer::ProjectTree::currentProject());
- const QString lowerBaseName = memberBaseName(existing.memberVariableName).toLower();
- const QStringList getterNames{lowerBaseName,
- "get_" + lowerBaseName,
- "get" + lowerBaseName,
- "is_" + lowerBaseName,
- "is" + lowerBaseName,
- settings->getGetterName(lowerBaseName)};
- const QStringList setterNames{"set_" + lowerBaseName,
- "set" + lowerBaseName,
- settings->getSetterName(lowerBaseName)};
- const QStringList resetNames{"reset_" + lowerBaseName,
- "reset" + lowerBaseName,
- settings->getResetName(lowerBaseName)};
- const QStringList signalNames{lowerBaseName + "_changed",
- lowerBaseName + "changed",
- settings->getSignalName(lowerBaseName)};
- for (const auto &memberFunctionName : memberFunctionNames) {
- const QString lowerName = memberFunctionName.toLower();
- if (getterNames.contains(lowerName))
- existing.getterName = memberFunctionName;
- else if (setterNames.contains(lowerName))
- existing.setterName = memberFunctionName;
- else if (resetNames.contains(lowerName))
- existing.resetName = memberFunctionName;
- else if (signalNames.contains(lowerName))
- existing.signalName = memberFunctionName;
- }
-}
-
-QList<Symbol *> getMemberFunctions(const Class *clazz)
-{
- QList<Symbol *> memberFunctions;
- for (auto it = clazz->memberBegin(); it != clazz->memberEnd(); ++it) {
- Symbol *const s = *it;
- if (!s->identifier() || !s->type())
- continue;
- if ((s->asDeclaration() && s->type()->asFunctionType()) || s->asFunction())
- memberFunctions << s;
- }
- return memberFunctions;
-}
-
-} // anonymous namespace
-
-void GenerateGetterSetter::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result)
-{
- ExistingGetterSetterData existing;
-
- const QList<AST *> &path = interface.path();
- // We expect something like
- // [0] TranslationUnitAST
- // [1] NamespaceAST
- // [2] LinkageBodyAST
- // [3] SimpleDeclarationAST
- // [4] ClassSpecifierAST
- // [5] SimpleDeclarationAST
- // [6] DeclaratorAST
- // [7] DeclaratorIdAST
- // [8] SimpleNameAST
-
- const int n = path.size();
- if (n < 6)
- return;
-
- int i = 1;
- const auto variableNameAST = path.at(n - i++)->asSimpleName();
- const auto declaratorId = path.at(n - i++)->asDeclaratorId();
- // DeclaratorAST might be preceded by PointerAST, e.g. for the case
- // "class C { char *@s; };", where '@' denotes the text cursor position.
- auto declarator = path.at(n - i++)->asDeclarator();
- if (!declarator) {
- --i;
- if (path.at(n - i++)->asPointer()) {
- if (n < 7)
- return;
- declarator = path.at(n - i++)->asDeclarator();
- }
- if (!declarator)
- return;
- }
- const auto variableDecl = path.at(n - i++)->asSimpleDeclaration();
- const auto classSpecifier = path.at(n - i++)->asClassSpecifier();
- const auto classDecl = path.at(n - i++)->asSimpleDeclaration();
-
- if (!(variableNameAST && declaratorId && variableDecl && classSpecifier && classDecl))
- return;
-
- // Do not get triggered on member functconstions and arrays
- if (declarator->postfix_declarator_list) {
- return;
- }
-
- // Construct getter and setter names
- const Name *variableName = variableNameAST->name;
- if (!variableName) {
- return;
- }
- const Identifier *variableId = variableName->identifier();
- if (!variableId) {
- return;
- }
- existing.memberVariableName = QString::fromUtf8(variableId->chars(), variableId->size());
-
- // Find the right symbol (for typeName) in the simple declaration
- Symbol *symbol = nullptr;
- const List<Symbol *> *symbols = variableDecl->symbols;
- QTC_ASSERT(symbols, return );
- for (; symbols; symbols = symbols->next) {
- Symbol *s = symbols->value;
- if (const Name *name = s->name()) {
- if (const Identifier *id = name->identifier()) {
- const QString symbolName = QString::fromUtf8(id->chars(), id->size());
- if (symbolName == existing.memberVariableName) {
- symbol = s;
- break;
- }
- }
- }
- }
- if (!symbol) {
- // no type can be determined
- return;
- }
- if (!symbol->asDeclaration()) {
- return;
- }
- existing.declarationSymbol = symbol->asDeclaration();
-
- existing.clazz = classSpecifier->symbol;
- if (!existing.clazz)
- return;
-
- auto file = interface.currentFile();
- // check if a Q_PROPERTY exist
- const QString baseName = memberBaseName(existing.memberVariableName);
- // eg: we have 'int m_test' and now 'Q_PROPERTY(int foo WRITE setTest MEMBER m_test NOTIFY tChanged)'
- for (auto it = classSpecifier->member_specifier_list; it; it = it->next) {
- if (it->value->asQtPropertyDeclaration()) {
- auto propDecl = it->value->asQtPropertyDeclaration();
- // iterator over 'READ ...', ...
- auto p = propDecl->property_declaration_item_list;
- // first check, if we have a MEMBER and the member is equal to the baseName
- for (; p; p = p->next) {
- const char *tokenString = file->tokenAt(p->value->item_name_token).spell();
- if (!qstrcmp(tokenString, "MEMBER")) {
- if (baseName == file->textOf(p->value->expression))
- return;
- }
- }
- // no MEMBER, but maybe the property name is the same
- const QString propertyName = file->textOf(propDecl->property_name);
- // we compare the baseName. e.g. 'test' instead of 'm_test'
- if (propertyName == baseName)
- return; // TODO Maybe offer quick fix "Add missing Q_PROPERTY Members"
- }
- }
-
- findExistingFunctions(existing, toStringList(getMemberFunctions(existing.clazz)));
- existing.qPropertyName = memberBaseName(existing.memberVariableName);
-
- const int possibleFlags = existing.computePossibleFlags();
- GenerateGetterSetterOp::generateQuickFixes(result, interface, existing, possibleFlags);
-}
-
-class MemberInfo
-{
-public:
- MemberInfo(ExistingGetterSetterData data, int possibleFlags)
- : data(data)
- , possibleFlags(possibleFlags)
- {}
-
- ExistingGetterSetterData data;
- int possibleFlags;
- int requestedFlags = 0;
-};
-using GetterSetterCandidates = std::vector<MemberInfo>;
-
-class CandidateTreeItem : public TreeItem
-{
-public:
- enum Column {
- NameColumn,
- GetterColumn,
- SetterColumn,
- SignalColumn,
- ResetColumn,
- QPropertyColumn,
- ConstantQPropertyColumn
- };
- using Flag = GenerateGetterSetterOp::GenerateFlag;
- constexpr static Flag ColumnFlag[] = {
- Flag::Invalid,
- Flag::GenerateGetter,
- Flag::GenerateSetter,
- Flag::GenerateSignal,
- Flag::GenerateReset,
- Flag::GenerateProperty,
- Flag::GenerateConstantProperty,
- };
-
- CandidateTreeItem(MemberInfo *memberInfo)
- : m_memberInfo(memberInfo)
- {}
-
-private:
- QVariant data(int column, int role) const override
- {
- if (role == Qt::DisplayRole && column == NameColumn)
- return m_memberInfo->data.memberVariableName;
- if (role == Qt::CheckStateRole && column > 0
- && column <= static_cast<int>(std::size(ColumnFlag))) {
- return m_memberInfo->requestedFlags & ColumnFlag[column] ? Qt::Checked : Qt::Unchecked;
- }
- return {};
- }
-
- bool setData(int column, const QVariant &data, int role) override
- {
- if (column < 1 || column > static_cast<int>(std::size(ColumnFlag)))
- return false;
- if (role != Qt::CheckStateRole)
- return false;
- if (!(m_memberInfo->possibleFlags & ColumnFlag[column]))
- return false;
- const bool nowChecked = data.toInt() == Qt::Checked;
- if (nowChecked)
- m_memberInfo->requestedFlags |= ColumnFlag[column];
- else
- m_memberInfo->requestedFlags &= ~ColumnFlag[column];
-
- if (nowChecked) {
- if (column == QPropertyColumn) {
- m_memberInfo->requestedFlags |= Flag::GenerateGetter;
- m_memberInfo->requestedFlags |= Flag::GenerateSetter;
- m_memberInfo->requestedFlags |= Flag::GenerateSignal;
- m_memberInfo->requestedFlags &= ~Flag::GenerateConstantProperty;
- } else if (column == ConstantQPropertyColumn) {
- m_memberInfo->requestedFlags |= Flag::GenerateGetter;
- m_memberInfo->requestedFlags &= ~Flag::GenerateSetter;
- m_memberInfo->requestedFlags &= ~Flag::GenerateSignal;
- m_memberInfo->requestedFlags &= ~Flag::GenerateReset;
- m_memberInfo->requestedFlags &= ~Flag::GenerateProperty;
- } else if (column == SetterColumn || column == SignalColumn || column == ResetColumn) {
- m_memberInfo->requestedFlags &= ~Flag::GenerateConstantProperty;
- }
- } else {
- if (column == SignalColumn)
- m_memberInfo->requestedFlags &= ~Flag::GenerateProperty;
- }
- for (int i = 0; i < 16; ++i) {
- const bool allowed = m_memberInfo->possibleFlags & (1 << i);
- if (!allowed)
- m_memberInfo->requestedFlags &= ~(1 << i); // clear bit
- }
- update();
- return true;
- }
-
- Qt::ItemFlags flags(int column) const override
- {
- if (column == NameColumn)
- return Qt::ItemIsEnabled;
- if (column < 1 || column > static_cast<int>(std::size(ColumnFlag)))
- return {};
- if (m_memberInfo->possibleFlags & ColumnFlag[column])
- return Qt::ItemIsEnabled | Qt::ItemIsUserCheckable;
- return {};
- }
-
- MemberInfo *const m_memberInfo;
-};
-
-class GenerateGettersSettersDialog : public QDialog
-{
- static constexpr CandidateTreeItem::Column CheckBoxColumn[4]
- = {CandidateTreeItem::Column::GetterColumn,
- CandidateTreeItem::Column::SetterColumn,
- CandidateTreeItem::Column::SignalColumn,
- CandidateTreeItem::Column::QPropertyColumn};
-
-public:
- GenerateGettersSettersDialog(const GetterSetterCandidates &candidates)
- : QDialog()
- , m_candidates(candidates)
- {
- using Flags = GenerateGetterSetterOp::GenerateFlag;
- setWindowTitle(Tr::tr("Getters and Setters"));
- const auto model = new TreeModel<TreeItem, CandidateTreeItem>(this);
- model->setHeader(QStringList({
- Tr::tr("Member"),
- Tr::tr("Getter"),
- Tr::tr("Setter"),
- Tr::tr("Signal"),
- Tr::tr("Reset"),
- Tr::tr("QProperty"),
- Tr::tr("Constant QProperty"),
- }));
- for (MemberInfo &candidate : m_candidates)
- model->rootItem()->appendChild(new CandidateTreeItem(&candidate));
- const auto view = new BaseTreeView(this);
- view->setModel(model);
- int optimalWidth = 0;
- for (int i = 0; i < model->columnCount(QModelIndex{}); ++i) {
- view->resizeColumnToContents(i);
- optimalWidth += view->columnWidth(i);
- }
-
- const auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
- connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
- connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
-
- const auto setCheckStateForAll = [model](int column, int checkState) {
- for (int i = 0; i < model->rowCount(); ++i)
- model->setData(model->index(i, column), checkState, Qt::CheckStateRole);
- };
- const auto preventPartiallyChecked = [](QCheckBox *checkbox) {
- if (checkbox->checkState() == Qt::PartiallyChecked)
- checkbox->setCheckState(Qt::Checked);
- };
- using Column = CandidateTreeItem::Column;
- const auto createConnections = [this, setCheckStateForAll, preventPartiallyChecked](
- QCheckBox *checkbox, Column column) {
- connect(checkbox, &QCheckBox::stateChanged, this, [setCheckStateForAll, column](int state) {
- if (state != Qt::PartiallyChecked)
- setCheckStateForAll(column, state);
- });
- connect(checkbox, &QCheckBox::clicked, this, [checkbox, preventPartiallyChecked] {
- preventPartiallyChecked(checkbox);
- });
- };
- std::array<QCheckBox *, 4> checkBoxes = {};
-
- static_assert(std::size(CheckBoxColumn) == checkBoxes.size(),
- "Must contain the same number of elements");
- for (std::size_t i = 0; i < checkBoxes.size(); ++i) {
- if (Utils::anyOf(candidates, [i](const MemberInfo &mi) {
- return mi.possibleFlags & CandidateTreeItem::ColumnFlag[CheckBoxColumn[i]];
- })) {
- const Column column = CheckBoxColumn[i];
- if (column == Column::GetterColumn)
- checkBoxes[i] = new QCheckBox(Tr::tr("Create getters for all members"));
- else if (column == Column::SetterColumn)
- checkBoxes[i] = new QCheckBox(Tr::tr("Create setters for all members"));
- else if (column == Column::SignalColumn)
- checkBoxes[i] = new QCheckBox(Tr::tr("Create signals for all members"));
- else if (column == Column::QPropertyColumn)
- checkBoxes[i] = new QCheckBox(Tr::tr("Create Q_PROPERTY for all members"));
-
- createConnections(checkBoxes[i], column);
- }
- }
- connect(model, &QAbstractItemModel::dataChanged, this, [this, checkBoxes] {
- const auto countExisting = [this](Flags flag) {
- return Utils::count(m_candidates, [flag](const MemberInfo &mi) {
- return !(mi.possibleFlags & flag);
- });
- };
- const auto countRequested = [this](Flags flag) {
- return Utils::count(m_candidates, [flag](const MemberInfo &mi) {
- return mi.requestedFlags & flag;
- });
- };
- const auto countToState = [this](int requestedCount, int alreadyExistsCount) {
- if (requestedCount == 0)
- return Qt::Unchecked;
- if (int(m_candidates.size()) - requestedCount == alreadyExistsCount)
- return Qt::Checked;
- return Qt::PartiallyChecked;
- };
- for (std::size_t i = 0; i < checkBoxes.size(); ++i) {
- if (checkBoxes[i]) {
- const Flags flag = CandidateTreeItem::ColumnFlag[CheckBoxColumn[i]];
- checkBoxes[i]->setCheckState(
- countToState(countRequested(flag), countExisting(flag)));
- }
- }
- });
-
- const auto mainLayout = new QVBoxLayout(this);
- mainLayout->addWidget(new QLabel(Tr::tr("Select the getters and setters "
- "to be created.")));
- for (auto checkBox : checkBoxes) {
- if (checkBox)
- mainLayout->addWidget(checkBox);
- }
- mainLayout->addWidget(view);
- mainLayout->addWidget(buttonBox);
- int left, right;
- mainLayout->getContentsMargins(&left, nullptr, &right, nullptr);
- optimalWidth += left + right;
- resize(optimalWidth, mainLayout->sizeHint().height());
- }
-
- GetterSetterCandidates candidates() const { return m_candidates; }
-
-private:
- GetterSetterCandidates m_candidates;
-};
-
-class GenerateGettersSettersOperation : public CppQuickFixOperation
-{
-public:
- GenerateGettersSettersOperation(const CppQuickFixInterface &interface)
- : CppQuickFixOperation(interface)
- {
- setDescription(Tr::tr("Create Getter and Setter Member Functions"));
-
- m_classAST = astForClassOperations(interface);
- if (!m_classAST)
- return;
- Class * const theClass = m_classAST->symbol;
- if (!theClass)
- return;
-
- // Go through all data members and try to find out whether they have getters and/or setters.
- QList<Symbol *> dataMembers;
- QList<Symbol *> memberFunctions;
- for (auto it = theClass->memberBegin(); it != theClass->memberEnd(); ++it) {
- Symbol *const s = *it;
- if (!s->identifier() || !s->type() || s->type().isTypedef())
- continue;
- if ((s->asDeclaration() && s->type()->asFunctionType()) || s->asFunction())
- memberFunctions << s;
- else if (s->asDeclaration() && (s->isPrivate() || s->isProtected()))
- dataMembers << s;
- }
-
- auto file = interface.currentFile();
- QStringList qPropertyNames; // name after MEMBER or name of the property
- for (auto it = m_classAST->member_specifier_list; it; it = it->next) {
- if (it->value->asQtPropertyDeclaration()) {
- auto propDecl = it->value->asQtPropertyDeclaration();
- // iterator over 'READ ...', ... and check if we have a MEMBER
- for (auto p = propDecl->property_declaration_item_list; p; p = p->next) {
- const char *tokenString = file->tokenAt(p->value->item_name_token).spell();
- if (!qstrcmp(tokenString, "MEMBER"))
- qPropertyNames << file->textOf(p->value->expression);
- }
- // no MEMBER, but maybe the property name is the same
- qPropertyNames << file->textOf(propDecl->property_name);
- }
- }
- const QStringList memberFunctionsAsStrings = toStringList(memberFunctions);
-
- for (Symbol *const member : std::as_const(dataMembers)) {
- ExistingGetterSetterData existing;
- existing.memberVariableName = QString::fromUtf8(member->identifier()->chars(),
- member->identifier()->size());
- existing.declarationSymbol = member->asDeclaration();
- existing.clazz = theClass;
-
- // check if a Q_PROPERTY exist
- const QString baseName = memberBaseName(existing.memberVariableName);
- if (qPropertyNames.contains(baseName)
- || qPropertyNames.contains(existing.memberVariableName))
- continue;
-
- findExistingFunctions(existing, memberFunctionsAsStrings);
- existing.qPropertyName = baseName;
-
- int possibleFlags = existing.computePossibleFlags();
- if (possibleFlags == 0)
- continue;
- m_candidates.emplace_back(existing, possibleFlags);
- }
- }
-
- GetterSetterCandidates candidates() const { return m_candidates; }
- bool isApplicable() const { return !m_candidates.empty(); }
-
- void setGetterSetterData(const GetterSetterCandidates &data)
- {
- m_candidates = data;
- m_hasData = true;
- }
-
-private:
- void perform() override
- {
- if (!m_hasData) {
- GenerateGettersSettersDialog dlg(m_candidates);
- if (dlg.exec() == QDialog::Rejected)
- return;
- m_candidates = dlg.candidates();
- }
- if (m_candidates.empty())
- return;
- GetterSetterRefactoringHelper helper(this,
- currentFile()->filePath(),
- m_candidates.front().data.clazz);
- for (MemberInfo &mi : m_candidates) {
- if (mi.requestedFlags != 0) {
- helper.performGeneration(mi.data, mi.requestedFlags);
- }
- }
- helper.applyChanges();
- }
-
- GetterSetterCandidates m_candidates;
- const ClassSpecifierAST *m_classAST = nullptr;
- bool m_hasData = false;
-};
-
-void GenerateGettersSettersForClass::doMatch(const CppQuickFixInterface &interface,
- QuickFixOperations &result)
-{
- const auto op = QSharedPointer<GenerateGettersSettersOperation>::create(interface);
- if (!op->isApplicable())
- return;
- if (m_test) {
- GetterSetterCandidates candidates = op->candidates();
- for (MemberInfo &mi : candidates) {
- mi.requestedFlags = mi.possibleFlags;
- using Flag = GenerateGetterSetterOp::GenerateFlag;
- mi.requestedFlags &= ~Flag::GenerateConstantProperty;
- }
- op->setGetterSetterData(candidates);
- }
- result << op;
-}
-
-
-namespace {
-
-class ExtractFunctionOptions
-{
-public:
- static bool isValidFunctionName(const QString &name)
- {
- return !name.isEmpty() && isValidIdentifier(name);
- }
-
- bool hasValidFunctionName() const
- {
- return isValidFunctionName(funcName);
- }
-
- QString funcName;
- InsertionPointLocator::AccessSpec access = InsertionPointLocator::Public;
-};
-
-class ExtractFunctionOperation : public CppQuickFixOperation
-{
-public:
- ExtractFunctionOperation(const CppQuickFixInterface &interface,
- int extractionStart,
- int extractionEnd,
- FunctionDefinitionAST *refFuncDef,
- Symbol *funcReturn,
- QList<QPair<QString, QString> > relevantDecls,
- ExtractFunction::FunctionNameGetter functionNameGetter
- = ExtractFunction::FunctionNameGetter())
- : CppQuickFixOperation(interface)
- , m_extractionStart(extractionStart)
- , m_extractionEnd(extractionEnd)
- , m_refFuncDef(refFuncDef)
- , m_funcReturn(funcReturn)
- , m_relevantDecls(relevantDecls)
- , m_functionNameGetter(functionNameGetter)
- {
- setDescription(Tr::tr("Extract Function"));
- }
-
- void perform() override
- {
- QTC_ASSERT(!m_funcReturn || !m_relevantDecls.isEmpty(), return);
- CppRefactoringChanges refactoring(snapshot());
- CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath());
-
- ExtractFunctionOptions options;
- if (m_functionNameGetter)
- options.funcName = m_functionNameGetter();
- else
- options = getOptions();
-
- if (!options.hasValidFunctionName())
- return;
- const QString &funcName = options.funcName;
-
- Function *refFunc = m_refFuncDef->symbol;
-
- // We don't need to rewrite the type for declarations made inside the reference function,
- // since their scope will remain the same. Then we preserve the original spelling style.
- // However, we must do so for the return type in the definition.
- SubstitutionEnvironment env;
- env.setContext(context());
- env.switchScope(refFunc);
- ClassOrNamespace *targetCoN = context().lookupType(refFunc->enclosingScope());
- if (!targetCoN)
- targetCoN = context().globalNamespace();
- UseMinimalNames subs(targetCoN);
- env.enter(&subs);
-
- Overview printer = CppCodeStyleSettings::currentProjectCodeStyleOverview();
- Control *control = context().bindings()->control().get();
- QString funcDef;
- QString funcDecl; // We generate a declaration only in the case of a member function.
- QString funcCall;
-
- Class *matchingClass = isMemberFunction(context(), refFunc);
-
- // Write return type.
- if (!m_funcReturn) {
- funcDef.append(QLatin1String("void "));
- if (matchingClass)
- funcDecl.append(QLatin1String("void "));
- } else {
- const FullySpecifiedType &fullType = rewriteType(m_funcReturn->type(), &env, control);
- funcDef.append(printer.prettyType(fullType) + QLatin1Char(' '));
- funcDecl.append(printer.prettyType(m_funcReturn->type()) + QLatin1Char(' '));
- }
-
- // Write class qualification, if any.
- if (matchingClass) {
- const Scope *current = matchingClass;
- QVector<const Name *> classes{matchingClass->name()};
- while (current->enclosingScope()->asClass()) {
- current = current->enclosingScope()->asClass();
- classes.prepend(current->name());
- }
- while (current->enclosingScope() && current->enclosingScope()->asNamespace()) {
- current = current->enclosingScope()->asNamespace();
- if (current->name())
- classes.prepend(current->name());
- }
- for (const Name *n : classes) {
- const Name *name = rewriteName(n, &env, control);
- funcDef.append(printer.prettyName(name));
- funcDef.append(QLatin1String("::"));
- }
- }
-
- // Write the extracted function itself and its call.
- funcDef.append(funcName);
- if (matchingClass)
- funcDecl.append(funcName);
- funcCall.append(funcName);
- funcDef.append(QLatin1Char('('));
- if (matchingClass)
- funcDecl.append(QLatin1Char('('));
- funcCall.append(QLatin1Char('('));
- for (int i = m_funcReturn ? 1 : 0; i < m_relevantDecls.length(); ++i) {
- QPair<QString, QString> p = m_relevantDecls.at(i);
- funcCall.append(p.first);
- funcDef.append(p.second);
- if (matchingClass)
- funcDecl.append(p.second);
- if (i < m_relevantDecls.length() - 1) {
- funcCall.append(QLatin1String(", "));
- funcDef.append(QLatin1String(", "));
- if (matchingClass)
- funcDecl.append(QLatin1String(", "));
- }
- }
- funcDef.append(QLatin1Char(')'));
- if (matchingClass)
- funcDecl.append(QLatin1Char(')'));
- funcCall.append(QLatin1Char(')'));
- if (refFunc->isConst()) {
- funcDef.append(QLatin1String(" const"));
- funcDecl.append(QLatin1String(" const"));
- }
- funcDef.append(QLatin1String("\n{\n"));
- QString extract = currentFile->textOf(m_extractionStart, m_extractionEnd);
- extract.replace(QChar::ParagraphSeparator, QLatin1String("\n"));
- if (!extract.endsWith(QLatin1Char('\n')) && m_funcReturn)
- extract.append(QLatin1Char('\n'));
- funcDef.append(extract);
- if (matchingClass)
- funcDecl.append(QLatin1String(";\n"));
- if (m_funcReturn) {
- funcDef.append(QLatin1String("\nreturn ")
- + m_relevantDecls.at(0).first
- + QLatin1Char(';'));
- funcCall.prepend(m_relevantDecls.at(0).second + QLatin1String(" = "));
- }
- funcDef.append(QLatin1String("\n}\n\n"));
- funcDef.replace(QChar::ParagraphSeparator, QLatin1String("\n"));
- funcDef.prepend(inlinePrefix(currentFile->filePath()));
- funcCall.append(QLatin1Char(';'));
-
- // Do not insert right between the function and an associated comment.
- int position = currentFile->startOf(m_refFuncDef);
- const QList<Token> functionDoc = commentsForDeclaration(
- m_refFuncDef->symbol, m_refFuncDef, *currentFile->document(),
- currentFile->cppDocument());
- if (!functionDoc.isEmpty()) {
- position = currentFile->cppDocument()->translationUnit()->getTokenPositionInDocument(
- functionDoc.first(), currentFile->document());
- }
-
- ChangeSet change;
- change.insert(position, funcDef);
- change.replace(m_extractionStart, m_extractionEnd, funcCall);
- currentFile->setChangeSet(change);
- currentFile->apply();
-
- // Write declaration, if necessary.
- if (matchingClass) {
- InsertionPointLocator locator(refactoring);
- const FilePath filePath = FilePath::fromUtf8(matchingClass->fileName());
- const InsertionLocation &location =
- locator.methodDeclarationInClass(filePath, matchingClass, options.access);
- CppRefactoringFilePtr declFile = refactoring.cppFile(filePath);
- change.clear();
- position = declFile->position(location.line(), location.column());
- change.insert(position, location.prefix() + funcDecl + location.suffix());
- declFile->setChangeSet(change);
- declFile->apply();
- }
- }
-
- ExtractFunctionOptions getOptions() const
- {
- QDialog dlg(Core::ICore::dialogParent());
- dlg.setWindowTitle(Tr::tr("Extract Function Refactoring"));
- auto layout = new QFormLayout(&dlg);
-
- auto funcNameEdit = new FancyLineEdit;
- funcNameEdit->setValidationFunction([](FancyLineEdit *edit, QString *) {
- return ExtractFunctionOptions::isValidFunctionName(edit->text());
- });
- layout->addRow(Tr::tr("Function name"), funcNameEdit);
-
- auto accessCombo = new QComboBox;
- accessCombo->addItem(
- InsertionPointLocator::accessSpecToString(InsertionPointLocator::Public),
- InsertionPointLocator::Public);
- accessCombo->addItem(
- InsertionPointLocator::accessSpecToString(InsertionPointLocator::PublicSlot),
- InsertionPointLocator::PublicSlot);
- accessCombo->addItem(
- InsertionPointLocator::accessSpecToString(InsertionPointLocator::Protected),
- InsertionPointLocator::Protected);
- accessCombo->addItem(
- InsertionPointLocator::accessSpecToString(InsertionPointLocator::ProtectedSlot),
- InsertionPointLocator::ProtectedSlot);
- accessCombo->addItem(
- InsertionPointLocator::accessSpecToString(InsertionPointLocator::Private),
- InsertionPointLocator::Private);
- accessCombo->addItem(
- InsertionPointLocator::accessSpecToString(InsertionPointLocator::PrivateSlot),
- InsertionPointLocator::PrivateSlot);
- layout->addRow(Tr::tr("Access"), accessCombo);
-
- auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
- QObject::connect(buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept);
- QObject::connect(buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject);
- QPushButton *ok = buttonBox->button(QDialogButtonBox::Ok);
- ok->setEnabled(false);
- QObject::connect(funcNameEdit, &Utils::FancyLineEdit::validChanged,
- ok, &QPushButton::setEnabled);
- layout->addWidget(buttonBox);
-
- if (dlg.exec() == QDialog::Accepted) {
- ExtractFunctionOptions options;
- options.funcName = funcNameEdit->text();
- options.access = static_cast<InsertionPointLocator::AccessSpec>(accessCombo->
- currentData().toInt());
- return options;
- }
- return ExtractFunctionOptions();
- }
-
- int m_extractionStart;
- int m_extractionEnd;
- FunctionDefinitionAST *m_refFuncDef;
- Symbol *m_funcReturn;
- QList<QPair<QString, QString> > m_relevantDecls;
- ExtractFunction::FunctionNameGetter m_functionNameGetter;
-};
-
-QPair<QString, QString> assembleDeclarationData(const QString &specifiers, DeclaratorAST *decltr,
- const CppRefactoringFilePtr &file,
- const Overview &printer)
-{
- QTC_ASSERT(decltr, return (QPair<QString, QString>()));
- if (decltr->core_declarator
- && decltr->core_declarator->asDeclaratorId()
- && decltr->core_declarator->asDeclaratorId()->name) {
- QString decltrText = file->textOf(file->startOf(decltr),
- file->endOf(decltr->core_declarator));
- if (!decltrText.isEmpty()) {
- const QString &name = printer.prettyName(
- decltr->core_declarator->asDeclaratorId()->name->name);
- QString completeDecl = specifiers;
- if (!decltrText.contains(QLatin1Char(' ')))
- completeDecl.append(QLatin1Char(' ') + decltrText);
- else
- completeDecl.append(decltrText);
- return {name, completeDecl};
- }
- }
- return QPair<QString, QString>();
-}
-
-class FunctionExtractionAnalyser : public ASTVisitor
-{
-public:
- FunctionExtractionAnalyser(TranslationUnit *unit,
- const int selStart,
- const int selEnd,
- const CppRefactoringFilePtr &file,
- const Overview &printer)
- : ASTVisitor(unit)
- , m_done(false)
- , m_failed(false)
- , m_selStart(selStart)
- , m_selEnd(selEnd)
- , m_extractionStart(0)
- , m_extractionEnd(0)
- , m_file(file)
- , m_printer(printer)
- {}
-
- bool operator()(FunctionDefinitionAST *refFunDef)
- {
- accept(refFunDef);
-
- if (!m_failed && m_extractionStart == m_extractionEnd)
- m_failed = true;
-
- return !m_failed;
- }
-
- bool preVisit(AST *) override
- {
- return !m_done;
- }
-
- void statement(StatementAST *stmt)
- {
- if (!stmt)
- return;
-
- const int stmtStart = m_file->startOf(stmt);
- const int stmtEnd = m_file->endOf(stmt);
-
- if (stmtStart >= m_selEnd
- || (m_extractionStart && stmtEnd > m_selEnd)) {
- m_done = true;
- return;
- }
-
- if (stmtStart >= m_selStart && !m_extractionStart)
- m_extractionStart = stmtStart;
- if (stmtEnd > m_extractionEnd && m_extractionStart)
- m_extractionEnd = stmtEnd;
-
- accept(stmt);
- }
-
- bool visit(CaseStatementAST *stmt) override
- {
- statement(stmt->statement);
- return false;
- }
-
- bool visit(CompoundStatementAST *stmt) override
- {
- for (StatementListAST *it = stmt->statement_list; it; it = it->next) {
- statement(it->value);
- if (m_done)
- break;
- }
- return false;
- }
-
- bool visit(DoStatementAST *stmt) override
- {
- statement(stmt->statement);
- return false;
- }
-
- bool visit(ForeachStatementAST *stmt) override
- {
- statement(stmt->statement);
- return false;
- }
-
- bool visit(RangeBasedForStatementAST *stmt) override
- {
- statement(stmt->statement);
- return false;
- }
-
- bool visit(ForStatementAST *stmt) override
- {
- statement(stmt->initializer);
- if (!m_done)
- statement(stmt->statement);
- return false;
- }
-
- bool visit(IfStatementAST *stmt) override
- {
- statement(stmt->statement);
- if (!m_done)
- statement(stmt->else_statement);
- return false;
- }
-
- bool visit(TryBlockStatementAST *stmt) override
- {
- statement(stmt->statement);
- for (CatchClauseListAST *it = stmt->catch_clause_list; it; it = it->next) {
- statement(it->value);
- if (m_done)
- break;
- }
- return false;
- }
-
- bool visit(WhileStatementAST *stmt) override
- {
- statement(stmt->statement);
- return false;
- }
-
- bool visit(DeclarationStatementAST *declStmt) override
- {
- // We need to collect the declarations we see before the extraction or even inside it.
- // They might need to be used as either a parameter or return value. Actually, we could
- // still obtain their types from the local uses, but it's good to preserve the original
- // typing style.
- if (declStmt
- && declStmt->declaration
- && declStmt->declaration->asSimpleDeclaration()) {
- SimpleDeclarationAST *simpleDecl = declStmt->declaration->asSimpleDeclaration();
- if (simpleDecl->decl_specifier_list
- && simpleDecl->declarator_list) {
- const QString &specifiers =
- m_file->textOf(m_file->startOf(simpleDecl),
- m_file->endOf(simpleDecl->decl_specifier_list->lastValue()));
- for (DeclaratorListAST *decltrList = simpleDecl->declarator_list;
- decltrList;
- decltrList = decltrList->next) {
- const QPair<QString, QString> p =
- assembleDeclarationData(specifiers, decltrList->value, m_file, m_printer);
- if (!p.first.isEmpty())
- m_knownDecls.insert(p.first, p.second);
- }
- }
- }
-
- return false;
- }
-
- bool visit(ReturnStatementAST *) override
- {
- if (m_extractionStart) {
- m_done = true;
- m_failed = true;
- }
-
- return false;
- }
-
- bool m_done;
- bool m_failed;
- const int m_selStart;
- const int m_selEnd;
- int m_extractionStart;
- int m_extractionEnd;
- QHash<QString, QString> m_knownDecls;
- CppRefactoringFilePtr m_file;
- const Overview &m_printer;
-};
-
-} // anonymous namespace
-
-ExtractFunction::ExtractFunction(FunctionNameGetter functionNameGetter)
- : m_functionNameGetter(functionNameGetter)
-{
-}
-
-void ExtractFunction::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result)
-{
- const CppRefactoringFilePtr file = interface.currentFile();
-
- // TODO: Fix upstream and uncomment; see QTCREATORBUG-28030.
-// if (CppModelManager::usesClangd(file->editor()->textDocument())
-// && file->cppDocument()->languageFeatures().cxxEnabled) {
-// return;
-// }
-
- QTextCursor cursor = file->cursor();
- if (!cursor.hasSelection())
- return;
-
- const QList<AST *> &path = interface.path();
- FunctionDefinitionAST *refFuncDef = nullptr; // The "reference" function, which we will extract from.
- for (int i = path.size() - 1; i >= 0; --i) {
- refFuncDef = path.at(i)->asFunctionDefinition();
- if (refFuncDef)
- break;
- }
-
- if (!refFuncDef
- || !refFuncDef->function_body
- || !refFuncDef->function_body->asCompoundStatement()
- || !refFuncDef->function_body->asCompoundStatement()->statement_list
- || !refFuncDef->symbol
- || !refFuncDef->symbol->name()
- || refFuncDef->symbol->enclosingScope()->asTemplate() /* TODO: Templates... */) {
- return;
- }
-
- // Adjust selection ends.
- int selStart = cursor.selectionStart();
- int selEnd = cursor.selectionEnd();
- if (selStart > selEnd)
- std::swap(selStart, selEnd);
-
- Overview printer;
-
- // Analyze the content to be extracted, which consists of determining the statements
- // which are complete and collecting the declarations seen.
- FunctionExtractionAnalyser analyser(interface.semanticInfo().doc->translationUnit(),
- selStart, selEnd,
- file,
- printer);
- if (!analyser(refFuncDef))
- return;
-
- // We also need to collect the declarations of the parameters from the reference function.
- QSet<QString> refFuncParams;
- if (refFuncDef->declarator->postfix_declarator_list
- && refFuncDef->declarator->postfix_declarator_list->value
- && refFuncDef->declarator->postfix_declarator_list->value->asFunctionDeclarator()) {
- FunctionDeclaratorAST *funcDecltr =
- refFuncDef->declarator->postfix_declarator_list->value->asFunctionDeclarator();
- if (funcDecltr->parameter_declaration_clause
- && funcDecltr->parameter_declaration_clause->parameter_declaration_list) {
- for (ParameterDeclarationListAST *it =
- funcDecltr->parameter_declaration_clause->parameter_declaration_list;
- it;
- it = it->next) {
- ParameterDeclarationAST *paramDecl = it->value->asParameterDeclaration();
- if (paramDecl->declarator) {
- const QString &specifiers =
- file->textOf(file->startOf(paramDecl),
- file->endOf(paramDecl->type_specifier_list->lastValue()));
- const QPair<QString, QString> &p =
- assembleDeclarationData(specifiers, paramDecl->declarator,
- file, printer);
- if (!p.first.isEmpty()) {
- analyser.m_knownDecls.insert(p.first, p.second);
- refFuncParams.insert(p.first);
- }
- }
- }
- }
- }
-
- // Identify what would be parameters for the new function and its return value, if any.
- Symbol *funcReturn = nullptr;
- QList<QPair<QString, QString> > relevantDecls;
- const SemanticInfo::LocalUseMap localUses = interface.semanticInfo().localUses;
- for (auto it = localUses.cbegin(), end = localUses.cend(); it != end; ++it) {
- bool usedBeforeExtraction = false;
- bool usedAfterExtraction = false;
- bool usedInsideExtraction = false;
- const QList<SemanticInfo::Use> &uses = it.value();
- for (const SemanticInfo::Use &use : uses) {
- if (use.isInvalid())
- continue;
-
- const int position = file->position(use.line, use.column);
- if (position < analyser.m_extractionStart)
- usedBeforeExtraction = true;
- else if (position >= analyser.m_extractionEnd)
- usedAfterExtraction = true;
- else
- usedInsideExtraction = true;
- }
-
- const QString &name = printer.prettyName(it.key()->name());
-
- if ((usedBeforeExtraction && usedInsideExtraction)
- || (usedInsideExtraction && refFuncParams.contains(name))) {
- QTC_ASSERT(analyser.m_knownDecls.contains(name), return);
- relevantDecls.push_back({name, analyser.m_knownDecls.value(name)});
- }
-
- // We assume that the first use of a local corresponds to its declaration.
- if (usedInsideExtraction && usedAfterExtraction && !usedBeforeExtraction) {
- if (!funcReturn) {
- QTC_ASSERT(analyser.m_knownDecls.contains(name), return);
- // The return, if any, is stored as the first item in the list.
- relevantDecls.push_front({name, analyser.m_knownDecls.value(name)});
- funcReturn = it.key();
- } else {
- // Would require multiple returns. (Unless we do fancy things, as pointed below.)
- return;
- }
- }
- }
-
- // The current implementation doesn't try to be too smart since it preserves the original form
- // of the declarations. This might be or not the desired effect. An improvement would be to
- // let the user somehow customize the function interface.
- result << new ExtractFunctionOperation(interface,
- analyser.m_extractionStart,
- analyser.m_extractionEnd,
- refFuncDef, funcReturn, relevantDecls,
- m_functionNameGetter);
-}
-
-namespace {
-
-struct ReplaceLiteralsResult
-{
- Token token;
- QString literalText;
-};
-
-template <class T>
-class ReplaceLiterals : private ASTVisitor
-{
-public:
- ReplaceLiterals(const CppRefactoringFilePtr &file, ChangeSet *changes, T *literal)
- : ASTVisitor(file->cppDocument()->translationUnit()), m_file(file), m_changes(changes),
- m_literal(literal)
- {
- m_result.token = m_file->tokenAt(literal->firstToken());
- m_literalTokenText = m_result.token.spell();
- m_result.literalText = QLatin1String(m_literalTokenText);
- if (m_result.token.isCharLiteral()) {
- m_result.literalText.prepend(QLatin1Char('\''));
- m_result.literalText.append(QLatin1Char('\''));
- if (m_result.token.kind() == T_WIDE_CHAR_LITERAL)
- m_result.literalText.prepend(QLatin1Char('L'));
- else if (m_result.token.kind() == T_UTF16_CHAR_LITERAL)
- m_result.literalText.prepend(QLatin1Char('u'));
- else if (m_result.token.kind() == T_UTF32_CHAR_LITERAL)
- m_result.literalText.prepend(QLatin1Char('U'));
- } else if (m_result.token.isStringLiteral()) {
- m_result.literalText.prepend(QLatin1Char('"'));
- m_result.literalText.append(QLatin1Char('"'));
- if (m_result.token.kind() == T_WIDE_STRING_LITERAL)
- m_result.literalText.prepend(QLatin1Char('L'));
- else if (m_result.token.kind() == T_UTF16_STRING_LITERAL)
- m_result.literalText.prepend(QLatin1Char('u'));
- else if (m_result.token.kind() == T_UTF32_STRING_LITERAL)
- m_result.literalText.prepend(QLatin1Char('U'));
- }
- }
-
- ReplaceLiteralsResult apply(AST *ast)
- {
- ast->accept(this);
- return m_result;
- }
-
-private:
- bool visit(T *ast) override
- {
- if (ast != m_literal
- && strcmp(m_file->tokenAt(ast->firstToken()).spell(), m_literalTokenText) != 0) {
- return true;
- }
- int start, end;
- m_file->startAndEndOf(ast->firstToken(), &start, &end);
- m_changes->replace(start, end, QLatin1String("newParameter"));
- return true;
- }
-
- const CppRefactoringFilePtr &m_file;
- ChangeSet *m_changes;
- T *m_literal;
- const char *m_literalTokenText;
- ReplaceLiteralsResult m_result;
-};
-
-class ExtractLiteralAsParameterOp : public CppQuickFixOperation
-{
-public:
- ExtractLiteralAsParameterOp(const CppQuickFixInterface &interface, int priority,
- ExpressionAST *literal, FunctionDefinitionAST *function)
- : CppQuickFixOperation(interface, priority),
- m_literal(literal),
- m_functionDefinition(function)
- {
- setDescription(Tr::tr("Extract Constant as Function Parameter"));
- }
-
- struct FoundDeclaration
- {
- FunctionDeclaratorAST *ast = nullptr;
- CppRefactoringFilePtr file;
- };
-
- FoundDeclaration findDeclaration(const CppRefactoringChanges &refactoring,
- FunctionDefinitionAST *ast)
- {
- FoundDeclaration result;
- Function *func = ast->symbol;
- if (Class *matchingClass = isMemberFunction(context(), func)) {
- // Dealing with member functions
- const QualifiedNameId *qName = func->name()->asQualifiedNameId();
- for (Symbol *s = matchingClass->find(qName->identifier()); s; s = s->next()) {
- if (!s->name()
- || !qName->identifier()->match(s->identifier())
- || !s->type()->asFunctionType()
- || !s->type().match(func->type())
- || s->asFunction()) {
- continue;
- }
-
- const FilePath declFilePath = matchingClass->filePath();
- result.file = refactoring.cppFile(declFilePath);
- ASTPath astPath(result.file->cppDocument());
- const QList<AST *> path = astPath(s->line(), s->column());
- SimpleDeclarationAST *simpleDecl = nullptr;
- for (AST *node : path) {
- simpleDecl = node->asSimpleDeclaration();
- if (simpleDecl) {
- if (simpleDecl->symbols && !simpleDecl->symbols->next) {
- result.ast = functionDeclarator(simpleDecl);
- return result;
- }
- }
- }
-
- if (simpleDecl)
- break;
- }
- } else if (Namespace *matchingNamespace = isNamespaceFunction(context(), func)) {
- // Dealing with free functions and inline member functions.
- bool isHeaderFile;
- FilePath declFilePath = correspondingHeaderOrSource(filePath(), &isHeaderFile);
- if (!declFilePath.exists())
- return FoundDeclaration();
- result.file = refactoring.cppFile(declFilePath);
- if (!result.file)
- return FoundDeclaration();
- const LookupContext lc(result.file->cppDocument(), snapshot());
- const QList<LookupItem> candidates = lc.lookup(func->name(), matchingNamespace);
- for (const LookupItem &candidate : candidates) {
- if (Symbol *s = candidate.declaration()) {
- if (s->asDeclaration()) {
- ASTPath astPath(result.file->cppDocument());
- const QList<AST *> path = astPath(s->line(), s->column());
- for (AST *node : path) {
- SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration();
- if (simpleDecl) {
- result.ast = functionDeclarator(simpleDecl);
- return result;
- }
- }
- }
- }
- }
- }
- return result;
- }
-
- void perform() override
- {
- FunctionDeclaratorAST *functionDeclaratorOfDefinition
- = functionDeclarator(m_functionDefinition);
- const CppRefactoringChanges refactoring(snapshot());
- const CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath());
- deduceTypeNameOfLiteral(currentFile->cppDocument());
-
- ChangeSet changes;
- if (NumericLiteralAST *concreteLiteral = m_literal->asNumericLiteral()) {
- m_literalInfo = ReplaceLiterals<NumericLiteralAST>(currentFile, &changes,
- concreteLiteral)
- .apply(m_functionDefinition->function_body);
- } else if (StringLiteralAST *concreteLiteral = m_literal->asStringLiteral()) {
- m_literalInfo = ReplaceLiterals<StringLiteralAST>(currentFile, &changes,
- concreteLiteral)
- .apply(m_functionDefinition->function_body);
- } else if (BoolLiteralAST *concreteLiteral = m_literal->asBoolLiteral()) {
- m_literalInfo = ReplaceLiterals<BoolLiteralAST>(currentFile, &changes,
- concreteLiteral)
- .apply(m_functionDefinition->function_body);
- }
- const FoundDeclaration functionDeclaration
- = findDeclaration(refactoring, m_functionDefinition);
- appendFunctionParameter(functionDeclaratorOfDefinition, currentFile, &changes,
- !functionDeclaration.ast);
- if (functionDeclaration.ast) {
- if (currentFile->filePath() != functionDeclaration.file->filePath()) {
- ChangeSet declChanges;
- appendFunctionParameter(functionDeclaration.ast, functionDeclaration.file, &declChanges,
- true);
- functionDeclaration.file->setChangeSet(declChanges);
- functionDeclaration.file->apply();
- } else {
- appendFunctionParameter(functionDeclaration.ast, currentFile, &changes,
- true);
- }
- }
- currentFile->setChangeSet(changes);
- currentFile->apply();
- QTextCursor c = currentFile->cursor();
- c.setPosition(c.position() - parameterName().length());
- editor()->setTextCursor(c);
- editor()->renameSymbolUnderCursor();
- }
-
-private:
- bool hasParameters(FunctionDeclaratorAST *ast) const
- {
- return ast->parameter_declaration_clause
- && ast->parameter_declaration_clause->parameter_declaration_list
- && ast->parameter_declaration_clause->parameter_declaration_list->value;
- }
-
- void deduceTypeNameOfLiteral(const Document::Ptr &document)
- {
- TypeOfExpression typeOfExpression;
- typeOfExpression.init(document, snapshot());
- Overview overview;
- Scope *scope = m_functionDefinition->symbol->enclosingScope();
- const QList<LookupItem> items = typeOfExpression(m_literal, document, scope);
- if (!items.isEmpty())
- m_typeName = overview.prettyType(items.first().type());
- }
-
- static QString parameterName() { return QLatin1String("newParameter"); }
-
- QString parameterDeclarationTextToInsert(FunctionDeclaratorAST *ast) const
- {
- QString str;
- if (hasParameters(ast))
- str = QLatin1String(", ");
- str += m_typeName;
- if (!m_typeName.endsWith(QLatin1Char('*')))
- str += QLatin1Char(' ');
- str += parameterName();
- return str;
- }
-
- FunctionDeclaratorAST *functionDeclarator(SimpleDeclarationAST *ast) const
- {
- for (DeclaratorListAST *decls = ast->declarator_list; decls; decls = decls->next) {
- FunctionDeclaratorAST * const functionDeclaratorAST = functionDeclarator(decls->value);
- if (functionDeclaratorAST)
- return functionDeclaratorAST;
- }
- return nullptr;
- }
-
- FunctionDeclaratorAST *functionDeclarator(DeclaratorAST *ast) const
- {
- for (PostfixDeclaratorListAST *pds = ast->postfix_declarator_list; pds; pds = pds->next) {
- FunctionDeclaratorAST *funcdecl = pds->value->asFunctionDeclarator();
- if (funcdecl)
- return funcdecl;
- }
- return nullptr;
- }
-
- FunctionDeclaratorAST *functionDeclarator(FunctionDefinitionAST *ast) const
- {
- return functionDeclarator(ast->declarator);
- }
-
- void appendFunctionParameter(FunctionDeclaratorAST *ast, const CppRefactoringFileConstPtr &file,
- ChangeSet *changes, bool addDefaultValue)
- {
- if (!ast)
- return;
- if (m_declarationInsertionString.isEmpty())
- m_declarationInsertionString = parameterDeclarationTextToInsert(ast);
- QString insertion = m_declarationInsertionString;
- if (addDefaultValue)
- insertion += QLatin1String(" = ") + m_literalInfo.literalText;
- changes->insert(file->startOf(ast->rparen_token), insertion);
- }
-
- ExpressionAST *m_literal;
- FunctionDefinitionAST *m_functionDefinition;
- QString m_typeName;
- QString m_declarationInsertionString;
- ReplaceLiteralsResult m_literalInfo;
-};
-
-} // anonymous namespace
-
-void ExtractLiteralAsParameter::doMatch(const CppQuickFixInterface &interface,
- QuickFixOperations &result)
-{
- const QList<AST *> &path = interface.path();
- if (path.count() < 2)
- return;
-
- AST * const lastAst = path.last();
- ExpressionAST *literal;
- if (!((literal = lastAst->asNumericLiteral())
- || (literal = lastAst->asStringLiteral())
- || (literal = lastAst->asBoolLiteral()))) {
- return;
- }
-
- FunctionDefinitionAST *function;
- int i = path.count() - 2;
- while (!(function = path.at(i)->asFunctionDefinition())) {
- // Ignore literals in lambda expressions for now.
- if (path.at(i)->asLambdaExpression())
- return;
- if (--i < 0)
- return;
- }
-
- PostfixDeclaratorListAST * const declaratorList = function->declarator->postfix_declarator_list;
- if (!declaratorList)
- return;
- if (FunctionDeclaratorAST *declarator = declaratorList->value->asFunctionDeclarator()) {
- if (declarator->parameter_declaration_clause
- && declarator->parameter_declaration_clause->dot_dot_dot_token) {
- // Do not handle functions with ellipsis parameter.
- return;
- }
- }
-
- const int priority = path.size() - 1;
- result << new ExtractLiteralAsParameterOp(interface, priority, literal, function);
-}
-
-namespace {
-
-class ConvertFromAndToPointerOp : public CppQuickFixOperation
-{
-public:
- enum Mode { FromPointer, FromVariable, FromReference };
-
- ConvertFromAndToPointerOp(const CppQuickFixInterface &interface, int priority, Mode mode,
- bool isAutoDeclaration,
- const SimpleDeclarationAST *simpleDeclaration,
- const DeclaratorAST *declaratorAST,
- const SimpleNameAST *identifierAST,
- Symbol *symbol)
- : CppQuickFixOperation(interface, priority)
- , m_mode(mode)
- , m_isAutoDeclaration(isAutoDeclaration)
- , m_simpleDeclaration(simpleDeclaration)
- , m_declaratorAST(declaratorAST)
- , m_identifierAST(identifierAST)
- , m_symbol(symbol)
- , m_refactoring(snapshot())
- , m_file(m_refactoring.cppFile(filePath()))
- , m_document(interface.semanticInfo().doc)
- {
- setDescription(
- mode == FromPointer
- ? Tr::tr("Convert to Stack Variable")
- : Tr::tr("Convert to Pointer"));
- }
-
- void perform() override
- {
- ChangeSet changes;
-
- switch (m_mode) {
- case FromPointer:
- removePointerOperator(changes);
- convertToStackVariable(changes);
- break;
- case FromReference:
- removeReferenceOperator(changes);
- Q_FALLTHROUGH();
- case FromVariable:
- convertToPointer(changes);
- break;
- }
-
- m_file->setChangeSet(changes);
- m_file->apply();
- }
-
-private:
- void removePointerOperator(ChangeSet &changes) const
- {
- if (!m_declaratorAST->ptr_operator_list)
- return;
- PointerAST *ptrAST = m_declaratorAST->ptr_operator_list->value->asPointer();
- QTC_ASSERT(ptrAST, return);
- const int pos = m_file->startOf(ptrAST->star_token);
- changes.remove(pos, pos + 1);
- }
-
- void removeReferenceOperator(ChangeSet &changes) const
- {
- ReferenceAST *refAST = m_declaratorAST->ptr_operator_list->value->asReference();
- QTC_ASSERT(refAST, return);
- const int pos = m_file->startOf(refAST->reference_token);
- changes.remove(pos, pos + 1);
- }
-
- void removeNewExpression(ChangeSet &changes, NewExpressionAST *newExprAST) const
- {
- ExpressionListAST *exprlist = nullptr;
- if (newExprAST->new_initializer) {
- if (ExpressionListParenAST *ast = newExprAST->new_initializer->asExpressionListParen())
- exprlist = ast->expression_list;
- else if (BracedInitializerAST *ast = newExprAST->new_initializer->asBracedInitializer())
- exprlist = ast->expression_list;
- }
-
- if (exprlist) {
- // remove 'new' keyword and type before initializer
- changes.remove(m_file->startOf(newExprAST->new_token),
- m_file->startOf(newExprAST->new_initializer));
-
- changes.remove(m_file->endOf(m_declaratorAST->equal_token - 1),
- m_file->startOf(m_declaratorAST->equal_token + 1));
- } else {
- // remove the whole new expression
- changes.remove(m_file->endOf(m_identifierAST->firstToken()),
- m_file->startOf(newExprAST->lastToken()));
- }
- }
-
- void removeNewKeyword(ChangeSet &changes, NewExpressionAST *newExprAST) const
- {
- // remove 'new' keyword before initializer
- changes.remove(m_file->startOf(newExprAST->new_token),
- m_file->startOf(newExprAST->new_type_id));
- }
-
- void convertToStackVariable(ChangeSet &changes) const
- {
- // Handle the initializer.
- if (m_declaratorAST->initializer) {
- if (NewExpressionAST *newExpression = m_declaratorAST->initializer->asNewExpression()) {
- if (m_isAutoDeclaration) {
- if (!newExpression->new_initializer)
- changes.insert(m_file->endOf(newExpression), QStringLiteral("()"));
- removeNewKeyword(changes, newExpression);
- } else {
- removeNewExpression(changes, newExpression);
- }
- }
- }
-
- // Fix all occurrences of the identifier in this function.
- ASTPath astPath(m_document);
- const QList<SemanticInfo::Use> uses = semanticInfo().localUses.value(m_symbol);
- for (const SemanticInfo::Use &use : uses) {
- const QList<AST *> path = astPath(use.line, use.column);
- AST *idAST = path.last();
- bool declarationFound = false;
- bool starFound = false;
- int ampersandPos = 0;
- bool memberAccess = false;
- bool deleteCall = false;
-
- for (int i = path.count() - 2; i >= 0; --i) {
- if (path.at(i) == m_declaratorAST) {
- declarationFound = true;
- break;
- }
- if (MemberAccessAST *memberAccessAST = path.at(i)->asMemberAccess()) {
- if (m_file->tokenAt(memberAccessAST->access_token).kind() != T_ARROW)
- continue;
- int pos = m_file->startOf(memberAccessAST->access_token);
- changes.replace(pos, pos + 2, QLatin1String("."));
- memberAccess = true;
- break;
- } else if (DeleteExpressionAST *deleteAST = path.at(i)->asDeleteExpression()) {
- const int pos = m_file->startOf(deleteAST->delete_token);
- changes.insert(pos, QLatin1String("// "));
- deleteCall = true;
- break;
- } else if (UnaryExpressionAST *unaryExprAST = path.at(i)->asUnaryExpression()) {
- const Token tk = m_file->tokenAt(unaryExprAST->unary_op_token);
- if (tk.kind() == T_STAR) {
- if (!starFound) {
- int pos = m_file->startOf(unaryExprAST->unary_op_token);
- changes.remove(pos, pos + 1);
- }
- starFound = true;
- } else if (tk.kind() == T_AMPER) {
- ampersandPos = m_file->startOf(unaryExprAST->unary_op_token);
- }
- } else if (PointerAST *ptrAST = path.at(i)->asPointer()) {
- if (!starFound) {
- const int pos = m_file->startOf(ptrAST->star_token);
- changes.remove(pos, pos);
- }
- starFound = true;
- } else if (path.at(i)->asFunctionDefinition()) {
- break;
- }
- }
- if (!declarationFound && !starFound && !memberAccess && !deleteCall) {
- if (ampersandPos) {
- changes.insert(ampersandPos, QLatin1String("&("));
- changes.insert(m_file->endOf(idAST->firstToken()), QLatin1String(")"));
- } else {
- changes.insert(m_file->startOf(idAST), QLatin1String("&"));
- }
- }
- }
- }
-
- QString typeNameOfDeclaration() const
- {
- if (!m_simpleDeclaration
- || !m_simpleDeclaration->decl_specifier_list
- || !m_simpleDeclaration->decl_specifier_list->value) {
- return QString();
- }
- NamedTypeSpecifierAST *namedType
- = m_simpleDeclaration->decl_specifier_list->value->asNamedTypeSpecifier();
- if (!namedType)
- return QString();
-
- Overview overview;
- return overview.prettyName(namedType->name->name);
- }
-
- void insertNewExpression(ChangeSet &changes, ExpressionAST *ast) const
- {
- const QString typeName = typeNameOfDeclaration();
- if (CallAST *callAST = ast->asCall()) {
- if (typeName.isEmpty()) {
- changes.insert(m_file->startOf(callAST), QLatin1String("new "));
- } else {
- changes.insert(m_file->startOf(callAST),
- QLatin1String("new ") + typeName + QLatin1Char('('));
- changes.insert(m_file->startOf(callAST->lastToken()), QLatin1String(")"));
- }
- } else {
- if (typeName.isEmpty())
- return;
- changes.insert(m_file->startOf(ast), QLatin1String(" = new ") + typeName);
- }
- }
-
- void insertNewExpression(ChangeSet &changes) const
- {
- const QString typeName = typeNameOfDeclaration();
- if (typeName.isEmpty())
- return;
- changes.insert(m_file->endOf(m_identifierAST->firstToken()),
- QLatin1String(" = new ") + typeName);
- }
-
- void convertToPointer(ChangeSet &changes) const
- {
- // Handle initializer.
- if (m_declaratorAST->initializer) {
- if (IdExpressionAST *idExprAST = m_declaratorAST->initializer->asIdExpression()) {
- changes.insert(m_file->startOf(idExprAST), QLatin1String("&"));
- } else if (CallAST *callAST = m_declaratorAST->initializer->asCall()) {
- insertNewExpression(changes, callAST);
- } else if (ExpressionListParenAST *exprListAST = m_declaratorAST->initializer
- ->asExpressionListParen()) {
- insertNewExpression(changes, exprListAST);
- } else if (BracedInitializerAST *bracedInitializerAST = m_declaratorAST->initializer
- ->asBracedInitializer()) {
- insertNewExpression(changes, bracedInitializerAST);
- }
- } else {
- insertNewExpression(changes);
- }
-
- // Fix all occurrences of the identifier in this function.
- ASTPath astPath(m_document);
- const QList<SemanticInfo::Use> uses = semanticInfo().localUses.value(m_symbol);
- for (const SemanticInfo::Use &use : uses) {
- const QList<AST *> path = astPath(use.line, use.column);
- AST *idAST = path.last();
- bool insertStar = true;
- for (int i = path.count() - 2; i >= 0; --i) {
- if (m_isAutoDeclaration && path.at(i) == m_declaratorAST) {
- insertStar = false;
- break;
- }
- if (MemberAccessAST *memberAccessAST = path.at(i)->asMemberAccess()) {
- const int pos = m_file->startOf(memberAccessAST->access_token);
- changes.replace(pos, pos + 1, QLatin1String("->"));
- insertStar = false;
- break;
- } else if (UnaryExpressionAST *unaryExprAST = path.at(i)->asUnaryExpression()) {
- if (m_file->tokenAt(unaryExprAST->unary_op_token).kind() == T_AMPER) {
- const int pos = m_file->startOf(unaryExprAST->unary_op_token);
- changes.remove(pos, pos + 1);
- insertStar = false;
- break;
- }
- } else if (path.at(i)->asFunctionDefinition()) {
- break;
- }
- }
- if (insertStar)
- changes.insert(m_file->startOf(idAST), QLatin1String("*"));
- }
- }
-
- const Mode m_mode;
- const bool m_isAutoDeclaration;
- const SimpleDeclarationAST * const m_simpleDeclaration;
- const DeclaratorAST * const m_declaratorAST;
- const SimpleNameAST * const m_identifierAST;
- Symbol * const m_symbol;
- const CppRefactoringChanges m_refactoring;
- const CppRefactoringFilePtr m_file;
- const Document::Ptr m_document;
-};
-
-} // anonymous namespace
-
-void ConvertFromAndToPointer::doMatch(const CppQuickFixInterface &interface,
- QuickFixOperations &result)
-{
- const QList<AST *> &path = interface.path();
- if (path.count() < 2)
- return;
- SimpleNameAST *identifier = path.last()->asSimpleName();
- if (!identifier)
- return;
- SimpleDeclarationAST *simpleDeclaration = nullptr;
- DeclaratorAST *declarator = nullptr;
- bool isFunctionLocal = false;
- bool isClassLocal = false;
- ConvertFromAndToPointerOp::Mode mode = ConvertFromAndToPointerOp::FromVariable;
- for (int i = path.count() - 2; i >= 0; --i) {
- AST *ast = path.at(i);
- if (!declarator && (declarator = ast->asDeclarator()))
- continue;
- if (!simpleDeclaration && (simpleDeclaration = ast->asSimpleDeclaration()))
- continue;
- if (declarator && simpleDeclaration) {
- if (ast->asClassSpecifier()) {
- isClassLocal = true;
- } else if (ast->asFunctionDefinition() && !isClassLocal) {
- isFunctionLocal = true;
- break;
- }
- }
- }
- if (!isFunctionLocal || !simpleDeclaration || !declarator)
- return;
-
- Symbol *symbol = nullptr;
- for (List<Symbol *> *lst = simpleDeclaration->symbols; lst; lst = lst->next) {
- if (lst->value->name() == identifier->name) {
- symbol = lst->value;
- break;
- }
- }
- if (!symbol)
- return;
-
- bool isAutoDeclaration = false;
- if (symbol->storage() == Symbol::Auto) {
- // For auto variables we must deduce the type from the initializer.
- if (!declarator->initializer)
- return;
-
- isAutoDeclaration = true;
- TypeOfExpression typeOfExpression;
- typeOfExpression.init(interface.semanticInfo().doc, interface.snapshot());
- typeOfExpression.setExpandTemplates(true);
- CppRefactoringFilePtr file = interface.currentFile();
- Scope *scope = file->scopeAt(declarator->firstToken());
- QList<LookupItem> result = typeOfExpression(file->textOf(declarator->initializer).toUtf8(),
- scope, TypeOfExpression::Preprocess);
- if (!result.isEmpty() && result.first().type()->asPointerType())
- mode = ConvertFromAndToPointerOp::FromPointer;
- } else if (declarator->ptr_operator_list) {
- for (PtrOperatorListAST *ops = declarator->ptr_operator_list; ops; ops = ops->next) {
- if (ops != declarator->ptr_operator_list) {
- // Bail out on more complex pointer types (e.g. pointer of pointer,
- // or reference of pointer).
- return;
- }
- if (ops->value->asPointer())
- mode = ConvertFromAndToPointerOp::FromPointer;
- else if (ops->value->asReference())
- mode = ConvertFromAndToPointerOp::FromReference;
- }
- }
-
- const int priority = path.size() - 1;
- result << new ConvertFromAndToPointerOp(interface, priority, mode, isAutoDeclaration,
- simpleDeclaration, declarator, identifier, symbol);
-}
-
-namespace {
-
-void extractNames(const CppRefactoringFilePtr &file,
- QtPropertyDeclarationAST *qtPropertyDeclaration,
- ExistingGetterSetterData &data)
-{
- QtPropertyDeclarationItemListAST *it = qtPropertyDeclaration->property_declaration_item_list;
- for (; it; it = it->next) {
- const char *tokenString = file->tokenAt(it->value->item_name_token).spell();
- if (!qstrcmp(tokenString, "READ")) {
- data.getterName = file->textOf(it->value->expression);
- } else if (!qstrcmp(tokenString, "WRITE")) {
- data.setterName = file->textOf(it->value->expression);
- } else if (!qstrcmp(tokenString, "RESET")) {
- data.resetName = file->textOf(it->value->expression);
- } else if (!qstrcmp(tokenString, "NOTIFY")) {
- data.signalName = file->textOf(it->value->expression);
- } else if (!qstrcmp(tokenString, "MEMBER")) {
- data.memberVariableName = file->textOf(it->value->expression);
- }
- }
-}
-
-} // anonymous namespace
-
-void InsertQtPropertyMembers::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result)
-{
- using Flag = GenerateGetterSetterOp::GenerateFlag;
- ExistingGetterSetterData existing;
- // check for Q_PROPERTY
-
- const QList<AST *> &path = interface.path();
- if (path.isEmpty())
- return;
-
- AST *const ast = path.last();
- QtPropertyDeclarationAST *qtPropertyDeclaration = ast->asQtPropertyDeclaration();
- if (!qtPropertyDeclaration || !qtPropertyDeclaration->type_id)
- return;
-
- ClassSpecifierAST *klass = nullptr;
- for (int i = path.size() - 2; i >= 0; --i) {
- klass = path.at(i)->asClassSpecifier();
- if (klass)
- break;
- }
- if (!klass)
- return;
- existing.clazz = klass->symbol;
-
- CppRefactoringFilePtr file = interface.currentFile();
- const QString propertyName = file->textOf(qtPropertyDeclaration->property_name);
- existing.qPropertyName = propertyName;
- extractNames(file, qtPropertyDeclaration, existing);
-
- Control *control = interface.currentFile()->cppDocument()->control();
-
- existing.declarationSymbol = control->newDeclaration(ast->firstToken(),
- qtPropertyDeclaration->property_name->name);
- existing.declarationSymbol->setVisibility(Symbol::Private);
- existing.declarationSymbol->setEnclosingScope(existing.clazz);
-
- {
- // create a 'right' Type Object
- // if we have Q_PROPERTY(int test ...) then we only get a NamedType for 'int', but we want
- // a IntegerType. So create a new dummy file with a dummy declaration to get the right
- // object
- QByteArray type = file->textOf(qtPropertyDeclaration->type_id).toUtf8();
- QByteArray newSource = file->document()
- ->toPlainText()
- .insert(file->startOf(qtPropertyDeclaration),
- QString::fromUtf8(type + " __dummy;\n"))
- .toUtf8();
-
- Document::Ptr doc = interface.snapshot().preprocessedDocument(newSource, "___quickfix.h");
- if (!doc->parse(Document::ParseTranlationUnit))
- return;
- doc->check();
- class TypeFinder : public ASTVisitor
- {
- public:
- FullySpecifiedType type;
- TypeFinder(TranslationUnit *u)
- : ASTVisitor(u)
- {}
- bool visit(SimpleDeclarationAST *ast) override
- {
- if (ast->symbols && !ast->symbols->next) {
- const Name *name = ast->symbols->value->name();
- if (name && name->asNameId() && name->asNameId()->identifier()) {
- const Identifier *id = name->asNameId()->identifier();
- if (QString::fromUtf8(id->chars(), id->size()) == "__dummy")
- type = ast->symbols->value->type();
- }
- }
- return true;
- }
- };
- TypeFinder finder(doc->translationUnit());
- finder.accept(doc->translationUnit()->ast());
- if (finder.type.type()->isUndefinedType())
- return;
- existing.declarationSymbol->setType(finder.type);
- existing.doc = doc; // to hold type
- }
- // check which methods are already there
- const bool haveFixMemberVariableName = !existing.memberVariableName.isEmpty();
- int generateFlags = Flag::GenerateMemberVariable;
- if (!existing.resetName.isEmpty())
- generateFlags |= Flag::GenerateReset;
- if (!existing.setterName.isEmpty())
- generateFlags |= Flag::GenerateSetter;
- if (!existing.getterName.isEmpty())
- generateFlags |= Flag::GenerateGetter;
- if (!existing.signalName.isEmpty())
- generateFlags |= Flag::GenerateSignal;
- Overview overview;
- for (int i = 0; i < existing.clazz->memberCount(); ++i) {
- Symbol *member = existing.clazz->memberAt(i);
- FullySpecifiedType type = member->type();
- if (member->asFunction() || (type.isValid() && type->asFunctionType())) {
- const QString name = overview.prettyName(member->name());
- if (name == existing.getterName)
- generateFlags &= ~Flag::GenerateGetter;
- else if (name == existing.setterName)
- generateFlags &= ~Flag::GenerateSetter;
- else if (name == existing.resetName)
- generateFlags &= ~Flag::GenerateReset;
- else if (name == existing.signalName)
- generateFlags &= ~Flag::GenerateSignal;
- } else if (member->asDeclaration()) {
- const QString name = overview.prettyName(member->name());
- if (haveFixMemberVariableName) {
- if (name == existing.memberVariableName) {
- generateFlags &= ~Flag::GenerateMemberVariable;
- }
- } else {
- const QString baseName = memberBaseName(name);
- if (existing.qPropertyName == baseName) {
- existing.memberVariableName = name;
- generateFlags &= ~Flag::GenerateMemberVariable;
- }
- }
- }
- }
- if (generateFlags & Flag::GenerateMemberVariable) {
- CppQuickFixSettings *settings = CppQuickFixProjectsSettings::getQuickFixSettings(
- ProjectExplorer::ProjectTree::currentProject());
- existing.memberVariableName = settings->getMemberVariableName(existing.qPropertyName);
- }
- if (generateFlags == 0) {
- // everything is already there
- return;
- }
- generateFlags |= Flag::HaveExistingQProperty;
- GenerateGetterSetterOp::generateQuickFixes(result, interface, existing, generateFlags);
-}
-
-namespace {
-
-class ApplyDeclDefLinkOperation : public CppQuickFixOperation
-{
-public:
- explicit ApplyDeclDefLinkOperation(const CppQuickFixInterface &interface,
- const std::shared_ptr<FunctionDeclDefLink> &link)
- : CppQuickFixOperation(interface, 100)
- , m_link(link)
- {}
-
- void perform() override
- {
- if (editor()->declDefLink() == m_link)
- editor()->applyDeclDefLinkChanges(/*don't jump*/false);
- }
-
-protected:
- virtual void performChanges(const CppRefactoringFilePtr &, const CppRefactoringChanges &)
- { /* never called since perform is overridden */ }
-
-private:
- std::shared_ptr<FunctionDeclDefLink> m_link;
-};
-
-} // anonymous namespace
-
-void ApplyDeclDefLinkChanges::doMatch(const CppQuickFixInterface &interface,
- QuickFixOperations &result)
-{
- std::shared_ptr<FunctionDeclDefLink> link = interface.editor()->declDefLink();
- if (!link || !link->isMarkerVisible())
- return;
-
- auto op = new ApplyDeclDefLinkOperation(interface, link);
- op->setDescription(Tr::tr("Apply Function Signature Changes"));
- result << op;
-}
-
-namespace {
-
-QString definitionSignature(const CppQuickFixInterface *assist,
- FunctionDefinitionAST *functionDefinitionAST,
- CppRefactoringFilePtr &baseFile,
- CppRefactoringFilePtr &targetFile,
- Scope *scope)
-{
- QTC_ASSERT(assist, return QString());
- QTC_ASSERT(functionDefinitionAST, return QString());
- QTC_ASSERT(scope, return QString());
- Function *func = functionDefinitionAST->symbol;
- QTC_ASSERT(func, return QString());
-
- LookupContext cppContext(targetFile->cppDocument(), assist->snapshot());
- ClassOrNamespace *cppCoN = cppContext.lookupType(scope);
- if (!cppCoN)
- cppCoN = cppContext.globalNamespace();
- SubstitutionEnvironment env;
- env.setContext(assist->context());
- env.switchScope(func->enclosingScope());
- UseMinimalNames q(cppCoN);
- env.enter(&q);
- Control *control = assist->context().bindings()->control().get();
- Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview();
- oo.showFunctionSignatures = true;
- oo.showReturnTypes = true;
- oo.showArgumentNames = true;
- oo.showEnclosingTemplate = true;
- oo.showTemplateParameters = true;
- oo.trailingReturnType = functionDefinitionAST->declarator
- && functionDefinitionAST->declarator->postfix_declarator_list
- && functionDefinitionAST->declarator->postfix_declarator_list->value
- && functionDefinitionAST->declarator->postfix_declarator_list
- ->value->asFunctionDeclarator()
- && functionDefinitionAST->declarator->postfix_declarator_list
- ->value->asFunctionDeclarator()->trailing_return_type;
- const Name *name = func->name();
- if (name && nameIncludesOperatorName(name)) {
- CoreDeclaratorAST *coreDeclarator = functionDefinitionAST->declarator->core_declarator;
- const QString operatorNameText = baseFile->textOf(coreDeclarator);
- oo.includeWhiteSpaceInOperatorName = operatorNameText.contains(QLatin1Char(' '));
- }
- const QString nameText = oo.prettyName(LookupContext::minimalName(func, cppCoN, control));
- oo.showTemplateParameters = false;
- const FullySpecifiedType tn = rewriteType(func->type(), &env, control);
-
- return oo.prettyType(tn, nameText);
-}
-
-class MoveFuncDefRefactoringHelper
-{
-public:
- enum MoveType {
- MoveOutside,
- MoveToCppFile,
- MoveOutsideMemberToCppFile
- };
-
- MoveFuncDefRefactoringHelper(CppQuickFixOperation *operation, MoveType type,
- const FilePath &fromFile, const FilePath &toFile)
- : m_operation(operation), m_type(type), m_changes(m_operation->snapshot())
- {
- m_fromFile = m_changes.cppFile(fromFile);
- m_toFile = (m_type == MoveOutside) ? m_fromFile : m_changes.cppFile(toFile);
- }
-
- void performMove(FunctionDefinitionAST *funcAST)
- {
- // Determine file, insert position and scope
- InsertionLocation l = insertLocationForMethodDefinition(
- funcAST->symbol, false, NamespaceHandling::Ignore,
- m_changes, m_toFile->filePath());
- const QString prefix = l.prefix();
- const QString suffix = l.suffix();
- const int insertPos = m_toFile->position(l.line(), l.column());
- Scope *scopeAtInsertPos = m_toFile->cppDocument()->scopeAt(l.line(), l.column());
-
- // construct definition
- const QString funcDec = inlinePrefix(m_toFile->filePath(), [this] { return m_type == MoveOutside; })
- + definitionSignature(m_operation, funcAST, m_fromFile, m_toFile,
- scopeAtInsertPos);
- QString funcDef = prefix + funcDec;
- const int startPosition = m_fromFile->endOf(funcAST->declarator);
- const int endPosition = m_fromFile->endOf(funcAST);
- funcDef += m_fromFile->textOf(startPosition, endPosition);
- funcDef += suffix;
-
- // insert definition at new position
- m_toFileChangeSet.insert(insertPos, funcDef);
- m_toFile->setOpenEditor(true, insertPos);
-
- // remove definition from fromFile
- if (m_type == MoveOutsideMemberToCppFile) {
- m_fromFileChangeSet.remove(m_fromFile->range(funcAST));
- } else {
- QString textFuncDecl = m_fromFile->textOf(funcAST);
- textFuncDecl.truncate(startPosition - m_fromFile->startOf(funcAST));
- if (textFuncDecl.left(7) == QLatin1String("inline "))
- textFuncDecl = textFuncDecl.mid(7);
- else
- textFuncDecl.replace(" inline ", QLatin1String(" "));
- textFuncDecl = textFuncDecl.trimmed() + QLatin1Char(';');
- m_fromFileChangeSet.replace(m_fromFile->range(funcAST), textFuncDecl);
- }
- }
-
- void applyChanges()
- {
- if (!m_toFileChangeSet.isEmpty()) {
- m_toFile->setChangeSet(m_toFileChangeSet);
- m_toFile->apply();
- }
- if (!m_fromFileChangeSet.isEmpty()) {
- m_fromFile->setChangeSet(m_fromFileChangeSet);
- m_fromFile->apply();
- }
- }
-
-private:
- CppQuickFixOperation *m_operation;
- MoveType m_type;
- CppRefactoringChanges m_changes;
- CppRefactoringFilePtr m_fromFile;
- CppRefactoringFilePtr m_toFile;
- ChangeSet m_fromFileChangeSet;
- ChangeSet m_toFileChangeSet;
-};
-
-class MoveFuncDefOutsideOp : public CppQuickFixOperation
-{
-public:
- MoveFuncDefOutsideOp(const CppQuickFixInterface &interface,
- MoveFuncDefRefactoringHelper::MoveType type,
- FunctionDefinitionAST *funcDef, const FilePath &cppFilePath)
- : CppQuickFixOperation(interface, 0)
- , m_funcDef(funcDef)
- , m_type(type)
- , m_cppFilePath(cppFilePath)
- , m_headerFilePath(funcDef->symbol->filePath())
- {
- if (m_type == MoveFuncDefRefactoringHelper::MoveOutside) {
- setDescription(Tr::tr("Move Definition Outside Class"));
- } else {
- const FilePath resolved = m_cppFilePath.relativePathFrom(m_headerFilePath.parentDir());
- setDescription(Tr::tr("Move Definition to %1").arg(resolved.displayName()));
- }
- }
-
- void perform() override
- {
- MoveFuncDefRefactoringHelper helper(this, m_type, m_headerFilePath, m_cppFilePath);
- helper.performMove(m_funcDef);
- helper.applyChanges();
- }
-
-private:
- FunctionDefinitionAST *m_funcDef;
- MoveFuncDefRefactoringHelper::MoveType m_type;
- const FilePath m_cppFilePath;
- const FilePath m_headerFilePath;
-};
-
-} // anonymous namespace
-
-void MoveFuncDefOutside::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result)
-{
- const QList<AST *> &path = interface.path();
- SimpleDeclarationAST *classAST = nullptr;
- FunctionDefinitionAST *funcAST = nullptr;
- bool moveOutsideMemberDefinition = false;
-
- const int pathSize = path.size();
- for (int idx = 1; idx < pathSize; ++idx) {
- if ((funcAST = path.at(idx)->asFunctionDefinition())) {
- // check cursor position
- if (idx != pathSize - 1 // Do not allow "void a() @ {..."
- && funcAST->function_body
- && !interface.isCursorOn(funcAST->function_body)) {
- if (path.at(idx - 1)->asTranslationUnit()) { // normal function
- if (idx + 3 < pathSize && path.at(idx + 3)->asQualifiedName()) // Outside member
- moveOutsideMemberDefinition = true; // definition
- break;
- }
-
- if (idx > 1) {
- if ((classAST = path.at(idx - 2)->asSimpleDeclaration())) // member function
- break;
- if (path.at(idx - 2)->asNamespace()) // normal function in namespace
- break;
- }
- if (idx > 2 && path.at(idx - 1)->asTemplateDeclaration()) {
- if ((classAST = path.at(idx - 3)->asSimpleDeclaration())) // member template
- break;
- }
- }
- funcAST = nullptr;
- }
- }
-
- if (!funcAST || !funcAST->symbol)
- return;
-
- bool isHeaderFile = false;
- const FilePath cppFileName = correspondingHeaderOrSource(interface.filePath(), &isHeaderFile);
-
- if (isHeaderFile && !cppFileName.isEmpty()) {
- const MoveFuncDefRefactoringHelper::MoveType type = moveOutsideMemberDefinition
- ? MoveFuncDefRefactoringHelper::MoveOutsideMemberToCppFile
- : MoveFuncDefRefactoringHelper::MoveToCppFile;
- result << new MoveFuncDefOutsideOp(interface, type, funcAST, cppFileName);
- }
-
- if (classAST)
- result << new MoveFuncDefOutsideOp(interface, MoveFuncDefRefactoringHelper::MoveOutside,
- funcAST, FilePath());
-
- return;
-}
-
-namespace {
-
-class MoveAllFuncDefOutsideOp : public CppQuickFixOperation
-{
-public:
- MoveAllFuncDefOutsideOp(const CppQuickFixInterface &interface,
- MoveFuncDefRefactoringHelper::MoveType type,
- ClassSpecifierAST *classDef, const FilePath &cppFileName)
- : CppQuickFixOperation(interface, 0)
- , m_type(type)
- , m_classDef(classDef)
- , m_cppFilePath(cppFileName)
- , m_headerFilePath(classDef->symbol->filePath())
- {
- if (m_type == MoveFuncDefRefactoringHelper::MoveOutside) {
- setDescription(Tr::tr("Definitions Outside Class"));
- } else {
- const FilePath resolved = m_cppFilePath.relativePathFrom(m_headerFilePath.parentDir());
- setDescription(Tr::tr("Move All Function Definitions to %1")
- .arg(resolved.displayName()));
- }
- }
-
- void perform() override
- {
- MoveFuncDefRefactoringHelper helper(this, m_type, m_headerFilePath, m_cppFilePath);
- for (DeclarationListAST *it = m_classDef->member_specifier_list; it; it = it->next) {
- if (FunctionDefinitionAST *funcAST = it->value->asFunctionDefinition()) {
- if (funcAST->symbol && !funcAST->symbol->isGenerated())
- helper.performMove(funcAST);
- }
- }
- helper.applyChanges();
- }
-
-private:
- MoveFuncDefRefactoringHelper::MoveType m_type;
- ClassSpecifierAST *m_classDef;
- const FilePath m_cppFilePath;
- const FilePath m_headerFilePath;
-};
-
-} // anonymous namespace
-
-void MoveAllFuncDefOutside::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result)
-{
- ClassSpecifierAST * const classAST = astForClassOperations(interface);
- if (!classAST)
- return;
-
- // Determine if the class has at least one function definition
- bool classContainsFunctions = false;
- for (DeclarationListAST *it = classAST->member_specifier_list; it; it = it->next) {
- if (FunctionDefinitionAST *funcAST = it->value->asFunctionDefinition()) {
- if (funcAST->symbol && !funcAST->symbol->isGenerated()) {
- classContainsFunctions = true;
- break;
- }
- }
- }
- if (!classContainsFunctions)
- return;
-
- bool isHeaderFile = false;
- const FilePath cppFileName = correspondingHeaderOrSource(interface.filePath(), &isHeaderFile);
- if (isHeaderFile && !cppFileName.isEmpty()) {
- result << new MoveAllFuncDefOutsideOp(interface,
- MoveFuncDefRefactoringHelper::MoveToCppFile,
- classAST, cppFileName);
- }
- result << new MoveAllFuncDefOutsideOp(interface, MoveFuncDefRefactoringHelper::MoveOutside,
- classAST, FilePath());
-}
-
-namespace {
-
-class MoveFuncDefToDeclOp : public CppQuickFixOperation
-{
-public:
- enum Type { Push, Pull };
- MoveFuncDefToDeclOp(const CppQuickFixInterface &interface,
- const FilePath &fromFilePath, const FilePath &toFilePath,
- FunctionDefinitionAST *funcAst, Function *func, const QString &declText,
- const ChangeSet::Range &fromRange,
- const ChangeSet::Range &toRange,
- Type type)
- : CppQuickFixOperation(interface, 0)
- , m_fromFilePath(fromFilePath)
- , m_toFilePath(toFilePath)
- , m_funcAST(funcAst)
- , m_func(func)
- , m_declarationText(declText)
- , m_fromRange(fromRange)
- , m_toRange(toRange)
- {
- if (type == Type::Pull) {
- setDescription(Tr::tr("Move Definition Here"));
- } else if (m_toFilePath == m_fromFilePath) {
- setDescription(Tr::tr("Move Definition to Class"));
- } else {
- const FilePath resolved = m_toFilePath.relativePathFrom(m_fromFilePath.parentDir());
- setDescription(Tr::tr("Move Definition to %1").arg(resolved.displayName()));
- }
- }
-
-private:
- void perform() override
- {
- CppRefactoringChanges refactoring(snapshot());
- CppRefactoringFilePtr fromFile = refactoring.cppFile(m_fromFilePath);
- CppRefactoringFilePtr toFile = refactoring.cppFile(m_toFilePath);
-
- ensureFuncDefAstAndRange(*fromFile);
- if (!m_funcAST)
- return;
-
- const QString wholeFunctionText = m_declarationText
- + fromFile->textOf(fromFile->endOf(m_funcAST->declarator),
- fromFile->endOf(m_funcAST->function_body));
-
- // Replace declaration with function and delete old definition
- ChangeSet toTarget;
- toTarget.replace(m_toRange, wholeFunctionText);
- if (m_toFilePath == m_fromFilePath)
- toTarget.remove(m_fromRange);
- toFile->setChangeSet(toTarget);
- toFile->setOpenEditor(true, m_toRange.start);
- toFile->apply();
- if (m_toFilePath != m_fromFilePath) {
- ChangeSet fromTarget;
- fromTarget.remove(m_fromRange);
- fromFile->setChangeSet(fromTarget);
- fromFile->apply();
- }
- }
-
- void ensureFuncDefAstAndRange(CppRefactoringFile &defFile)
- {
- if (m_funcAST) {
- QTC_CHECK(m_fromRange.end > m_fromRange.start);
- return;
- }
- QTC_ASSERT(m_func, return);
- const QList<AST *> astPath = ASTPath(defFile.cppDocument())(m_func->line(),
- m_func->column());
- if (astPath.isEmpty())
- return;
- for (auto it = std::rbegin(astPath); it != std::rend(astPath); ++it) {
- m_funcAST = (*it)->asFunctionDefinition();
- if (!m_funcAST)
- continue;
- AST *astForRange = m_funcAST;
- const auto prev = std::next(it);
- if (prev != std::rend(astPath)) {
- if (const auto templAst = (*prev)->asTemplateDeclaration())
- astForRange = templAst;
- }
- m_fromRange = defFile.range(astForRange);
- return;
- }
- }
-
- const FilePath m_fromFilePath;
- const FilePath m_toFilePath;
- FunctionDefinitionAST *m_funcAST;
- Function *m_func;
- const QString m_declarationText;
- ChangeSet::Range m_fromRange;
- const ChangeSet::Range m_toRange;
-};
-
-} // anonymous namespace
-
-void MoveFuncDefToDeclPush::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result)
-{
- const QList<AST *> &path = interface.path();
- AST *completeDefAST = nullptr;
- FunctionDefinitionAST *funcAST = nullptr;
-
- const int pathSize = path.size();
- for (int idx = 1; idx < pathSize; ++idx) {
- if ((funcAST = path.at(idx)->asFunctionDefinition())) {
- AST *enclosingAST = path.at(idx - 1);
- if (enclosingAST->asClassSpecifier())
- return;
-
- // check cursor position
- if (idx != pathSize - 1 // Do not allow "void a() @ {..."
- && funcAST->function_body
- && !interface.isCursorOn(funcAST->function_body)) {
- completeDefAST = enclosingAST->asTemplateDeclaration() ? enclosingAST : funcAST;
- break;
- }
- funcAST = nullptr;
- }
- }
-
- if (!funcAST || !funcAST->symbol)
- return;
-
- const CppRefactoringChanges refactoring(interface.snapshot());
- const CppRefactoringFilePtr defFile = refactoring.cppFile(interface.filePath());
- const ChangeSet::Range defRange = defFile->range(completeDefAST);
-
- // Determine declaration (file, range, text);
- ChangeSet::Range declRange;
- QString declText;
- FilePath declFilePath;
-
- Function *func = funcAST->symbol;
- if (Class *matchingClass = isMemberFunction(interface.context(), func)) {
- // Dealing with member functions
- const QualifiedNameId *qName = func->name()->asQualifiedNameId();
- for (Symbol *symbol = matchingClass->find(qName->identifier());
- symbol; symbol = symbol->next()) {
- Symbol *s = symbol;
- if (func->enclosingScope()->asTemplate()) {
- if (const Template *templ = s->type()->asTemplateType()) {
- if (Symbol *decl = templ->declaration()) {
- if (decl->type()->asFunctionType())
- s = decl;
- }
- }
- }
- if (!s->name()
- || !qName->identifier()->match(s->identifier())
- || !s->type()->asFunctionType()
- || !s->type().match(func->type())
- || s->asFunction()) {
- continue;
- }
-
- declFilePath = matchingClass->filePath();
- const CppRefactoringFilePtr declFile = refactoring.cppFile(declFilePath);
- ASTPath astPath(declFile->cppDocument());
- const QList<AST *> path = astPath(s->line(), s->column());
- for (int idx = path.size() - 1; idx > 0; --idx) {
- AST *node = path.at(idx);
- if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) {
- if (simpleDecl->symbols && !simpleDecl->symbols->next) {
- declRange = declFile->range(simpleDecl);
- declText = declFile->textOf(simpleDecl);
- declText.remove(-1, 1); // remove ';' from declaration text
- break;
- }
- }
- }
-
- if (!declText.isEmpty())
- break;
- }
- } else if (Namespace *matchingNamespace = isNamespaceFunction(interface.context(), func)) {
- // Dealing with free functions
- bool isHeaderFile = false;
- declFilePath = correspondingHeaderOrSource(interface.filePath(), &isHeaderFile);
- if (isHeaderFile)
- return;
-
- const CppRefactoringFilePtr declFile = refactoring.cppFile(declFilePath);
- const LookupContext lc(declFile->cppDocument(), interface.snapshot());
- const QList<LookupItem> candidates = lc.lookup(func->name(), matchingNamespace);
- for (const LookupItem &candidate : candidates) {
- if (Symbol *s = candidate.declaration()) {
- if (s->asDeclaration()) {
- ASTPath astPath(declFile->cppDocument());
- const QList<AST *> path = astPath(s->line(), s->column());
- for (AST *node : path) {
- if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) {
- declRange = declFile->range(simpleDecl);
- declText = declFile->textOf(simpleDecl);
- declText.remove(-1, 1); // remove ';' from declaration text
- break;
- }
- }
- }
- }
-
- if (!declText.isEmpty()) {
- declText.prepend(inlinePrefix(declFilePath));
- break;
- }
- }
- }
-
- if (!declFilePath.isEmpty() && !declText.isEmpty())
- result << new MoveFuncDefToDeclOp(interface,
- interface.filePath(),
- declFilePath,
- funcAST, func, declText,
- defRange, declRange, MoveFuncDefToDeclOp::Push);
-}
-
-void MoveFuncDefToDeclPull::doMatch(const CppQuickFixInterface &interface,
- QuickFixOperations &result)
-{
- const QList<AST *> &path = interface.path();
- for (auto it = std::rbegin(path); it != std::rend(path); ++it) {
- SimpleDeclarationAST * const simpleDecl = (*it)->asSimpleDeclaration();
- if (!simpleDecl)
- continue;
- const auto prev = std::next(it);
- if (prev != std::rend(path) && (*prev)->asStatement())
- return;
- if (!simpleDecl->symbols || !simpleDecl->symbols->value || simpleDecl->symbols->next)
- return;
- Declaration * const decl = simpleDecl->symbols->value->asDeclaration();
- if (!decl)
- return;
- Function * const funcDecl = decl->type()->asFunctionType();
- if (!funcDecl)
- return;
- if (funcDecl->isSignal() || funcDecl->isPureVirtual() || funcDecl->isFriend())
- return;
-
- // Is there a definition?
- SymbolFinder symbolFinder;
- Function * const funcDef = symbolFinder.findMatchingDefinition(decl, interface.snapshot(),
- true);
- if (!funcDef)
- return;
-
- QString declText = interface.currentFile()->textOf(simpleDecl);
- declText.chop(1); // semicolon
- declText.prepend(inlinePrefix(interface.filePath(), [funcDecl] {
- return !funcDecl->enclosingScope()->asClass();
- }));
- result << new MoveFuncDefToDeclOp(interface, funcDef->filePath(), decl->filePath(), nullptr,
- funcDef, declText, {},
- interface.currentFile()->range(simpleDecl),
- MoveFuncDefToDeclOp::Pull);
- return;
- }
-}
-
-
-namespace {
-
-class AssignToLocalVariableOperation : public CppQuickFixOperation
-{
-public:
- explicit AssignToLocalVariableOperation(const CppQuickFixInterface &interface,
- const int insertPos, const AST *ast, const Name *name)
- : CppQuickFixOperation(interface)
- , m_insertPos(insertPos)
- , m_ast(ast)
- , m_name(name)
- , m_oo(CppCodeStyleSettings::currentProjectCodeStyleOverview())
- , m_originalName(m_oo.prettyName(m_name))
- , m_file(CppRefactoringChanges(snapshot()).cppFile(filePath()))
- {
- setDescription(Tr::tr("Assign to Local Variable"));
- }
-
-private:
- void perform() override
- {
- QString type = deduceType();
- if (type.isEmpty())
- return;
- const int origNameLength = m_originalName.length();
- const QString varName = constructVarName();
- const QString insertString = type.replace(type.length() - origNameLength, origNameLength,
- varName + QLatin1String(" = "));
- ChangeSet changes;
- changes.insert(m_insertPos, insertString);
- m_file->setChangeSet(changes);
- m_file->apply();
-
- // move cursor to new variable name
- QTextCursor c = m_file->cursor();
- c.setPosition(m_insertPos + insertString.length() - varName.length() - 3);
- c.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
- editor()->setTextCursor(c);
- }
-
- QString deduceType() const
- {
- const auto settings = CppQuickFixProjectsSettings::getQuickFixSettings(
- ProjectExplorer::ProjectTree::currentProject());
- if (m_file->cppDocument()->languageFeatures().cxx11Enabled && settings->useAuto)
- return "auto " + m_originalName;
-
- TypeOfExpression typeOfExpression;
- typeOfExpression.init(semanticInfo().doc, snapshot(), context().bindings());
- typeOfExpression.setExpandTemplates(true);
- Scope * const scope = m_file->scopeAt(m_ast->firstToken());
- const QList<LookupItem> result = typeOfExpression(m_file->textOf(m_ast).toUtf8(),
- scope, TypeOfExpression::Preprocess);
- if (result.isEmpty())
- return {};
-
- SubstitutionEnvironment env;
- env.setContext(context());
- env.switchScope(result.first().scope());
- ClassOrNamespace *con = typeOfExpression.context().lookupType(scope);
- if (!con)
- con = typeOfExpression.context().globalNamespace();
- UseMinimalNames q(con);
- env.enter(&q);
-
- Control *control = context().bindings()->control().get();
- FullySpecifiedType type = rewriteType(result.first().type(), &env, control);
-
- return m_oo.prettyType(type, m_name);
- }
-
- QString constructVarName() const
- {
- QString newName = m_originalName;
- if (newName.startsWith(QLatin1String("get"), Qt::CaseInsensitive)
- && newName.length() > 3
- && newName.at(3).isUpper()) {
- newName.remove(0, 3);
- newName.replace(0, 1, newName.at(0).toLower());
- } else if (newName.startsWith(QLatin1String("to"), Qt::CaseInsensitive)
- && newName.length() > 2
- && newName.at(2).isUpper()) {
- newName.remove(0, 2);
- newName.replace(0, 1, newName.at(0).toLower());
- } else {
- newName.replace(0, 1, newName.at(0).toUpper());
- newName.prepend(QLatin1String("local"));
- }
- return newName;
- }
-
- const int m_insertPos;
- const AST * const m_ast;
- const Name * const m_name;
- const Overview m_oo;
- const QString m_originalName;
- const CppRefactoringFilePtr m_file;
-};
-
-} // anonymous namespace
-
-void AssignToLocalVariable::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result)
-{
- const QList<AST *> &path = interface.path();
- AST *outerAST = nullptr;
- SimpleNameAST *nameAST = nullptr;
-
- for (int i = path.size() - 3; i >= 0; --i) {
- if (CallAST *callAST = path.at(i)->asCall()) {
- if (!interface.isCursorOn(callAST))
- return;
- if (i - 2 >= 0) {
- const int idx = i - 2;
- if (path.at(idx)->asSimpleDeclaration())
- return;
- if (path.at(idx)->asExpressionStatement())
- return;
- if (path.at(idx)->asMemInitializer())
- return;
- if (path.at(idx)->asCall()) { // Fallback if we have a->b()->c()...
- --i;
- continue;
- }
- }
- for (int a = i - 1; a > 0; --a) {
- if (path.at(a)->asBinaryExpression())
- return;
- if (path.at(a)->asReturnStatement())
- return;
- if (path.at(a)->asCall())
- return;
- }
-
- if (MemberAccessAST *member = path.at(i + 1)->asMemberAccess()) { // member
- if (NameAST *name = member->member_name)
- nameAST = name->asSimpleName();
- } else if (QualifiedNameAST *qname = path.at(i + 2)->asQualifiedName()) { // static or
- nameAST = qname->unqualified_name->asSimpleName(); // func in ns
- } else { // normal
- nameAST = path.at(i + 2)->asSimpleName();
- }
-
- if (nameAST) {
- outerAST = callAST;
- break;
- }
- } else if (NewExpressionAST *newexp = path.at(i)->asNewExpression()) {
- if (!interface.isCursorOn(newexp))
- return;
- if (i - 2 >= 0) {
- const int idx = i - 2;
- if (path.at(idx)->asSimpleDeclaration())
- return;
- if (path.at(idx)->asExpressionStatement())
- return;
- if (path.at(idx)->asMemInitializer())
- return;
- }
- for (int a = i - 1; a > 0; --a) {
- if (path.at(a)->asReturnStatement())
- return;
- if (path.at(a)->asCall())
- return;
- }
-
- if (NamedTypeSpecifierAST *ts = path.at(i + 2)->asNamedTypeSpecifier()) {
- nameAST = ts->name->asSimpleName();
- outerAST = newexp;
- break;
- }
- }
- }
-
- if (outerAST && nameAST) {
- const CppRefactoringFilePtr file = interface.currentFile();
- QList<LookupItem> items;
- TypeOfExpression typeOfExpression;
- typeOfExpression.init(interface.semanticInfo().doc, interface.snapshot(),
- interface.context().bindings());
- typeOfExpression.setExpandTemplates(true);
-
- // If items are empty, AssignToLocalVariableOperation will fail.
- items = typeOfExpression(file->textOf(outerAST).toUtf8(),
- file->scopeAt(outerAST->firstToken()),
- TypeOfExpression::Preprocess);
- if (items.isEmpty())
- return;
-
- if (CallAST *callAST = outerAST->asCall()) {
- items = typeOfExpression(file->textOf(callAST->base_expression).toUtf8(),
- file->scopeAt(callAST->base_expression->firstToken()),
- TypeOfExpression::Preprocess);
- } else {
- items = typeOfExpression(file->textOf(nameAST).toUtf8(),
- file->scopeAt(nameAST->firstToken()),
- TypeOfExpression::Preprocess);
- }
-
- for (const LookupItem &item : std::as_const(items)) {
- if (!item.declaration())
- continue;
-
- if (Function *func = item.declaration()->asFunction()) {
- if (func->isSignal() || func->returnType()->asVoidType())
- return;
- } else if (Declaration *dec = item.declaration()->asDeclaration()) {
- if (Function *func = dec->type()->asFunctionType()) {
- if (func->isSignal() || func->returnType()->asVoidType())
- return;
- }
- }
-
- const Name *name = nameAST->name;
- const int insertPos = interface.currentFile()->startOf(outerAST);
- result << new AssignToLocalVariableOperation(interface, insertPos, outerAST, name);
- return;
- }
- }
-}
-
-namespace {
-
-class OptimizeForLoopOperation: public CppQuickFixOperation
-{
-public:
- OptimizeForLoopOperation(const CppQuickFixInterface &interface, const ForStatementAST *forAst,
- const bool optimizePostcrement, const ExpressionAST *expression,
- const FullySpecifiedType &type)
- : CppQuickFixOperation(interface)
- , m_forAst(forAst)
- , m_optimizePostcrement(optimizePostcrement)
- , m_expression(expression)
- , m_type(type)
- {
- setDescription(Tr::tr("Optimize for-Loop"));
- }
-
- void perform() override
- {
- QTC_ASSERT(m_forAst, return);
-
- const Utils::FilePath filePath = currentFile()->filePath();
- const CppRefactoringChanges refactoring(snapshot());
- const CppRefactoringFilePtr file = refactoring.cppFile(filePath);
- ChangeSet change;
-
- // Optimize post (in|de)crement operator to pre (in|de)crement operator
- if (m_optimizePostcrement && m_forAst->expression) {
- PostIncrDecrAST *incrdecr = m_forAst->expression->asPostIncrDecr();
- if (incrdecr && incrdecr->base_expression && incrdecr->incr_decr_token) {
- change.flip(file->range(incrdecr->base_expression),
- file->range(incrdecr->incr_decr_token));
- }
- }
-
- // Optimize Condition
- int renamePos = -1;
- if (m_expression) {
- QString varName = QLatin1String("total");
-
- if (file->textOf(m_forAst->initializer).length() == 1) {
- Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview();
- const QString typeAndName = oo.prettyType(m_type, varName);
- renamePos = file->endOf(m_forAst->initializer) - 1 + typeAndName.length();
- change.insert(file->endOf(m_forAst->initializer) - 1, // "-1" because of ";"
- typeAndName + QLatin1String(" = ") + file->textOf(m_expression));
- } else {
- // Check if varName is already used
- if (DeclarationStatementAST *ds = m_forAst->initializer->asDeclarationStatement()) {
- if (DeclarationAST *decl = ds->declaration) {
- if (SimpleDeclarationAST *sdecl = decl->asSimpleDeclaration()) {
- for (;;) {
- bool match = false;
- for (DeclaratorListAST *it = sdecl->declarator_list; it;
- it = it->next) {
- if (file->textOf(it->value->core_declarator) == varName) {
- varName += QLatin1Char('X');
- match = true;
- break;
- }
- }
- if (!match)
- break;
- }
- }
- }
- }
-
- renamePos = file->endOf(m_forAst->initializer) + 1;
- change.insert(file->endOf(m_forAst->initializer) - 1, // "-1" because of ";"
- QLatin1String(", ") + varName + QLatin1String(" = ")
- + file->textOf(m_expression));
- }
-
- ChangeSet::Range exprRange(file->startOf(m_expression), file->endOf(m_expression));
- change.replace(exprRange, varName);
- }
-
- file->setChangeSet(change);
- file->apply();
-
- // Select variable name and trigger symbol rename
- if (renamePos != -1) {
- QTextCursor c = file->cursor();
- c.setPosition(renamePos);
- editor()->setTextCursor(c);
- editor()->renameSymbolUnderCursor();
- c.select(QTextCursor::WordUnderCursor);
- editor()->setTextCursor(c);
- }
- }
-
-private:
- const ForStatementAST *m_forAst;
- const bool m_optimizePostcrement;
- const ExpressionAST *m_expression;
- const FullySpecifiedType m_type;
-};
-
-} // anonymous namespace
-
-void OptimizeForLoop::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result)
-{
- const QList<AST *> path = interface.path();
- ForStatementAST *forAst = nullptr;
- if (!path.isEmpty())
- forAst = path.last()->asForStatement();
- if (!forAst || !interface.isCursorOn(forAst))
- return;
-
- // Check for optimizing a postcrement
- const CppRefactoringFilePtr file = interface.currentFile();
- bool optimizePostcrement = false;
- if (forAst->expression) {
- if (PostIncrDecrAST *incrdecr = forAst->expression->asPostIncrDecr()) {
- const Token t = file->tokenAt(incrdecr->incr_decr_token);
- if (t.is(T_PLUS_PLUS) || t.is(T_MINUS_MINUS))
- optimizePostcrement = true;
- }
- }
-
- // Check for optimizing condition
- bool optimizeCondition = false;
- FullySpecifiedType conditionType;
- ExpressionAST *conditionExpression = nullptr;
- if (forAst->initializer && forAst->condition) {
- if (BinaryExpressionAST *binary = forAst->condition->asBinaryExpression()) {
- // Get the expression against which we should evaluate
- IdExpressionAST *conditionId = binary->left_expression->asIdExpression();
- if (conditionId) {
- conditionExpression = binary->right_expression;
- } else {
- conditionId = binary->right_expression->asIdExpression();
- conditionExpression = binary->left_expression;
- }
-
- if (conditionId && conditionExpression
- && !(conditionExpression->asNumericLiteral()
- || conditionExpression->asStringLiteral()
- || conditionExpression->asIdExpression()
- || conditionExpression->asUnaryExpression())) {
- // Determine type of for initializer
- FullySpecifiedType initializerType;
- if (DeclarationStatementAST *stmt = forAst->initializer->asDeclarationStatement()) {
- if (stmt->declaration) {
- if (SimpleDeclarationAST *decl = stmt->declaration->asSimpleDeclaration()) {
- if (decl->symbols) {
- if (Symbol *symbol = decl->symbols->value)
- initializerType = symbol->type();
- }
- }
- }
- }
-
- // Determine type of for condition
- TypeOfExpression typeOfExpression;
- typeOfExpression.init(interface.semanticInfo().doc, interface.snapshot(),
- interface.context().bindings());
- typeOfExpression.setExpandTemplates(true);
- Scope *scope = file->scopeAt(conditionId->firstToken());
- const QList<LookupItem> conditionItems = typeOfExpression(
- conditionId, interface.semanticInfo().doc, scope);
- if (!conditionItems.isEmpty())
- conditionType = conditionItems.first().type();
-
- if (conditionType.isValid()
- && (file->textOf(forAst->initializer) == QLatin1String(";")
- || initializerType == conditionType)) {
- optimizeCondition = true;
- }
- }
- }
- }
-
- if (optimizePostcrement || optimizeCondition) {
- result << new OptimizeForLoopOperation(interface, forAst, optimizePostcrement,
- optimizeCondition ? conditionExpression : nullptr,
- conditionType);
- }
-}
-
-namespace {
-
-class EscapeStringLiteralOperation: public CppQuickFixOperation
-{
-public:
- EscapeStringLiteralOperation(const CppQuickFixInterface &interface,
- ExpressionAST *literal, bool escape)
- : CppQuickFixOperation(interface)
- , m_literal(literal)
- , m_escape(escape)
- {
- if (m_escape) {
- setDescription(Tr::tr("Escape String Literal as UTF-8"));
- } else {
- setDescription(Tr::tr("Unescape String Literal as UTF-8"));
- }
- }
-
-private:
- static inline bool isDigit(quint8 ch, int base)
- {
- if (base == 8)
- return ch >= '0' && ch < '8';
- if (base == 16)
- return isxdigit(ch);
- return false;
- }
-
- static QByteArrayList escapeString(const QByteArray &contents)
- {
- QByteArrayList newContents;
- QByteArray chunk;
- bool wasEscaped = false;
- for (const quint8 c : contents) {
- const bool needsEscape = !isascii(c) || !isprint(c);
- if (!needsEscape && wasEscaped && std::isxdigit(c) && !chunk.isEmpty()) {
- newContents << chunk;
- chunk.clear();
- }
- if (needsEscape)
- chunk += QByteArray("\\x") + QByteArray::number(c, 16).rightJustified(2, '0');
- else
- chunk += c;
- wasEscaped = needsEscape;
- }
- if (!chunk.isEmpty())
- newContents << chunk;
- return newContents;
- }
-
- static QByteArray unescapeString(const QByteArray &contents)
- {
- QByteArray newContents;
- const int len = contents.length();
- for (int i = 0; i < len; ++i) {
- quint8 c = contents.at(i);
- if (c == '\\' && i < len - 1) {
- int idx = i + 1;
- quint8 ch = contents.at(idx);
- int base = 0;
- int maxlen = 0;
- if (isDigit(ch, 8)) {
- base = 8;
- maxlen = 3;
- } else if ((ch == 'x' || ch == 'X') && idx < len - 1) {
- base = 16;
- maxlen = 2;
- ch = contents.at(++idx);
- }
- if (base > 0) {
- QByteArray buf;
- while (isDigit(ch, base) && idx < len && buf.length() < maxlen) {
- buf += ch;
- ++idx;
- if (idx == len)
- break;
- ch = contents.at(idx);
- }
- if (!buf.isEmpty()) {
- bool ok;
- uint value = buf.toUInt(&ok, base);
- // Don't unescape isascii() && !isprint()
- if (ok && (!isascii(value) || isprint(value))) {
- newContents += value;
- i = idx - 1;
- continue;
- }
- }
- }
- newContents += c;
- c = contents.at(++i);
- }
- newContents += c;
- }
- return newContents;
- }
-
- // QuickFixOperation interface
-public:
- void perform() override
- {
- CppRefactoringChanges refactoring(snapshot());
- CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath());
-
- const int startPos = currentFile->startOf(m_literal);
- const int endPos = currentFile->endOf(m_literal);
-
- StringLiteralAST *stringLiteral = m_literal->asStringLiteral();
- QTC_ASSERT(stringLiteral, return);
- const QByteArray oldContents(currentFile->tokenAt(stringLiteral->literal_token).
- identifier->chars());
- QByteArrayList newContents;
- if (m_escape)
- newContents = escapeString(oldContents);
- else
- newContents = {unescapeString(oldContents)};
-
- if (newContents.isEmpty()
- || (newContents.size() == 1 && newContents.first() == oldContents)) {
- return;
- }
-
- QTextCodec *utf8codec = QTextCodec::codecForName("UTF-8");
- QScopedPointer<QTextDecoder> decoder(utf8codec->makeDecoder());
- ChangeSet changes;
-
- bool replace = true;
- for (const QByteArray &chunk : std::as_const(newContents)) {
- const QString str = decoder->toUnicode(chunk);
- const QByteArray utf8buf = str.toUtf8();
- if (!utf8codec->canEncode(str) || chunk != utf8buf)
- return;
- if (replace)
- changes.replace(startPos + 1, endPos - 1, str);
- else
- changes.insert(endPos, "\"" + str + "\"");
- replace = false;
- }
- currentFile->setChangeSet(changes);
- currentFile->apply();
- }
-
-private:
- ExpressionAST *m_literal;
- bool m_escape;
-};
-
-} // anonymous namespace
-
-void EscapeStringLiteral::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result)
-{
- const QList<AST *> &path = interface.path();
- if (path.isEmpty())
- return;
-
- AST * const lastAst = path.last();
- ExpressionAST *literal = lastAst->asStringLiteral();
- if (!literal)
- return;
-
- StringLiteralAST *stringLiteral = literal->asStringLiteral();
- CppRefactoringFilePtr file = interface.currentFile();
- const QByteArray contents(file->tokenAt(stringLiteral->literal_token).identifier->chars());
-
- bool canEscape = false;
- bool canUnescape = false;
- for (int i = 0; i < contents.length(); ++i) {
- quint8 c = contents.at(i);
- if (!isascii(c) || !isprint(c)) {
- canEscape = true;
- } else if (c == '\\' && i < contents.length() - 1) {
- c = contents.at(++i);
- if ((c >= '0' && c < '8') || c == 'x' || c == 'X')
- canUnescape = true;
- }
- }
-
- if (canEscape)
- result << new EscapeStringLiteralOperation(interface, literal, true);
-
- if (canUnescape)
- result << new EscapeStringLiteralOperation(interface, literal, false);
-}
-
-
-namespace {
-
-class ConvertQt4ConnectOperation: public CppQuickFixOperation
-{
-public:
- ConvertQt4ConnectOperation(const CppQuickFixInterface &interface, const ChangeSet &changes)
- : CppQuickFixOperation(interface, 1), m_changes(changes)
- {
- setDescription(Tr::tr("Convert connect() to Qt 5 Style"));
- }
-
-private:
- void perform() override
- {
- CppRefactoringChanges refactoring(snapshot());
- CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath());
- currentFile->setChangeSet(m_changes);
- currentFile->apply();
- }
-
- const ChangeSet m_changes;
-};
-
-Symbol *skipForwardDeclarations(const QList<Symbol *> &symbols)
-{
- for (Symbol *symbol : symbols) {
- if (!symbol->type()->asForwardClassDeclarationType())
- return symbol;
- }
-
- return nullptr;
-}
-
-bool findRawAccessFunction(Class *klass, PointerType *pointerType, QString *objAccessFunction)
-{
- QList<Function *> candidates;
- for (auto it = klass->memberBegin(), end = klass->memberEnd(); it != end; ++it) {
- if (Function *func = (*it)->asFunction()) {
- const Name *funcName = func->name();
- if (!funcName->asOperatorNameId()
- && !funcName->asConversionNameId()
- && func->returnType().type() == pointerType
- && func->isConst()
- && func->argumentCount() == 0) {
- candidates << func;
- }
- }
- }
- const Name *funcName = nullptr;
- switch (candidates.size()) {
- case 0:
- return false;
- case 1:
- funcName = candidates.first()->name();
- break;
- default:
- // Multiple candidates - prefer a function named data
- for (Function *func : std::as_const(candidates)) {
- if (!strcmp(func->name()->identifier()->chars(), "data")) {
- funcName = func->name();
- break;
- }
- }
- if (!funcName)
- funcName = candidates.first()->name();
- }
- const Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview();
- *objAccessFunction = QLatin1Char('.') + oo.prettyName(funcName) + QLatin1String("()");
- return true;
-}
-
-PointerType *determineConvertedType(NamedType *namedType, const LookupContext &context,
- Scope *scope, QString *objAccessFunction)
-{
- if (!namedType)
- return nullptr;
- if (ClassOrNamespace *binding = context.lookupType(namedType->name(), scope)) {
- if (Symbol *objectClassSymbol = skipForwardDeclarations(binding->symbols())) {
- if (Class *klass = objectClassSymbol->asClass()) {
- for (auto it = klass->memberBegin(), end = klass->memberEnd(); it != end; ++it) {
- if (Function *func = (*it)->asFunction()) {
- if (const ConversionNameId *conversionName =
- func->name()->asConversionNameId()) {
- if (PointerType *type = conversionName->type()->asPointerType()) {
- if (findRawAccessFunction(klass, type, objAccessFunction))
- return type;
- }
- }
- }
- }
- }
- }
- }
-
- return nullptr;
-}
-
-Class *senderOrReceiverClass(const CppQuickFixInterface &interface,
- const CppRefactoringFilePtr &file,
- const ExpressionAST *objectPointerAST,
- Scope *objectPointerScope,
- QString *objAccessFunction)
-{
- const LookupContext &context = interface.context();
-
- QByteArray objectPointerExpression;
- if (objectPointerAST)
- objectPointerExpression = file->textOf(objectPointerAST).toUtf8();
- else
- objectPointerExpression = "this";
-
- TypeOfExpression toe;
- toe.setExpandTemplates(true);
- toe.init(interface.semanticInfo().doc, interface.snapshot(), context.bindings());
- const QList<LookupItem> objectPointerExpressions = toe(objectPointerExpression,
- objectPointerScope, TypeOfExpression::Preprocess);
- QTC_ASSERT(!objectPointerExpressions.isEmpty(), return nullptr);
-
- Type *objectPointerTypeBase = objectPointerExpressions.first().type().type();
- QTC_ASSERT(objectPointerTypeBase, return nullptr);
-
- PointerType *objectPointerType = objectPointerTypeBase->asPointerType();
- if (!objectPointerType) {
- objectPointerType = determineConvertedType(objectPointerTypeBase->asNamedType(), context,
- objectPointerScope, objAccessFunction);
- }
- QTC_ASSERT(objectPointerType, return nullptr);
-
- Type *objectTypeBase = objectPointerType->elementType().type(); // Dereference
- QTC_ASSERT(objectTypeBase, return nullptr);
-
- NamedType *objectType = objectTypeBase->asNamedType();
- QTC_ASSERT(objectType, return nullptr);
-
- ClassOrNamespace *objectClassCON = context.lookupType(objectType->name(), objectPointerScope);
- if (!objectClassCON) {
- objectClassCON = objectPointerExpressions.first().binding();
- QTC_ASSERT(objectClassCON, return nullptr);
- }
- QTC_ASSERT(!objectClassCON->symbols().isEmpty(), return nullptr);
-
- Symbol *objectClassSymbol = skipForwardDeclarations(objectClassCON->symbols());
- QTC_ASSERT(objectClassSymbol, return nullptr);
-
- return objectClassSymbol->asClass();
-}
-
-bool findConnectReplacement(const CppQuickFixInterface &interface,
- const ExpressionAST *objectPointerAST,
- const QtMethodAST *methodAST,
- const CppRefactoringFilePtr &file,
- QString *replacement,
- QString *objAccessFunction)
-{
- // Get name of method
- if (!methodAST->declarator || !methodAST->declarator->core_declarator)
- return false;
-
- DeclaratorIdAST *methodDeclIdAST = methodAST->declarator->core_declarator->asDeclaratorId();
- if (!methodDeclIdAST)
- return false;
-
- NameAST *methodNameAST = methodDeclIdAST->name;
- if (!methodNameAST)
- return false;
-
- // Lookup object pointer type
- Scope *scope = file->scopeAt(methodAST->firstToken());
- Class *objectClass = senderOrReceiverClass(interface, file, objectPointerAST, scope,
- objAccessFunction);
- QTC_ASSERT(objectClass, return false);
-
- // Look up member function in call, including base class members.
- const LookupContext &context = interface.context();
- const QList<LookupItem> methodResults = context.lookup(methodNameAST->name, objectClass);
- if (methodResults.isEmpty())
- return false; // Maybe mis-spelled signal/slot name
-
- Scope *baseClassScope = methodResults.at(0).scope(); // FIXME: Handle overloads
- QTC_ASSERT(baseClassScope, return false);
-
- Class *classOfMethod = baseClassScope->asClass(); // Declaration point of signal/slot
- QTC_ASSERT(classOfMethod, return false);
-
- Symbol *method = methodResults.at(0).declaration();
- QTC_ASSERT(method, return false);
-
- // Minimize qualification
- Control *control = context.bindings()->control().get();
- ClassOrNamespace *functionCON = context.lookupParent(scope);
- const Name *shortName = LookupContext::minimalName(method, functionCON, control);
- if (!shortName->asQualifiedNameId())
- shortName = control->qualifiedNameId(classOfMethod->name(), shortName);
-
- const Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview();
- *replacement = QLatin1Char('&') + oo.prettyName(shortName);
- return true;
-}
-
-bool onConnectOrDisconnectCall(AST *ast, const ExpressionListAST **arguments)
-{
- if (!ast)
- return false;
-
- CallAST *call = ast->asCall();
- if (!call)
- return false;
-
- if (!call->base_expression)
- return false;
-
- const IdExpressionAST *idExpr = call->base_expression->asIdExpression();
- if (!idExpr || !idExpr->name || !idExpr->name->name)
- return false;
-
- const ExpressionListAST *args = call->expression_list;
- if (!arguments)
- return false;
-
- const Identifier *id = idExpr->name->name->identifier();
- if (!id)
- return false;
-
- const QByteArray name(id->chars(), id->size());
- if (name != "connect" && name != "disconnect")
- return false;
-
- if (arguments)
- *arguments = args;
- return true;
-}
-
-// Might modify arg* output arguments even if false is returned.
-bool collectConnectArguments(const ExpressionListAST *arguments,
- const ExpressionAST **arg1, const QtMethodAST **arg2,
- const ExpressionAST **arg3, const QtMethodAST **arg4)
-{
- if (!arguments || !arg1 || !arg2 || !arg3 || !arg4)
- return false;
-
- *arg1 = arguments->value;
- arguments = arguments->next;
- if (!arg1 || !arguments)
- return false;
-
- *arg2 = arguments->value->asQtMethod();
- arguments = arguments->next;
- if (!*arg2 || !arguments)
- return false;
-
- *arg3 = arguments->value;
- if (!*arg3)
- return false;
-
- // Take care of three-arg version, with 'this' receiver.
- if (QtMethodAST *receiverMethod = arguments->value->asQtMethod()) {
- *arg3 = nullptr; // Means 'this'
- *arg4 = receiverMethod;
- return true;
- }
-
- arguments = arguments->next;
- if (!arguments)
- return false;
-
- *arg4 = arguments->value->asQtMethod();
- if (!*arg4)
- return false;
-
- return true;
-}
-
-} // anonynomous namespace
-
-void ConvertQt4Connect::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result)
-{
- const QList<AST *> &path = interface.path();
-
- for (int i = path.size(); --i >= 0; ) {
- const ExpressionListAST *arguments;
- if (!onConnectOrDisconnectCall(path.at(i), &arguments))
- continue;
-
- const ExpressionAST *arg1, *arg3;
- const QtMethodAST *arg2, *arg4;
- if (!collectConnectArguments(arguments, &arg1, &arg2, &arg3, &arg4))
- continue;
-
- const CppRefactoringFilePtr file = interface.currentFile();
-
- QString newSignal;
- QString senderAccessFunc;
- if (!findConnectReplacement(interface, arg1, arg2, file, &newSignal, &senderAccessFunc))
- continue;
-
- QString newMethod;
- QString receiverAccessFunc;
- if (!findConnectReplacement(interface, arg3, arg4, file, &newMethod, &receiverAccessFunc))
- continue;
-
- ChangeSet changes;
- changes.replace(file->endOf(arg1), file->endOf(arg1), senderAccessFunc);
- changes.replace(file->startOf(arg2), file->endOf(arg2), newSignal);
- if (!arg3)
- newMethod.prepend(QLatin1String("this, "));
- else
- changes.replace(file->endOf(arg3), file->endOf(arg3), receiverAccessFunc);
- changes.replace(file->startOf(arg4), file->endOf(arg4), newMethod);
-
- result << new ConvertQt4ConnectOperation(interface, changes);
- return;
- }
-}
-
-void ExtraRefactoringOperations::doMatch(const CppQuickFixInterface &interface,
- QuickFixOperations &result)
-{
- const auto processor = CppModelManager::cppEditorDocumentProcessor(interface.filePath());
- if (processor) {
- const auto clangFixItOperations = processor->extraRefactoringOperations(interface);
- result.append(clangFixItOperations);
- }
-}
-
-namespace {
-
-/**
- * @brief The NameCounter class counts the parts of a name. E.g. 2 for std::vector or 1 for variant
- */
-class NameCounter : private NameVisitor
-{
-public:
- int count(const Name *name)
- {
- counter = 0;
- accept(name);
- return counter;
- }
-
-private:
- void visit(const Identifier *) override { ++counter; }
- void visit(const DestructorNameId *) override { ++counter; }
- void visit(const TemplateNameId *) override { ++counter; }
- void visit(const QualifiedNameId *name) override
- {
- if (name->base())
- accept(name->base());
- accept(name->name());
- }
- int counter;
-};
-
-/**
- * @brief getBaseName returns the base name of a qualified name or nullptr.
- * E.g.: foo::bar => foo; bar => bar
- * @param name The Name, maybe qualified
- * @return The base name of the qualified name or nullptr
- */
-const Identifier *getBaseName(const Name *name)
-{
- class GetBaseName : public NameVisitor
- {
- void visit(const Identifier *name) override { baseName = name; }
- void visit(const QualifiedNameId *name) override
- {
- if (name->base())
- accept(name->base());
- else
- accept(name->name());
- }
-
- public:
- const Identifier *baseName = nullptr;
- };
- GetBaseName getter;
- getter.accept(name);
- return getter.baseName;
-}
-
-/**
- * @brief countNames counts the parts of the Name.
- * E.g. if the name is std::vector, the function returns 2, if the name is variant, returns 1
- * @param name The name that should be counted
- * @return the number of parts of the name
- */
-int countNames(const Name *name)
-{
- return NameCounter{}.count(name);
-}
-
-/**
- * @brief removeLine removes the whole line in which the ast node is located if there are otherwise only whitespaces
- * @param file The file in which the AST node is located
- * @param ast The ast node
- * @param changeSet The ChangeSet of the file
- */
-void removeLine(const CppRefactoringFile *file, AST *ast, ChangeSet &changeSet)
-{
- RefactoringFile::Range range = file->range(ast);
- --range.start;
- while (range.start >= 0) {
- QChar current = file->charAt(range.start);
- if (!current.isSpace()) {
- ++range.start;
- break;
- }
- if (current == QChar::ParagraphSeparator)
- break;
- --range.start;
- }
- range.start = std::max(0, range.start);
- while (range.end < file->document()->characterCount()) {
- QChar current = file->charAt(range.end);
- if (!current.isSpace())
- break;
- if (current == QChar::ParagraphSeparator)
- break;
- ++range.end;
- }
- range.end = std::min(file->document()->characterCount(), range.end);
- const bool newLineStart = file->charAt(range.start) == QChar::ParagraphSeparator;
- const bool newLineEnd = file->charAt(range.end) == QChar::ParagraphSeparator;
- if (!newLineEnd && newLineStart)
- ++range.start;
- changeSet.remove(range);
-}
-
-/**
- * @brief The RemoveNamespaceVisitor class removes a using namespace and rewrites all types that
- * are in the namespace if needed
- */
-class RemoveNamespaceVisitor : public ASTVisitor
-{
-public:
- constexpr static int SearchGlobalUsingDirectivePos = std::numeric_limits<int>::max();
- RemoveNamespaceVisitor(const CppRefactoringFile *file,
- const Snapshot &snapshot,
- const Name *namespace_,
- int symbolPos,
- bool removeAllAtGlobalScope)
- : ASTVisitor(file->cppDocument()->translationUnit())
- , m_file(file)
- , m_snapshot(snapshot)
- , m_namespace(namespace_)
- , m_missingNamespace(toString(namespace_) + "::")
- , m_context(m_file->cppDocument(), m_snapshot)
- , m_symbolPos(symbolPos)
- , m_removeAllAtGlobalScope(removeAllAtGlobalScope)
-
- {}
-
- const ChangeSet &getChanges() { return m_changeSet; }
-
- /**
- * @brief isGlobalUsingNamespace return true if the using namespace that should be removed
- * is not scoped and other files that include this file will also use the using namespace
- * @return true if using namespace statement is global and not scoped, false otherwise
- */
- bool isGlobalUsingNamespace() const { return m_parentNode == nullptr; }
-
- /**
- * @brief foundGlobalUsingNamespace return true if removeAllAtGlobalScope is false and
- * another using namespace is found at the global scope, so that other files that include this
- * file don't have to be processed
- * @return true if there was a 'global' second using namespace in this file and
- * removeAllAtGlobalScope is false
- */
- bool foundGlobalUsingNamespace() const { return m_foundNamespace; }
-
-private:
- bool preVisit(AST *ast) override
- {
- if (!m_start) {
- if (ast->asTranslationUnit())
- return true;
- if (UsingDirectiveAST *usingDirective = ast->asUsingDirective()) {
- if (nameEqual(usingDirective->name->name, m_namespace)) {
- if (m_symbolPos == SearchGlobalUsingDirectivePos) {
- // we have found a global using directive, so lets start
- m_start = true;
- removeLine(m_file, ast, m_changeSet);
- return false;
- }
- // ignore the using namespace that should be removed
- if (m_file->endOf(ast) != m_symbolPos) {
- if (m_removeAllAtGlobalScope)
- removeLine(m_file, ast, m_changeSet);
- else
- m_done = true;
- }
- }
- }
- // if the end of the ast is before we should start, we are not interested in the node
- if (m_file->endOf(ast) <= m_symbolPos)
- return false;
-
- if (m_file->startOf(ast) > m_symbolPos)
- m_start = true;
- }
- return !m_foundNamespace && !m_done;
- }
-
- bool visit(NamespaceAST *ast) override
- {
- if (m_start && nameEqual(m_namespace, ast->symbol->name()))
- return false;
-
- return m_start;
- }
-
- // scopes for using namespace statements:
- bool visit(LinkageBodyAST *ast) override { return visitNamespaceScope(ast); }
- bool visit(CompoundStatementAST *ast) override { return visitNamespaceScope(ast); }
- bool visitNamespaceScope(AST *ast)
- {
- ++m_namespaceScopeCounter;
- if (!m_start)
- m_parentNode = ast;
- return true;
- }
-
- void endVisit(LinkageBodyAST *ast) override { endVisitNamespaceScope(ast); }
- void endVisit(CompoundStatementAST *ast) override { endVisitNamespaceScope(ast); }
- void endVisitNamespaceScope(AST *ast)
- {
- --m_namespaceScopeCounter;
- m_foundNamespace = false;
- // if we exit the scope of the using namespace we are done
- if (ast == m_parentNode)
- m_done = true;
- }
-
- bool visit(UsingDirectiveAST *ast) override
- {
- if (nameEqual(ast->name->name, m_namespace)) {
- if (m_removeAllAtGlobalScope && m_namespaceScopeCounter == 0)
- removeLine(m_file, ast, m_changeSet);
- else
- m_foundNamespace = true;
- return false;
- }
- return handleAstWithLongestName(ast);
- }
-
- bool visit(DeclaratorIdAST *ast) override
- {
- // e.g. we have the following code and get the following Lookup items:
- // namespace test {
- // struct foo { // 1. item with test::foo
- // foo(); // 2. item with test::foo::foo
- // };
- // }
- // using namespace foo;
- // foo::foo() { ... } // 3. item with foo::foo
- // Our current name is foo::foo so we have to match with the 2. item / longest name
- return handleAstWithLongestName(ast);
- }
-
- template<typename AST>
- bool handleAstWithLongestName(AST *ast)
- {
- if (m_start) {
- Scope *scope = m_file->scopeAt(ast->firstToken());
- const QList<LookupItem> localLookup = m_context.lookup(ast->name->name, scope);
- QList<const Name *> longestName;
- for (const LookupItem &item : localLookup) {
- QList<const Name *> names
- = m_context.fullyQualifiedName(item.declaration(),
- LookupContext::HideInlineNamespaces);
- if (names.length() > longestName.length())
- longestName = names;
- }
- const int currentNameCount = countNames(ast->name->name);
- const bool needNew = needMissingNamespaces(std::move(longestName), currentNameCount);
- if (needNew)
- insertMissingNamespace(ast);
- }
- return false;
- }
-
- bool visit(NamedTypeSpecifierAST *ast) override { return handleAstWithName(ast); }
-
- bool visit(IdExpressionAST *ast) override { return handleAstWithName(ast); }
-
- template<typename AST>
- bool handleAstWithName(AST *ast)
- {
- if (m_start) {
- Scope *scope = m_file->scopeAt(ast->firstToken());
- const Name *wantToLookup = ast->name->name;
- // first check if the base name is a typedef. Consider the following example:
- // using namespace std;
- // using vec = std::vector<int>;
- // vec::iterator it; // we have to lookup 'vec' and not iterator (would result in
- // std::vector<int>::iterator => std::vec::iterator, which is wrong)
- const Name *baseName = getBaseName(wantToLookup);
- QList<LookupItem> typedefCandidates = m_context.lookup(baseName, scope);
- if (!typedefCandidates.isEmpty()) {
- if (typedefCandidates.front().declaration()->isTypedef())
- wantToLookup = baseName;
- }
-
- const QList<LookupItem> lookups = m_context.lookup(wantToLookup, scope);
- if (!lookups.empty()) {
- QList<const Name *> fullName
- = m_context.fullyQualifiedName(lookups.first().declaration(),
- LookupContext::HideInlineNamespaces);
- const int currentNameCount = countNames(wantToLookup);
- const bool needNamespace = needMissingNamespaces(std::move(fullName),
- currentNameCount);
- if (needNamespace)
- insertMissingNamespace(ast);
- }
- }
- return true;
- }
-
- template<typename AST>
- void insertMissingNamespace(AST *ast)
- {
- DestructorNameAST *destructorName = ast->name->asDestructorName();
- if (destructorName)
- m_changeSet.insert(m_file->startOf(destructorName->unqualified_name), m_missingNamespace);
- else
- m_changeSet.insert(m_file->startOf(ast->name), m_missingNamespace);
- m_changeSet.operationList().last().setFormat1(false);
- }
-
- bool needMissingNamespaces(QList<const Name *> &&fullName, int currentNameCount)
- {
- if (currentNameCount > fullName.length())
- return false;
-
- // eg. fullName = std::vector, currentName = vector => result should be std
- fullName.erase(fullName.end() - currentNameCount, fullName.end());
- if (fullName.empty())
- return false;
- return nameEqual(m_namespace, fullName.last());
- }
-
- static bool nameEqual(const Name *name1, const Name *name2)
- {
- return Matcher::match(name1, name2);
- }
-
- QString toString(const Name *id)
- {
- const Identifier *identifier = id->asNameId();
- QTC_ASSERT(identifier, return {});
- return QString::fromUtf8(identifier->chars(), identifier->size());
- }
-
- const CppRefactoringFile *const m_file;
- const Snapshot &m_snapshot;
-
- const Name *m_namespace; // the name of the namespace that should be removed
- const QString m_missingNamespace; // that should be added if a type was using the namespace
- LookupContext m_context;
- ChangeSet m_changeSet;
- const int m_symbolPos; // the end position of the start symbol
- bool m_done = false;
- bool m_start = false;
- // true if a using namespace was found at a scope and the scope should be left
- bool m_foundNamespace = false;
- bool m_removeAllAtGlobalScope;
- // the scope where the using namespace that should be removed is valid
- AST *m_parentNode = nullptr;
- int m_namespaceScopeCounter = 0;
-};
-
-class RemoveUsingNamespaceOperation : public CppQuickFixOperation
-{
- struct Node
- {
- Document::Ptr document;
- bool hasGlobalUsingDirective = false;
- int unprocessedParents;
- std::vector<std::reference_wrapper<Node>> includes;
- std::vector<std::reference_wrapper<Node>> includedBy;
- Node() = default;
- Node(const Node &) = delete;
- Node(Node &&) = delete;
- };
-
-public:
- RemoveUsingNamespaceOperation(const CppQuickFixInterface &interface,
- UsingDirectiveAST *usingDirective,
- bool removeAllAtGlobalScope)
- : CppQuickFixOperation(interface, 1)
- , m_usingDirective(usingDirective)
- , m_removeAllAtGlobalScope(removeAllAtGlobalScope)
- {
- const QString name = Overview{}.prettyName(usingDirective->name->name);
- if (m_removeAllAtGlobalScope) {
- setDescription(Tr::tr(
- "Remove All Occurrences of \"using namespace %1\" in Global Scope "
- "and Adjust Type Names Accordingly")
- .arg(name));
- } else {
- setDescription(Tr::tr("Remove \"using namespace %1\" and "
- "Adjust Type Names Accordingly")
- .arg(name));
- }
- }
-
-private:
- std::map<Utils::FilePath, Node> buildIncludeGraph(CppRefactoringChanges &refactoring)
- {
- using namespace ProjectExplorer;
- using namespace Utils;
-
- const Snapshot &s = refactoring.snapshot();
- std::map<Utils::FilePath, Node> includeGraph;
-
- auto handleFile = [&](const FilePath &filePath, Document::Ptr doc, auto shouldHandle) {
- Node &node = includeGraph[filePath];
- node.document = doc;
- for (const Document::Include &include : doc->resolvedIncludes()) {
- const FilePath filePath = include.resolvedFileName();
- if (shouldHandle(filePath)) {
- Node &includedNode = includeGraph[filePath];
- includedNode.includedBy.push_back(node);
- node.includes.push_back(includedNode);
- }
- }
- };
-
- if (const Project *project = ProjectManager::projectForFile(filePath())) {
- const FilePaths files = project->files(ProjectExplorer::Project::SourceFiles);
- QSet<FilePath> projectFiles(files.begin(), files.end());
- for (const auto &file : files) {
- const Document::Ptr doc = s.document(file);
- if (!doc)
- continue;
- handleFile(file, doc, [&](const FilePath &file) {
- return projectFiles.contains(file);
- });
- }
- } else {
- for (auto i = s.begin(); i != s.end(); ++i) {
- if (ProjectFile::classify(i.key().toString()) != ProjectFile::Unsupported) {
- handleFile(i.key(), i.value(), [](const FilePath &file) {
- return ProjectFile::classify(file.toString()) != ProjectFile::Unsupported;
- });
- }
- }
- }
- for (auto &[_, node] : includeGraph) {
- Q_UNUSED(_)
- node.unprocessedParents = static_cast<int>(node.includes.size());
- }
- return includeGraph;
- }
-
- void removeAllUsingsAtGlobalScope(CppRefactoringChanges &refactoring)
- {
- auto includeGraph = buildIncludeGraph(refactoring);
- std::vector<std::reference_wrapper<Node>> nodesWithProcessedParents;
- for (auto &[_, node] : includeGraph) {
- Q_UNUSED(_)
- if (!node.unprocessedParents)
- nodesWithProcessedParents.push_back(node);
- }
- while (!nodesWithProcessedParents.empty()) {
- Node &node = nodesWithProcessedParents.back();
- nodesWithProcessedParents.pop_back();
- CppRefactoringFilePtr file = refactoring.cppFile(node.document->filePath());
- const bool parentHasUsing = Utils::anyOf(node.includes, &Node::hasGlobalUsingDirective);
- const int startPos = parentHasUsing
- ? 0
- : RemoveNamespaceVisitor::SearchGlobalUsingDirectivePos;
- const bool noGlobalUsing = refactorFile(file, refactoring.snapshot(), startPos);
- node.hasGlobalUsingDirective = !noGlobalUsing || parentHasUsing;
-
- for (Node &subNode : node.includedBy) {
- --subNode.unprocessedParents;
- if (subNode.unprocessedParents == 0)
- nodesWithProcessedParents.push_back(subNode);
- }
- }
- }
-
- void perform() override
- {
- CppRefactoringChanges refactoring(snapshot());
- CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath());
- if (m_removeAllAtGlobalScope) {
- removeAllUsingsAtGlobalScope(refactoring);
- } else if (refactorFile(currentFile,
- refactoring.snapshot(),
- currentFile->endOf(m_usingDirective),
- true)) {
- processIncludes(refactoring, filePath());
- }
-
- for (auto &file : std::as_const(m_changes))
- file->apply();
- }
-
- /**
- * @brief refactorFile remove using namespace xyz in the given file and rewrite types
- * @param file The file that should be processed
- * @param snapshot The snapshot to work on
- * @param startSymbol start processing after this index
- * @param removeUsing if the using directive is in this file, remove it
- * @return true if the using statement is global and there is no other global using namespace
- */
- bool refactorFile(CppRefactoringFilePtr &file,
- const Snapshot &snapshot,
- int startSymbol,
- bool removeUsing = false)
- {
- RemoveNamespaceVisitor visitor(file.get(),
- snapshot,
- m_usingDirective->name->name,
- startSymbol,
- m_removeAllAtGlobalScope);
- visitor.accept(file->cppDocument()->translationUnit()->ast());
- Utils::ChangeSet changes = visitor.getChanges();
- if (removeUsing)
- removeLine(file.get(), m_usingDirective, changes);
- if (!changes.isEmpty()) {
- file->setChangeSet(changes);
- // apply changes at the end, otherwise the symbol finder will fail to resolve symbols if
- // the using namespace is missing
- m_changes.insert(file);
- }
- return visitor.isGlobalUsingNamespace() && !visitor.foundGlobalUsingNamespace();
- }
-
- void processIncludes(CppRefactoringChanges &refactoring, const FilePath &filePath)
- {
- QList<Snapshot::IncludeLocation>
- includeLocationsOfDocument = refactoring.snapshot().includeLocationsOfDocument(filePath);
- for (Snapshot::IncludeLocation &loc : includeLocationsOfDocument) {
- if (!Utils::insert(m_processed, loc.first))
- continue;
-
- CppRefactoringFilePtr file = refactoring.cppFile(loc.first->filePath());
- const bool noGlobalUsing = refactorFile(file,
- refactoring.snapshot(),
- file->position(loc.second, 1));
- if (noGlobalUsing)
- processIncludes(refactoring, loc.first->filePath());
- }
- }
-
- QSet<Document::Ptr> m_processed;
- QSet<CppRefactoringFilePtr> m_changes;
-
- UsingDirectiveAST *m_usingDirective;
- bool m_removeAllAtGlobalScope;
-};
-} // namespace
-
-void RemoveUsingNamespace::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result)
-{
- const QList<AST *> &path = interface.path();
- // We expect something like
- // [0] TranslationUnitAST
- // ...
- // [] UsingDirectiveAST : if activated at 'using namespace'
- // [] NameAST (optional): if activated at the name e.g. 'std'
- int n = path.size() - 1;
- if (n <= 0)
- return;
- if (path.last()->asName())
- --n;
- UsingDirectiveAST *usingDirective = path.at(n)->asUsingDirective();
- if (usingDirective && usingDirective->name->name->asNameId()) {
- result << new RemoveUsingNamespaceOperation(interface, usingDirective, false);
- const bool isHeader = ProjectFile::isHeader(ProjectFile::classify(interface.filePath().toString()));
- if (isHeader && path.at(n - 1)->asTranslationUnit()) // using namespace at global scope
- result << new RemoveUsingNamespaceOperation(interface, usingDirective, true);
- }
-}
-
-namespace {
-
-struct ParentClassConstructorInfo;
-
-class ConstructorMemberInfo
-{
-public:
- ConstructorMemberInfo(const QString &name, Symbol *symbol, int numberOfMember)
- : memberVariableName(name)
- , parameterName(memberBaseName(name))
- , symbol(symbol)
- , type(symbol->type())
- , numberOfMember(numberOfMember)
- {}
- ConstructorMemberInfo(const QString &memberName,
- const QString &paramName,
- const QString &defaultValue,
- Symbol *symbol,
- const ParentClassConstructorInfo *parentClassConstructor)
- : parentClassConstructor(parentClassConstructor)
- , memberVariableName(memberName)
- , parameterName(paramName)
- , defaultValue(defaultValue)
- , init(defaultValue.isEmpty())
- , symbol(symbol)
- , type(symbol->type())
- {}
- const ParentClassConstructorInfo *parentClassConstructor = nullptr;
- QString memberVariableName;
- QString parameterName;
- QString defaultValue;
- bool init = true;
- bool customValueType; // for the generation later
- Symbol *symbol; // for the right type later
- FullySpecifiedType type;
- int numberOfMember; // first member, second member, ...
-};
-
-class ConstructorParams : public QAbstractTableModel
-{
- Q_OBJECT
- std::list<ConstructorMemberInfo> candidates;
- std::vector<ConstructorMemberInfo *> infos;
-
- void validateOrder()
- {
- // parameters with default values must be at the end
- bool foundWithDefault = false;
- for (auto info : infos) {
- if (info->init) {
- if (foundWithDefault && info->defaultValue.isEmpty()) {
- emit validOrder(false);
- return;
- }
- foundWithDefault |= !info->defaultValue.isEmpty();
- }
- }
- emit validOrder(true);
- }
-
-public:
- enum Column { ShouldInitColumn, MemberNameColumn, ParameterNameColumn, DefaultValueColumn };
- template<typename... _Args>
- void emplaceBackParameter(_Args &&...__args)
- {
- candidates.emplace_back(std::forward<_Args>(__args)...);
- infos.push_back(&candidates.back());
- }
- const std::vector<ConstructorMemberInfo *> &getInfos() const { return infos; }
- void addRow(ConstructorMemberInfo *info)
- {
- beginInsertRows({}, rowCount(), rowCount());
- infos.push_back(info);
- endInsertRows();
- validateOrder();
- }
- void removeRow(ConstructorMemberInfo *info)
- {
- for (auto iter = infos.begin(); iter != infos.end(); ++iter) {
- if (*iter == info) {
- const auto index = iter - infos.begin();
- beginRemoveRows({}, index, index);
- infos.erase(iter);
- endRemoveRows();
- validateOrder();
- return;
- }
- }
- }
-
- int selectedCount() const
- {
- return Utils::count(infos, [](const ConstructorMemberInfo *mi) {
- return mi->init && !mi->parentClassConstructor;
- });
- }
- int memberCount() const
- {
- return Utils::count(infos, [](const ConstructorMemberInfo *mi) {
- return !mi->parentClassConstructor;
- });
- }
-
- int rowCount(const QModelIndex & /*parent*/ = {}) const override { return int(infos.size()); }
- int columnCount(const QModelIndex & /*parent*/ = {}) const override { return 4; }
- QVariant data(const QModelIndex &index, int role) const override
- {
- if (index.row() < 0 || index.row() >= rowCount())
- return {};
- if (role == Qt::CheckStateRole && index.column() == ShouldInitColumn
- && !infos[index.row()]->parentClassConstructor)
- return infos[index.row()]->init ? Qt::Checked : Qt::Unchecked;
- if (role == Qt::DisplayRole && index.column() == MemberNameColumn)
- return infos[index.row()]->memberVariableName;
- if ((role == Qt::DisplayRole || role == Qt::EditRole)
- && index.column() == ParameterNameColumn)
- return infos[index.row()]->parameterName;
- if ((role == Qt::DisplayRole || role == Qt::EditRole)
- && index.column() == DefaultValueColumn)
- return infos[index.row()]->defaultValue;
- if ((role == Qt::ToolTipRole) && index.column() > 0)
- return Overview{}.prettyType(infos[index.row()]->symbol->type());
- return {};
- }
- bool setData(const QModelIndex &index, const QVariant &value, int role) override
- {
- if (index.column() == ShouldInitColumn && role == Qt::CheckStateRole) {
- if (infos[index.row()]->parentClassConstructor)
- return false;
- infos[index.row()]->init = value.toInt() == Qt::Checked;
- emit dataChanged(this->index(index.row(), 0), this->index(index.row(), columnCount()));
- validateOrder();
- return true;
- }
- if (index.column() == ParameterNameColumn && role == Qt::EditRole) {
- infos[index.row()]->parameterName = value.toString();
- return true;
- }
- if (index.column() == DefaultValueColumn && role == Qt::EditRole) {
- infos[index.row()]->defaultValue = value.toString();
- validateOrder();
- return true;
- }
- return false;
- }
- Qt::DropActions supportedDropActions() const override { return Qt::MoveAction; }
- Qt::ItemFlags flags(const QModelIndex &index) const override
- {
- if (!index.isValid())
- return Qt::ItemIsSelectable | Qt::ItemIsDropEnabled;
-
- Qt::ItemFlags f{};
- if (infos[index.row()]->init) {
- f |= Qt::ItemIsDragEnabled;
- f |= Qt::ItemIsSelectable;
- }
-
- if (index.column() == ShouldInitColumn && !infos[index.row()]->parentClassConstructor)
- return f | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable;
- if (!infos[index.row()]->init)
- return f;
- if (index.column() == MemberNameColumn)
- return f | Qt::ItemIsEnabled;
- if (index.column() == ParameterNameColumn || index.column() == DefaultValueColumn)
- return f | Qt::ItemIsEnabled | Qt::ItemIsEditable;
- return {};
- }
-
- QVariant headerData(int section, Qt::Orientation orientation, int role) const override
- {
- if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
- switch (section) {
- case ShouldInitColumn:
- return Tr::tr("Initialize in Constructor");
- case MemberNameColumn:
- return Tr::tr("Member Name");
- case ParameterNameColumn:
- return Tr::tr("Parameter Name");
- case DefaultValueColumn:
- return Tr::tr("Default Value");
- }
- }
- return {};
- }
- bool dropMimeData(const QMimeData *data,
- Qt::DropAction /*action*/,
- int row,
- int /*column*/,
- const QModelIndex & /*parent*/) override
- {
- if (row == -1)
- row = rowCount();
- bool ok;
- int sourceRow = data->data("application/x-qabstractitemmodeldatalist").toInt(&ok);
- if (ok) {
- if (sourceRow == row || row == sourceRow + 1)
- return false;
- beginMoveRows({}, sourceRow, sourceRow, {}, row);
- infos.insert(infos.begin() + row, infos.at(sourceRow));
- if (row < sourceRow)
- ++sourceRow;
- infos.erase(infos.begin() + sourceRow);
- validateOrder();
- return true;
- }
- return false;
- }
-
- QMimeData *mimeData(const QModelIndexList &indexes) const override
- {
- for (const auto &i : indexes) {
- if (!i.isValid())
- continue;
- auto data = new QMimeData();
- data->setData("application/x-qabstractitemmodeldatalist",
- QString::number(i.row()).toLatin1());
- return data;
- }
- return nullptr;
- }
-
- class TableViewStyle : public QProxyStyle
- {
- public:
- TableViewStyle(QStyle *style)
- : QProxyStyle(style)
- {}
-
- void drawPrimitive(PrimitiveElement element,
- const QStyleOption *option,
- QPainter *painter,
- const QWidget *widget) const override
- {
- if (element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull()) {
- QStyleOption opt(*option);
- opt.rect.setLeft(0);
- if (widget)
- opt.rect.setRight(widget->width());
- QProxyStyle::drawPrimitive(element, &opt, painter, widget);
- return;
- }
- QProxyStyle::drawPrimitive(element, option, painter, widget);
- }
- };
-signals:
- void validOrder(bool valid);
-};
-
-class TopMarginDelegate : public QStyledItemDelegate
-{
-public:
- TopMarginDelegate(QObject *parent = nullptr)
- : QStyledItemDelegate(parent)
- {}
-
- void paint(QPainter *painter,
- const QStyleOptionViewItem &option,
- const QModelIndex &index) const override
- {
- Q_ASSERT(index.isValid());
- QStyleOptionViewItem opt = option;
- initStyleOption(&opt, index);
- const QWidget *widget = option.widget;
- QStyle *style = widget ? widget->style() : QApplication::style();
- if (opt.rect.height() > 20)
- opt.rect.adjust(0, 5, 0, 0);
- style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, widget);
- }
-};
-
-struct ParentClassConstructorParameter : public ConstructorMemberInfo
-{
- QString originalDefaultValue;
- QString declaration; // displayed in the treeView
- ParentClassConstructorParameter(const QString &name,
- const QString &defaultValue,
- Symbol *symbol,
- const ParentClassConstructorInfo *parentClassConstructor);
-
- ParentClassConstructorParameter(const ParentClassConstructorParameter &) = delete;
- ParentClassConstructorParameter(ParentClassConstructorParameter &&) = default;
-};
-
-struct ParentClassConstructorInfo
-{
- ParentClassConstructorInfo(const QString &name, ConstructorParams &model)
- : className(name)
- , model(model)
- {}
- bool useInConstructor = false;
- const QString className;
- QString declaration;
- std::vector<ParentClassConstructorParameter> parameters;
- ConstructorParams &model;
-
- ParentClassConstructorInfo(const ParentClassConstructorInfo &) = delete;
- ParentClassConstructorInfo(ParentClassConstructorInfo &&) = default;
-
- void addParameter(ParentClassConstructorParameter &param) { model.addRow(&param); }
- void removeParameter(ParentClassConstructorParameter &param) { model.removeRow(&param); }
- void removeAllParameters()
- {
- for (auto &param : parameters)
- model.removeRow(&param);
- }
-};
-
-ParentClassConstructorParameter::ParentClassConstructorParameter(
- const QString &name,
- const QString &defaultValue,
- Symbol *symbol,
- const ParentClassConstructorInfo *parentClassConstructor)
- : ConstructorMemberInfo(parentClassConstructor->className + "::" + name,
- name,
- defaultValue,
- symbol,
- parentClassConstructor)
- , originalDefaultValue(defaultValue)
- , declaration(Overview{}.prettyType(symbol->type(), name)
- + (defaultValue.isEmpty() ? QString{} : " = " + defaultValue))
-{}
-
-using ParentClassConstructors = std::vector<ParentClassConstructorInfo>;
-
-class ParentClassesModel : public QAbstractItemModel
-{
- ParentClassConstructors &constructors;
-
-public:
- ParentClassesModel(QObject *parent, ParentClassConstructors &constructors)
- : QAbstractItemModel(parent)
- , constructors(constructors)
- {}
- QModelIndex index(int row, int column, const QModelIndex &parent = {}) const override
- {
- if (!parent.isValid())
- return createIndex(row, column, nullptr);
- if (parent.internalPointer())
- return {};
- auto index = createIndex(row, column, &constructors.at(parent.row()));
- return index;
- }
- QModelIndex parent(const QModelIndex &index) const override
- {
- if (!index.isValid())
- return {};
- auto *parent = static_cast<ParentClassConstructorInfo *>(index.internalPointer());
- if (!parent)
- return {};
- int i = 0;
- for (const auto &info : constructors) {
- if (&info == parent)
- return createIndex(i, 0, nullptr);
- ++i;
- }
- return {};
- }
- int rowCount(const QModelIndex &parent = {}) const override
- {
- if (!parent.isValid())
- return static_cast<int>(constructors.size());
- auto info = static_cast<ParentClassConstructorInfo *>(parent.internalPointer());
- if (!info)
- return static_cast<int>(constructors.at(parent.row()).parameters.size());
- return 0;
- }
- int columnCount(const QModelIndex & /*parent*/ = {}) const override { return 1; }
- QVariant data(const QModelIndex &index, int role) const override
- {
- if (!index.isValid())
- return {};
- auto info = static_cast<ParentClassConstructorInfo *>(index.internalPointer());
-
- if (info) {
- const auto &parameter = info->parameters.at(index.row());
- if (role == Qt::CheckStateRole)
- return parameter.init ? Qt::Checked : Qt::Unchecked;
- if (role == Qt::DisplayRole)
- return parameter.declaration;
- return {};
- }
- const auto &constructor = constructors.at(index.row());
- if (role == Qt::CheckStateRole)
- return constructor.useInConstructor ? Qt::PartiallyChecked : Qt::Unchecked;
- if (role == Qt::DisplayRole)
- return constructor.declaration;
-
- // Highlight the selected items
- if (role == Qt::FontRole && constructor.useInConstructor) {
- QFont font = QApplication::font();
- font.setBold(true);
- return font;
- }
- // Create a margin between sets of constructors for base classes
- if (role == Qt::SizeHintRole && index.row() > 0
- && constructor.className != constructors.at(index.row() - 1).className) {
- return QSize(-1, 25);
- }
- return {};
- }
- bool setData(const QModelIndex &index, const QVariant &value, int /*role*/) override
- {
- if (index.isValid() && index.column() == 0) {
- auto info = static_cast<ParentClassConstructorInfo *>(index.internalPointer());
- if (info) {
- const bool nowUse = value.toBool();
- auto &param = info->parameters.at(index.row());
- param.init = nowUse;
- if (nowUse)
- info->addParameter(param);
- else
- info->removeParameter(param);
- return true;
- }
- auto &newConstructor = constructors.at(index.row());
- // You have to select a base class constructor
- if (newConstructor.useInConstructor)
- return false;
- auto c = std::find_if(constructors.begin(), constructors.end(), [&](const auto &c) {
- return c.className == newConstructor.className && c.useInConstructor;
- });
- QTC_ASSERT(c == constructors.end(), return false;);
- c->useInConstructor = false;
- newConstructor.useInConstructor = true;
- emit dataChanged(this->index(index.row(), 0), this->index(index.row(), columnCount()));
- auto parentIndex = this->index(index.row(), 0);
- emit dataChanged(this->index(0, 0, parentIndex),
- this->index(rowCount(parentIndex), columnCount()));
- const int oldIndex = c - constructors.begin();
- emit dataChanged(this->index(oldIndex, 0), this->index(oldIndex, columnCount()));
- parentIndex = this->index(oldIndex, 0);
- emit dataChanged(this->index(0, 0, parentIndex),
- this->index(rowCount(parentIndex), columnCount()));
- // update other table
- c->removeAllParameters();
- for (auto &p : newConstructor.parameters)
- if (p.init)
- newConstructor.addParameter(p);
- return true;
- }
- return false;
- }
- QVariant headerData(int section, Qt::Orientation orientation, int role) const override
- {
- if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
- switch (section) {
- case 0:
- return Tr::tr("Base Class Constructors");
- }
- }
- return {};
- }
- Qt::ItemFlags flags(const QModelIndex &index) const override
- {
- if (index.isValid()) {
- Qt::ItemFlags f;
- auto info = static_cast<ParentClassConstructorInfo *>(index.internalPointer());
- if (!info || info->useInConstructor) {
- f |= Qt::ItemIsEnabled;
- }
- f |= Qt::ItemIsUserCheckable;
-
- return f;
- }
- return {};
- }
-};
-
-class GenerateConstructorDialog : public QDialog
-{
-public:
- GenerateConstructorDialog(ConstructorParams *constructorParamsModel,
- ParentClassConstructors &constructors)
- {
- setWindowTitle(Tr::tr("Constructor"));
-
- const auto treeModel = new ParentClassesModel(this, constructors);
- const auto treeView = new QTreeView(this);
- treeView->setModel(treeModel);
- treeView->setItemDelegate(new TopMarginDelegate(this));
- treeView->expandAll();
-
- const auto view = new QTableView(this);
- view->setModel(constructorParamsModel);
- int optimalWidth = 0;
- for (int i = 0; i < constructorParamsModel->columnCount(QModelIndex{}); ++i) {
- view->resizeColumnToContents(i);
- optimalWidth += view->columnWidth(i);
- }
- view->resizeRowsToContents();
- view->verticalHeader()->setDefaultSectionSize(view->rowHeight(0));
- view->setSelectionBehavior(QAbstractItemView::SelectRows);
- view->setSelectionMode(QAbstractItemView::SingleSelection);
- view->setDragEnabled(true);
- view->setDropIndicatorShown(true);
- view->setDefaultDropAction(Qt::MoveAction);
- view->setDragDropMode(QAbstractItemView::InternalMove);
- view->setDragDropOverwriteMode(false);
- view->horizontalHeader()->setStretchLastSection(true);
- view->setStyle(new ConstructorParams::TableViewStyle(view->style()));
-
- const auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
- connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
- connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
-
- const auto errorLabel = new QLabel(
- Tr::tr("Parameters without default value must come before parameters with default value."));
- errorLabel->setStyleSheet("color: #ff0000");
- errorLabel->setVisible(false);
- QSizePolicy labelSizePolicy = errorLabel->sizePolicy();
- labelSizePolicy.setRetainSizeWhenHidden(true);
- errorLabel->setSizePolicy(labelSizePolicy);
- connect(constructorParamsModel, &ConstructorParams::validOrder, this,
- [errorLabel, button = buttonBox->button(QDialogButtonBox::Ok)](bool valid) {
- button->setEnabled(valid);
- errorLabel->setVisible(!valid);
- });
-
- // setup select all/none checkbox
- QCheckBox *const checkBox = new QCheckBox(Tr::tr("Initialize all members"));
- checkBox->setChecked(true);
- connect(checkBox, &QCheckBox::stateChanged, this,
- [model = constructorParamsModel](int state) {
- if (state != Qt::PartiallyChecked) {
- for (int i = 0; i < model->rowCount(); ++i)
- model->setData(model->index(i, ConstructorParams::ShouldInitColumn),
- state,
- Qt::CheckStateRole);
- }
- });
- connect(checkBox, &QCheckBox::clicked, this, [checkBox] {
- if (checkBox->checkState() == Qt::PartiallyChecked)
- checkBox->setCheckState(Qt::Checked);
- });
- connect(constructorParamsModel,
- &QAbstractItemModel::dataChanged,
- this,
- [model = constructorParamsModel, checkBox] {
- const auto state = [model, selectedCount = model->selectedCount()]() {
- if (selectedCount == 0)
- return Qt::Unchecked;
- if (static_cast<int>(model->memberCount() == selectedCount))
- return Qt::Checked;
- return Qt::PartiallyChecked;
- }();
- checkBox->setCheckState(state);
- });
-
- using A = InsertionPointLocator::AccessSpec;
- auto accessCombo = new QComboBox;
- connect(accessCombo, &QComboBox::currentIndexChanged, this, [this, accessCombo] {
- const auto data = accessCombo->currentData();
- m_accessSpec = static_cast<A>(data.toInt());
- });
- for (auto a : {A::Public, A::Protected, A::Private})
- accessCombo->addItem(InsertionPointLocator::accessSpecToString(a), a);
- const auto row = new QHBoxLayout();
- row->addWidget(new QLabel(Tr::tr("Access") + ":"));
- row->addWidget(accessCombo);
- row->addSpacerItem(new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum));
-
- const auto mainLayout = new QVBoxLayout(this);
- mainLayout->addWidget(
- new QLabel(Tr::tr("Select the members to be initialized in the constructor.\n"
- "Use drag and drop to change the order of the parameters.")));
- mainLayout->addLayout(row);
- mainLayout->addWidget(checkBox);
- mainLayout->addWidget(view);
- mainLayout->addWidget(treeView);
- mainLayout->addWidget(errorLabel);
- mainLayout->addWidget(buttonBox);
- int left, right;
- mainLayout->getContentsMargins(&left, nullptr, &right, nullptr);
- optimalWidth += left + right;
- resize(optimalWidth, mainLayout->sizeHint().height());
- }
-
- InsertionPointLocator::AccessSpec accessSpec() const { return m_accessSpec; }
-
-private:
- InsertionPointLocator::AccessSpec m_accessSpec;
-};
-
-class GenerateConstructorOperation : public CppQuickFixOperation
-{
-public:
- GenerateConstructorOperation(const CppQuickFixInterface &interface)
- : CppQuickFixOperation(interface)
- {
- setDescription(Tr::tr("Generate Constructor"));
-
- m_classAST = astForClassOperations(interface);
- if (!m_classAST)
- return;
- Class *const theClass = m_classAST->symbol;
- if (!theClass)
- return;
-
- // Go through all members and find member variable declarations
- int memberCounter = 0;
- for (auto it = theClass->memberBegin(); it != theClass->memberEnd(); ++it) {
- Symbol *const s = *it;
- if (!s->identifier() || !s->type() || s->type().isTypedef())
- continue;
- if ((s->asDeclaration() && s->type()->asFunctionType()) || s->asFunction())
- continue;
- if (s->asDeclaration() && (s->isPrivate() || s->isProtected()) && !s->isStatic()) {
- const auto name = QString::fromUtf8(s->identifier()->chars(),
- s->identifier()->size());
- parameterModel.emplaceBackParameter(name, s, memberCounter++);
- }
- }
- Overview o = CppCodeStyleSettings::currentProjectCodeStyleOverview();
- o.showArgumentNames = true;
- o.showReturnTypes = true;
- o.showDefaultArguments = true;
- o.showTemplateParameters = true;
- o.showFunctionSignatures = true;
- LookupContext context(currentFile()->cppDocument(), interface.snapshot());
- for (BaseClass *bc : theClass->baseClasses()) {
- const QString className = o.prettyName(bc->name());
-
- ClassOrNamespace *localLookupType = context.lookupType(bc);
- QList<LookupItem> localLookup = localLookupType->lookup(bc->name());
- for (auto &li : localLookup) {
- Symbol *d = li.declaration();
- if (!d->asClass())
- continue;
- for (auto i = d->asClass()->memberBegin(); i != d->asClass()->memberEnd(); ++i) {
- Symbol *s = *i;
- if (s->isProtected() || s->isPublic()) {
- if (s->name()->match(d->name())) {
- // we have found a constructor
- Function *func = s->type().type()->asFunctionType();
- if (!func)
- continue;
- const bool isFirst = parentClassConstructors.empty()
- || parentClassConstructors.back().className
- != className;
- parentClassConstructors.emplace_back(className, parameterModel);
- ParentClassConstructorInfo &constructor = parentClassConstructors.back();
- constructor.declaration = className + o.prettyType(func->type());
- constructor.declaration.replace("std::__1::__get_nullptr_t()",
- "nullptr");
- constructor.useInConstructor = isFirst;
- for (auto arg = func->memberBegin(); arg != func->memberEnd(); ++arg) {
- Symbol *param = *arg;
- Argument *argument = param->asArgument();
- if (!argument) // can also be a block
- continue;
- const QString name = o.prettyName(param->name());
- const StringLiteral *ini = argument->initializer();
- QString defaultValue;
- if (ini)
- defaultValue = QString::fromUtf8(ini->chars(), ini->size())
- .replace("std::__1::__get_nullptr_t()",
- "nullptr");
- constructor.parameters.emplace_back(name,
- defaultValue,
- param,
- &constructor);
- // do not show constructors like QObject(QObjectPrivate & dd, ...)
- ReferenceType *ref = param->type()->asReferenceType();
- if (ref && name == "dd") {
- auto type = o.prettyType(ref->elementType());
- if (type.startsWith("Q") && type.endsWith("Private")) {
- parentClassConstructors.pop_back();
- break;
- }
- }
- }
- }
- }
- }
- }
- }
-
- // add params to parameter lists
- for (auto &c : parentClassConstructors)
- if (c.useInConstructor)
- for (auto &p : c.parameters)
- if (p.init)
- c.addParameter(p);
- }
-
- bool isApplicable() const
- {
- return parameterModel.rowCount() > 0
- || Utils::anyOf(parentClassConstructors,
- [](const auto &parent) { return !parent.parameters.empty(); });
- }
-
- void setTest(bool isTest = true) { m_test = isTest; }
-
-private:
- void perform() override
- {
- auto infos = parameterModel.getInfos();
-
- InsertionPointLocator::AccessSpec accessSpec = InsertionPointLocator::Public;
- if (!m_test) {
- GenerateConstructorDialog dlg(&parameterModel, parentClassConstructors);
- if (dlg.exec() == QDialog::Rejected)
- return;
- accessSpec = dlg.accessSpec();
- infos = parameterModel.getInfos();
- } else {
-#ifdef WITH_TESTS
- ParentClassesModel model(nullptr, parentClassConstructors);
- QAbstractItemModelTester tester(&model);
-#endif
- if (infos.size() >= 3) {
- // if we are testing and have 3 or more members => change the order
- // move first element to the back
- infos.push_back(infos[0]);
- infos.erase(infos.begin());
- }
- for (auto info : infos) {
- if (info->memberVariableName.startsWith("di_"))
- info->defaultValue = "42";
- }
- for (auto &c : parentClassConstructors) {
- if (c.useInConstructor) {
- for (auto &p : c.parameters) {
- if (!p.init && p.parameterName.startsWith("use_")) {
- infos.push_back(&p);
- p.init = true;
- }
- }
- }
- }
- }
- if (infos.empty())
- return;
- struct GenerateConstructorRefactoringHelper : public GetterSetterRefactoringHelper
- {
- const ClassSpecifierAST *m_classAST;
- InsertionPointLocator::AccessSpec m_accessSpec;
- GenerateConstructorRefactoringHelper(CppQuickFixOperation *operation,
- const FilePath &filePath,
- Class *clazz,
- const ClassSpecifierAST *classAST,
- InsertionPointLocator::AccessSpec accessSpec)
- : GetterSetterRefactoringHelper(operation, filePath, clazz)
- , m_classAST(classAST)
- , m_accessSpec(accessSpec)
- {}
- void generateConstructor(std::vector<ConstructorMemberInfo *> members,
- const ParentClassConstructors &parentClassConstructors)
- {
- auto constructorLocation = m_settings->determineSetterLocation(int(members.size()));
- if (constructorLocation == CppQuickFixSettings::FunctionLocation::CppFile
- && !hasSourceFile())
- constructorLocation = CppQuickFixSettings::FunctionLocation::OutsideClass;
-
- Overview overview = CppCodeStyleSettings::currentProjectCodeStyleOverview();
- overview.showTemplateParameters = true;
-
- InsertionLocation implLoc;
- QString implCode;
- CppRefactoringFilePtr implFile;
- QString className = overview.prettyName(m_class->name());
- QStringList insertedNamespaces;
- if (constructorLocation == CppQuickFixSettings::FunctionLocation::CppFile) {
- implLoc = sourceLocationFor(m_class, &insertedNamespaces);
- implFile = m_sourceFile;
- if (m_settings->rewriteTypesinCppFile())
- implCode = symbolAt(m_class, m_sourceFile, implLoc);
- else
- implCode = className;
- implCode += "::" + className + "(";
- } else if (constructorLocation
- == CppQuickFixSettings::FunctionLocation::OutsideClass) {
- implLoc = insertLocationForMethodDefinition(m_class,
- false,
- NamespaceHandling::Ignore,
- m_changes,
- m_headerFile->filePath(),
- &insertedNamespaces);
- implFile = m_headerFile;
- implCode = symbolAt(m_class, m_headerFile, implLoc);
- implCode += "::" + className + "(";
- }
-
- QString inClassDeclaration = overview.prettyName(m_class->name()) + "(";
- QString constructorBody = members.empty() ? QString(") {}") : QString(") : ");
- for (auto &member : members) {
- if (isValueType(member->symbol, &member->customValueType))
- member->type.setConst(false);
- else
- member->type = makeConstRef(member->type);
-
- inClassDeclaration += overview.prettyType(member->type, member->parameterName);
- if (!member->defaultValue.isEmpty())
- inClassDeclaration += " = " + member->defaultValue;
- inClassDeclaration += ", ";
- if (implFile) {
- FullySpecifiedType type = typeAt(member->type,
- m_class,
- implFile,
- implLoc,
- insertedNamespaces);
- implCode += overview.prettyType(type, member->parameterName) + ", ";
- }
- }
- Utils::sort(members, &ConstructorMemberInfo::numberOfMember);
- // first, do the base classes
- for (const auto &parent : parentClassConstructors) {
- if (!parent.useInConstructor)
- continue;
- // Check if we really need a constructor
- if (Utils::anyOf(parent.parameters, [](const auto &param) {
- return param.init || param.originalDefaultValue.isEmpty();
- })) {
- int defaultAtEndCount = 0;
- for (auto i = parent.parameters.crbegin(); i != parent.parameters.crend();
- ++i) {
- if (i->init || i->originalDefaultValue.isEmpty())
- break;
- ++defaultAtEndCount;
- }
- const int numberOfParameters = static_cast<int>(parent.parameters.size())
- - defaultAtEndCount;
- constructorBody += parent.className + "(";
- int counter = 0;
- for (const auto &param : parent.parameters) {
- if (++counter > numberOfParameters)
- break;
- if (param.init) {
- if (param.customValueType)
- constructorBody += "std::move(" + param.parameterName + ')';
- else
- constructorBody += param.parameterName;
- } else if (!param.originalDefaultValue.isEmpty())
- constructorBody += param.originalDefaultValue;
- else
- constructorBody += "/* insert value */";
- constructorBody += ", ";
- }
- constructorBody.resize(constructorBody.length() - 2);
- constructorBody += "),\n";
- }
- }
- for (auto &member : members) {
- if (member->parentClassConstructor)
- continue;
- QString param = member->parameterName;
- if (member->customValueType)
- param = "std::move(" + member->parameterName + ')';
- constructorBody += member->memberVariableName + '(' + param + "),\n";
- }
- if (!members.empty()) {
- inClassDeclaration.resize(inClassDeclaration.length() - 2);
- constructorBody.remove(constructorBody.length() - 2, 1); // ..),\n => ..)\n
- constructorBody += "{}";
- if (!implCode.isEmpty())
- implCode.resize(implCode.length() - 2);
- }
- implCode += constructorBody;
-
- if (constructorLocation == CppQuickFixSettings::FunctionLocation::InsideClass)
- inClassDeclaration += constructorBody;
- else
- inClassDeclaration += QLatin1String(");");
-
- TranslationUnit *tu = m_headerFile->cppDocument()->translationUnit();
- insertAndIndent(m_headerFile,
- m_locator.constructorDeclarationInClass(tu,
- m_classAST,
- m_accessSpec,
- int(members.size())),
- inClassDeclaration);
-
- if (constructorLocation == CppQuickFixSettings::FunctionLocation::CppFile) {
- addSourceFileCode(implCode);
- } else if (constructorLocation
- == CppQuickFixSettings::FunctionLocation::OutsideClass) {
- if (m_isHeaderHeaderFile)
- implCode.prepend("inline ");
- insertAndIndent(m_headerFile, implLoc, implCode);
- }
- }
- };
- GenerateConstructorRefactoringHelper helper(this,
- currentFile()->filePath(),
- m_classAST->symbol,
- m_classAST,
- accessSpec);
-
- auto members = Utils::filtered(infos, [](const auto mi) {
- return mi->init || mi->parentClassConstructor;
- });
- helper.generateConstructor(std::move(members), parentClassConstructors);
- helper.applyChanges();
- }
-
- ConstructorParams parameterModel;
- ParentClassConstructors parentClassConstructors;
- const ClassSpecifierAST *m_classAST = nullptr;
- bool m_test = false;
-};
-} // namespace
-void GenerateConstructor::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result)
-{
- const auto op = QSharedPointer<GenerateConstructorOperation>::create(interface);
- if (!op->isApplicable())
- return;
- op->setTest(m_test);
- result << op;
-}
-
-namespace {
-class ConvertCommentStyleOp : public CppQuickFixOperation
-{
-public:
- ConvertCommentStyleOp(const CppQuickFixInterface &interface, const QList<Token> &tokens,
- Kind kind)
- : CppQuickFixOperation(interface),
- m_tokens(tokens),
- m_kind(kind),
- m_wasCxxStyle(m_kind == T_CPP_COMMENT || m_kind == T_CPP_DOXY_COMMENT),
- m_isDoxygen(m_kind == T_DOXY_COMMENT || m_kind == T_CPP_DOXY_COMMENT)
- {
- setDescription(m_wasCxxStyle ? Tr::tr("Convert Comment to C-Style")
- : Tr::tr("Convert Comment to C++-Style"));
- }
-
-private:
- // Turns every line of a C-style comment into a C++-style comment and vice versa.
- // For C++ -> C, we use one /* */ comment block per line. However, doxygen
- // requires a single comment, so there we just replace the prefix with whitespace and
- // add the start and end comment in extra lines.
- // For cosmetic reasons, we offer some convenience functionality:
- // - Turn /***** ... into ////// ... and vice versa
- // - With C -> C++, remove leading asterisks.
- // - With C -> C++, remove the first and last line of a block if they have no content
- // other than the comment start and end characters.
- // - With C++ -> C, try to align the end comment characters.
- // These are obviously heuristics; we do not guarantee perfect results for everybody.
- // We also don't second-guess the users's selection: E.g. if there is an empty
- // line between the tokens, then it's not the same doxygen comment, but we merge
- // it anyway in C++ to C mode.
- void perform() override
- {
- TranslationUnit * const tu = currentFile()->cppDocument()->translationUnit();
- const QString newCommentStart = getNewCommentStart();
- ChangeSet changeSet;
- int endCommentColumn = -1;
- const QChar oldFillChar = m_wasCxxStyle ? '/' : '*';
- const QChar newFillChar = m_wasCxxStyle ? '*' : '/';
-
- for (const Token &token : m_tokens) {
- const int startPos = tu->getTokenPositionInDocument(token, textDocument());
- const int endPos = tu->getTokenEndPositionInDocument(token, textDocument());
-
- if (m_wasCxxStyle && m_isDoxygen) {
- // Replace "///" characters with whitespace (to keep alignment).
- // The insertion of "/*" and "*/" is done once after the loop.
- changeSet.replace(startPos, startPos + 3, " ");
- continue;
- }
-
- const QTextBlock firstBlock = textDocument()->findBlock(startPos);
- const QTextBlock lastBlock = textDocument()->findBlock(endPos);
- for (QTextBlock block = firstBlock; block.isValid() && block.position() <= endPos;
- block = block.next()) {
- const QString &blockText = block.text();
- const int firstColumn = block == firstBlock ? startPos - block.position() : 0;
- const int endColumn = block == lastBlock ? endPos - block.position()
- : block.length();
-
- // Returns true if the current line looks like "/********/" or "//////////",
- // as is often the case at the start and end of comment blocks.
- const auto fillChecker = [&] {
- if (m_isDoxygen)
- return false;
- QString textToCheck = blockText;
- if (block == firstBlock)
- textToCheck.remove(0, 1);
- if (block == lastBlock)
- textToCheck.chop(block.length() - endColumn);
- return Utils::allOf(textToCheck, [oldFillChar](const QChar &c)
- { return c == oldFillChar || c == ' ';
- }) && textToCheck.count(oldFillChar) > 2;
- };
-
- // Returns the index of the first character of actual comment content,
- // as opposed to visual stuff like slashes, stars or whitespace.
- const auto indexOfActualContent = [&] {
- const int offset = block == firstBlock ? firstColumn + newCommentStart.length()
- : firstColumn;
-
- for (int i = offset, lastFillChar = -1; i < blockText.length(); ++i) {
- if (blockText.at(i) == oldFillChar) {
- lastFillChar = i;
- continue;
- }
- if (!blockText.at(i).isSpace())
- return lastFillChar + 1;
- }
- return -1;
- };
-
- if (fillChecker()) {
- const QString replacement = QString(endColumn - 1 - firstColumn, newFillChar);
- changeSet.replace(block.position() + firstColumn,
- block.position() + endColumn - 1,
- replacement);
- if (m_wasCxxStyle) {
- changeSet.replace(block.position() + firstColumn,
- block.position() + firstColumn + 1, "/");
- changeSet.insert(block.position() + endColumn - 1, "*");
- endCommentColumn = endColumn - 1;
- }
- continue;
- }
-
- // Remove leading noise or even the entire block, if applicable.
- const bool blockIsRemovable = (block == firstBlock || block == lastBlock)
- && firstBlock != lastBlock;
- const auto removeBlock = [&] {
- changeSet.remove(block.position() + firstColumn, block.position() + endColumn);
- };
- const int contentIndex = indexOfActualContent();
- int removed = 0;
- if (contentIndex == -1) {
- if (blockIsRemovable) {
- removeBlock();
- continue;
- } else if (!m_wasCxxStyle) {
- changeSet.replace(block.position() + firstColumn,
- block.position() + endColumn - 1, newCommentStart);
- continue;
- }
- } else if (block == lastBlock && contentIndex == endColumn - 1) {
- if (blockIsRemovable) {
- removeBlock();
- break;
- }
- } else {
- changeSet.remove(block.position() + firstColumn,
- block.position() + firstColumn + contentIndex);
- removed = contentIndex;
- }
-
- if (block == firstBlock) {
- changeSet.replace(startPos, startPos + newCommentStart.length(),
- newCommentStart);
- } else {
- // If the line starts with enough whitespace, replace it with the
- // comment start characters, so we don't move the content to the right
- // unnecessarily. Otherwise, insert the comment start characters.
- if (blockText.startsWith(QString(newCommentStart.size() + removed + 1, ' '))) {
- changeSet.replace(block.position(),
- block.position() + newCommentStart.length(),
- newCommentStart);
- } else {
- changeSet.insert(block.position(), newCommentStart);
- }
- }
-
- if (block == lastBlock) {
- if (m_wasCxxStyle) {
- // This is for proper alignment of the end comment character.
- if (endCommentColumn != -1) {
- const int endCommentPos = block.position() + endCommentColumn;
- if (endPos < endCommentPos)
- changeSet.insert(endPos, QString(endCommentPos - endPos - 1, ' '));
- }
- changeSet.insert(endPos, " */");
- } else {
- changeSet.remove(endPos - 2, endPos);
- }
- }
- }
- }
-
- if (m_wasCxxStyle && m_isDoxygen) {
- const int startPos = tu->getTokenPositionInDocument(m_tokens.first(), textDocument());
- const int endPos = tu->getTokenEndPositionInDocument(m_tokens.last(), textDocument());
- changeSet.insert(startPos, "/*!\n");
- changeSet.insert(endPos, "\n*/");
- }
-
- changeSet.apply(textDocument());
- }
-
- QString getNewCommentStart() const
- {
- if (m_wasCxxStyle) {
- if (m_isDoxygen)
- return "/*!";
- return "/*";
- }
- if (m_isDoxygen)
- return "//!";
- return "//";
- }
-
- const QList<Token> m_tokens;
- const Kind m_kind;
- const bool m_wasCxxStyle;
- const bool m_isDoxygen;
-};
-} // namespace
-
-void ConvertCommentStyle::doMatch(const CppQuickFixInterface &interface,
- TextEditor::QuickFixOperations &result)
-{
- // If there's a selection, then it must entirely consist of comment tokens.
- // If there's no selection, the cursor must be on a comment.
- const QList<Token> &cursorTokens = interface.currentFile()->tokensForCursor();
- if (cursorTokens.empty())
- return;
- if (!cursorTokens.front().isComment())
- return;
-
- // All tokens must be the same kind of comment, but we make an exception for doxygen comments
- // that start with "///", as these are often not intended to be doxygen. For our purposes,
- // we treat them as normal comments.
- const auto effectiveKind = [&interface](const Token &token) {
- if (token.kind() != T_CPP_DOXY_COMMENT)
- return token.kind();
- TranslationUnit * const tu = interface.currentFile()->cppDocument()->translationUnit();
- const int startPos = tu->getTokenPositionInDocument(token, interface.textDocument());
- const QString commentStart = interface.textAt(startPos, 3);
- return commentStart == "///" ? T_CPP_COMMENT : T_CPP_DOXY_COMMENT;
- };
- const Kind kind = effectiveKind(cursorTokens.first());
- for (int i = 1; i < cursorTokens.count(); ++i) {
- if (effectiveKind(cursorTokens.at(i)) != kind)
- return;
- }
-
- // Ok, all tokens are of same(ish) comment type, offer quickfix.
- result << new ConvertCommentStyleOp(interface, cursorTokens, kind);
-}
-
-namespace {
-class MoveFunctionCommentsOp : public CppQuickFixOperation
-{
-public:
- enum class Direction { ToDecl, ToDef };
- MoveFunctionCommentsOp(const CppQuickFixInterface &interface, const Symbol *symbol,
- const QList<Token> &commentTokens, Direction direction)
- : CppQuickFixOperation(interface), m_symbol(symbol), m_commentTokens(commentTokens)
- {
- setDescription(direction == Direction::ToDecl
- ? Tr::tr("Move Function Documentation to Declaration")
- : Tr::tr("Move Function Documentation to Definition"));
- }
-
-private:
- void perform() override
- {
- const auto textDoc = const_cast<QTextDocument *>(currentFile()->document());
- const int pos = currentFile()->cppDocument()->translationUnit()->getTokenPositionInDocument(
- m_symbol->sourceLocation(), textDoc);
- QTextCursor cursor(textDoc);
- cursor.setPosition(pos);
- const CursorInEditor cursorInEditor(cursor, currentFile()->filePath(), editor(),
- editor()->textDocument());
- const auto callback = [symbolLoc = m_symbol->toLink(), comments = m_commentTokens]
- (const Link &link) {
- moveComments(link, symbolLoc, comments);
- };
- CppModelManager::followSymbol(cursorInEditor, callback, true, false,
- FollowSymbolMode::Exact);
- }
-
- static void moveComments(const Link &targetLoc, const Link &symbolLoc,
- const QList<Token> &comments)
- {
- if (!targetLoc.hasValidTarget() || targetLoc.hasSameLocation(symbolLoc))
- return;
-
- CppRefactoringChanges changes(CppModelManager::snapshot());
- const CppRefactoringFilePtr sourceFile = changes.cppFile(symbolLoc.targetFilePath);
- const CppRefactoringFilePtr targetFile
- = targetLoc.targetFilePath == symbolLoc.targetFilePath
- ? sourceFile
- : changes.cppFile(targetLoc.targetFilePath);
- const Document::Ptr &targetCppDoc = targetFile->cppDocument();
- const QList<AST *> targetAstPath = ASTPath(targetCppDoc)(
- targetLoc.targetLine, targetLoc.targetColumn + 1);
- if (targetAstPath.isEmpty())
- return;
- const AST *targetDeclAst = nullptr;
- for (auto it = std::next(std::rbegin(targetAstPath));
- it != std::rend(targetAstPath); ++it) {
- AST * const node = *it;
- if (node->asDeclaration()) {
- targetDeclAst = node;
- continue;
- }
- if (targetDeclAst)
- break;
- }
- if (!targetDeclAst)
- return;
- const int insertionPos = targetCppDoc->translationUnit()->getTokenPositionInDocument(
- targetDeclAst->firstToken(), targetFile->document());
- const TranslationUnit * const sourceTu = sourceFile->cppDocument()->translationUnit();
- const int sourceCommentStartPos = sourceTu->getTokenPositionInDocument(
- comments.first(), sourceFile->document());
- const int sourceCommentEndPos = sourceTu->getTokenEndPositionInDocument(
- comments.last(), sourceFile->document());
-
- // Manually adjust indentation, as both our built-in indenter and ClangFormat
- // are unreliable with regards to comment continuation lines.
- auto tabSettings = [](CppRefactoringFilePtr file) {
- if (auto editor = file->editor())
- return editor->textDocument()->tabSettings();
- return ProjectExplorer::actualTabSettings(file->filePath(), nullptr);
- };
- const TabSettings &sts = tabSettings(sourceFile);
- const TabSettings &tts = tabSettings(targetFile);
- const QTextBlock insertionBlock = targetFile->document()->findBlock(insertionPos);
- const int insertionColumn = tts.columnAt(insertionBlock.text(),
- insertionPos - insertionBlock.position());
- const QTextBlock removalBlock = sourceFile->document()->findBlock(sourceCommentStartPos);
- const QTextBlock removalBlockEnd = sourceFile->document()->findBlock(sourceCommentEndPos);
- const int removalColumn = sts.columnAt(removalBlock.text(),
- sourceCommentStartPos - removalBlock.position());
- const int columnOffset = insertionColumn - removalColumn;
- QString functionDoc;
- if (columnOffset != 0) {
- for (QTextBlock block = removalBlock;
- block.isValid() && block != removalBlockEnd.next();
- block = block.next()) {
- QString text = block.text() + QChar::ParagraphSeparator;
- if (block == removalBlockEnd)
- text = text.left(sourceCommentEndPos - block.position());
- if (block == removalBlock) {
- text = text.mid(sourceCommentStartPos - block.position());
- } else {
- int lineIndentColumn = sts.indentationColumn(text) + columnOffset;
- text.replace(0,
- TabSettings::firstNonSpace(text),
- tts.indentationString(0, lineIndentColumn, 0, insertionBlock));
- }
- functionDoc += text;
- }
- } else {
- functionDoc = sourceFile->textOf(sourceCommentStartPos, sourceCommentEndPos);
- }
-
- // Remove comment plus leading and trailing whitespace, including trailing newline.
- const auto removeAtSource = [&](ChangeSet &changeSet) {
- int removalPos = sourceCommentStartPos;
- const QChar newline(QChar::ParagraphSeparator);
- while (true) {
- const int prev = removalPos - 1;
- if (prev < 0)
- break;
- const QChar prevChar = sourceFile->charAt(prev);
- if (!prevChar.isSpace() || prevChar == newline)
- break;
- removalPos = prev;
- }
- int removalEndPos = sourceCommentEndPos;
- while (true) {
- if (removalEndPos == sourceFile->document()->characterCount())
- break;
- const QChar nextChar = sourceFile->charAt(removalEndPos);
- if (!nextChar.isSpace())
- break;
- ++removalEndPos;
- if (nextChar == newline)
- break;
- }
- changeSet.remove(removalPos, removalEndPos);
- };
-
- ChangeSet targetChangeSet;
- targetChangeSet.insert(insertionPos, functionDoc);
- targetChangeSet.insert(insertionPos, "\n");
- targetChangeSet.insert(insertionPos, QString(insertionColumn, ' '));
- if (targetFile == sourceFile)
- removeAtSource(targetChangeSet);
- targetFile->setChangeSet(targetChangeSet);
- const bool targetFileSuccess = targetFile->apply();
- if (targetFile == sourceFile || !targetFileSuccess)
- return;
- ChangeSet sourceChangeSet;
- removeAtSource(sourceChangeSet);
- sourceFile->setChangeSet(sourceChangeSet);
- sourceFile->apply();
- }
-
- const Symbol * const m_symbol;
- const QList<Token> m_commentTokens;
-};
-} // namespace
-
-void MoveFunctionComments::doMatch(const CppQuickFixInterface &interface,
- TextEditor::QuickFixOperations &result)
-{
- const QList<AST *> &astPath = interface.path();
- if (astPath.isEmpty())
- return;
- const Symbol *symbol = nullptr;
- MoveFunctionCommentsOp::Direction direction = MoveFunctionCommentsOp::Direction::ToDecl;
- for (auto it = std::next(std::rbegin(astPath)); it != std::rend(astPath); ++it) {
- if (const auto func = (*it)->asFunctionDefinition()) {
- symbol = func->symbol;
- direction = MoveFunctionCommentsOp::Direction::ToDecl;
- break;
- }
- const auto decl = (*it)->asSimpleDeclaration();
- if (!decl || !decl->declarator_list)
- continue;
- for (auto it = decl->declarator_list->begin();
- !symbol && it != decl->declarator_list->end(); ++it) {
- PostfixDeclaratorListAST * const funcDecls = (*it)->postfix_declarator_list;
- if (!funcDecls)
- continue;
- for (auto it = funcDecls->begin(); it != funcDecls->end(); ++it) {
- if (const auto func = (*it)->asFunctionDeclarator()) {
- symbol = func->symbol;
- direction = MoveFunctionCommentsOp::Direction::ToDef;
- break;
- }
- }
- }
-
- }
- if (!symbol)
- return;
-
- if (const QList<Token> commentTokens = commentsForDeclaration(
- symbol, *interface.textDocument(), interface.currentFile()->cppDocument());
- !commentTokens.isEmpty()) {
- result << new MoveFunctionCommentsOp(interface, symbol, commentTokens, direction);
- }
-}
-
-namespace {
-class ConvertToMetaMethodCallOp : public CppQuickFixOperation
-{
-public:
- ConvertToMetaMethodCallOp(const CppQuickFixInterface &interface, CallAST *callAst)
- : CppQuickFixOperation(interface), m_callAst(callAst)
- {
- setDescription(Tr::tr("Convert function call to Qt meta-method invocation"));
- }
-
-private:
- void perform() override
- {
- // Construct the argument list.
- Overview ov;
- QStringList arguments;
- for (ExpressionListAST *it = m_callAst->expression_list; it; it = it->next) {
- if (!it->value)
- return;
- const FullySpecifiedType argType
- = typeOfExpr(it->value, currentFile(), snapshot(), context());
- if (!argType.isValid())
- return;
- arguments << QString::fromUtf8("Q_ARG(%1, %2)")
- .arg(ov.prettyType(argType), currentFile()->textOf(it->value));
- }
- QString argsString = arguments.join(", ");
- if (!argsString.isEmpty())
- argsString.prepend(", ");
-
- // Construct the replace string.
- const auto memberAccessAst = m_callAst->base_expression->asMemberAccess();
- QTC_ASSERT(memberAccessAst, return);
- QString baseExpr = currentFile()->textOf(memberAccessAst->base_expression);
- const FullySpecifiedType baseExprType
- = typeOfExpr(memberAccessAst->base_expression, currentFile(), snapshot(), context());
- if (!baseExprType.isValid())
- return;
- if (!baseExprType->asPointerType())
- baseExpr.prepend('&');
- const QString functionName = currentFile()->textOf(memberAccessAst->member_name);
- const QString qMetaObject = "QMetaObject";
- const QString newCall = QString::fromUtf8("%1::invokeMethod(%2, \"%3\"%4)")
- .arg(qMetaObject, baseExpr, functionName, argsString);
-
- // Determine the start and end positions of the replace operation.
- // If the call is preceded by an "emit" keyword, that one has to be removed as well.
- int firstToken = m_callAst->firstToken();
- if (firstToken > 0)
- switch (semanticInfo().doc->translationUnit()->tokenKind(firstToken - 1)) {
- case T_EMIT: case T_Q_EMIT: --firstToken; break;
- default: break;
- }
- const TranslationUnit *const tu = semanticInfo().doc->translationUnit();
- const int startPos = tu->getTokenPositionInDocument(firstToken, textDocument());
- const int endPos = tu->getTokenPositionInDocument(m_callAst->lastToken(), textDocument());
-
- // Replace the old call with the new one.
- ChangeSet changes;
- changes.replace(startPos, endPos, newCall);
-
- // Insert include for QMetaObject, if necessary.
- const Identifier qMetaObjectId(qPrintable(qMetaObject), qMetaObject.size());
- Scope * const scope = currentFile()->scopeAt(firstToken);
- const QList<LookupItem> results = context().lookup(&qMetaObjectId, scope);
- bool isDeclared = false;
- for (const LookupItem &item : results) {
- if (Symbol *declaration = item.declaration(); declaration && declaration->asClass()) {
- isDeclared = true;
- break;
- }
- }
- if (!isDeclared) {
- insertNewIncludeDirective('<' + qMetaObject + '>', currentFile(), semanticInfo().doc,
- changes);
- }
-
- // Apply the changes.
- currentFile()->setChangeSet(changes);
- currentFile()->apply();
- }
-
- const CallAST * const m_callAst;
-};
-} // namespace
-
-void ConvertToMetaMethodCall::doMatch(const CppQuickFixInterface &interface,
- TextEditor::QuickFixOperations &result)
-{
- const Document::Ptr &cppDoc = interface.currentFile()->cppDocument();
- const QList<AST *> path = ASTPath(cppDoc)(interface.cursor());
- if (path.isEmpty())
- return;
-
- // Are we on a member function call?
- CallAST *callAst = nullptr;
- for (auto it = path.crbegin(); it != path.crend(); ++it) {
- if ((callAst = (*it)->asCall()))
- break;
- }
- if (!callAst || !callAst->base_expression)
- return;
- ExpressionAST *baseExpr = nullptr;
- const NameAST *nameAst = nullptr;
- if (const MemberAccessAST * const ast = callAst->base_expression->asMemberAccess()) {
- baseExpr = ast->base_expression;
- nameAst = ast->member_name;
- }
- if (!baseExpr || !nameAst || !nameAst->name)
- return;
-
- // Locate called function and check whether it is invokable.
- Scope *scope = cppDoc->globalNamespace();
- for (auto it = path.crbegin(); it != path.crend(); ++it) {
- if (const CompoundStatementAST * const stmtAst = (*it)->asCompoundStatement()) {
- scope = stmtAst->symbol;
- break;
- }
- }
- const LookupContext context(cppDoc, interface.snapshot());
- TypeOfExpression exprType;
- exprType.setExpandTemplates(true);
- exprType.init(cppDoc, interface.snapshot());
- const QList<LookupItem> typeMatches = exprType(callAst->base_expression, cppDoc, scope);
- for (const LookupItem &item : typeMatches) {
- if (const auto func = item.type()->asFunctionType(); func && func->methodKey()) {
- result << new ConvertToMetaMethodCallOp(interface, callAst);
- return;
- }
- }
-}
-
-void createCppQuickFixes()
-{
- new AddIncludeForUndefinedIdentifier;
-
- new FlipLogicalOperands;
- new InverseLogicalComparison;
- new RewriteLogicalAnd;
-
- new ConvertToCamelCase;
-
- new ConvertCStringToNSString;
- new ConvertNumericLiteral;
- new TranslateStringLiteral;
- new WrapStringLiteral;
-
- new MoveDeclarationOutOfIf;
- new MoveDeclarationOutOfWhile;
-
- new SplitIfStatement;
- new SplitSimpleDeclaration;
-
- new AddBracesToControlStatement;
- new RearrangeParamDeclarationList;
- new ReformatPointerDeclaration;
-
- new CompleteSwitchCaseStatement;
- new InsertQtPropertyMembers;
- new ConvertQt4Connect;
-
- new ApplyDeclDefLinkChanges;
- new ConvertFromAndToPointer;
- new ExtractFunction;
- new ExtractLiteralAsParameter;
- new GenerateGetterSetter;
- new GenerateGettersSettersForClass;
- new InsertDeclFromDef;
- new InsertDefFromDecl;
- new AddDeclarationForUndeclaredIdentifier;
- new InsertDefsFromDecls;
-
- new MoveFuncDefOutside;
- new MoveAllFuncDefOutside;
- new MoveFuncDefToDeclPush;
- new MoveFuncDefToDeclPull;
-
- new AssignToLocalVariable;
-
- new InsertVirtualMethods;
-
- new OptimizeForLoop;
-
- new EscapeStringLiteral;
-
- new ExtraRefactoringOperations;
-
- new RemoveUsingNamespace;
- new GenerateConstructor;
- new ConvertCommentStyle;
- new MoveFunctionComments;
- new ConvertToMetaMethodCall;
-}
-
-void destroyCppQuickFixes()
-{
- for (int i = g_cppQuickFixFactories.size(); --i >= 0; )
- delete g_cppQuickFixFactories.at(i);
-}
-
-} // namespace Internal
-} // namespace CppEditor
-
-#include "cppquickfixes.moc"
diff --git a/src/plugins/cppeditor/cppquickfixes.h b/src/plugins/cppeditor/cppquickfixes.h
deleted file mode 100644
index bd47b318c7..0000000000
--- a/src/plugins/cppeditor/cppquickfixes.h
+++ /dev/null
@@ -1,630 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#pragma once
-
-#include "cppquickfix.h"
-
-#include <variant>
-
-///
-/// Adding New Quick Fixes
-///
-/// When adding new Quick Fixes, make sure that the doMatch() function is "cheap".
-/// Otherwise, since the match() functions are also called to generate context menu
-/// entries, the user might experience a delay opening the context menu.
-///
-
-namespace CppEditor {
-namespace Internal {
-using TypeOrExpr = std::variant<const CPlusPlus::ExpressionAST *, CPlusPlus::FullySpecifiedType>;
-
-void createCppQuickFixes();
-void destroyCppQuickFixes();
-
-class ExtraRefactoringOperations : public CppQuickFixFactory
-{
-public:
- void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override;
-};
-
-/*!
- Adds an include for an undefined identifier or only forward declared identifier.
-
- Activates on: the undefined identifier
-*/
-class AddIncludeForUndefinedIdentifier : public CppQuickFixFactory
-{
-public:
- void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override;
-};
-
-// Exposed for tests
-class AddIncludeForUndefinedIdentifierOp: public CppQuickFixOperation
-{
-public:
- AddIncludeForUndefinedIdentifierOp(const CppQuickFixInterface &interface, int priority,
- const QString &include);
- void perform() override;
-
- QString include() const { return m_include; }
-
-private:
- QString m_include;
-};
-
-class AddForwardDeclForUndefinedIdentifierOp: public CppQuickFixOperation
-{
-public:
- AddForwardDeclForUndefinedIdentifierOp(const CppQuickFixInterface &interface, int priority,
- const QString &fqClassName, int symbolPos);
-private:
- void perform() override;
-
- const QString m_className;
- const int m_symbolPos;
-};
-
-/*!
- Rewrite
- a op b
-
- As
- b flipop a
-
- Activates on: <= < > >= == != && ||
-*/
-class FlipLogicalOperands: public CppQuickFixFactory
-{
-public:
- void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override;
-};
-
-/*!
- Rewrite
- a op b -> !(a invop b)
- (a op b) -> !(a invop b)
- !(a op b) -> (a invob b)
-
- Activates on: <= < > >= == !=
-*/
-class InverseLogicalComparison: public CppQuickFixFactory
-{
-public:
- void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override;
-};
-
-/*!
- Rewrite
- !a && !b
-
- As
- !(a || b)
-
- Activates on: &&
-*/
-class RewriteLogicalAnd: public CppQuickFixFactory
-{
-public:
- void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override;
-};
-
-/*!
- Replace
- "abcd"
- QLatin1String("abcd")
- QLatin1Literal("abcd")
-
- With
- @"abcd"
-
- Activates on: the string literal, if the file type is a Objective-C(++) file.
-*/
-class ConvertCStringToNSString: public CppQuickFixFactory
-{
-public:
- void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override;
-};
-
-/*!
- Base class for converting numeric literals between decimal, octal and hex.
- Does the base check for the specific ones and parses the number.
-
- Test cases:
- 0xFA0Bu;
- 0X856A;
- 298.3;
- 199;
- 074;
- 199L;
- 074L;
- -199;
- -017;
- 0783; // invalid octal
- 0; // border case, allow only hex<->decimal
-
- Activates on: numeric literals
-*/
-class ConvertNumericLiteral: public CppQuickFixFactory
-{
-public:
- void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override;
-};
-
-/*!
- Replace
- "abcd"
-
- With
- tr("abcd") or
- QCoreApplication::translate("CONTEXT", "abcd") or
- QT_TRANSLATE_NOOP("GLOBAL", "abcd")
-
- depending on what is available.
-
- Activates on: the string literal
-*/
-class TranslateStringLiteral: public CppQuickFixFactory
-{
-public:
- void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override;
-};
-
-/*!
- Replace
- "abcd" -> QLatin1String("abcd")
- @"abcd" -> QLatin1String("abcd") (Objective C)
- 'a' -> QLatin1Char('a')
- 'a' -> "a"
- "a" -> 'a' or QLatin1Char('a') (Single character string constants)
- "\n" -> '\n', QLatin1Char('\n')
-
- Except if they are already enclosed in
- QLatin1Char, QT_TRANSLATE_NOOP, tr,
- trUtf8, QLatin1Literal, QLatin1String
-
- Activates on: the string or character literal
-*/
-
-class WrapStringLiteral: public CppQuickFixFactory
-{
-public:
- void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override;
-};
-
-/*!
- Turns "an_example_symbol" into "anExampleSymbol" and
- "AN_EXAMPLE_SYMBOL" into "AnExampleSymbol".
-
- Activates on: identifiers
-*/
-class ConvertToCamelCase : public CppQuickFixFactory
-{
-public:
- ConvertToCamelCase(bool test = false) : CppQuickFixFactory(), m_test(test) {}
-
- void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override;
-
-private:
- const bool m_test;
-};
-
-/*!
- Replace
- if (Type name = foo()) {...}
-
- With
- Type name = foo();
- if (name) {...}
-
- Activates on: the name of the introduced variable
-*/
-class MoveDeclarationOutOfIf: public CppQuickFixFactory
-{
-public:
- void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override;
-};
-
-/*!
- Replace
- while (Type name = foo()) {...}
-
- With
- Type name;
- while ((name = foo()) != 0) {...}
-
- Activates on: the name of the introduced variable
-*/
-class MoveDeclarationOutOfWhile: public CppQuickFixFactory
-{
-public:
- void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override;
-};
-
-/*!
- Replace
- if (something && something_else) {
- }
-
- with
- if (something)
- if (something_else) {
- }
- }
-
- and
- if (something || something_else)
- x;
-
- with
- if (something)
- x;
- else if (something_else)
- x;
-
- Activates on: && or ||
-*/
-class SplitIfStatement: public CppQuickFixFactory
-{
-public:
- void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override;
-};
-
-/*!
- Rewrite
- int *a, b;
-
- As
- int *a;
- int b;
-
- Activates on: the type or the variable names.
-*/
-class SplitSimpleDeclaration: public CppQuickFixFactory
-{
-public:
- void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override;
-};
-
-/*!
- Add curly braces to a control statement that doesn't already contain a
- compound statement. I.e.
-
- if (a)
- b;
- becomes
- if (a) {
- b;
- }
-
- Activates on: the keyword
-*/
-class AddBracesToControlStatement : public CppQuickFixFactory
-{
-public:
- void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override;
-};
-
-/*!
- Switches places of the parameter declaration under cursor
- with the next or the previous one in the parameter declaration list
-
- Activates on: parameter declarations
-*/
-class RearrangeParamDeclarationList : public CppQuickFixFactory
-{
-public:
- void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override;
-};
-
-/*!
- Reformats a pointer, reference or rvalue reference type/declaration.
-
- Works also with selections (except when the cursor is not on any AST).
-
- Activates on: simple declarations, parameters and return types of function
- declarations and definitions, control flow statements (if,
- while, for, foreach) with declarations.
-*/
-class ReformatPointerDeclaration : public CppQuickFixFactory
-{
-public:
- void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override;
-};
-
-/*!
- Adds missing case statements for "switch (enumVariable)"
- */
-class CompleteSwitchCaseStatement: public CppQuickFixFactory
-{
-public:
- CompleteSwitchCaseStatement() { setClangdReplacement({12}); }
-
-private:
- void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override;
-};
-
-/*!
- Adds a declarations to a definition
- */
-class InsertDeclFromDef: public CppQuickFixFactory
-{
-public:
- void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override;
-};
-
-/*!
- Adds a definition for a declaration.
- */
-class InsertDefFromDecl: public CppQuickFixFactory
-{
-public:
- void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override;
- bool m_defPosOutsideClass = false;
-};
-
-class AddDeclarationForUndeclaredIdentifier : public CppQuickFixFactory
-{
-public:
- void doMatch(const CppQuickFixInterface &interface,
- TextEditor::QuickFixOperations &result) override;
-
-#ifdef WITH_TESTS
- void setMembersOnly() { m_membersOnly = true; }
-#endif
-
-private:
- void collectOperations(const CppQuickFixInterface &interface,
- TextEditor::QuickFixOperations &result);
- void handleCall(const CPlusPlus::CallAST *call, const CppQuickFixInterface &interface,
- TextEditor::QuickFixOperations &result);
-
- // Returns whether to still do other checks.
- bool checkForMemberInitializer(const CppQuickFixInterface &interface,
- TextEditor::QuickFixOperations &result);
-
- void maybeAddMember(const CppQuickFixInterface &interface, CPlusPlus::Scope *scope,
- const QByteArray &classTypeExpr, const TypeOrExpr &typeOrExpr,
- const CPlusPlus::CallAST *call, TextEditor::QuickFixOperations &result);
-
- void maybeAddStaticMember(
- const CppQuickFixInterface &interface, const CPlusPlus::QualifiedNameAST *qualName,
- const TypeOrExpr &typeOrExpr, const CPlusPlus::CallAST *call,
- TextEditor::QuickFixOperations &result);
-
- bool m_membersOnly = false;
-};
-
-/*!
- Adds a definition for any number of member function declarations.
- */
-class InsertDefsFromDecls : public CppQuickFixFactory
-{
-public:
- void doMatch(const CppQuickFixInterface &interface,
- TextEditor::QuickFixOperations &result) override;
-
- enum class Mode {
- Off, // Testing: simulates user canceling the dialog
- Impl, // Testing: simulates user choosing cpp file for every function
- Alternating, // Testing: simulates user choosing a different DefPos for every function
- User // Normal interactive mode
- };
- void setMode(Mode mode) { m_mode = mode; }
-
-private:
- Mode m_mode = Mode::User;
-};
-
-/*!
- Extracts the selected code and puts it to a function
- */
-class ExtractFunction : public CppQuickFixFactory
-{
-public:
- using FunctionNameGetter = std::function<QString()>;
-
- ExtractFunction(FunctionNameGetter functionNameGetter = FunctionNameGetter());
- void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override;
-
-private:
- FunctionNameGetter m_functionNameGetter; // For tests to avoid GUI pop-up.
-};
-
-/*!
- Extracts the selected constant and converts it to a parameter of the current function.
-
- Activates on numeric, bool, character, or string literal in the function body.
- */
-class ExtractLiteralAsParameter : public CppQuickFixFactory
-{
-public:
- void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override;
-};
-
-/*!
- Converts the selected variable to a pointer if it is a stack variable or reference, or vice versa.
-
- Activates on variable declarations.
- */
-class ConvertFromAndToPointer : public CppQuickFixFactory
-{
-public:
- void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override;
-};
-
-/*!
- Adds getter and setter functions for a member variable
- */
-class GenerateGetterSetter : public CppQuickFixFactory
-{
-public:
- void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override;
-};
-
-/*!
- Adds getter and setter functions for several member variables
- */
-class GenerateGettersSettersForClass : public CppQuickFixFactory
-{
-protected:
- void setTest() { m_test = true; }
-
-private:
- void doMatch(const CppQuickFixInterface &interface,
- TextEditor::QuickFixOperations &result) override;
-
- bool m_test = false;
-};
-
-/*!
- Adds missing members for a Q_PROPERTY
- */
-class InsertQtPropertyMembers : public CppQuickFixFactory
-{
-public:
- void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override;
-};
-
-/*!
- Converts a Qt 4 QObject::connect() to Qt 5 style.
- */
-class ConvertQt4Connect : public CppQuickFixFactory
-{
-public:
- void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override;
-};
-
-/*!
- Applies function signature changes
- */
-class ApplyDeclDefLinkChanges: public CppQuickFixFactory
-{
-public:
- void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override;
-};
-
-/*!
- Moves the definition of a member function outside the class or moves the definition of a member
- function or a normal function to the implementation file.
- */
-class MoveFuncDefOutside: public CppQuickFixFactory
-{
-public:
- void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override;
-};
-
-/*!
- Moves all member function definitions outside the class or to the implementation file.
- */
-class MoveAllFuncDefOutside: public CppQuickFixFactory
-{
-public:
- void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override;
-};
-
-/*!
- Moves the definition of a function to its declaration, with the cursor on the definition.
- */
-class MoveFuncDefToDeclPush : public CppQuickFixFactory
-{
-public:
- void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override;
-};
-
-/*!
- Moves the definition of a function to its declaration, with the cursor on the declaration.
- */
-class MoveFuncDefToDeclPull : public CppQuickFixFactory
-{
-public:
- void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override;
-};
-
-/*!
- Assigns the return value of a function call or a new expression to a local variable
- */
-class AssignToLocalVariable : public CppQuickFixFactory
-{
-public:
- void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override;
-};
-
-/*!
- Optimizes a for loop to avoid permanent condition check and forces to use preincrement
- or predecrement operators in the expression of the for loop.
- */
-class OptimizeForLoop : public CppQuickFixFactory
-{
-public:
- void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override;
-};
-
-/*!
- Escapes or unescapes a string literal as UTF-8.
-
- Escapes non-ASCII characters in a string literal to hexadecimal escape sequences.
- Unescapes octal or hexadecimal escape sequences in a string literal.
- String literals are handled as UTF-8 even if file's encoding is not UTF-8.
- */
-class EscapeStringLiteral : public CppQuickFixFactory
-{
-public:
- void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override;
-};
-
-/*!
- Removes a using directive (using namespace xyz).
-
-*/
-class RemoveUsingNamespace : public CppQuickFixFactory
-{
-public:
- RemoveUsingNamespace() { setClangdReplacement({10}); }
-
-private:
- void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override;
-};
-
-/*!
- Generate constructor
- */
-class GenerateConstructor : public CppQuickFixFactory
-{
-protected:
- void setTest() { m_test = true; }
-
-private:
- void doMatch(const CppQuickFixInterface &interface,
- TextEditor::QuickFixOperations &result) override;
-
- bool m_test = false;
-};
-
-//! Converts C-style to C++-style comments and vice versa
-class ConvertCommentStyle : public CppQuickFixFactory
-{
-private:
- void doMatch(const CppQuickFixInterface &interface,
- TextEditor::QuickFixOperations &result) override;
-};
-
-//! Moves function documentation between declaration and implementation.
-class MoveFunctionComments : public CppQuickFixFactory
-{
-private:
- void doMatch(const CppQuickFixInterface &interface,
- TextEditor::QuickFixOperations &result) override;
-};
-
-//! Converts a normal function call into a meta method invocation, if the functions is
-//! marked as invokable.
-class ConvertToMetaMethodCall : public CppQuickFixFactory
-{
-private:
- void doMatch(const CppQuickFixInterface &interface,
- TextEditor::QuickFixOperations &result) override;
-};
-
-} // namespace Internal
-} // namespace CppEditor
diff --git a/src/plugins/cppeditor/cpprefactoringchanges.cpp b/src/plugins/cppeditor/cpprefactoringchanges.cpp
index 053daeb245..d9e5a1c883 100644
--- a/src/plugins/cppeditor/cpprefactoringchanges.cpp
+++ b/src/plugins/cppeditor/cpprefactoringchanges.cpp
@@ -4,6 +4,8 @@
#include "cpprefactoringchanges.h"
#include "cppeditorconstants.h"
+#include "cppeditorwidget.h"
+#include "cppsemanticinfo.h"
#include "cppworkingcopy.h"
#include <projectexplorer/editorconfiguration.h>
@@ -19,6 +21,7 @@
#include <utility>
+using namespace Core;
using namespace CPlusPlus;
using namespace Utils;
@@ -41,10 +44,15 @@ CppRefactoringChanges::CppRefactoringChanges(const Snapshot &snapshot)
{
}
-CppRefactoringFilePtr CppRefactoringChanges::file(TextEditor::TextEditorWidget *editor, const Document::Ptr &document)
+CppRefactoringFilePtr CppRefactoringChanges::file(
+ TextEditor::TextEditorWidget *editor, const Document::Ptr &document)
{
CppRefactoringFilePtr result(new CppRefactoringFile(editor));
result->setCppDocument(document);
+ if (const auto cppEditorWidget = qobject_cast<CppEditorWidget *>(editor)) {
+ result->m_data = QSharedPointer<CppRefactoringChangesData>::create(
+ cppEditorWidget->semanticInfo().snapshot);
+ }
return result;
}
@@ -55,6 +63,18 @@ TextEditor::RefactoringFilePtr CppRefactoringChanges::file(const FilePath &fileP
CppRefactoringFilePtr CppRefactoringChanges::cppFile(const Utils::FilePath &filePath) const
{
+ // Prefer documents from editors, as these are already parsed and up to date with regards to
+ // unsaved changes.
+ const QList<IEditor *> editors = DocumentModel::editorsForFilePath(filePath);
+ for (IEditor *editor : editors) {
+ if (const auto textEditor = qobject_cast<TextEditor::BaseTextEditor *>(editor)) {
+ if (const auto editorWidget = qobject_cast<CppEditorWidget *>(
+ textEditor->editorWidget())) {
+ return file(editorWidget, editorWidget->semanticInfo().doc);
+ }
+ }
+ }
+
return CppRefactoringFilePtr(new CppRefactoringFile(filePath, m_data));
}
@@ -201,6 +221,9 @@ ChangeSet::Range CppRefactoringFile::range(const AST *ast) const
int CppRefactoringFile::startOf(unsigned index) const
{
+ if (const auto loc = expansionLoc(index))
+ return loc->first;
+
int line, column;
cppDocument()->translationUnit()->getPosition(tokenAt(index).utf16charsBegin(), &line, &column);
return document()->findBlockByNumber(line - 1).position() + column - 1;
@@ -209,15 +232,14 @@ int CppRefactoringFile::startOf(unsigned index) const
int CppRefactoringFile::startOf(const AST *ast) const
{
QTC_ASSERT(ast, return 0);
- int firstToken = ast->firstToken();
- const int lastToken = ast->lastToken();
- while (tokenAt(firstToken).generated() && firstToken < lastToken)
- ++firstToken;
- return startOf(firstToken);
+ return startOf(ast->firstToken());
}
int CppRefactoringFile::endOf(unsigned index) const
{
+ if (const auto loc = expansionLoc(index))
+ return loc->first + loc->second;
+
int line, column;
cppDocument()->translationUnit()->getPosition(tokenAt(index).utf16charsEnd(), &line, &column);
return document()->findBlockByNumber(line - 1).position() + column - 1;
@@ -228,14 +250,17 @@ int CppRefactoringFile::endOf(const AST *ast) const
QTC_ASSERT(ast, return 0);
int lastToken = ast->lastToken() - 1;
QTC_ASSERT(lastToken >= 0, return -1);
- const int firstToken = ast->firstToken();
- while (tokenAt(lastToken).generated() && lastToken > firstToken)
- --lastToken;
return endOf(lastToken);
}
void CppRefactoringFile::startAndEndOf(unsigned index, int *start, int *end) const
{
+ if (const auto loc = expansionLoc(index)) {
+ *start = loc->first;
+ *end = loc->first + loc->second;
+ return;
+ }
+
int line, column;
Token token(tokenAt(index));
cppDocument()->translationUnit()->getPosition(token.utf16charsBegin(), &line, &column);
@@ -243,6 +268,13 @@ void CppRefactoringFile::startAndEndOf(unsigned index, int *start, int *end) con
*end = *start + token.utf16chars();
}
+std::optional<std::pair<int, int> > CppRefactoringFile::expansionLoc(unsigned int index) const
+{
+ if (!tokenAt(index).expanded())
+ return {};
+ return cppDocument()->translationUnit()->getExpansionPosition(index);
+}
+
QString CppRefactoringFile::textOf(const AST *ast) const
{
return textOf(startOf(ast), endOf(ast));
diff --git a/src/plugins/cppeditor/cpprefactoringchanges.h b/src/plugins/cppeditor/cpprefactoringchanges.h
index 6350751a34..957d02674c 100644
--- a/src/plugins/cppeditor/cpprefactoringchanges.h
+++ b/src/plugins/cppeditor/cpprefactoringchanges.h
@@ -11,6 +11,8 @@
#include <texteditor/refactoringchanges.h>
+#include <optional>
+
namespace CppEditor {
class CppRefactoringChanges;
class CppRefactoringFile;
@@ -30,7 +32,6 @@ public:
bool isCursorOn(unsigned tokenIndex) const;
bool isCursorOn(const CPlusPlus::AST *ast) const;
- Range range(int start, int end) const;
Range range(unsigned tokenIndex) const;
Range range(const CPlusPlus::AST *ast) const;
@@ -43,6 +44,8 @@ public:
void startAndEndOf(unsigned index, int *start, int *end) const;
+ std::optional<std::pair<int, int>> expansionLoc(unsigned index) const;
+
QList<CPlusPlus::Token> tokensForCursor() const;
QList<CPlusPlus::Token> tokensForCursor(const QTextCursor &cursor) const;
QList<CPlusPlus::Token> tokensForLine(int line) const;
diff --git a/src/plugins/cppeditor/cpprenaming_test.cpp b/src/plugins/cppeditor/cpprenaming_test.cpp
index 2827434be5..a31d00fa07 100644
--- a/src/plugins/cppeditor/cpprenaming_test.cpp
+++ b/src/plugins/cppeditor/cpprenaming_test.cpp
@@ -5,7 +5,7 @@
#include "cppeditorwidget.h"
#include "cppmodelmanager.h"
-#include "cppquickfix_test.h"
+#include "quickfixes/cppquickfix_test.h"
#include <texteditor/texteditor.h>
diff --git a/src/plugins/cppeditor/cppsemanticinfoupdater.cpp b/src/plugins/cppeditor/cppsemanticinfoupdater.cpp
index e398c14b86..f9e13df082 100644
--- a/src/plugins/cppeditor/cppsemanticinfoupdater.cpp
+++ b/src/plugins/cppeditor/cppsemanticinfoupdater.cpp
@@ -9,8 +9,6 @@
#include <cplusplus/CppDocument.h>
#include <cplusplus/TranslationUnit.h>
-#include <extensionsystem/pluginmanager.h>
-
#include <utils/async.h>
#include <utils/futuresynchronizer.h>
#include <utils/qtcassert.h>
@@ -143,7 +141,7 @@ void SemanticInfoUpdater::updateDetached(const SemanticInfo::Source &source)
});
const auto future = Utils::asyncRun(CppModelManager::sharedThreadPool(), doUpdate, source);
d->m_watcher->setFuture(future);
- ExtensionSystem::PluginManager::futureSynchronizer()->addFuture(future);
+ Utils::futureSynchronizer()->addFuture(future);
}
SemanticInfo SemanticInfoUpdater::semanticInfo() const
diff --git a/src/plugins/cppeditor/cpptoolsreuse.cpp b/src/plugins/cppeditor/cpptoolsreuse.cpp
index 947ecdd4c6..6e0802b4bb 100644
--- a/src/plugins/cppeditor/cpptoolsreuse.cpp
+++ b/src/plugins/cppeditor/cpptoolsreuse.cpp
@@ -15,9 +15,9 @@
#include "cppfilesettingspage.h"
#include "cpphighlighter.h"
#include "cppqtstyleindenter.h"
-#include "cppquickfixassistant.h"
#include "cpprefactoringchanges.h"
#include "projectinfo.h"
+#include "quickfixes/cppquickfixassistant.h"
#include <coreplugin/documentmanager.h>
#include <coreplugin/editormanager/editormanager.h>
@@ -365,15 +365,10 @@ bool fileSizeExceedsLimit(const FilePath &filePath, int sizeLimitInMb)
const qint64 fileSizeInMB = filePath.fileSize() / (1000 * 1000);
if (fileSizeInMB > sizeLimitInMb) {
- const QString msg = Tr::tr("C++ Indexer: Skipping file \"%1\" because it is too big.")
- .arg(filePath.displayName());
-
- QMetaObject::invokeMethod(Core::MessageManager::instance(),
- [msg]() { Core::MessageManager::writeSilently(msg); });
-
+ Core::MessageManager::writeSilently(Tr::tr("C++ Indexer: Skipping file \"%1\" because "
+ "it is too big.").arg(filePath.displayName()));
return true;
}
-
return false;
}
diff --git a/src/plugins/cppeditor/cpptoolstestcase.cpp b/src/plugins/cppeditor/cpptoolstestcase.cpp
index 869bbf9b84..70435abe9f 100644
--- a/src/plugins/cppeditor/cpptoolstestcase.cpp
+++ b/src/plugins/cppeditor/cpptoolstestcase.cpp
@@ -510,4 +510,19 @@ int clangdIndexingTimeout()
return intervalAsInt;
}
+SourceFilesRefreshGuard::SourceFilesRefreshGuard()
+{
+ connect(CppModelManager::instance(), &CppModelManager::sourceFilesRefreshed, this, [this] {
+ m_refreshed = true;
+ });
+}
+
+bool SourceFilesRefreshGuard::wait()
+{
+ for (int i = 0; i < 10 && !m_refreshed; ++i) {
+ CppEditor::Tests::waitForSignalOrTimeout(
+ CppModelManager::instance(), &CppModelManager::sourceFilesRefreshed, 1000);
+ }
+ return m_refreshed;
+}
} // namespace CppEditor::Tests
diff --git a/src/plugins/cppeditor/cpptoolstestcase.h b/src/plugins/cppeditor/cpptoolstestcase.h
index 3bbeff0bb6..6e9f0133ba 100644
--- a/src/plugins/cppeditor/cpptoolstestcase.h
+++ b/src/plugins/cppeditor/cpptoolstestcase.h
@@ -205,5 +205,17 @@ private:
TemporaryCopiedDir();
};
+class SourceFilesRefreshGuard : public QObject
+{
+public:
+ SourceFilesRefreshGuard();
+
+ void reset() { m_refreshed = false; }
+ bool wait();
+
+private:
+ bool m_refreshed = false;
+};
+
} // namespace Tests
} // namespace CppEditor
diff --git a/src/plugins/cppeditor/cppuseselectionsupdater.cpp b/src/plugins/cppeditor/cppuseselectionsupdater.cpp
index 67fb6d7a1b..bf1eabd153 100644
--- a/src/plugins/cppeditor/cppuseselectionsupdater.cpp
+++ b/src/plugins/cppeditor/cppuseselectionsupdater.cpp
@@ -7,8 +7,6 @@
#include "cppeditorwidget.h"
#include "cppmodelmanager.h"
-#include <extensionsystem/pluginmanager.h>
-
#include <utils/futuresynchronizer.h>
#include <utils/qtcassert.h>
#include <utils/textutils.h>
@@ -75,7 +73,7 @@ CppUseSelectionsUpdater::RunnerInfo CppUseSelectionsUpdater::update(CallType cal
m_runnerWordStartPosition = params.textCursor.position();
m_runnerWatcher->setFuture(cppEditorDocument->cursorInfo(params));
- ExtensionSystem::PluginManager::futureSynchronizer()->addFuture(m_runnerWatcher->future());
+ Utils::futureSynchronizer()->addFuture(m_runnerWatcher->future());
return RunnerInfo::Started;
} else { // synchronous case
abortSchedule();
diff --git a/src/plugins/cppeditor/cursorineditor.h b/src/plugins/cppeditor/cursorineditor.h
index 46dd4b2b2a..77c853f97b 100644
--- a/src/plugins/cppeditor/cursorineditor.h
+++ b/src/plugins/cppeditor/cursorineditor.h
@@ -3,6 +3,7 @@
#pragma once
+#include <cplusplus/CppDocument.h>
#include <utils/fileutils.h>
#include <utils/link.h>
@@ -22,15 +23,18 @@ class CursorInEditor
{
public:
CursorInEditor(const QTextCursor &cursor, const Utils::FilePath &filePath,
- CppEditorWidget *editorWidget = nullptr,
- TextEditor::TextDocument *textDocument = nullptr)
+ CppEditorWidget *editorWidget = nullptr,
+ TextEditor::TextDocument *textDocument = nullptr,
+ const CPlusPlus::Document::Ptr &cppDocument = {})
: m_cursor(cursor)
, m_filePath(filePath)
, m_editorWidget(editorWidget)
, m_textDocument(textDocument)
+ , m_cppDocument(cppDocument)
{}
CppEditorWidget *editorWidget() const { return m_editorWidget; }
TextEditor::TextDocument *textDocument() const { return m_textDocument; }
+ CPlusPlus::Document::Ptr cppDocument() const { return m_cppDocument; }
const QTextCursor &cursor() const { return m_cursor; }
const Utils::FilePath &filePath() const { return m_filePath; }
private:
@@ -38,6 +42,7 @@ private:
Utils::FilePath m_filePath;
CppEditorWidget *m_editorWidget = nullptr;
TextEditor::TextDocument * const m_textDocument;
+ const CPlusPlus::Document::Ptr m_cppDocument;
};
} // namespace CppEditor
diff --git a/src/plugins/cppeditor/fileandtokenactions_test.cpp b/src/plugins/cppeditor/fileandtokenactions_test.cpp
index 5acca7fa11..7c8175fa18 100644
--- a/src/plugins/cppeditor/fileandtokenactions_test.cpp
+++ b/src/plugins/cppeditor/fileandtokenactions_test.cpp
@@ -4,13 +4,13 @@
#include "fileandtokenactions_test.h"
#include "cppeditorwidget.h"
-#include "cppquickfix.h"
-#include "cppquickfixassistant.h"
-#include "cppinsertvirtualmethods.h"
#include "cppmodelmanager.h"
#include "cpptoolstestcase.h"
#include "cppworkingcopy.h"
#include "projectinfo.h"
+#include "quickfixes/cppquickfix.h"
+#include "quickfixes/cppquickfixassistant.h"
+#include "quickfixes/cppinsertvirtualmethods.h"
#include <coreplugin/editormanager/editormanager.h>
#include <projectexplorer/project.h>
diff --git a/src/plugins/cppeditor/quickfixes/assigntolocalvariable.cpp b/src/plugins/cppeditor/quickfixes/assigntolocalvariable.cpp
new file mode 100644
index 0000000000..ae63681dc2
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/assigntolocalvariable.cpp
@@ -0,0 +1,510 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "assigntolocalvariable.h"
+
+#include "../cppcodestylesettings.h"
+#include "../cppeditortr.h"
+#include "../cppeditorwidget.h"
+#include "../cpprefactoringchanges.h"
+#include "cppquickfix.h"
+#include "cppquickfixprojectsettings.h"
+
+#include <cplusplus/CppRewriter.h>
+#include <cplusplus/Overview.h>
+#include <cplusplus/TypeOfExpression.h>
+#include <projectexplorer/projecttree.h>
+
+#ifdef WITH_TESTS
+#include "cppquickfix_test.h"
+#include <QtTest>
+#endif
+
+using namespace CPlusPlus;
+using namespace Utils;
+
+namespace CppEditor::Internal {
+namespace {
+
+class AssignToLocalVariableOperation : public CppQuickFixOperation
+{
+public:
+ explicit AssignToLocalVariableOperation(const CppQuickFixInterface &interface,
+ const int insertPos, const AST *ast, const Name *name)
+ : CppQuickFixOperation(interface)
+ , m_insertPos(insertPos)
+ , m_ast(ast)
+ , m_name(name)
+ , m_oo(CppCodeStyleSettings::currentProjectCodeStyleOverview())
+ , m_originalName(m_oo.prettyName(m_name))
+ , m_file(interface.currentFile())
+ {
+ setDescription(Tr::tr("Assign to Local Variable"));
+ }
+
+private:
+ void perform() override
+ {
+ QString type = deduceType();
+ if (type.isEmpty())
+ return;
+ const int origNameLength = m_originalName.length();
+ const QString varName = constructVarName();
+ const QString insertString = type.replace(type.length() - origNameLength, origNameLength,
+ varName + QLatin1String(" = "));
+ m_file->apply(ChangeSet::makeInsert(m_insertPos, insertString));
+
+ // move cursor to new variable name
+ QTextCursor c = m_file->cursor();
+ c.setPosition(m_insertPos + insertString.length() - varName.length() - 3);
+ c.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
+ editor()->setTextCursor(c);
+ }
+
+ QString deduceType() const
+ {
+ const auto settings = CppQuickFixProjectsSettings::getQuickFixSettings(
+ ProjectExplorer::ProjectTree::currentProject());
+ if (m_file->cppDocument()->languageFeatures().cxx11Enabled && settings->useAuto)
+ return "auto " + m_originalName;
+
+ TypeOfExpression typeOfExpression;
+ typeOfExpression.init(semanticInfo().doc, snapshot(), context().bindings());
+ typeOfExpression.setExpandTemplates(true);
+ Scope * const scope = m_file->scopeAt(m_ast->firstToken());
+ const QList<LookupItem> result = typeOfExpression(m_file->textOf(m_ast).toUtf8(),
+ scope, TypeOfExpression::Preprocess);
+ if (result.isEmpty())
+ return {};
+
+ SubstitutionEnvironment env;
+ env.setContext(context());
+ env.switchScope(result.first().scope());
+ ClassOrNamespace *con = typeOfExpression.context().lookupType(scope);
+ if (!con)
+ con = typeOfExpression.context().globalNamespace();
+ UseMinimalNames q(con);
+ env.enter(&q);
+
+ Control *control = context().bindings()->control().get();
+ FullySpecifiedType type = rewriteType(result.first().type(), &env, control);
+
+ return m_oo.prettyType(type, m_name);
+ }
+
+ QString constructVarName() const
+ {
+ QString newName = m_originalName;
+ if (newName.startsWith(QLatin1String("get"), Qt::CaseInsensitive)
+ && newName.length() > 3
+ && newName.at(3).isUpper()) {
+ newName.remove(0, 3);
+ newName.replace(0, 1, newName.at(0).toLower());
+ } else if (newName.startsWith(QLatin1String("to"), Qt::CaseInsensitive)
+ && newName.length() > 2
+ && newName.at(2).isUpper()) {
+ newName.remove(0, 2);
+ newName.replace(0, 1, newName.at(0).toLower());
+ } else {
+ newName.replace(0, 1, newName.at(0).toUpper());
+ newName.prepend(QLatin1String("local"));
+ }
+ return newName;
+ }
+
+ const int m_insertPos;
+ const AST * const m_ast;
+ const Name * const m_name;
+ const Overview m_oo;
+ const QString m_originalName;
+ const CppRefactoringFilePtr m_file;
+};
+
+//! Assigns the return value of a function call or a new expression to a local variable
+class AssignToLocalVariable : public CppQuickFixFactory
+{
+#ifdef WITH_TESTS
+public:
+ static QObject *createTest();
+#endif
+
+private:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ const QList<AST *> &path = interface.path();
+ AST *outerAST = nullptr;
+ SimpleNameAST *nameAST = nullptr;
+
+ for (int i = path.size() - 3; i >= 0; --i) {
+ if (CallAST *callAST = path.at(i)->asCall()) {
+ if (!interface.isCursorOn(callAST))
+ return;
+ if (i - 2 >= 0) {
+ const int idx = i - 2;
+ if (path.at(idx)->asSimpleDeclaration())
+ return;
+ if (path.at(idx)->asExpressionStatement())
+ return;
+ if (path.at(idx)->asMemInitializer())
+ return;
+ if (path.at(idx)->asCall()) { // Fallback if we have a->b()->c()...
+ --i;
+ continue;
+ }
+ }
+ for (int a = i - 1; a > 0; --a) {
+ if (path.at(a)->asBinaryExpression())
+ return;
+ if (path.at(a)->asReturnStatement())
+ return;
+ if (path.at(a)->asCall())
+ return;
+ }
+
+ if (MemberAccessAST *member = path.at(i + 1)->asMemberAccess()) { // member
+ if (NameAST *name = member->member_name)
+ nameAST = name->asSimpleName();
+ } else if (QualifiedNameAST *qname = path.at(i + 2)->asQualifiedName()) { // static or
+ nameAST = qname->unqualified_name->asSimpleName(); // func in ns
+ } else { // normal
+ nameAST = path.at(i + 2)->asSimpleName();
+ }
+
+ if (nameAST) {
+ outerAST = callAST;
+ break;
+ }
+ } else if (NewExpressionAST *newexp = path.at(i)->asNewExpression()) {
+ if (!interface.isCursorOn(newexp))
+ return;
+ if (i - 2 >= 0) {
+ const int idx = i - 2;
+ if (path.at(idx)->asSimpleDeclaration())
+ return;
+ if (path.at(idx)->asExpressionStatement())
+ return;
+ if (path.at(idx)->asMemInitializer())
+ return;
+ }
+ for (int a = i - 1; a > 0; --a) {
+ if (path.at(a)->asReturnStatement())
+ return;
+ if (path.at(a)->asCall())
+ return;
+ }
+
+ if (NamedTypeSpecifierAST *ts = path.at(i + 2)->asNamedTypeSpecifier()) {
+ nameAST = ts->name->asSimpleName();
+ outerAST = newexp;
+ break;
+ }
+ }
+ }
+
+ if (outerAST && nameAST) {
+ const CppRefactoringFilePtr file = interface.currentFile();
+ QList<LookupItem> items;
+ TypeOfExpression typeOfExpression;
+ typeOfExpression.init(interface.semanticInfo().doc, interface.snapshot(),
+ interface.context().bindings());
+ typeOfExpression.setExpandTemplates(true);
+
+ // If items are empty, AssignToLocalVariableOperation will fail.
+ items = typeOfExpression(file->textOf(outerAST).toUtf8(),
+ file->scopeAt(outerAST->firstToken()),
+ TypeOfExpression::Preprocess);
+ if (items.isEmpty())
+ return;
+
+ if (CallAST *callAST = outerAST->asCall()) {
+ items = typeOfExpression(file->textOf(callAST->base_expression).toUtf8(),
+ file->scopeAt(callAST->base_expression->firstToken()),
+ TypeOfExpression::Preprocess);
+ } else {
+ items = typeOfExpression(file->textOf(nameAST).toUtf8(),
+ file->scopeAt(nameAST->firstToken()),
+ TypeOfExpression::Preprocess);
+ }
+
+ for (const LookupItem &item : std::as_const(items)) {
+ if (!item.declaration())
+ continue;
+
+ if (Function *func = item.declaration()->asFunction()) {
+ if (func->isSignal() || func->returnType()->asVoidType())
+ return;
+ } else if (Declaration *dec = item.declaration()->asDeclaration()) {
+ if (Function *func = dec->type()->asFunctionType()) {
+ if (func->isSignal() || func->returnType()->asVoidType())
+ return;
+ }
+ }
+
+ const Name *name = nameAST->name;
+ const int insertPos = interface.currentFile()->startOf(outerAST);
+ result << new AssignToLocalVariableOperation(interface, insertPos, outerAST, name);
+ return;
+ }
+ }
+ }
+};
+
+#ifdef WITH_TESTS
+using namespace Tests;
+
+class AssignToLocalVariableTest : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void testTemplates()
+ {
+ QList<TestDocumentPtr> testDocuments;
+ QByteArray original;
+ QByteArray expected;
+
+ // Header File
+ original =
+ "template <typename T>\n"
+ "class List {\n"
+ "public:\n"
+ " T first();"
+ "};\n"
+ ;
+ expected = original;
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File
+ original =
+ "#include \"file.h\"\n"
+ "void foo() {\n"
+ " List<int> list;\n"
+ " li@st.first();\n"
+ "}\n";
+ expected =
+ "#include \"file.h\"\n"
+ "void foo() {\n"
+ " List<int> list;\n"
+ " auto localFirst = list.first();\n"
+ "}\n";
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+ AssignToLocalVariable factory;
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+
+ void test_data()
+ {
+ QTest::addColumn<QByteArray>("original");
+ QTest::addColumn<QByteArray>("expected");
+
+ // Check: Add local variable for a free function.
+ QTest::newRow("freeFunction")
+ << QByteArray(
+ "int foo() {return 1;}\n"
+ "void bar() {fo@o();}\n")
+ << QByteArray(
+ "int foo() {return 1;}\n"
+ "void bar() {auto localFoo = foo();}\n");
+
+ // Check: Add local variable for a member function.
+ QTest::newRow("memberFunction")
+ << QByteArray(
+ "class Foo {public: int* fooFunc();}\n"
+ "void bar() {\n"
+ " Foo *f = new Foo;\n"
+ " @f->fooFunc();\n"
+ "}\n")
+ << QByteArray(
+ "class Foo {public: int* fooFunc();}\n"
+ "void bar() {\n"
+ " Foo *f = new Foo;\n"
+ " auto localFooFunc = f->fooFunc();\n"
+ "}\n");
+
+ // Check: Add local variable for a member function, cursor in the middle (QTCREATORBUG-10355)
+ QTest::newRow("memberFunction2ndGrade1")
+ << QByteArray(
+ "struct Foo {int* func();};\n"
+ "struct Baz {Foo* foo();};\n"
+ "void bar() {\n"
+ " Baz *b = new Baz;\n"
+ " b->foo@()->func();\n"
+ "}")
+ << QByteArray(
+ "struct Foo {int* func();};\n"
+ "struct Baz {Foo* foo();};\n"
+ "void bar() {\n"
+ " Baz *b = new Baz;\n"
+ " auto localFunc = b->foo()->func();\n"
+ "}");
+
+ // Check: Add local variable for a member function, cursor on function call (QTCREATORBUG-10355)
+ QTest::newRow("memberFunction2ndGrade2")
+ << QByteArray(
+ "struct Foo {int* func();};\n"
+ "struct Baz {Foo* foo();};\n"
+ "void bar() {\n"
+ " Baz *b = new Baz;\n"
+ " b->foo()->f@unc();\n"
+ "}")
+ << QByteArray(
+ "struct Foo {int* func();};\n"
+ "struct Baz {Foo* foo();};\n"
+ "void bar() {\n"
+ " Baz *b = new Baz;\n"
+ " auto localFunc = b->foo()->func();\n"
+ "}");
+
+ // Check: Add local variable for a static member function.
+ QTest::newRow("staticMemberFunction")
+ << QByteArray(
+ "class Foo {public: static int* fooFunc();}\n"
+ "void bar() {\n"
+ " Foo::fooF@unc();\n"
+ "}")
+ << QByteArray(
+ "class Foo {public: static int* fooFunc();}\n"
+ "void bar() {\n"
+ " auto localFooFunc = Foo::fooFunc();\n"
+ "}");
+
+ // Check: Add local variable for a new Expression.
+ QTest::newRow("newExpression")
+ << QByteArray(
+ "class Foo {}\n"
+ "void bar() {\n"
+ " new Fo@o;\n"
+ "}")
+ << QByteArray(
+ "class Foo {}\n"
+ "void bar() {\n"
+ " auto localFoo = new Foo;\n"
+ "}");
+
+ // Check: No trigger for function inside member initialization list.
+ QTest::newRow("noInitializationList")
+ << QByteArray(
+ "class Foo\n"
+ "{\n"
+ " public: Foo : m_i(fooF@unc()) {}\n"
+ " int fooFunc() {return 2;}\n"
+ " int m_i;\n"
+ "};\n")
+ << QByteArray();
+
+ // Check: No trigger for void functions.
+ QTest::newRow("noVoidFunction")
+ << QByteArray(
+ "void foo() {}\n"
+ "void bar() {fo@o();}")
+ << QByteArray();
+
+ // Check: No trigger for void member functions.
+ QTest::newRow("noVoidMemberFunction")
+ << QByteArray(
+ "class Foo {public: void fooFunc();}\n"
+ "void bar() {\n"
+ " Foo *f = new Foo;\n"
+ " @f->fooFunc();\n"
+ "}")
+ << QByteArray();
+
+ // Check: No trigger for void static member functions.
+ QTest::newRow("noVoidStaticMemberFunction")
+ << QByteArray(
+ "class Foo {public: static void fooFunc();}\n"
+ "void bar() {\n"
+ " Foo::fo@oFunc();\n"
+ "}")
+ << QByteArray();
+
+ // Check: No trigger for functions in expressions.
+ QTest::newRow("noFunctionInExpression")
+ << QByteArray(
+ "int foo(int a) {return a;}\n"
+ "int bar() {return 1;}"
+ "void baz() {foo(@bar() + bar());}")
+ << QByteArray();
+
+ // Check: No trigger for functions in functions. (QTCREATORBUG-9510)
+ QTest::newRow("noFunctionInFunction")
+ << QByteArray(
+ "int foo(int a, int b) {return a + b;}\n"
+ "int bar(int a) {return a;}\n"
+ "void baz() {\n"
+ " int a = foo(ba@r(), bar());\n"
+ "}\n")
+ << QByteArray();
+
+ // Check: No trigger for functions in return statements (classes).
+ QTest::newRow("noReturnClass1")
+ << QByteArray(
+ "class Foo {public: static void fooFunc();}\n"
+ "Foo* bar() {\n"
+ " return new Fo@o;\n"
+ "}")
+ << QByteArray();
+
+ // Check: No trigger for functions in return statements (classes). (QTCREATORBUG-9525)
+ QTest::newRow("noReturnClass2")
+ << QByteArray(
+ "class Foo {public: int fooFunc();}\n"
+ "int bar() {\n"
+ " return (new Fo@o)->fooFunc();\n"
+ "}")
+ << QByteArray();
+
+ // Check: No trigger for functions in return statements (functions).
+ QTest::newRow("noReturnFunc1")
+ << QByteArray(
+ "class Foo {public: int fooFunc();}\n"
+ "int bar() {\n"
+ " return Foo::fooFu@nc();\n"
+ "}")
+ << QByteArray();
+
+ // Check: No trigger for functions in return statements (functions). (QTCREATORBUG-9525)
+ QTest::newRow("noReturnFunc2")
+ << QByteArray(
+ "int bar() {\n"
+ " return list.firs@t().foo;\n"
+ "}\n")
+ << QByteArray();
+
+ // Check: No trigger for functions which does not match in signature.
+ QTest::newRow("noSignatureMatch")
+ << QByteArray(
+ "int someFunc(int);\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ " some@Func();\n"
+ "}")
+ << QByteArray();
+ }
+
+ void test()
+ {
+ QFETCH(QByteArray, original);
+ QFETCH(QByteArray, expected);
+ AssignToLocalVariable factory;
+ QuickFixOperationTest(singleDocument(original, expected), &factory);
+ }
+};
+
+QObject *AssignToLocalVariable::createTest() { return new AssignToLocalVariableTest; }
+
+#endif // WITH_TESTS
+} // namespace
+
+void registerAssignToLocalVariableQuickfix()
+{
+ CppQuickFixFactory::registerFactory<AssignToLocalVariable>();
+}
+
+} // namespace CppEditor::Internal
+
+#ifdef WITH_TESTS
+#include <assigntolocalvariable.moc>
+#endif
diff --git a/src/plugins/cppeditor/quickfixes/assigntolocalvariable.h b/src/plugins/cppeditor/quickfixes/assigntolocalvariable.h
new file mode 100644
index 0000000000..d8cd793c9f
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/assigntolocalvariable.h
@@ -0,0 +1,8 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+namespace CppEditor::Internal {
+void registerAssignToLocalVariableQuickfix();
+} // namespace CppEditor::Internal
diff --git a/src/plugins/cppeditor/quickfixes/bringidentifierintoscope.cpp b/src/plugins/cppeditor/quickfixes/bringidentifierintoscope.cpp
new file mode 100644
index 0000000000..3c4df4cb76
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/bringidentifierintoscope.cpp
@@ -0,0 +1,1497 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "bringidentifierintoscope.h"
+
+#include "../cppeditortr.h"
+#include "../cpplocatordata.h"
+#include "../cppprojectfile.h"
+#include "../cpprefactoringchanges.h"
+#include "../indexitem.h"
+#include "cppquickfix.h"
+#include "cppquickfixhelpers.h"
+
+#include <cplusplus/Overview.h>
+#include <projectexplorer/projectnodes.h>
+#include <projectexplorer/projecttree.h>
+#include <texteditor/quickfix.h>
+
+#ifdef WITH_TESTS
+#include "../cppsourceprocessertesthelper.h"
+#include "../cpptoolstestcase.h"
+#include "cppquickfix_test.h"
+#include <QtTest>
+#endif
+
+using namespace CPlusPlus;
+using namespace ProjectExplorer;
+using namespace Utils;
+
+#ifdef WITH_TESTS
+namespace CppEditor::Internal::Tests {
+typedef QList<TestDocumentPtr> QuickFixTestDocuments;
+}
+Q_DECLARE_METATYPE(CppEditor::Internal::Tests::QuickFixTestDocuments)
+#endif
+
+namespace CppEditor::Internal {
+namespace {
+
+static QString findShortestInclude(
+ const QString currentDocumentFilePath,
+ const QString candidateFilePath,
+ const HeaderPaths &headerPaths)
+{
+ QString result;
+
+ const QFileInfo fileInfo(candidateFilePath);
+
+ if (fileInfo.path() == QFileInfo(currentDocumentFilePath).path()) {
+ result = QLatin1Char('"') + fileInfo.fileName() + QLatin1Char('"');
+ } else {
+ for (const HeaderPath &headerPath : headerPaths) {
+ if (!candidateFilePath.startsWith(headerPath.path))
+ continue;
+ QString relativePath = candidateFilePath.mid(headerPath.path.size());
+ if (!relativePath.isEmpty() && relativePath.at(0) == QLatin1Char('/'))
+ relativePath = relativePath.mid(1);
+ if (result.isEmpty() || relativePath.size() + 2 < result.size())
+ result = QLatin1Char('<') + relativePath + QLatin1Char('>');
+ }
+ }
+
+ return result;
+}
+
+static QString findMatchingInclude(const QString &className, const HeaderPaths &headerPaths)
+{
+ const QStringList candidateFileNames{
+ className,
+ className + ".h",
+ className + ".hpp",
+ className.toLower(),
+ className.toLower() + ".h",
+ className.toLower() + ".hpp"};
+ for (const QString &fileName : candidateFileNames) {
+ for (const HeaderPath &headerPath : headerPaths) {
+ const QString headerPathCandidate = headerPath.path + QLatin1Char('/') + fileName;
+ const QFileInfo fileInfo(headerPathCandidate);
+ if (fileInfo.exists() && fileInfo.isFile())
+ return '<' + fileName + '>';
+ }
+ }
+ return {};
+}
+
+static HeaderPaths relevantHeaderPaths(const QString &filePath)
+{
+ HeaderPaths headerPaths;
+
+ const QList<ProjectPart::ConstPtr> projectParts = CppModelManager::projectPart(filePath);
+ if (projectParts.isEmpty()) { // Not part of any project, better use all include paths than none
+ headerPaths += CppModelManager::headerPaths();
+ } else {
+ for (const ProjectPart::ConstPtr &part : projectParts)
+ headerPaths += part->headerPaths;
+ }
+
+ return headerPaths;
+}
+
+static NameAST *nameUnderCursor(const QList<AST *> &path)
+{
+ if (path.isEmpty())
+ return nullptr;
+
+ NameAST *nameAst = nullptr;
+ for (int i = path.size() - 1; i >= 0; --i) {
+ AST * const ast = path.at(i);
+ if (SimpleNameAST *simpleName = ast->asSimpleName()) {
+ nameAst = simpleName;
+ } else if (TemplateIdAST *templateId = ast->asTemplateId()) {
+ nameAst = templateId;
+ } else if (nameAst && ast->asNamedTypeSpecifier()) {
+ break; // Stop at "Foo" for "N::Bar<@Foo>"
+ } else if (QualifiedNameAST *qualifiedName = ast->asQualifiedName()) {
+ nameAst = qualifiedName;
+ break;
+ }
+ }
+
+ return nameAst;
+}
+
+enum class LookupResult { Declared, ForwardDeclared, NotDeclared };
+static LookupResult lookUpDefinition(const CppQuickFixInterface &interface, const NameAST *nameAst)
+{
+ QTC_ASSERT(nameAst && nameAst->name, return LookupResult::NotDeclared);
+
+ // Find the enclosing scope
+ int line, column;
+ const Document::Ptr doc = interface.semanticInfo().doc;
+ doc->translationUnit()->getTokenPosition(nameAst->firstToken(), &line, &column);
+ Scope *scope = doc->scopeAt(line, column);
+ if (!scope)
+ return LookupResult::NotDeclared;
+
+ // Try to find the class/template definition
+ const Name *name = nameAst->name;
+ const QList<LookupItem> results = interface.context().lookup(name, scope);
+ LookupResult best = LookupResult::NotDeclared;
+ for (const LookupItem &item : results) {
+ if (Symbol *declaration = item.declaration()) {
+ if (declaration->asClass())
+ return LookupResult::Declared;
+ if (declaration->asForwardClassDeclaration()) {
+ best = LookupResult::ForwardDeclared;
+ continue;
+ }
+ if (Template *templ = declaration->asTemplate()) {
+ if (Symbol *declaration = templ->declaration()) {
+ if (declaration->asClass())
+ return LookupResult::Declared;
+ if (declaration->asForwardClassDeclaration()) {
+ best = LookupResult::ForwardDeclared;
+ continue;
+ }
+ }
+ }
+ return LookupResult::Declared;
+ }
+ }
+
+ return best;
+}
+
+static QString templateNameAsString(const TemplateNameId *templateName)
+{
+ const Identifier *id = templateName->identifier();
+ return QString::fromUtf8(id->chars(), id->size());
+}
+
+static Snapshot forwardingHeaders(const CppQuickFixInterface &interface)
+{
+ Snapshot result;
+
+ const Snapshot docs = interface.snapshot();
+ for (Document::Ptr doc : docs) {
+ if (doc->globalSymbolCount() == 0 && doc->resolvedIncludes().size() == 1)
+ result.insert(doc);
+ }
+
+ return result;
+}
+
+static QList<IndexItem::Ptr> matchName(const Name *name, QString *className)
+{
+ if (!name)
+ return {};
+
+ QString simpleName;
+ QList<IndexItem::Ptr> matches;
+ CppLocatorData *locatorData = CppModelManager::locatorData();
+ const Overview oo;
+ if (const QualifiedNameId *qualifiedName = name->asQualifiedNameId()) {
+ const Name *name = qualifiedName->name();
+ if (const TemplateNameId *templateName = name->asTemplateNameId()) {
+ *className = templateNameAsString(templateName);
+ } else {
+ simpleName = oo.prettyName(name);
+ *className = simpleName;
+ matches = locatorData->findSymbols(IndexItem::Class, *className);
+ if (matches.isEmpty()) {
+ if (const Name *name = qualifiedName->base()) {
+ if (const TemplateNameId *templateName = name->asTemplateNameId())
+ *className = templateNameAsString(templateName);
+ else
+ *className = oo.prettyName(name);
+ }
+ }
+ }
+ } else if (const TemplateNameId *templateName = name->asTemplateNameId()) {
+ *className = templateNameAsString(templateName);
+ } else {
+ *className = oo.prettyName(name);
+ }
+
+ if (matches.isEmpty())
+ matches = locatorData->findSymbols(IndexItem::Class, *className);
+
+ if (matches.isEmpty() && !simpleName.isEmpty())
+ *className = simpleName;
+
+ return matches;
+}
+
+class AddIncludeForUndefinedIdentifierOp : public CppQuickFixOperation
+{
+public:
+ AddIncludeForUndefinedIdentifierOp(const CppQuickFixInterface &interface, int priority,
+ const QString &include);
+ void perform() override;
+
+ QString include() const { return m_include; }
+
+private:
+ QString m_include;
+};
+
+class AddForwardDeclForUndefinedIdentifierOp : public CppQuickFixOperation
+{
+public:
+ AddForwardDeclForUndefinedIdentifierOp(const CppQuickFixInterface &interface, int priority,
+ const QString &fqClassName, int symbolPos);
+private:
+ void perform() override
+ {
+ const QStringList parts = m_className.split("::");
+ QTC_ASSERT(!parts.isEmpty(), return);
+ const QStringList namespaces = parts.mid(0, parts.length() - 1);
+
+ CppRefactoringFilePtr file = currentFile();
+
+ NSVisitor visitor(file.data(), namespaces, m_symbolPos);
+ visitor.accept(file->cppDocument()->translationUnit()->ast());
+ const auto stringToInsert = [&visitor, symbol = parts.last()] {
+ QString s = "\n";
+ for (const QString &ns : visitor.remainingNamespaces())
+ s += "namespace " + ns + " { ";
+ s += "class " + symbol + ';';
+ for (int i = 0; i < visitor.remainingNamespaces().size(); ++i)
+ s += " }";
+ return s;
+ };
+
+ int insertPos = 0;
+
+ // Find the position to insert:
+ // If we have a matching namespace, we do the insertion there.
+ // If we don't have a matching namespace, but there is another namespace in the file,
+ // we assume that to be a good position for our insertion.
+ // Otherwise, do the insertion after the last include that comes before the use of the symbol.
+ // If there is no such include, do the insertion before the first token.
+ if (visitor.enclosingNamespace()) {
+ insertPos = file->startOf(visitor.enclosingNamespace()->linkage_body) + 1;
+ } else if (visitor.firstNamespace()) {
+ insertPos = file->startOf(visitor.firstNamespace());
+ } else {
+ const QTextCursor tc = file->document()->find(
+ QRegularExpression("^\\s*#include .*$"),
+ m_symbolPos,
+ QTextDocument::FindBackward | QTextDocument::FindCaseSensitively);
+ if (!tc.isNull())
+ insertPos = tc.position() + 1;
+ else if (visitor.firstToken())
+ insertPos = file->startOf(visitor.firstToken());
+ }
+
+ QString insertion = stringToInsert();
+ if (file->charAt(insertPos - 1) != QChar::ParagraphSeparator)
+ insertion.prepend('\n');
+ if (file->charAt(insertPos) != QChar::ParagraphSeparator)
+ insertion.append('\n');
+ file->apply(ChangeSet::makeInsert(insertPos, insertion));
+ }
+
+ const QString m_className;
+ const int m_symbolPos;
+};
+
+AddIncludeForUndefinedIdentifierOp::AddIncludeForUndefinedIdentifierOp(
+ const CppQuickFixInterface &interface, int priority, const QString &include)
+ : CppQuickFixOperation(interface, priority)
+ , m_include(include)
+{
+ setDescription(Tr::tr("Add #include %1").arg(m_include));
+}
+
+void AddIncludeForUndefinedIdentifierOp::perform()
+{
+ CppRefactoringFilePtr file = currentFile();
+
+ ChangeSet changes;
+ insertNewIncludeDirective(m_include, file, semanticInfo().doc, changes);
+ file->apply(changes);
+}
+
+AddForwardDeclForUndefinedIdentifierOp::AddForwardDeclForUndefinedIdentifierOp(
+ const CppQuickFixInterface &interface,
+ int priority,
+ const QString &fqClassName,
+ int symbolPos)
+ : CppQuickFixOperation(interface, priority), m_className(fqClassName), m_symbolPos(symbolPos)
+{
+ setDescription(Tr::tr("Add Forward Declaration for %1").arg(m_className));
+}
+
+/*!
+ Adds an include for an undefined identifier or only forward declared identifier.
+ Activates on: the undefined identifier
+*/
+class AddIncludeForUndefinedIdentifier : public CppQuickFixFactory
+{
+#ifdef WITH_TESTS
+public:
+ static QObject *createTest();
+#endif
+
+private:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ const NameAST *nameAst = nameUnderCursor(interface.path());
+ if (!nameAst || !nameAst->name)
+ return;
+
+ const LookupResult lookupResult = lookUpDefinition(interface, nameAst);
+ if (lookupResult == LookupResult::Declared)
+ return;
+
+ QString className;
+ const QString currentDocumentFilePath = interface.semanticInfo().doc->filePath().toString();
+ const HeaderPaths headerPaths = relevantHeaderPaths(currentDocumentFilePath);
+ FilePaths headers;
+
+ const QList<IndexItem::Ptr> matches = matchName(nameAst->name, &className);
+ // Find an include file through the locator
+ if (!matches.isEmpty()) {
+ QList<IndexItem::Ptr> indexItems;
+ const Snapshot forwardHeaders = forwardingHeaders(interface);
+ for (const IndexItem::Ptr &info : matches) {
+ if (!info || info->symbolName() != className)
+ continue;
+ indexItems << info;
+
+ Snapshot localForwardHeaders = forwardHeaders;
+ localForwardHeaders.insert(interface.snapshot().document(info->filePath()));
+ FilePaths headerAndItsForwardingHeaders;
+ headerAndItsForwardingHeaders << info->filePath();
+ headerAndItsForwardingHeaders += localForwardHeaders.filesDependingOn(info->filePath());
+
+ for (const FilePath &header : std::as_const(headerAndItsForwardingHeaders)) {
+ const QString include = findShortestInclude(currentDocumentFilePath,
+ header.toString(),
+ headerPaths);
+ if (include.size() > 2) {
+ const QString headerFileName = info->filePath().fileName();
+ QTC_ASSERT(!headerFileName.isEmpty(), break);
+
+ int priority = 0;
+ if (headerFileName == className)
+ priority = 2;
+ else if (headerFileName.at(1).isUpper())
+ priority = 1;
+
+ result << new AddIncludeForUndefinedIdentifierOp(interface, priority,
+ include);
+ headers << header;
+ }
+ }
+ }
+
+ if (lookupResult == LookupResult::NotDeclared && indexItems.size() == 1) {
+ QString qualifiedName = Overview().prettyName(nameAst->name);
+ if (qualifiedName.startsWith("::"))
+ qualifiedName.remove(0, 2);
+ if (indexItems.first()->scopedSymbolName().endsWith(qualifiedName)) {
+ const Node * const node = ProjectTree::nodeForFile(interface.filePath());
+ FileType fileType = node && node->asFileNode() ? node->asFileNode()->fileType()
+ : FileType::Unknown;
+ if (fileType == FileType::Unknown
+ && ProjectFile::isHeader(ProjectFile::classify(interface.filePath().toString()))) {
+ fileType = FileType::Header;
+ }
+ if (fileType == FileType::Header) {
+ result << new AddForwardDeclForUndefinedIdentifierOp(
+ interface, 0, indexItems.first()->scopedSymbolName(),
+ interface.currentFile()->startOf(nameAst));
+ }
+ }
+ }
+ }
+
+ if (className.isEmpty())
+ return;
+
+ // Fallback: Check the include paths for files that look like candidates
+ // for the given name.
+ if (!Utils::contains(headers,
+ [&className](const FilePath &fp) { return fp.fileName() == className; })) {
+ const QString include = findMatchingInclude(className, headerPaths);
+ const auto matcher = [&include](const TextEditor::QuickFixOperation::Ptr &o) {
+ const auto includeOp = o.dynamicCast<AddIncludeForUndefinedIdentifierOp>();
+ return includeOp && includeOp->include() == include;
+ };
+ if (!include.isEmpty() && !contains(result, matcher))
+ result << new AddIncludeForUndefinedIdentifierOp(interface, 1, include);
+ }
+ }
+};
+
+#ifdef WITH_TESTS
+using namespace Tests;
+using namespace ::CppEditor::Tests;
+using namespace ::CppEditor::Tests::Internal; // FIXME: Get rid of this nonsense namespace
+
+/// Delegates directly to AddIncludeForUndefinedIdentifierOp for easier testing.
+class AddIncludeForUndefinedIdentifierTestFactory : public CppQuickFixFactory
+{
+public:
+ AddIncludeForUndefinedIdentifierTestFactory(const QString &include)
+ : m_include(include) {}
+
+ void doMatch(const CppQuickFixInterface &cppQuickFixInterface, QuickFixOperations &result) override
+ {
+ result << new AddIncludeForUndefinedIdentifierOp(cppQuickFixInterface, 0, m_include);
+ }
+
+private:
+ const QString m_include;
+};
+
+class AddForwardDeclForUndefinedIdentifierTestFactory : public CppQuickFixFactory
+{
+public:
+ AddForwardDeclForUndefinedIdentifierTestFactory(const QString &className, int symbolPos)
+ : m_className(className), m_symbolPos(symbolPos) {}
+
+ void doMatch(const CppQuickFixInterface &cppQuickFixInterface, QuickFixOperations &result) override
+ {
+ result << new AddForwardDeclForUndefinedIdentifierOp(cppQuickFixInterface, 0,
+ m_className, m_symbolPos);
+ }
+
+private:
+ const QString m_className;
+ const int m_symbolPos;
+};
+
+class BringIdentifierIntoScopeTest : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void testAddIncludeForUndefinedIdentifier_data()
+ {
+ QTest::addColumn<QString>("headerPath");
+ QTest::addColumn<QuickFixTestDocuments>("testDocuments");
+ QTest::addColumn<int>("refactoringOperationIndex");
+ QTest::addColumn<QString>("includeForTestFactory");
+
+ const int firstRefactoringOperation = 0;
+ const int secondRefactoringOperation = 1;
+
+ QList<TestDocumentPtr> testDocuments;
+ QByteArray original;
+ QByteArray expected;
+
+ // -------------------------------------------------------------------------------------------
+
+ // Header File
+ original = "class Foo {};\n";
+ expected = original;
+ testDocuments << CppTestDocument::create("afile.h", original, expected);
+
+ // Source File
+ original =
+ "#include \"header.h\"\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ " Fo@o foo;\n"
+ "}\n"
+ ;
+ expected =
+ "#include \"afile.h\"\n"
+ "#include \"header.h\"\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ " Foo foo;\n"
+ "}\n"
+ ;
+ testDocuments << CppTestDocument::create("afile.cpp", original, expected);
+ QTest::newRow("onSimpleName")
+ << TestIncludePaths::globalIncludePath()
+ << testDocuments << firstRefactoringOperation << "";
+ testDocuments.clear();
+
+ // -------------------------------------------------------------------------------------------
+
+ // Header File
+ original = "namespace N { class Foo {}; }\n";
+ expected = original;
+ testDocuments << CppTestDocument::create("afile.h", original, expected);
+
+ // Source File
+ original =
+ "#include \"header.h\"\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ " N::Fo@o foo;\n"
+ "}\n"
+ ;
+ expected =
+ "#include \"afile.h\"\n"
+ "#include \"header.h\"\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ " N::Foo foo;\n"
+ "}\n"
+ ;
+ testDocuments << CppTestDocument::create("afile.cpp", original, expected);
+ QTest::newRow("onNameOfQualifiedName")
+ << TestIncludePaths::globalIncludePath()
+ << testDocuments << firstRefactoringOperation << "";
+ testDocuments.clear();
+
+ // -------------------------------------------------------------------------------------------
+
+ // Header File
+ original = "namespace N { class Foo {}; }\n";
+ expected = original;
+ testDocuments << CppTestDocument::create("afile.h", original, expected);
+
+ // Source File
+ original =
+ "#include \"header.h\"\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ " @N::Foo foo;\n"
+ "}\n"
+ ;
+ expected =
+ "#include \"afile.h\"\n"
+ "#include \"header.h\"\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ " N::Foo foo;\n"
+ "}\n"
+ ;
+ testDocuments << CppTestDocument::create("afile.cpp", original, expected);
+ QTest::newRow("onBaseOfQualifiedName")
+ << TestIncludePaths::globalIncludePath()
+ << testDocuments << firstRefactoringOperation << "";
+ testDocuments.clear();
+
+ // -------------------------------------------------------------------------------------------
+
+ // Header File
+ original = "class Foo { static void bar() {} };\n";
+ expected = original;
+ testDocuments << CppTestDocument::create("afile.h", original, expected);
+
+ // Source File
+ original =
+ "#include \"header.h\"\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ " @Foo::bar();\n"
+ "}\n"
+ ;
+ expected =
+ "#include \"afile.h\"\n"
+ "#include \"header.h\"\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ " Foo::bar();\n"
+ "}\n"
+ ;
+ testDocuments << CppTestDocument::create("afile.cpp", original, expected);
+ QTest::newRow("onBaseOfQualifiedClassName")
+ << TestIncludePaths::globalIncludePath()
+ << testDocuments << firstRefactoringOperation << "";
+ testDocuments.clear();
+
+ // -------------------------------------------------------------------------------------------
+
+ // Header File
+ original = "template <typename T> class Foo { static void bar() {} };\n";
+ expected = original;
+ testDocuments << CppTestDocument::create("afile.h", original, expected);
+
+ // Source File
+ original =
+ "#include \"header.h\"\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ " @Foo<int>::bar();\n"
+ "}\n"
+ ;
+ expected =
+ "#include \"afile.h\"\n"
+ "#include \"header.h\"\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ " Foo<int>::bar();\n"
+ "}\n"
+ ;
+ testDocuments << CppTestDocument::create("afile.cpp", original, expected);
+ QTest::newRow("onBaseOfQualifiedTemplateClassName")
+ << TestIncludePaths::globalIncludePath()
+ << testDocuments << firstRefactoringOperation << "";
+ testDocuments.clear();
+
+ // -------------------------------------------------------------------------------------------
+
+ // Header File
+ original = "namespace N { template <typename T> class Foo {}; }\n";
+ expected = original;
+ testDocuments << CppTestDocument::create("afile.h", original, expected);
+
+ // Source File
+ original =
+ "#include \"header.h\"\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ " @N::Foo<Bar> foo;\n"
+ "}\n"
+ ;
+ expected =
+ "#include \"afile.h\"\n"
+ "#include \"header.h\"\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ " N::Foo<Bar> foo;\n"
+ "}\n"
+ ;
+ testDocuments << CppTestDocument::create("afile.cpp", original, expected);
+ QTest::newRow("onTemplateName")
+ << TestIncludePaths::globalIncludePath()
+ << testDocuments << firstRefactoringOperation << "";
+ testDocuments.clear();
+
+ // -------------------------------------------------------------------------------------------
+
+ // Header File
+ original = "namespace N { template <typename T> class Foo {}; }\n";
+ expected = original;
+ testDocuments << CppTestDocument::create("afile.h", original, expected);
+
+ // Source File
+ original =
+ "#include \"header.h\"\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ " N::Bar<@Foo> foo;\n"
+ "}\n"
+ ;
+ expected =
+ "#include \"afile.h\"\n"
+ "#include \"header.h\"\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ " N::Bar<Foo> foo;\n"
+ "}\n"
+ ;
+ testDocuments << CppTestDocument::create("afile.cpp", original, expected);
+ QTest::newRow("onTemplateNameInsideArguments")
+ << TestIncludePaths::globalIncludePath()
+ << testDocuments << firstRefactoringOperation << "";
+ testDocuments.clear();
+
+ // -------------------------------------------------------------------------------------------
+
+ // Header File
+ original = "class Foo {};\n";
+ expected = original;
+ testDocuments << CppTestDocument::create("afile.h", original, expected);
+
+ // Source File
+ original =
+ "#include \"header.h\"\n"
+ "\n"
+ "class Foo;\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ " @Foo foo;\n"
+ "}\n"
+ ;
+ expected =
+ "#include \"afile.h\"\n"
+ "#include \"header.h\"\n"
+ "\n"
+ "class Foo;\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ " Foo foo;\n"
+ "}\n"
+ ;
+ testDocuments << CppTestDocument::create("afile.cpp", original, expected);
+ QTest::newRow("withForwardDeclaration")
+ << TestIncludePaths::globalIncludePath()
+ << testDocuments << firstRefactoringOperation << "";
+ testDocuments.clear();
+
+ // -------------------------------------------------------------------------------------------
+
+ // Header File
+ original = "template<class T> class Foo {};\n";
+ expected = original;
+ testDocuments << CppTestDocument::create("afile.h", original, expected);
+
+ // Source File
+ original =
+ "#include \"header.h\"\n"
+ "\n"
+ "template<class T> class Foo;\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ " @Foo foo;\n"
+ "}\n"
+ ;
+ expected =
+ "#include \"afile.h\"\n"
+ "#include \"header.h\"\n"
+ "\n"
+ "template<class T> class Foo;\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ " Foo foo;\n"
+ "}\n"
+ ;
+ testDocuments << CppTestDocument::create("afile.cpp", original, expected);
+ QTest::newRow("withForwardDeclaration2")
+ << TestIncludePaths::globalIncludePath()
+ << testDocuments << firstRefactoringOperation << "";
+ testDocuments.clear();
+
+ // -------------------------------------------------------------------------------------------
+
+ // Header File
+ original = "template<class T> class QMyClass {};\n";
+ expected = original;
+ testDocuments << CppTestDocument::create("qmyclass.h", original, expected);
+
+ // Forward Header File
+ original = "#include \"qmyclass.h\"\n";
+ expected = original;
+ testDocuments << CppTestDocument::create("QMyClass", original, expected);
+
+ // Source File
+ original =
+ "#include \"header.h\"\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ " @QMyClass c;\n"
+ "}\n"
+ ;
+ expected =
+ "#include \"QMyClass\"\n"
+ "#include \"header.h\"\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ " QMyClass c;\n"
+ "}\n"
+ ;
+ testDocuments << CppTestDocument::create("afile.cpp", original, expected);
+ QTest::newRow("withForwardHeader")
+ << TestIncludePaths::globalIncludePath()
+ << testDocuments << secondRefactoringOperation << "";
+ testDocuments.clear();
+
+ // -------------------------------------------------------------------------------------------
+
+ original =
+ "void @f();\n"
+ "#include \"file.moc\";\n"
+ ;
+ expected =
+ "#include \"file.h\"\n"
+ "\n"
+ "void f();\n"
+ "#include \"file.moc\";\n"
+ ;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+ QTest::newRow("insertingIgnoreMoc")
+ << TestIncludePaths::globalIncludePath()
+ << testDocuments << firstRefactoringOperation << "\"file.h\"";
+ testDocuments.clear();
+
+ // -------------------------------------------------------------------------------------------
+
+ original =
+ "#include \"y.h\"\n"
+ "#include \"z.h\"\n"
+ "\n@"
+ ;
+ expected =
+ "#include \"file.h\"\n"
+ "#include \"y.h\"\n"
+ "#include \"z.h\"\n"
+ "\n"
+ ;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+ QTest::newRow("insertingSortingTop")
+ << TestIncludePaths::globalIncludePath()
+ << testDocuments << firstRefactoringOperation << "\"file.h\"";
+ testDocuments.clear();
+
+ // -------------------------------------------------------------------------------------------
+
+ original =
+ "#include \"a.h\"\n"
+ "#include \"z.h\"\n"
+ "\n@"
+ ;
+ expected =
+ "#include \"a.h\"\n"
+ "#include \"file.h\"\n"
+ "#include \"z.h\"\n"
+ "\n"
+ ;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+ QTest::newRow("insertingSortingMiddle")
+ << TestIncludePaths::globalIncludePath()
+ << testDocuments << firstRefactoringOperation << "\"file.h\"";
+ testDocuments.clear();
+
+ // -------------------------------------------------------------------------------------------
+
+ original =
+ "#include \"a.h\"\n"
+ "#include \"b.h\"\n"
+ "\n@"
+ ;
+ expected =
+ "#include \"a.h\"\n"
+ "#include \"b.h\"\n"
+ "#include \"file.h\"\n"
+ "\n"
+ ;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+ QTest::newRow("insertingSortingBottom")
+ << TestIncludePaths::globalIncludePath()
+ << testDocuments << firstRefactoringOperation << "\"file.h\"";
+ testDocuments.clear();
+
+ // -------------------------------------------------------------------------------------------
+
+ original =
+ "#include \"b.h\"\n"
+ "#include \"a.h\"\n"
+ "\n@"
+ ;
+ expected =
+ "#include \"b.h\"\n"
+ "#include \"a.h\"\n"
+ "#include \"file.h\"\n"
+ "\n"
+ ;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+ QTest::newRow("inserting_appendToUnsorted")
+ << TestIncludePaths::globalIncludePath()
+ << testDocuments << firstRefactoringOperation << "\"file.h\"";
+ testDocuments.clear();
+
+ // -------------------------------------------------------------------------------------------
+
+ original =
+ "#include <a.h>\n"
+ "#include <b.h>\n"
+ "\n@"
+ ;
+ expected =
+ "#include \"file.h\"\n"
+ "\n"
+ "#include <a.h>\n"
+ "#include <b.h>\n"
+ "\n"
+ ;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+ QTest::newRow("inserting_firstLocalIncludeAtFront")
+ << TestIncludePaths::globalIncludePath()
+ << testDocuments << firstRefactoringOperation << "\"file.h\"";
+ testDocuments.clear();
+
+ // -------------------------------------------------------------------------------------------
+
+ original =
+ "#include \"a.h\"\n"
+ "#include \"b.h\"\n"
+ "\n"
+ "void @f();\n"
+ ;
+ expected =
+ "#include \"a.h\"\n"
+ "#include \"b.h\"\n"
+ "\n"
+ "#include <file.h>\n"
+ "\n"
+ "void f();\n"
+ ;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+ QTest::newRow("firstGlobalIncludeAtBack")
+ << TestIncludePaths::globalIncludePath()
+ << testDocuments << firstRefactoringOperation << "<file.h>";
+ testDocuments.clear();
+
+ // -------------------------------------------------------------------------------------------
+
+ original =
+ "#include \"prefixa.h\"\n"
+ "#include \"prefixb.h\"\n"
+ "\n"
+ "#include \"foo.h\"\n"
+ "\n@"
+ ;
+ expected =
+ "#include \"prefixa.h\"\n"
+ "#include \"prefixb.h\"\n"
+ "#include \"prefixc.h\"\n"
+ "\n"
+ "#include \"foo.h\"\n"
+ "\n"
+ ;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+ QTest::newRow("inserting_preferGroupWithLongerMatchingPrefix")
+ << TestIncludePaths::globalIncludePath()
+ << testDocuments << firstRefactoringOperation << "\"prefixc.h\"";
+ testDocuments.clear();
+
+ // -------------------------------------------------------------------------------------------
+
+ original =
+ "#include \"lib/file.h\"\n"
+ "#include \"lib/fileother.h\"\n"
+ "\n@"
+ ;
+ expected =
+ "#include \"lib/file.h\"\n"
+ "#include \"lib/fileother.h\"\n"
+ "\n"
+ "#include \"file.h\"\n"
+ "\n"
+ ;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+ QTest::newRow("inserting_newGroupIfOnlyDifferentIncludeDirs")
+ << TestIncludePaths::globalIncludePath()
+ << testDocuments << firstRefactoringOperation << "\"file.h\"";
+ testDocuments.clear();
+
+ // -------------------------------------------------------------------------------------------
+
+ original =
+ "#include <lib/file.h>\n"
+ "#include <otherlib/file.h>\n"
+ "#include <utils/file.h>\n"
+ "\n@"
+ ;
+ expected =
+ "#include <firstlib/file.h>\n"
+ "#include <lib/file.h>\n"
+ "#include <otherlib/file.h>\n"
+ "#include <utils/file.h>\n"
+ "\n"
+ ;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+ QTest::newRow("inserting_mixedDirsSorted")
+ << TestIncludePaths::globalIncludePath()
+ << testDocuments << firstRefactoringOperation << "<firstlib/file.h>";
+ testDocuments.clear();
+
+ // -------------------------------------------------------------------------------------------
+
+ original =
+ "#include <otherlib/file.h>\n"
+ "#include <lib/file.h>\n"
+ "#include <utils/file.h>\n"
+ "\n@"
+ ;
+ expected =
+ "#include <otherlib/file.h>\n"
+ "#include <lib/file.h>\n"
+ "#include <utils/file.h>\n"
+ "#include <lastlib/file.h>\n"
+ "\n"
+ ;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+ QTest::newRow("inserting_mixedDirsUnsorted")
+ << TestIncludePaths::globalIncludePath()
+ << testDocuments << firstRefactoringOperation << "<lastlib/file.h>";
+ testDocuments.clear();
+
+ // -------------------------------------------------------------------------------------------
+
+ original =
+ "#include \"a.h\"\n"
+ "#include <global.h>\n"
+ "\n@"
+ ;
+ expected =
+ "#include \"a.h\"\n"
+ "#include \"z.h\"\n"
+ "#include <global.h>\n"
+ "\n"
+ ;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+ QTest::newRow("inserting_mixedIncludeTypes1")
+ << TestIncludePaths::globalIncludePath()
+ << testDocuments << firstRefactoringOperation << "\"z.h\"";
+ testDocuments.clear();
+
+ // -------------------------------------------------------------------------------------------
+
+ original =
+ "#include \"z.h\"\n"
+ "#include <global.h>\n"
+ "\n@"
+ ;
+ expected =
+ "#include \"a.h\"\n"
+ "#include \"z.h\"\n"
+ "#include <global.h>\n"
+ "\n"
+ ;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+ QTest::newRow("inserting_mixedIncludeTypes2")
+ << TestIncludePaths::globalIncludePath()
+ << testDocuments << firstRefactoringOperation << "\"a.h\"";
+ testDocuments.clear();
+
+ // -------------------------------------------------------------------------------------------
+
+ original =
+ "#include \"z.h\"\n"
+ "#include <global.h>\n"
+ "\n@"
+ ;
+ expected =
+ "#include \"z.h\"\n"
+ "#include \"lib/file.h\"\n"
+ "#include <global.h>\n"
+ "\n"
+ ;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+ QTest::newRow("inserting_mixedIncludeTypes3")
+ << TestIncludePaths::globalIncludePath()
+ << testDocuments << firstRefactoringOperation << "\"lib/file.h\"";
+ testDocuments.clear();
+
+ // -------------------------------------------------------------------------------------------
+
+ original =
+ "#include \"z.h\"\n"
+ "#include <global.h>\n"
+ "\n@"
+ ;
+ expected =
+ "#include \"z.h\"\n"
+ "#include <global.h>\n"
+ "#include <lib/file.h>\n"
+ "\n"
+ ;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+ QTest::newRow("inserting_mixedIncludeTypes4")
+ << TestIncludePaths::globalIncludePath()
+ << testDocuments << firstRefactoringOperation << "<lib/file.h>";
+ testDocuments.clear();
+
+ // -------------------------------------------------------------------------------------------
+
+ original =
+ "void @f();\n"
+ ;
+ expected =
+ "#include \"file.h\"\n"
+ "\n"
+ "void f();\n"
+ ;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+ QTest::newRow("inserting_noinclude")
+ << TestIncludePaths::globalIncludePath()
+ << testDocuments << firstRefactoringOperation << "\"file.h\"";
+ testDocuments.clear();
+
+ // -------------------------------------------------------------------------------------------
+
+ original =
+ "#ifndef FOO_H\n"
+ "#define FOO_H\n"
+ "void @f();\n"
+ "#endif\n"
+ ;
+ expected =
+ "#ifndef FOO_H\n"
+ "#define FOO_H\n"
+ "\n"
+ "#include \"file.h\"\n"
+ "\n"
+ "void f();\n"
+ "#endif\n"
+ ;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+ QTest::newRow("inserting_onlyIncludeGuard")
+ << TestIncludePaths::globalIncludePath()
+ << testDocuments << firstRefactoringOperation << "\"file.h\"";
+ testDocuments.clear();
+
+ // -------------------------------------------------------------------------------------------
+
+ original =
+ "\n"
+ "// comment\n"
+ "\n"
+ "void @f();\n"
+ ;
+ expected =
+ "\n"
+ "// comment\n"
+ "\n"
+ "#include \"file.h\"\n"
+ "\n"
+ "void @f();\n"
+ ;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+ QTest::newRow("inserting_veryFirstIncludeCppStyleCommentOnTop")
+ << TestIncludePaths::globalIncludePath()
+ << testDocuments << firstRefactoringOperation << "\"file.h\"";
+ testDocuments.clear();
+
+ // -------------------------------------------------------------------------------------------
+
+ original =
+ "\n"
+ "/*\n"
+ " comment\n"
+ " */\n"
+ "\n"
+ "void @f();\n"
+ ;
+ expected =
+ "\n"
+ "/*\n"
+ " comment\n"
+ " */\n"
+ "\n"
+ "#include \"file.h\"\n"
+ "\n"
+ "void @f();\n"
+ ;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+ QTest::newRow("inserting_veryFirstIncludeCStyleCommentOnTop")
+ << TestIncludePaths::globalIncludePath()
+ << testDocuments << firstRefactoringOperation << "\"file.h\"";
+ testDocuments.clear();
+
+ // -------------------------------------------------------------------------------------------
+
+ original =
+ "@QDir dir;\n"
+ ;
+ expected =
+ "#include <QDir>\n"
+ "\n"
+ "QDir dir;\n"
+ ;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+ QTest::newRow("inserting_checkQSomethingInQtIncludePaths")
+ << TestIncludePaths::globalQtCoreIncludePath()
+ << testDocuments << firstRefactoringOperation << "";
+ testDocuments.clear();
+
+ original =
+ "std::s@tring s;\n"
+ ;
+ expected =
+ "#include <string>\n"
+ "\n"
+ "std::string s;\n"
+ ;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+ QTest::newRow("inserting_std::string")
+ << TestIncludePaths::globalIncludePath()
+ << testDocuments << firstRefactoringOperation << "";
+ testDocuments.clear();
+
+ original = "class A{};";
+ testDocuments << CppTestDocument::create("a.h", original, original);
+ original = "class B{};";
+ testDocuments << CppTestDocument::create("b.h", original, original);
+ original =
+ "#include \"b.h\"\n"
+ "@A a;\n"
+ "B b;";
+ expected =
+ "#include \"b.h\"\n\n"
+ "#include \"a.h\"\n"
+ "A a;\n"
+ "B b;";
+ testDocuments << CppTestDocument::create("b.cpp", original, expected);
+ QTest::newRow("preserve first header")
+ << TestIncludePaths::globalIncludePath()
+ << testDocuments << firstRefactoringOperation << "";
+ testDocuments.clear();
+
+ original = "class C{};";
+ testDocuments << CppTestDocument::create("c.h", original, original);
+ original = "class B{};";
+ testDocuments << CppTestDocument::create("b.h", original, original);
+ original =
+ "#include \"c.h\"\n"
+ "C c;\n"
+ "@B b;";
+ expected =
+ "#include \"b.h\"\n"
+ "#include \"c.h\"\n"
+ "C c;\n"
+ "B b;";
+ testDocuments << CppTestDocument::create("x.cpp", original, expected);
+ QTest::newRow("do not preserve first header")
+ << TestIncludePaths::globalIncludePath()
+ << testDocuments << firstRefactoringOperation << "";
+ testDocuments.clear();
+ }
+
+ void testAddIncludeForUndefinedIdentifier()
+ {
+ QFETCH(QString, headerPath);
+ QFETCH(QuickFixTestDocuments, testDocuments);
+ QFETCH(int, refactoringOperationIndex);
+ QFETCH(QString, includeForTestFactory);
+
+ TemporaryDir temporaryDir;
+ QVERIFY(temporaryDir.isValid());
+ for (const TestDocumentPtr &testDocument : std::as_const(testDocuments))
+ testDocument->setBaseDirectory(temporaryDir.path());
+
+ QScopedPointer<CppQuickFixFactory> factory;
+ if (includeForTestFactory.isEmpty())
+ factory.reset(new AddIncludeForUndefinedIdentifier);
+ else
+ factory.reset(new AddIncludeForUndefinedIdentifierTestFactory(includeForTestFactory));
+
+ QuickFixOperationTest::run(testDocuments, factory.data(), headerPath,
+ refactoringOperationIndex);
+ }
+
+ void testAddIncludeForUndefinedIdentifierNoDoubleQtHeaderInclude()
+ {
+ TemporaryDir temporaryDir;
+ QVERIFY(temporaryDir.isValid());
+
+ QList<TestDocumentPtr> testDocuments;
+ QByteArray original;
+ QByteArray expected;
+
+ const QByteArray base = temporaryDir.path().toUtf8();
+
+ // This file makes the QDir definition available so that locator finds it.
+ original = expected = "#include <QDir>\n"
+ "void avoidBeingRecognizedAsForwardingHeader();";
+ testDocuments << CppTestDocument::create(base + "/fileUsingQDir.cpp", original, expected);
+
+ original = expected = "@QDir dir;\n";
+ testDocuments << CppTestDocument::create(base + "/fileWantsToUseQDir.cpp", original, expected);
+
+ AddIncludeForUndefinedIdentifier factory;
+ const QStringList expectedOperations = QStringList("Add #include <QDir>");
+ QuickFixOfferedOperationsTest(
+ testDocuments,
+ &factory,
+ ProjectExplorer::toUserHeaderPaths(
+ QStringList{TestIncludePaths::globalQtCoreIncludePath()}),
+ expectedOperations);
+ }
+
+ void testAddForwardDeclForUndefinedIdentifier_data()
+ {
+ QTest::addColumn<QuickFixTestDocuments>("testDocuments");
+ QTest::addColumn<QString>("symbol");
+ QTest::addColumn<int>("symbolPos");
+
+ QByteArray original;
+ QByteArray expected;
+
+ original =
+ "#pragma once\n"
+ "\n"
+ "void f(const Blu@bb &b)\n"
+ "{\n"
+ "}\n"
+ ;
+ expected =
+ "#pragma once\n"
+ "\n"
+ "\n"
+ "class Blubb;\n"
+ "void f(const Blubb &b)\n"
+ "{\n"
+ "}\n"
+ ;
+ QTest::newRow("unqualified symbol")
+ << QuickFixTestDocuments{CppTestDocument::create("theheader.h", original, expected)}
+ << "Blubb" << original.indexOf('@');
+
+ original =
+ "#pragma once\n"
+ "\n"
+ "namespace NS {\n"
+ "class C;\n"
+ "}\n"
+ "void f(const NS::Blu@bb &b)\n"
+ "{\n"
+ "}\n"
+ ;
+ expected =
+ "#pragma once\n"
+ "\n"
+ "namespace NS {\n"
+ "\n"
+ "class Blubb;\n"
+ "class C;\n"
+ "}\n"
+ "void f(const NS::Blubb &b)\n"
+ "{\n"
+ "}\n"
+ ;
+ QTest::newRow("qualified symbol, full namespace present")
+ << QuickFixTestDocuments{CppTestDocument::create("theheader.h", original, expected)}
+ << "NS::Blubb" << original.indexOf('@');
+
+ original =
+ "#pragma once\n"
+ "\n"
+ "namespace NS {\n"
+ "class C;\n"
+ "}\n"
+ "void f(const NS::NS2::Blu@bb &b)\n"
+ "{\n"
+ "}\n"
+ ;
+ expected =
+ "#pragma once\n"
+ "\n"
+ "namespace NS {\n"
+ "\n"
+ "namespace NS2 { class Blubb; }\n"
+ "class C;\n"
+ "}\n"
+ "void f(const NS::NS2::Blubb &b)\n"
+ "{\n"
+ "}\n"
+ ;
+ QTest::newRow("qualified symbol, partial namespace present")
+ << QuickFixTestDocuments{CppTestDocument::create("theheader.h", original, expected)}
+ << "NS::NS2::Blubb" << original.indexOf('@');
+
+ original =
+ "#pragma once\n"
+ "\n"
+ "namespace NS {\n"
+ "class C;\n"
+ "}\n"
+ "void f(const NS2::Blu@bb &b)\n"
+ "{\n"
+ "}\n"
+ ;
+ expected =
+ "#pragma once\n"
+ "\n"
+ "\n"
+ "namespace NS2 { class Blubb; }\n"
+ "namespace NS {\n"
+ "class C;\n"
+ "}\n"
+ "void f(const NS2::Blubb &b)\n"
+ "{\n"
+ "}\n"
+ ;
+ QTest::newRow("qualified symbol, other namespace present")
+ << QuickFixTestDocuments{CppTestDocument::create("theheader.h", original, expected)}
+ << "NS2::Blubb" << original.indexOf('@');
+
+ original =
+ "#pragma once\n"
+ "\n"
+ "void f(const NS2::Blu@bb &b)\n"
+ "{\n"
+ "}\n"
+ ;
+ expected =
+ "#pragma once\n"
+ "\n"
+ "\n"
+ "namespace NS2 { class Blubb; }\n"
+ "void f(const NS2::Blubb &b)\n"
+ "{\n"
+ "}\n"
+ ;
+ QTest::newRow("qualified symbol, no namespace present")
+ << QuickFixTestDocuments{CppTestDocument::create("theheader.h", original, expected)}
+ << "NS2::Blubb" << original.indexOf('@');
+
+ original =
+ "#pragma once\n"
+ "\n"
+ "void f(const NS2::Blu@bb &b)\n"
+ "{\n"
+ "}\n"
+ "namespace NS2 {}\n"
+ ;
+ expected =
+ "#pragma once\n"
+ "\n"
+ "\n"
+ "namespace NS2 { class Blubb; }\n"
+ "void f(const NS2::Blubb &b)\n"
+ "{\n"
+ "}\n"
+ "namespace NS2 {}\n"
+ ;
+ QTest::newRow("qualified symbol, existing namespace after symbol")
+ << QuickFixTestDocuments{CppTestDocument::create("theheader.h", original, expected)}
+ << "NS2::Blubb" << original.indexOf('@');
+ }
+
+ void testAddForwardDeclForUndefinedIdentifier()
+ {
+ QFETCH(QuickFixTestDocuments, testDocuments);
+ QFETCH(QString, symbol);
+ QFETCH(int, symbolPos);
+
+ TemporaryDir temporaryDir;
+ QVERIFY(temporaryDir.isValid());
+ testDocuments.first()->setBaseDirectory(temporaryDir.path());
+
+ QScopedPointer<CppQuickFixFactory> factory(
+ new AddForwardDeclForUndefinedIdentifierTestFactory(symbol, symbolPos));
+ QuickFixOperationTest::run({testDocuments}, factory.data(), ".", 0);
+ }
+};
+
+QObject *AddIncludeForUndefinedIdentifier::createTest()
+{
+ return new BringIdentifierIntoScopeTest;
+}
+
+#endif // WITH_TESTS
+} // namespace
+
+void registerBringIdentifierIntoScopeQuickfixes()
+{
+ CppQuickFixFactory::registerFactory<AddIncludeForUndefinedIdentifier>();
+}
+
+} // namespace CppEditor::Internal
+
+#ifdef WITH_TESTS
+#include <bringidentifierintoscope.moc>
+#endif
diff --git a/src/plugins/cppeditor/quickfixes/bringidentifierintoscope.h b/src/plugins/cppeditor/quickfixes/bringidentifierintoscope.h
new file mode 100644
index 0000000000..b2f8bbe5d6
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/bringidentifierintoscope.h
@@ -0,0 +1,8 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+namespace CppEditor::Internal {
+void registerBringIdentifierIntoScopeQuickfixes();
+} // namespace CppEditor::Internal
diff --git a/src/plugins/cppeditor/quickfixes/completeswitchstatement.cpp b/src/plugins/cppeditor/quickfixes/completeswitchstatement.cpp
new file mode 100644
index 0000000000..15b8e592ed
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/completeswitchstatement.cpp
@@ -0,0 +1,793 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "completeswitchstatement.h"
+
+#include "../cppeditortr.h"
+#include "../cpprefactoringchanges.h"
+#include "cppquickfix.h"
+
+#include <cplusplus/Overview.h>
+#include <cplusplus/TypeOfExpression.h>
+
+#ifdef WITH_TESTS
+#include "cppquickfix_test.h"
+#include <QtTest>
+#endif
+
+using namespace CPlusPlus;
+using namespace Utils;
+
+namespace CppEditor::Internal {
+namespace {
+
+class CaseStatementCollector : public ASTVisitor
+{
+public:
+ CaseStatementCollector(Document::Ptr document, const Snapshot &snapshot,
+ Scope *scope)
+ : ASTVisitor(document->translationUnit()),
+ document(document),
+ scope(scope)
+ {
+ typeOfExpression.init(document, snapshot);
+ }
+
+ QStringList operator ()(AST *ast)
+ {
+ values.clear();
+ foundCaseStatementLevel = false;
+ accept(ast);
+ return values;
+ }
+
+ bool preVisit(AST *ast) override {
+ if (CaseStatementAST *cs = ast->asCaseStatement()) {
+ foundCaseStatementLevel = true;
+ if (ExpressionAST *csExpression = cs->expression) {
+ if (ExpressionAST *expression = csExpression->asIdExpression()) {
+ QList<LookupItem> candidates = typeOfExpression(expression, document, scope);
+ if (!candidates.isEmpty() && candidates.first().declaration()) {
+ Symbol *decl = candidates.first().declaration();
+ values << prettyPrint.prettyName(LookupContext::fullyQualifiedName(decl));
+ }
+ }
+ }
+ return true;
+ } else if (foundCaseStatementLevel) {
+ return false;
+ }
+ return true;
+ }
+
+ Overview prettyPrint;
+ bool foundCaseStatementLevel = false;
+ QStringList values;
+ TypeOfExpression typeOfExpression;
+ Document::Ptr document;
+ Scope *scope;
+};
+
+class CompleteSwitchCaseStatementOp: public CppQuickFixOperation
+{
+public:
+ CompleteSwitchCaseStatementOp(const CppQuickFixInterface &interface,
+ int priority, CompoundStatementAST *compoundStatement, const QStringList &values)
+ : CppQuickFixOperation(interface, priority)
+ , compoundStatement(compoundStatement)
+ , values(values)
+ {
+ setDescription(Tr::tr("Complete Switch Statement"));
+ }
+
+ void perform() override
+ {
+ currentFile()->apply(ChangeSet::makeInsert(
+ currentFile()->endOf(compoundStatement->lbrace_token),
+ QLatin1String("\ncase ") + values.join(QLatin1String(":\nbreak;\ncase "))
+ + QLatin1String(":\nbreak;")));
+ }
+
+ CompoundStatementAST *compoundStatement;
+ QStringList values;
+};
+
+static Enum *findEnum(const QList<LookupItem> &results, const LookupContext &ctxt)
+{
+ for (const LookupItem &result : results) {
+ const FullySpecifiedType fst = result.type();
+
+ Type *type = result.declaration() ? result.declaration()->type().type()
+ : fst.type();
+
+ if (!type)
+ continue;
+ if (Enum *e = type->asEnumType())
+ return e;
+ if (const NamedType *namedType = type->asNamedType()) {
+ if (ClassOrNamespace *con = ctxt.lookupType(namedType->name(), result.scope())) {
+ QList<Enum *> enums = con->unscopedEnums();
+ const QList<Symbol *> symbols = con->symbols();
+ for (Symbol * const s : symbols) {
+ if (const auto e = s->asEnum())
+ enums << e;
+ }
+ const Name *referenceName = namedType->name();
+ if (const QualifiedNameId *qualifiedName = referenceName->asQualifiedNameId())
+ referenceName = qualifiedName->name();
+ for (Enum *e : std::as_const(enums)) {
+ if (const Name *candidateName = e->name()) {
+ if (candidateName->match(referenceName))
+ return e;
+ }
+ }
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+Enum *conditionEnum(const CppQuickFixInterface &interface, SwitchStatementAST *statement)
+{
+ Block *block = statement->symbol;
+ Scope *scope = interface.semanticInfo().doc->scopeAt(block->line(), block->column());
+ TypeOfExpression typeOfExpression;
+ typeOfExpression.setExpandTemplates(true);
+ typeOfExpression.init(interface.semanticInfo().doc, interface.snapshot());
+ const QList<LookupItem> results = typeOfExpression(statement->condition,
+ interface.semanticInfo().doc,
+ scope);
+
+ return findEnum(results, typeOfExpression.context());
+}
+
+//! Adds missing case statements for "switch (enumVariable)"
+class CompleteSwitchStatement: public CppQuickFixFactory
+{
+public:
+ CompleteSwitchStatement() { setClangdReplacement({12}); }
+#ifdef WITH_TESTS
+ static QObject *createTest();
+#endif
+
+private:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ const QList<AST *> &path = interface.path();
+
+ if (path.isEmpty())
+ return;
+
+ // look for switch statement
+ for (int depth = path.size() - 1; depth >= 0; --depth) {
+ AST *ast = path.at(depth);
+ SwitchStatementAST *switchStatement = ast->asSwitchStatement();
+ if (switchStatement) {
+ if (!switchStatement->statement || !switchStatement->symbol)
+ return;
+ CompoundStatementAST *compoundStatement = switchStatement->statement->asCompoundStatement();
+ if (!compoundStatement) // we ignore pathologic case "switch (t) case A: ;"
+ return;
+ // look if the condition's type is an enum
+ if (Enum *e = conditionEnum(interface, switchStatement)) {
+ // check the possible enum values
+ QStringList values;
+ Overview prettyPrint;
+ for (int i = 0; i < e->memberCount(); ++i) {
+ if (Declaration *decl = e->memberAt(i)->asDeclaration())
+ values << prettyPrint.prettyName(LookupContext::fullyQualifiedName(decl));
+ }
+ // Get the used values
+ Block *block = switchStatement->symbol;
+ CaseStatementCollector caseValues(interface.semanticInfo().doc, interface.snapshot(),
+ interface.semanticInfo().doc->scopeAt(block->line(), block->column()));
+ const QStringList usedValues = caseValues(switchStatement);
+ // save the values that would be added
+ for (const QString &usedValue : usedValues)
+ values.removeAll(usedValue);
+ if (!values.isEmpty())
+ result << new CompleteSwitchCaseStatementOp(interface, depth,
+ compoundStatement, values);
+ return;
+ }
+
+ return;
+ }
+ }
+ }
+};
+
+#ifdef WITH_TESTS
+using namespace Tests;
+
+class CompleteSwitchStatementTest : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void test_data()
+ {
+ QTest::addColumn<QByteArray>("original");
+ QTest::addColumn<QByteArray>("expected");
+
+ using QByteArray = QByteArray;
+
+ // Checks: All enum values are added as case statements for a blank switch.
+ QTest::newRow("basic1")
+ << QByteArray(
+ "enum EnumType { V1, V2 };\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ " EnumType t;\n"
+ " @switch (t) {\n"
+ " }\n"
+ "}\n")
+ << QByteArray(
+ "enum EnumType { V1, V2 };\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ " EnumType t;\n"
+ " switch (t) {\n"
+ " case V1:\n"
+ " break;\n"
+ " case V2:\n"
+ " break;\n"
+ " }\n"
+ "}\n");
+
+ // Same as above for enum class.
+ QTest::newRow("basic1_enum class")
+ << QByteArray(
+ "enum class EnumType { V1, V2 };\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ " EnumType t;\n"
+ " @switch (t) {\n"
+ " }\n"
+ "}\n")
+ << QByteArray(
+ "enum class EnumType { V1, V2 };\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ " EnumType t;\n"
+ " switch (t) {\n"
+ " case EnumType::V1:\n"
+ " break;\n"
+ " case EnumType::V2:\n"
+ " break;\n"
+ " }\n"
+ "}\n");
+
+ // Same as above with the cursor somewhere in the body.
+ QTest::newRow("basic1_enum class, cursor in the body")
+ << QByteArray(
+ "enum class EnumType { V1, V2 };\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ " EnumType t;\n"
+ " switch (t) {\n"
+ " @}\n"
+ "}\n")
+ << QByteArray(
+ "enum class EnumType { V1, V2 };\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ " EnumType t;\n"
+ " switch (t) {\n"
+ " case EnumType::V1:\n"
+ " break;\n"
+ " case EnumType::V2:\n"
+ " break;\n"
+ " }\n"
+ "}\n");
+
+ // Checks: All enum values are added as case statements for a blank switch when
+ // the variable is declared alongside the enum definition.
+ QTest::newRow("basic1_enum_with_declaration")
+ << QByteArray(
+ "enum EnumType { V1, V2 } t;\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ " @switch (t) {\n"
+ " }\n"
+ "}\n")
+ << QByteArray(
+ "enum EnumType { V1, V2 } t;\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ " switch (t) {\n"
+ " case V1:\n"
+ " break;\n"
+ " case V2:\n"
+ " break;\n"
+ " }\n"
+ "}\n");
+
+ // Same as above for enum class.
+ QTest::newRow("basic1_enum_with_declaration_enumClass")
+ << QByteArray(
+ "enum class EnumType { V1, V2 } t;\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ " @switch (t) {\n"
+ " }\n"
+ "}\n")
+ << QByteArray(
+ "enum class EnumType { V1, V2 } t;\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ " switch (t) {\n"
+ " case EnumType::V1:\n"
+ " break;\n"
+ " case EnumType::V2:\n"
+ " break;\n"
+ " }\n"
+ "}\n");
+
+ // Checks: All enum values are added as case statements for a blank switch
+ // for anonymous enums.
+ QTest::newRow("basic1_anonymous_enum")
+ << QByteArray(
+ "enum { V1, V2 } t;\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ " @switch (t) {\n"
+ " }\n"
+ "}\n")
+ << QByteArray(
+ "enum { V1, V2 } t;\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ " switch (t) {\n"
+ " case V1:\n"
+ " break;\n"
+ " case V2:\n"
+ " break;\n"
+ " }\n"
+ "}\n");
+
+ // Checks: All enum values are added as case statements for a blank switch with a default case.
+ QTest::newRow("basic2")
+ << QByteArray(
+ "enum EnumType { V1, V2 };\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ " EnumType t;\n"
+ " @switch (t) {\n"
+ " default:\n"
+ " break;\n"
+ " }\n"
+ "}\n")
+ << QByteArray(
+ "enum EnumType { V1, V2 };\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ " EnumType t;\n"
+ " switch (t) {\n"
+ " case V1:\n"
+ " break;\n"
+ " case V2:\n"
+ " break;\n"
+ " default:\n"
+ " break;\n"
+ " }\n"
+ "}\n");
+
+ // Same as above for enum class.
+ QTest::newRow("basic2_enumClass")
+ << QByteArray(
+ "enum class EnumType { V1, V2 };\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ " EnumType t;\n"
+ " @switch (t) {\n"
+ " default:\n"
+ " break;\n"
+ " }\n"
+ "}\n")
+ << QByteArray(
+ "enum class EnumType { V1, V2 };\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ " EnumType t;\n"
+ " switch (t) {\n"
+ " case EnumType::V1:\n"
+ " break;\n"
+ " case EnumType::V2:\n"
+ " break;\n"
+ " default:\n"
+ " break;\n"
+ " }\n"
+ "}\n");
+
+ // Checks: Enum type in class is found.
+ QTest::newRow("enumTypeInClass")
+ << QByteArray(
+ "struct C { enum EnumType { V1, V2 }; };\n"
+ "\n"
+ "void f(C::EnumType t) {\n"
+ " @switch (t) {\n"
+ " }\n"
+ "}\n")
+ << QByteArray(
+ "struct C { enum EnumType { V1, V2 }; };\n"
+ "\n"
+ "void f(C::EnumType t) {\n"
+ " switch (t) {\n"
+ " case C::V1:\n"
+ " break;\n"
+ " case C::V2:\n"
+ " break;\n"
+ " }\n"
+ "}\n");
+
+ // Same as above for enum class.
+ QTest::newRow("enumClassInClass")
+ << QByteArray(
+ "struct C { enum class EnumType { V1, V2 }; };\n"
+ "\n"
+ "void f(C::EnumType t) {\n"
+ " @switch (t) {\n"
+ " }\n"
+ "}\n")
+ << QByteArray(
+ "struct C { enum class EnumType { V1, V2 }; };\n"
+ "\n"
+ "void f(C::EnumType t) {\n"
+ " switch (t) {\n"
+ " case C::EnumType::V1:\n"
+ " break;\n"
+ " case C::EnumType::V2:\n"
+ " break;\n"
+ " }\n"
+ "}\n");
+
+ // Checks: Enum type in namespace is found.
+ QTest::newRow("enumTypeInNamespace")
+ << QByteArray(
+ "namespace N { enum EnumType { V1, V2 }; };\n"
+ "\n"
+ "void f(N::EnumType t) {\n"
+ " @switch (t) {\n"
+ " }\n"
+ "}\n")
+ << QByteArray(
+ "namespace N { enum EnumType { V1, V2 }; };\n"
+ "\n"
+ "void f(N::EnumType t) {\n"
+ " switch (t) {\n"
+ " case N::V1:\n"
+ " break;\n"
+ " case N::V2:\n"
+ " break;\n"
+ " }\n"
+ "}\n");
+
+ // Same as above for enum class.
+ QTest::newRow("enumClassInNamespace")
+ << QByteArray(
+ "namespace N { enum class EnumType { V1, V2 }; };\n"
+ "\n"
+ "void f(N::EnumType t) {\n"
+ " @switch (t) {\n"
+ " }\n"
+ "}\n")
+ << QByteArray(
+ "namespace N { enum class EnumType { V1, V2 }; };\n"
+ "\n"
+ "void f(N::EnumType t) {\n"
+ " switch (t) {\n"
+ " case N::EnumType::V1:\n"
+ " break;\n"
+ " case N::EnumType::V2:\n"
+ " break;\n"
+ " }\n"
+ "}\n");
+
+ // Checks: The missing enum value is added.
+ QTest::newRow("oneValueMissing")
+ << QByteArray(
+ "enum EnumType { V1, V2 };\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ " EnumType t;\n"
+ " @switch (t) {\n"
+ " case V2:\n"
+ " break;\n"
+ " default:\n"
+ " break;\n"
+ " }\n"
+ "}\n")
+ << QByteArray(
+ "enum EnumType { V1, V2 };\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ " EnumType t;\n"
+ " switch (t) {\n"
+ " case V1:\n"
+ " break;\n"
+ " case V2:\n"
+ " break;\n"
+ " default:\n"
+ " break;\n"
+ " }\n"
+ "}\n");
+
+ // Checks: Same as above for enum class.
+ QTest::newRow("oneValueMissing_enumClass")
+ << QByteArray(
+ "enum class EnumType { V1, V2 };\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ " EnumType t;\n"
+ " @switch (t) {\n"
+ " case EnumType::V2:\n"
+ " break;\n"
+ " default:\n"
+ " break;\n"
+ " }\n"
+ "}\n")
+ << QByteArray(
+ "enum class EnumType { V1, V2 };\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ " EnumType t;\n"
+ " switch (t) {\n"
+ " case EnumType::V1:\n"
+ " break;\n"
+ " case EnumType::V2:\n"
+ " break;\n"
+ " default:\n"
+ " break;\n"
+ " }\n"
+ "}\n");
+
+ // Checks: Find the correct enum type despite there being a declaration with the same name.
+ QTest::newRow("QTCREATORBUG10366_1")
+ << QByteArray(
+ "enum test { TEST_1, TEST_2 };\n"
+ "\n"
+ "void f() {\n"
+ " enum test test;\n"
+ " @switch (test) {\n"
+ " }\n"
+ "}\n")
+ << QByteArray(
+ "enum test { TEST_1, TEST_2 };\n"
+ "\n"
+ "void f() {\n"
+ " enum test test;\n"
+ " switch (test) {\n"
+ " case TEST_1:\n"
+ " break;\n"
+ " case TEST_2:\n"
+ " break;\n"
+ " }\n"
+ "}\n");
+
+ // Same as above for enum class.
+ QTest::newRow("QTCREATORBUG10366_1_enumClass")
+ << QByteArray(
+ "enum class test { TEST_1, TEST_2 };\n"
+ "\n"
+ "void f() {\n"
+ " enum test test;\n"
+ " @switch (test) {\n"
+ " }\n"
+ "}\n")
+ << QByteArray(
+ "enum class test { TEST_1, TEST_2 };\n"
+ "\n"
+ "void f() {\n"
+ " enum test test;\n"
+ " switch (test) {\n"
+ " case test::TEST_1:\n"
+ " break;\n"
+ " case test::TEST_2:\n"
+ " break;\n"
+ " }\n"
+ "}\n");
+
+ // Checks: Find the correct enum type despite there being a declaration with the same name.
+ QTest::newRow("QTCREATORBUG10366_2")
+ << QByteArray(
+ "enum test1 { Wrong11, Wrong12 };\n"
+ "enum test { Right1, Right2 };\n"
+ "enum test2 { Wrong21, Wrong22 };\n"
+ "\n"
+ "int main() {\n"
+ " enum test test;\n"
+ " @switch (test) {\n"
+ " }\n"
+ "}\n")
+ << QByteArray(
+ "enum test1 { Wrong11, Wrong12 };\n"
+ "enum test { Right1, Right2 };\n"
+ "enum test2 { Wrong21, Wrong22 };\n"
+ "\n"
+ "int main() {\n"
+ " enum test test;\n"
+ " switch (test) {\n"
+ " case Right1:\n"
+ " break;\n"
+ " case Right2:\n"
+ " break;\n"
+ " }\n"
+ "}\n");
+
+ // Same as above for enum class.
+ QTest::newRow("QTCREATORBUG10366_2_enumClass")
+ << QByteArray(
+ "enum class test1 { Wrong11, Wrong12 };\n"
+ "enum class test { Right1, Right2 };\n"
+ "enum class test2 { Wrong21, Wrong22 };\n"
+ "\n"
+ "int main() {\n"
+ " enum test test;\n"
+ " @switch (test) {\n"
+ " }\n"
+ "}\n")
+ << QByteArray(
+ "enum class test1 { Wrong11, Wrong12 };\n"
+ "enum class test { Right1, Right2 };\n"
+ "enum class test2 { Wrong21, Wrong22 };\n"
+ "\n"
+ "int main() {\n"
+ " enum test test;\n"
+ " switch (test) {\n"
+ " case test::Right1:\n"
+ " break;\n"
+ " case test::Right2:\n"
+ " break;\n"
+ " }\n"
+ "}\n");
+
+ // Checks: Do not crash on incomplete case statetement.
+ QTest::newRow("doNotCrashOnIncompleteCase")
+ << QByteArray(
+ "enum E {};\n"
+ "void f(E o)\n"
+ "{\n"
+ " @switch (o)\n"
+ " {\n"
+ " case\n"
+ " }\n"
+ "}\n")
+ << QByteArray();
+
+ // Same as above for enum class.
+ QTest::newRow("doNotCrashOnIncompleteCase_enumClass")
+ << QByteArray(
+ "enum class E {};\n"
+ "void f(E o)\n"
+ "{\n"
+ " @switch (o)\n"
+ " {\n"
+ " case\n"
+ " }\n"
+ "}\n")
+ << QByteArray();
+
+ // Checks: complete switch statement where enum is goes via a template type parameter
+ QTest::newRow("QTCREATORBUG-24752")
+ << QByteArray(
+ "enum E {EA, EB};\n"
+ "template<typename T> struct S {\n"
+ " static T theType() { return T(); }\n"
+ "};\n"
+ "int main() {\n"
+ " @switch (S<E>::theType()) {\n"
+ " }\n"
+ "}\n")
+ << QByteArray(
+ "enum E {EA, EB};\n"
+ "template<typename T> struct S {\n"
+ " static T theType() { return T(); }\n"
+ "};\n"
+ "int main() {\n"
+ " switch (S<E>::theType()) {\n"
+ " case EA:\n"
+ " break;\n"
+ " case EB:\n"
+ " break;\n"
+ " }\n"
+ "}\n");
+
+ // Same as above for enum class.
+ QTest::newRow("QTCREATORBUG-24752_enumClass")
+ << QByteArray(
+ "enum class E {A, B};\n"
+ "template<typename T> struct S {\n"
+ " static T theType() { return T(); }\n"
+ "};\n"
+ "int main() {\n"
+ " @switch (S<E>::theType()) {\n"
+ " }\n"
+ "}\n")
+ << QByteArray(
+ "enum class E {A, B};\n"
+ "template<typename T> struct S {\n"
+ " static T theType() { return T(); }\n"
+ "};\n"
+ "int main() {\n"
+ " switch (S<E>::theType()) {\n"
+ " case E::A:\n"
+ " break;\n"
+ " case E::B:\n"
+ " break;\n"
+ " }\n"
+ "}\n");
+
+ // Checks: Complete switch statement where enum is return type of a template function
+ // which is outside the scope of the return value.
+ // TODO: Type minimization.
+ QTest::newRow("QTCREATORBUG-25998")
+ << QByteArray(
+ "template <typename T> T enumCast(int value) { return static_cast<T>(value); }\n"
+ "class Test {\n"
+ " enum class E { V1, V2 };"
+ " void func(int i) {\n"
+ " @switch (enumCast<E>(i)) {\n"
+ " }\n"
+ " }\n"
+ "};\n")
+ << QByteArray(
+ "template <typename T> T enumCast(int value) { return static_cast<T>(value); }\n"
+ "class Test {\n"
+ " enum class E { V1, V2 };"
+ " void func(int i) {\n"
+ " switch (enumCast<E>(i)) {\n"
+ " case Test::E::V1:\n"
+ " break;\n"
+ " case Test::E::V2:\n"
+ " break;\n"
+ " }\n"
+ " }\n"
+ "};\n");
+ }
+
+ void test()
+ {
+ QFETCH(QByteArray, original);
+ QFETCH(QByteArray, expected);
+ CompleteSwitchStatement factory;
+ QuickFixOperationTest(singleDocument(original, expected), &factory);
+ }
+};
+
+QObject *CompleteSwitchStatement::createTest() { return new CompleteSwitchStatementTest; }
+
+#endif // WITH_TESTS
+} // namespace
+
+void registerCompleteSwitchStatementQuickfix()
+{
+ CppQuickFixFactory::registerFactory<CompleteSwitchStatement>();
+}
+
+} // namespace CppEditor::Internal
+
+#ifdef WITH_TESTS
+#include <completeswitchstatement.moc>
+#endif
diff --git a/src/plugins/cppeditor/quickfixes/completeswitchstatement.h b/src/plugins/cppeditor/quickfixes/completeswitchstatement.h
new file mode 100644
index 0000000000..dc2293b1b2
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/completeswitchstatement.h
@@ -0,0 +1,8 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+namespace CppEditor::Internal {
+void registerCompleteSwitchStatementQuickfix();
+} // namespace CppEditor::Internal
diff --git a/src/plugins/cppeditor/quickfixes/convertfromandtopointer.cpp b/src/plugins/cppeditor/quickfixes/convertfromandtopointer.cpp
new file mode 100644
index 0000000000..5eea005200
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/convertfromandtopointer.cpp
@@ -0,0 +1,704 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "convertfromandtopointer.h"
+
+#include "../cppeditortr.h"
+#include "../cpprefactoringchanges.h"
+#include "cppquickfix.h"
+
+#include <cplusplus/ASTPath.h>
+#include <cplusplus/Overview.h>
+#include <cplusplus/TypeOfExpression.h>
+
+#ifdef WITH_TESTS
+#include "cppquickfix_test.h"
+#include <QtTest>
+#endif
+
+using namespace CPlusPlus;
+using namespace Utils;
+
+namespace CppEditor::Internal {
+namespace {
+
+class ConvertFromAndToPointerOp : public CppQuickFixOperation
+{
+public:
+ enum Mode { FromPointer, FromVariable, FromReference };
+
+ ConvertFromAndToPointerOp(const CppQuickFixInterface &interface, int priority, Mode mode,
+ bool isAutoDeclaration,
+ const SimpleDeclarationAST *simpleDeclaration,
+ const DeclaratorAST *declaratorAST,
+ const SimpleNameAST *identifierAST,
+ Symbol *symbol)
+ : CppQuickFixOperation(interface, priority)
+ , m_mode(mode)
+ , m_isAutoDeclaration(isAutoDeclaration)
+ , m_simpleDeclaration(simpleDeclaration)
+ , m_declaratorAST(declaratorAST)
+ , m_identifierAST(identifierAST)
+ , m_symbol(symbol)
+ , m_refactoring(snapshot())
+ , m_file(currentFile())
+ , m_document(interface.semanticInfo().doc)
+ {
+ setDescription(
+ mode == FromPointer
+ ? Tr::tr("Convert to Stack Variable")
+ : Tr::tr("Convert to Pointer"));
+ }
+
+ void perform() override
+ {
+ ChangeSet changes;
+
+ switch (m_mode) {
+ case FromPointer:
+ removePointerOperator(changes);
+ convertToStackVariable(changes);
+ break;
+ case FromReference:
+ removeReferenceOperator(changes);
+ Q_FALLTHROUGH();
+ case FromVariable:
+ convertToPointer(changes);
+ break;
+ }
+
+ m_file->apply(changes);
+ }
+
+private:
+ void removePointerOperator(ChangeSet &changes) const
+ {
+ if (!m_declaratorAST->ptr_operator_list)
+ return;
+ PointerAST *ptrAST = m_declaratorAST->ptr_operator_list->value->asPointer();
+ QTC_ASSERT(ptrAST, return);
+ const int pos = m_file->startOf(ptrAST->star_token);
+ changes.remove(pos, pos + 1);
+ }
+
+ void removeReferenceOperator(ChangeSet &changes) const
+ {
+ ReferenceAST *refAST = m_declaratorAST->ptr_operator_list->value->asReference();
+ QTC_ASSERT(refAST, return);
+ const int pos = m_file->startOf(refAST->reference_token);
+ changes.remove(pos, pos + 1);
+ }
+
+ void removeNewExpression(ChangeSet &changes, NewExpressionAST *newExprAST) const
+ {
+ ExpressionListAST *exprlist = nullptr;
+ if (newExprAST->new_initializer) {
+ if (ExpressionListParenAST *ast = newExprAST->new_initializer->asExpressionListParen())
+ exprlist = ast->expression_list;
+ else if (BracedInitializerAST *ast = newExprAST->new_initializer->asBracedInitializer())
+ exprlist = ast->expression_list;
+ }
+
+ if (exprlist) {
+ // remove 'new' keyword and type before initializer
+ changes.remove(m_file->startOf(newExprAST->new_token),
+ m_file->startOf(newExprAST->new_initializer));
+
+ changes.remove(m_file->endOf(m_declaratorAST->equal_token - 1),
+ m_file->startOf(m_declaratorAST->equal_token + 1));
+ } else {
+ // remove the whole new expression
+ changes.remove(m_file->endOf(m_identifierAST->firstToken()),
+ m_file->startOf(newExprAST->lastToken()));
+ }
+ }
+
+ void removeNewKeyword(ChangeSet &changes, NewExpressionAST *newExprAST) const
+ {
+ // remove 'new' keyword before initializer
+ changes.remove(m_file->startOf(newExprAST->new_token),
+ m_file->startOf(newExprAST->new_type_id));
+ }
+
+ void convertToStackVariable(ChangeSet &changes) const
+ {
+ // Handle the initializer.
+ if (m_declaratorAST->initializer) {
+ if (NewExpressionAST *newExpression = m_declaratorAST->initializer->asNewExpression()) {
+ if (m_isAutoDeclaration) {
+ if (!newExpression->new_initializer)
+ changes.insert(m_file->endOf(newExpression), QStringLiteral("()"));
+ removeNewKeyword(changes, newExpression);
+ } else {
+ removeNewExpression(changes, newExpression);
+ }
+ }
+ }
+
+ // Fix all occurrences of the identifier in this function.
+ ASTPath astPath(m_document);
+ const QList<SemanticInfo::Use> uses = semanticInfo().localUses.value(m_symbol);
+ for (const SemanticInfo::Use &use : uses) {
+ const QList<AST *> path = astPath(use.line, use.column);
+ AST *idAST = path.last();
+ bool declarationFound = false;
+ bool starFound = false;
+ int ampersandPos = 0;
+ bool memberAccess = false;
+ bool deleteCall = false;
+
+ for (int i = path.count() - 2; i >= 0; --i) {
+ if (path.at(i) == m_declaratorAST) {
+ declarationFound = true;
+ break;
+ }
+ if (MemberAccessAST *memberAccessAST = path.at(i)->asMemberAccess()) {
+ if (m_file->tokenAt(memberAccessAST->access_token).kind() != T_ARROW)
+ continue;
+ int pos = m_file->startOf(memberAccessAST->access_token);
+ changes.replace(pos, pos + 2, QLatin1String("."));
+ memberAccess = true;
+ break;
+ } else if (DeleteExpressionAST *deleteAST = path.at(i)->asDeleteExpression()) {
+ const int pos = m_file->startOf(deleteAST->delete_token);
+ changes.insert(pos, QLatin1String("// "));
+ deleteCall = true;
+ break;
+ } else if (UnaryExpressionAST *unaryExprAST = path.at(i)->asUnaryExpression()) {
+ const Token tk = m_file->tokenAt(unaryExprAST->unary_op_token);
+ if (tk.kind() == T_STAR) {
+ if (!starFound) {
+ int pos = m_file->startOf(unaryExprAST->unary_op_token);
+ changes.remove(pos, pos + 1);
+ }
+ starFound = true;
+ } else if (tk.kind() == T_AMPER) {
+ ampersandPos = m_file->startOf(unaryExprAST->unary_op_token);
+ }
+ } else if (PointerAST *ptrAST = path.at(i)->asPointer()) {
+ if (!starFound) {
+ const int pos = m_file->startOf(ptrAST->star_token);
+ changes.remove(pos, pos);
+ }
+ starFound = true;
+ } else if (path.at(i)->asFunctionDefinition()) {
+ break;
+ }
+ }
+ if (!declarationFound && !starFound && !memberAccess && !deleteCall) {
+ if (ampersandPos) {
+ changes.insert(ampersandPos, QLatin1String("&("));
+ changes.insert(m_file->endOf(idAST->firstToken()), QLatin1String(")"));
+ } else {
+ changes.insert(m_file->startOf(idAST), QLatin1String("&"));
+ }
+ }
+ }
+ }
+
+ QString typeNameOfDeclaration() const
+ {
+ if (!m_simpleDeclaration
+ || !m_simpleDeclaration->decl_specifier_list
+ || !m_simpleDeclaration->decl_specifier_list->value) {
+ return QString();
+ }
+ NamedTypeSpecifierAST *namedType
+ = m_simpleDeclaration->decl_specifier_list->value->asNamedTypeSpecifier();
+ if (!namedType)
+ return QString();
+
+ Overview overview;
+ return overview.prettyName(namedType->name->name);
+ }
+
+ void insertNewExpression(ChangeSet &changes, ExpressionAST *ast) const
+ {
+ const QString typeName = typeNameOfDeclaration();
+ if (CallAST *callAST = ast->asCall()) {
+ if (typeName.isEmpty()) {
+ changes.insert(m_file->startOf(callAST), QLatin1String("new "));
+ } else {
+ changes.insert(m_file->startOf(callAST),
+ QLatin1String("new ") + typeName + QLatin1Char('('));
+ changes.insert(m_file->startOf(callAST->lastToken()), QLatin1String(")"));
+ }
+ } else {
+ if (typeName.isEmpty())
+ return;
+ changes.insert(m_file->startOf(ast), QLatin1String(" = new ") + typeName);
+ }
+ }
+
+ void insertNewExpression(ChangeSet &changes) const
+ {
+ const QString typeName = typeNameOfDeclaration();
+ if (typeName.isEmpty())
+ return;
+ changes.insert(m_file->endOf(m_identifierAST->firstToken()),
+ QLatin1String(" = new ") + typeName);
+ }
+
+ void convertToPointer(ChangeSet &changes) const
+ {
+ // Handle initializer.
+ if (m_declaratorAST->initializer) {
+ if (IdExpressionAST *idExprAST = m_declaratorAST->initializer->asIdExpression()) {
+ changes.insert(m_file->startOf(idExprAST), QLatin1String("&"));
+ } else if (CallAST *callAST = m_declaratorAST->initializer->asCall()) {
+ insertNewExpression(changes, callAST);
+ } else if (ExpressionListParenAST *exprListAST = m_declaratorAST->initializer
+ ->asExpressionListParen()) {
+ insertNewExpression(changes, exprListAST);
+ } else if (BracedInitializerAST *bracedInitializerAST = m_declaratorAST->initializer
+ ->asBracedInitializer()) {
+ insertNewExpression(changes, bracedInitializerAST);
+ }
+ } else {
+ insertNewExpression(changes);
+ }
+
+ // Fix all occurrences of the identifier in this function.
+ ASTPath astPath(m_document);
+ const QList<SemanticInfo::Use> uses = semanticInfo().localUses.value(m_symbol);
+ for (const SemanticInfo::Use &use : uses) {
+ const QList<AST *> path = astPath(use.line, use.column);
+ AST *idAST = path.last();
+ bool insertStar = true;
+ for (int i = path.count() - 2; i >= 0; --i) {
+ if (m_isAutoDeclaration && path.at(i) == m_declaratorAST) {
+ insertStar = false;
+ break;
+ }
+ if (MemberAccessAST *memberAccessAST = path.at(i)->asMemberAccess()) {
+ const int pos = m_file->startOf(memberAccessAST->access_token);
+ changes.replace(pos, pos + 1, QLatin1String("->"));
+ insertStar = false;
+ break;
+ } else if (UnaryExpressionAST *unaryExprAST = path.at(i)->asUnaryExpression()) {
+ if (m_file->tokenAt(unaryExprAST->unary_op_token).kind() == T_AMPER) {
+ const int pos = m_file->startOf(unaryExprAST->unary_op_token);
+ changes.remove(pos, pos + 1);
+ insertStar = false;
+ break;
+ }
+ } else if (path.at(i)->asFunctionDefinition()) {
+ break;
+ }
+ }
+ if (insertStar)
+ changes.insert(m_file->startOf(idAST), QLatin1String("*"));
+ }
+ }
+
+ const Mode m_mode;
+ const bool m_isAutoDeclaration;
+ const SimpleDeclarationAST * const m_simpleDeclaration;
+ const DeclaratorAST * const m_declaratorAST;
+ const SimpleNameAST * const m_identifierAST;
+ Symbol * const m_symbol;
+ const CppRefactoringChanges m_refactoring;
+ const CppRefactoringFilePtr m_file;
+ const Document::Ptr m_document;
+};
+
+/*!
+ Converts the selected variable to a pointer if it is a stack variable or reference, or vice versa.
+ Activates on variable declarations.
+ */
+class ConvertFromAndToPointer : public CppQuickFixFactory
+{
+#ifdef WITH_TESTS
+public:
+ static QObject *createTest();
+#endif
+
+private:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ const QList<AST *> &path = interface.path();
+ if (path.count() < 2)
+ return;
+ SimpleNameAST *identifier = path.last()->asSimpleName();
+ if (!identifier)
+ return;
+ SimpleDeclarationAST *simpleDeclaration = nullptr;
+ DeclaratorAST *declarator = nullptr;
+ bool isFunctionLocal = false;
+ bool isClassLocal = false;
+ ConvertFromAndToPointerOp::Mode mode = ConvertFromAndToPointerOp::FromVariable;
+ for (int i = path.count() - 2; i >= 0; --i) {
+ AST *ast = path.at(i);
+ if (!declarator && (declarator = ast->asDeclarator()))
+ continue;
+ if (!simpleDeclaration && (simpleDeclaration = ast->asSimpleDeclaration()))
+ continue;
+ if (declarator && simpleDeclaration) {
+ if (ast->asClassSpecifier()) {
+ isClassLocal = true;
+ } else if (ast->asFunctionDefinition() && !isClassLocal) {
+ isFunctionLocal = true;
+ break;
+ }
+ }
+ }
+ if (!isFunctionLocal || !simpleDeclaration || !declarator)
+ return;
+
+ Symbol *symbol = nullptr;
+ for (List<Symbol *> *lst = simpleDeclaration->symbols; lst; lst = lst->next) {
+ if (lst->value->name() == identifier->name) {
+ symbol = lst->value;
+ break;
+ }
+ }
+ if (!symbol)
+ return;
+
+ bool isAutoDeclaration = false;
+ if (symbol->storage() == Symbol::Auto) {
+ // For auto variables we must deduce the type from the initializer.
+ if (!declarator->initializer)
+ return;
+
+ isAutoDeclaration = true;
+ TypeOfExpression typeOfExpression;
+ typeOfExpression.init(interface.semanticInfo().doc, interface.snapshot());
+ typeOfExpression.setExpandTemplates(true);
+ CppRefactoringFilePtr file = interface.currentFile();
+ Scope *scope = file->scopeAt(declarator->firstToken());
+ QList<LookupItem> result = typeOfExpression(file->textOf(declarator->initializer).toUtf8(),
+ scope, TypeOfExpression::Preprocess);
+ if (!result.isEmpty() && result.first().type()->asPointerType())
+ mode = ConvertFromAndToPointerOp::FromPointer;
+ } else if (declarator->ptr_operator_list) {
+ for (PtrOperatorListAST *ops = declarator->ptr_operator_list; ops; ops = ops->next) {
+ if (ops != declarator->ptr_operator_list) {
+ // Bail out on more complex pointer types (e.g. pointer of pointer,
+ // or reference of pointer).
+ return;
+ }
+ if (ops->value->asPointer())
+ mode = ConvertFromAndToPointerOp::FromPointer;
+ else if (ops->value->asReference())
+ mode = ConvertFromAndToPointerOp::FromReference;
+ }
+ }
+
+ const int priority = path.size() - 1;
+ result << new ConvertFromAndToPointerOp(interface, priority, mode, isAutoDeclaration,
+ simpleDeclaration, declarator, identifier, symbol);
+ }
+};
+
+#ifdef WITH_TESTS
+using namespace Tests;
+
+class ConvertFromAndToPointerTest : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void test_data()
+ {
+ QTest::addColumn<QByteArray>("original");
+ QTest::addColumn<QByteArray>("expected");
+
+ QTest::newRow("ConvertFromPointer")
+ << QByteArray("void foo() {\n"
+ " QString *@str;\n"
+ " if (!str->isEmpty())\n"
+ " str->clear();\n"
+ " f1(*str);\n"
+ " f2(str);\n"
+ "}\n")
+ << QByteArray("void foo() {\n"
+ " QString str;\n"
+ " if (!str.isEmpty())\n"
+ " str.clear();\n"
+ " f1(str);\n"
+ " f2(&str);\n"
+ "}\n");
+
+ QTest::newRow("ConvertToPointer")
+ << QByteArray("void foo() {\n"
+ " QString @str;\n"
+ " if (!str.isEmpty())\n"
+ " str.clear();\n"
+ " f1(str);\n"
+ " f2(&str);\n"
+ "}\n")
+ << QByteArray("void foo() {\n"
+ " QString *str = new QString;\n"
+ " if (!str->isEmpty())\n"
+ " str->clear();\n"
+ " f1(*str);\n"
+ " f2(str);\n"
+ "}\n");
+
+ QTest::newRow("ConvertReferenceToPointer")
+ << QByteArray("void foo() {\n"
+ " QString narf;"
+ " QString &@str = narf;\n"
+ " if (!str.isEmpty())\n"
+ " str.clear();\n"
+ " f1(str);\n"
+ " f2(&str);\n"
+ "}\n")
+ << QByteArray("void foo() {\n"
+ " QString narf;"
+ " QString *str = &narf;\n"
+ " if (!str->isEmpty())\n"
+ " str->clear();\n"
+ " f1(*str);\n"
+ " f2(str);\n"
+ "}\n");
+
+ QTest::newRow("ConvertFromPointer_withInitializer")
+ << QByteArray("void foo() {\n"
+ " QString *@str = new QString(QLatin1String(\"schnurz\"));\n"
+ " if (!str->isEmpty())\n"
+ " str->clear();\n"
+ "}\n")
+ << QByteArray("void foo() {\n"
+ " QString str(QLatin1String(\"schnurz\"));\n"
+ " if (!str.isEmpty())\n"
+ " str.clear();\n"
+ "}\n");
+
+ QTest::newRow("ConvertFromPointer_withBareInitializer")
+ << QByteArray("void foo() {\n"
+ " QString *@str = new QString;\n"
+ " if (!str->isEmpty())\n"
+ " str->clear();\n"
+ "}\n")
+ << QByteArray("void foo() {\n"
+ " QString str;\n"
+ " if (!str.isEmpty())\n"
+ " str.clear();\n"
+ "}\n");
+
+ QTest::newRow("ConvertFromPointer_withEmptyInitializer")
+ << QByteArray("void foo() {\n"
+ " QString *@str = new QString();\n"
+ " if (!str->isEmpty())\n"
+ " str->clear();\n"
+ "}\n")
+ << QByteArray("void foo() {\n"
+ " QString str;\n"
+ " if (!str.isEmpty())\n"
+ " str.clear();\n"
+ "}\n");
+
+ QTest::newRow("ConvertFromPointer_structWithPointer")
+ << QByteArray("struct Bar{ QString *str; };\n"
+ "void foo() {\n"
+ " Bar *@bar = new Bar;\n"
+ " bar->str = new QString;\n"
+ " delete bar->str;\n"
+ " delete bar;\n"
+ "}\n")
+ << QByteArray("struct Bar{ QString *str; };\n"
+ "void foo() {\n"
+ " Bar bar;\n"
+ " bar.str = new QString;\n"
+ " delete bar.str;\n"
+ " // delete bar;\n"
+ "}\n");
+
+ QTest::newRow("ConvertToPointer_withInitializer")
+ << QByteArray("void foo() {\n"
+ " QString @str = QLatin1String(\"narf\");\n"
+ " if (!str.isEmpty())\n"
+ " str.clear();\n"
+ "}\n")
+ << QByteArray("void foo() {\n"
+ " QString *str = new QString(QLatin1String(\"narf\"));\n"
+ " if (!str->isEmpty())\n"
+ " str->clear();\n"
+ "}\n");
+
+ QTest::newRow("ConvertToPointer_withParenInitializer")
+ << QByteArray("void foo() {\n"
+ " QString @str(QLatin1String(\"narf\"));\n"
+ " if (!str.isEmpty())\n"
+ " str.clear();\n"
+ "}\n")
+ << QByteArray("void foo() {\n"
+ " QString *str = new QString(QLatin1String(\"narf\"));\n"
+ " if (!str->isEmpty())\n"
+ " str->clear();\n"
+ "}\n");
+
+ QTest::newRow("ConvertToPointer_noTriggerRValueRefs")
+ << QByteArray("void foo(Narf &&@narf) {}\n")
+ << QByteArray();
+
+ QTest::newRow("ConvertToPointer_noTriggerGlobal")
+ << QByteArray("int @global;\n")
+ << QByteArray();
+
+ QTest::newRow("ConvertToPointer_noTriggerClassMember")
+ << QByteArray("struct C { int @member; };\n")
+ << QByteArray();
+
+ QTest::newRow("ConvertToPointer_noTriggerClassMember2")
+ << QByteArray("void f() { struct C { int @member; }; }\n")
+ << QByteArray();
+
+ QTest::newRow("ConvertToPointer_functionOfFunctionLocalClass")
+ << QByteArray("void f() {\n"
+ " struct C {\n"
+ " void g() { int @member; }\n"
+ " };\n"
+ "}\n")
+ << QByteArray("void f() {\n"
+ " struct C {\n"
+ " void g() { int *member; }\n"
+ " };\n"
+ "}\n");
+
+ QTest::newRow("ConvertToPointer_redeclaredVariable_block")
+ << QByteArray("void foo() {\n"
+ " QString @str;\n"
+ " str.clear();\n"
+ " {\n"
+ " QString str;\n"
+ " str.clear();\n"
+ " }\n"
+ " f1(str);\n"
+ "}\n")
+ << QByteArray("void foo() {\n"
+ " QString *str = new QString;\n"
+ " str->clear();\n"
+ " {\n"
+ " QString str;\n"
+ " str.clear();\n"
+ " }\n"
+ " f1(*str);\n"
+ "}\n");
+
+ QTest::newRow("ConvertAutoFromPointer")
+ << QByteArray("void foo() {\n"
+ " auto @str = new QString(QLatin1String(\"foo\"));\n"
+ " if (!str->isEmpty())\n"
+ " str->clear();\n"
+ " f1(*str);\n"
+ " f2(str);\n"
+ "}\n")
+ << QByteArray("void foo() {\n"
+ " auto str = QString(QLatin1String(\"foo\"));\n"
+ " if (!str.isEmpty())\n"
+ " str.clear();\n"
+ " f1(str);\n"
+ " f2(&str);\n"
+ "}\n");
+
+ QTest::newRow("ConvertAutoFromPointer2")
+ << QByteArray("void foo() {\n"
+ " auto *@str = new QString;\n"
+ " if (!str->isEmpty())\n"
+ " str->clear();\n"
+ " f1(*str);\n"
+ " f2(str);\n"
+ "}\n")
+ << QByteArray("void foo() {\n"
+ " auto str = QString();\n"
+ " if (!str.isEmpty())\n"
+ " str.clear();\n"
+ " f1(str);\n"
+ " f2(&str);\n"
+ "}\n");
+
+ QTest::newRow("ConvertAutoToPointer")
+ << QByteArray("void foo() {\n"
+ " auto @str = QString(QLatin1String(\"foo\"));\n"
+ " if (!str.isEmpty())\n"
+ " str.clear();\n"
+ " f1(str);\n"
+ " f2(&str);\n"
+ "}\n")
+ << QByteArray("void foo() {\n"
+ " auto @str = new QString(QLatin1String(\"foo\"));\n"
+ " if (!str->isEmpty())\n"
+ " str->clear();\n"
+ " f1(*str);\n"
+ " f2(str);\n"
+ "}\n");
+
+ QTest::newRow("ConvertToPointerWithMacro")
+ << QByteArray("#define BAR bar\n"
+ "void func()\n"
+ "{\n"
+ " int @foo = 42;\n"
+ " int bar;\n"
+ " BAR = foo;\n"
+ "}\n")
+ << QByteArray("#define BAR bar\n"
+ "void func()\n"
+ "{\n"
+ " int *foo = 42;\n"
+ " int bar;\n"
+ " BAR = *foo;\n"
+ "}\n");
+
+ QString testObjAndFunc = "struct Object\n"
+ "{\n"
+ " Object(%1){}\n"
+ "};\n"
+ "void func()\n"
+ "{\n"
+ " %2\n"
+ "}\n";
+
+ QTest::newRow("ConvertToStack1_QTCREATORBUG23181")
+ << QByteArray(testObjAndFunc.arg("int").arg("Object *@obj = new Object(0);").toUtf8())
+ << QByteArray(testObjAndFunc.arg("int").arg("Object obj(0);").toUtf8());
+
+ QTest::newRow("ConvertToStack2_QTCREATORBUG23181")
+ << QByteArray(testObjAndFunc.arg("int").arg("Object *@obj = new Object{0};").toUtf8())
+ << QByteArray(testObjAndFunc.arg("int").arg("Object obj{0};").toUtf8());
+
+ QTest::newRow("ConvertToPointer1_QTCREATORBUG23181")
+ << QByteArray(testObjAndFunc.arg("").arg("Object @obj;").toUtf8())
+ << QByteArray(testObjAndFunc.arg("").arg("Object *obj = new Object;").toUtf8());
+
+ QTest::newRow("ConvertToPointer2_QTCREATORBUG23181")
+ << QByteArray(testObjAndFunc.arg("").arg("Object @obj();").toUtf8())
+ << QByteArray(testObjAndFunc.arg("").arg("Object *obj = new Object();").toUtf8());
+
+ QTest::newRow("ConvertToPointer3_QTCREATORBUG23181")
+ << QByteArray(testObjAndFunc.arg("").arg("Object @obj{};").toUtf8())
+ << QByteArray(testObjAndFunc.arg("").arg("Object *obj = new Object{};").toUtf8());
+
+ QTest::newRow("ConvertToPointer4_QTCREATORBUG23181")
+ << QByteArray(testObjAndFunc.arg("int").arg("Object @obj(0);").toUtf8())
+ << QByteArray(testObjAndFunc.arg("int").arg("Object *obj = new Object(0);").toUtf8());
+
+
+ }
+
+ void test()
+ {
+ QFETCH(QByteArray, original);
+ QFETCH(QByteArray, expected);
+ ConvertFromAndToPointer factory;
+ QuickFixOperationTest(singleDocument(original, expected), &factory);
+ }
+};
+
+QObject *ConvertFromAndToPointer::createTest() { return new ConvertFromAndToPointerTest; }
+
+#endif // WITH_TESTS
+} // namespace
+
+void registerConvertFromAndToPointerQuickfix()
+{
+ CppQuickFixFactory::registerFactory<ConvertFromAndToPointer>();
+}
+
+} // namespace CppEditor::Internal
+
+#ifdef WITH_TESTS
+#include <convertfromandtopointer.moc>
+#endif
diff --git a/src/plugins/cppeditor/quickfixes/convertfromandtopointer.h b/src/plugins/cppeditor/quickfixes/convertfromandtopointer.h
new file mode 100644
index 0000000000..0deb6c0da9
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/convertfromandtopointer.h
@@ -0,0 +1,8 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+namespace CppEditor::Internal {
+void registerConvertFromAndToPointerQuickfix();
+} // namespace CppEditor::Internal
diff --git a/src/plugins/cppeditor/quickfixes/convertnumericliteral.cpp b/src/plugins/cppeditor/quickfixes/convertnumericliteral.cpp
new file mode 100644
index 0000000000..6fb5becf34
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/convertnumericliteral.cpp
@@ -0,0 +1,199 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "convertnumericliteral.h"
+
+#include "../cppeditortr.h"
+#include "../cpprefactoringchanges.h"
+#include "cppquickfix.h"
+
+#include <bitset>
+
+using namespace CPlusPlus;
+using namespace Utils;
+
+namespace CppEditor::Internal {
+namespace {
+
+class ConvertNumericLiteralOp: public CppQuickFixOperation
+{
+public:
+ ConvertNumericLiteralOp(const CppQuickFixInterface &interface, int start, int end,
+ const QString &replacement)
+ : CppQuickFixOperation(interface)
+ , start(start)
+ , end(end)
+ , replacement(replacement)
+ {}
+
+ void perform() override
+ {
+ currentFile()->apply(ChangeSet::makeReplace(start, end, replacement));
+ }
+
+private:
+ int start, end;
+ QString replacement;
+};
+
+/*!
+ Base class for converting numeric literals between decimal, octal and hex.
+ Does the base check for the specific ones and parses the number.
+
+ Test cases:
+ 0xFA0Bu;
+ 0X856A;
+ 298.3;
+ 199;
+ 074;
+ 199L;
+ 074L;
+ -199;
+ -017;
+ 0783; // invalid octal
+ 0; // border case, allow only hex<->decimal
+
+ Activates on: numeric literals
+*/
+class ConvertNumericLiteral : public CppQuickFixFactory
+{
+#ifdef WITH_TESTS
+public:
+ static QObject *createTest() { return new QObject; }
+#endif
+
+private:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ const QList<AST *> &path = interface.path();
+ CppRefactoringFilePtr file = interface.currentFile();
+
+ if (path.isEmpty())
+ return;
+
+ NumericLiteralAST *literal = path.last()->asNumericLiteral();
+
+ if (!literal)
+ return;
+
+ Token token = file->tokenAt(literal->asNumericLiteral()->literal_token);
+ if (!token.is(T_NUMERIC_LITERAL))
+ return;
+ const NumericLiteral *numeric = token.number;
+ if (numeric->isDouble() || numeric->isFloat())
+ return;
+
+ // remove trailing L or U and stuff
+ const char * const spell = numeric->chars();
+ int numberLength = numeric->size();
+ while (numberLength > 0 && !std::isxdigit(spell[numberLength - 1]))
+ --numberLength;
+ if (numberLength < 1)
+ return;
+
+ // convert to number
+ bool valid;
+ ulong value = 0;
+ const QString x = QString::fromUtf8(spell).left(numberLength);
+ if (x.startsWith("0b", Qt::CaseInsensitive))
+ value = x.mid(2).toULong(&valid, 2);
+ else
+ value = x.toULong(&valid, 0);
+
+ if (!valid)
+ return;
+
+ const int priority = path.size() - 1; // very high priority
+ const int start = file->startOf(literal);
+ const char * const str = numeric->chars();
+
+ const bool isBinary = numberLength > 2 && str[0] == '0' && (str[1] == 'b' || str[1] == 'B');
+ const bool isOctal = numberLength >= 2 && str[0] == '0' && str[1] >= '0' && str[1] <= '7';
+ const bool isDecimal = !(isBinary || isOctal || numeric->isHex());
+
+ if (!numeric->isHex()) {
+ /*
+ Convert integer literal to hex representation.
+ Replace
+ 0b100000
+ 32
+ 040
+ With
+ 0x20
+
+ */
+ const QString replacement = QString::asprintf("0x%lX", value);
+ auto op = new ConvertNumericLiteralOp(interface, start, start + numberLength, replacement);
+ op->setDescription(Tr::tr("Convert to Hexadecimal"));
+ op->setPriority(priority);
+ result << op;
+ }
+
+ if (!isOctal) {
+ /*
+ Convert integer literal to octal representation.
+ Replace
+ 0b100000
+ 32
+ 0x20
+ With
+ 040
+ */
+ const QString replacement = QString::asprintf("0%lo", value);
+ auto op = new ConvertNumericLiteralOp(interface, start, start + numberLength, replacement);
+ op->setDescription(Tr::tr("Convert to Octal"));
+ op->setPriority(priority);
+ result << op;
+ }
+
+ if (!isDecimal) {
+ /*
+ Convert integer literal to decimal representation.
+ Replace
+ 0b100000
+ 0x20
+ 040
+ With
+ 32
+ */
+ const QString replacement = QString::asprintf("%lu", value);
+ auto op = new ConvertNumericLiteralOp(interface, start, start + numberLength, replacement);
+ op->setDescription(Tr::tr("Convert to Decimal"));
+ op->setPriority(priority);
+ result << op;
+ }
+
+ if (!isBinary) {
+ /*
+ Convert integer literal to binary representation.
+ Replace
+ 32
+ 0x20
+ 040
+ With
+ 0b100000
+ */
+ QString replacement = "0b";
+ if (value == 0) {
+ replacement.append('0');
+ } else {
+ std::bitset<std::numeric_limits<decltype (value)>::digits> b(value);
+ QRegularExpression re("^[0]*");
+ replacement.append(QString::fromStdString(b.to_string()).remove(re));
+ }
+ auto op = new ConvertNumericLiteralOp(interface, start, start + numberLength, replacement);
+ op->setDescription(Tr::tr("Convert to Binary"));
+ op->setPriority(priority);
+ result << op;
+ }
+ }
+};
+
+} // namespace
+
+void registerConvertNumericLiteralQuickfix()
+{
+ CppQuickFixFactory::registerFactory<ConvertNumericLiteral>();
+}
+
+} // namespace CppEditor::Internal
diff --git a/src/plugins/cppeditor/quickfixes/convertnumericliteral.h b/src/plugins/cppeditor/quickfixes/convertnumericliteral.h
new file mode 100644
index 0000000000..4158807c68
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/convertnumericliteral.h
@@ -0,0 +1,8 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+namespace CppEditor::Internal {
+void registerConvertNumericLiteralQuickfix();
+} // namespace CppEditor::Internal
diff --git a/src/plugins/cppeditor/quickfixes/convertqt4connect.cpp b/src/plugins/cppeditor/quickfixes/convertqt4connect.cpp
new file mode 100644
index 0000000000..b9f08e9021
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/convertqt4connect.cpp
@@ -0,0 +1,505 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "convertqt4connect.h"
+
+#include "../cppcodestylesettings.h"
+#include "../cppeditortr.h"
+#include "../cpprefactoringchanges.h"
+#include "cppquickfix.h"
+
+#include <cplusplus/Overview.h>
+#include <cplusplus/TypeOfExpression.h>
+
+#ifdef WITH_TESTS
+#include "cppquickfix_test.h"
+#include <QtTest>
+#endif
+
+using namespace CPlusPlus;
+using namespace TextEditor;
+using namespace Utils;
+
+namespace CppEditor::Internal {
+namespace {
+
+class ConvertQt4ConnectOperation: public CppQuickFixOperation
+{
+public:
+ ConvertQt4ConnectOperation(const CppQuickFixInterface &interface, const ChangeSet &changes)
+ : CppQuickFixOperation(interface, 1), m_changes(changes)
+ {
+ setDescription(Tr::tr("Convert connect() to Qt 5 Style"));
+ }
+
+private:
+ void perform() override
+ {
+ currentFile()->apply(m_changes);
+ }
+
+ const ChangeSet m_changes;
+};
+
+static Symbol *skipForwardDeclarations(const QList<Symbol *> &symbols)
+{
+ for (Symbol *symbol : symbols) {
+ if (!symbol->type()->asForwardClassDeclarationType())
+ return symbol;
+ }
+
+ return nullptr;
+}
+
+static bool findRawAccessFunction(Class *klass, PointerType *pointerType, QString *objAccessFunction)
+{
+ QList<Function *> candidates;
+ for (auto it = klass->memberBegin(), end = klass->memberEnd(); it != end; ++it) {
+ if (Function *func = (*it)->asFunction()) {
+ const Name *funcName = func->name();
+ if (!funcName->asOperatorNameId()
+ && !funcName->asConversionNameId()
+ && func->returnType().type() == pointerType
+ && func->isConst()
+ && func->argumentCount() == 0) {
+ candidates << func;
+ }
+ }
+ }
+ const Name *funcName = nullptr;
+ switch (candidates.size()) {
+ case 0:
+ return false;
+ case 1:
+ funcName = candidates.first()->name();
+ break;
+ default:
+ // Multiple candidates - prefer a function named data
+ for (Function *func : std::as_const(candidates)) {
+ if (!strcmp(func->name()->identifier()->chars(), "data")) {
+ funcName = func->name();
+ break;
+ }
+ }
+ if (!funcName)
+ funcName = candidates.first()->name();
+ }
+ const Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview();
+ *objAccessFunction = QLatin1Char('.') + oo.prettyName(funcName) + QLatin1String("()");
+ return true;
+}
+
+static PointerType *determineConvertedType(
+ NamedType *namedType, const LookupContext &context, Scope *scope, QString *objAccessFunction)
+{
+ if (!namedType)
+ return nullptr;
+ if (ClassOrNamespace *binding = context.lookupType(namedType->name(), scope)) {
+ if (Symbol *objectClassSymbol = skipForwardDeclarations(binding->symbols())) {
+ if (Class *klass = objectClassSymbol->asClass()) {
+ for (auto it = klass->memberBegin(), end = klass->memberEnd(); it != end; ++it) {
+ if (Function *func = (*it)->asFunction()) {
+ if (const ConversionNameId *conversionName =
+ func->name()->asConversionNameId()) {
+ if (PointerType *type = conversionName->type()->asPointerType()) {
+ if (findRawAccessFunction(klass, type, objAccessFunction))
+ return type;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+static Class *senderOrReceiverClass(
+ const CppQuickFixInterface &interface,
+ const CppRefactoringFilePtr &file,
+ const ExpressionAST *objectPointerAST,
+ Scope *objectPointerScope,
+ QString *objAccessFunction)
+{
+ const LookupContext &context = interface.context();
+
+ QByteArray objectPointerExpression;
+ if (objectPointerAST)
+ objectPointerExpression = file->textOf(objectPointerAST).toUtf8();
+ else
+ objectPointerExpression = "this";
+
+ TypeOfExpression toe;
+ toe.setExpandTemplates(true);
+ toe.init(interface.semanticInfo().doc, interface.snapshot(), context.bindings());
+ const QList<LookupItem> objectPointerExpressions = toe(objectPointerExpression,
+ objectPointerScope, TypeOfExpression::Preprocess);
+ QTC_ASSERT(!objectPointerExpressions.isEmpty(), return nullptr);
+
+ Type *objectPointerTypeBase = objectPointerExpressions.first().type().type();
+ QTC_ASSERT(objectPointerTypeBase, return nullptr);
+
+ PointerType *objectPointerType = objectPointerTypeBase->asPointerType();
+ if (!objectPointerType) {
+ objectPointerType = determineConvertedType(objectPointerTypeBase->asNamedType(), context,
+ objectPointerScope, objAccessFunction);
+ }
+ QTC_ASSERT(objectPointerType, return nullptr);
+
+ Type *objectTypeBase = objectPointerType->elementType().type(); // Dereference
+ QTC_ASSERT(objectTypeBase, return nullptr);
+
+ NamedType *objectType = objectTypeBase->asNamedType();
+ QTC_ASSERT(objectType, return nullptr);
+
+ ClassOrNamespace *objectClassCON = context.lookupType(objectType->name(), objectPointerScope);
+ if (!objectClassCON) {
+ objectClassCON = objectPointerExpressions.first().binding();
+ QTC_ASSERT(objectClassCON, return nullptr);
+ }
+ QTC_ASSERT(!objectClassCON->symbols().isEmpty(), return nullptr);
+
+ Symbol *objectClassSymbol = skipForwardDeclarations(objectClassCON->symbols());
+ QTC_ASSERT(objectClassSymbol, return nullptr);
+
+ return objectClassSymbol->asClass();
+}
+
+static bool findConnectReplacement(
+ const CppQuickFixInterface &interface,
+ const ExpressionAST *objectPointerAST,
+ const QtMethodAST *methodAST,
+ const CppRefactoringFilePtr &file,
+ QString *replacement,
+ QString *objAccessFunction)
+{
+ // Get name of method
+ if (!methodAST->declarator || !methodAST->declarator->core_declarator)
+ return false;
+
+ DeclaratorIdAST *methodDeclIdAST = methodAST->declarator->core_declarator->asDeclaratorId();
+ if (!methodDeclIdAST)
+ return false;
+
+ NameAST *methodNameAST = methodDeclIdAST->name;
+ if (!methodNameAST)
+ return false;
+
+ // Lookup object pointer type
+ Scope *scope = file->scopeAt(methodAST->firstToken());
+ Class *objectClass = senderOrReceiverClass(interface, file, objectPointerAST, scope,
+ objAccessFunction);
+ QTC_ASSERT(objectClass, return false);
+
+ // Look up member function in call, including base class members.
+ const LookupContext &context = interface.context();
+ const QList<LookupItem> methodResults = context.lookup(methodNameAST->name, objectClass);
+ if (methodResults.isEmpty())
+ return false; // Maybe mis-spelled signal/slot name
+
+ Scope *baseClassScope = methodResults.at(0).scope(); // FIXME: Handle overloads
+ QTC_ASSERT(baseClassScope, return false);
+
+ Class *classOfMethod = baseClassScope->asClass(); // Declaration point of signal/slot
+ QTC_ASSERT(classOfMethod, return false);
+
+ Symbol *method = methodResults.at(0).declaration();
+ QTC_ASSERT(method, return false);
+
+ // Minimize qualification
+ Control *control = context.bindings()->control().get();
+ ClassOrNamespace *functionCON = context.lookupParent(scope);
+ const Name *shortName = LookupContext::minimalName(method, functionCON, control);
+ if (!shortName->asQualifiedNameId())
+ shortName = control->qualifiedNameId(classOfMethod->name(), shortName);
+
+ const Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview();
+ *replacement = QLatin1Char('&') + oo.prettyName(shortName);
+ return true;
+}
+
+static bool onConnectOrDisconnectCall(AST *ast, const ExpressionListAST **arguments)
+{
+ if (!ast)
+ return false;
+
+ CallAST *call = ast->asCall();
+ if (!call)
+ return false;
+
+ if (!call->base_expression)
+ return false;
+
+ const IdExpressionAST *idExpr = call->base_expression->asIdExpression();
+ if (!idExpr || !idExpr->name || !idExpr->name->name)
+ return false;
+
+ const ExpressionListAST *args = call->expression_list;
+ if (!arguments)
+ return false;
+
+ const Identifier *id = idExpr->name->name->identifier();
+ if (!id)
+ return false;
+
+ const QByteArray name(id->chars(), id->size());
+ if (name != "connect" && name != "disconnect")
+ return false;
+
+ if (arguments)
+ *arguments = args;
+ return true;
+}
+
+// Might modify arg* output arguments even if false is returned.
+static bool collectConnectArguments(
+ const ExpressionListAST *arguments,
+ const ExpressionAST **arg1,
+ const QtMethodAST **arg2,
+ const ExpressionAST **arg3,
+ const QtMethodAST **arg4)
+{
+ if (!arguments || !arg1 || !arg2 || !arg3 || !arg4)
+ return false;
+
+ *arg1 = arguments->value;
+ arguments = arguments->next;
+ if (!arg1 || !arguments)
+ return false;
+
+ *arg2 = arguments->value->asQtMethod();
+ arguments = arguments->next;
+ if (!*arg2 || !arguments)
+ return false;
+
+ *arg3 = arguments->value;
+ if (!*arg3)
+ return false;
+
+ // Take care of three-arg version, with 'this' receiver.
+ if (QtMethodAST *receiverMethod = arguments->value->asQtMethod()) {
+ *arg3 = nullptr; // Means 'this'
+ *arg4 = receiverMethod;
+ return true;
+ }
+
+ arguments = arguments->next;
+ if (!arguments)
+ return false;
+
+ *arg4 = arguments->value->asQtMethod();
+ if (!*arg4)
+ return false;
+
+ return true;
+}
+
+//! Converts a Qt 4 QObject::connect() to Qt 5 style.
+class ConvertQt4Connect : public CppQuickFixFactory
+{
+public:
+#ifdef WITH_TESTS
+ static QObject *createTest();
+#endif
+
+private:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ const QList<AST *> &path = interface.path();
+
+ for (int i = path.size(); --i >= 0; ) {
+ const ExpressionListAST *arguments;
+ if (!onConnectOrDisconnectCall(path.at(i), &arguments))
+ continue;
+
+ const ExpressionAST *arg1, *arg3;
+ const QtMethodAST *arg2, *arg4;
+ if (!collectConnectArguments(arguments, &arg1, &arg2, &arg3, &arg4))
+ continue;
+
+ const CppRefactoringFilePtr file = interface.currentFile();
+
+ QString newSignal;
+ QString senderAccessFunc;
+ if (!findConnectReplacement(interface, arg1, arg2, file, &newSignal, &senderAccessFunc))
+ continue;
+
+ QString newMethod;
+ QString receiverAccessFunc;
+ if (!findConnectReplacement(interface, arg3, arg4, file, &newMethod, &receiverAccessFunc))
+ continue;
+
+ ChangeSet changes;
+ changes.replace(file->endOf(arg1), file->endOf(arg1), senderAccessFunc);
+ changes.replace(file->startOf(arg2), file->endOf(arg2), newSignal);
+ if (!arg3)
+ newMethod.prepend(QLatin1String("this, "));
+ else
+ changes.replace(file->endOf(arg3), file->endOf(arg3), receiverAccessFunc);
+ changes.replace(file->startOf(arg4), file->endOf(arg4), newMethod);
+
+ result << new ConvertQt4ConnectOperation(interface, changes);
+ return;
+ }
+ }
+};
+
+#ifdef WITH_TESTS
+using namespace Tests;
+class ConvertQt4ConnectTest : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void testOutOfClass()
+ {
+ QByteArray prefix =
+ "class QObject {};\n"
+ "class TestClass : public QObject\n"
+ "{\n"
+ "public:\n"
+ " void setProp(int) {}\n"
+ " void sigFoo(int) {}\n"
+ "};\n"
+ "\n"
+ "int foo()\n"
+ "{\n";
+
+ QByteArray suffix = "\n}\n";
+
+ QByteArray original = prefix
+ + " TestClass obj;\n"
+ " conne@ct(&obj, SIGNAL(sigFoo(int)), &obj, SLOT(setProp(int)));"
+ + suffix;
+
+ QByteArray expected = prefix
+ + " TestClass obj;\n"
+ " connect(&obj, &TestClass::sigFoo, &obj, &TestClass::setProp);"
+ + suffix;
+
+ QList<TestDocumentPtr> testDocuments;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+ ConvertQt4Connect factory;
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+
+ void testWithinClass_data()
+ {
+ QTest::addColumn<QByteArray>("original");
+ QTest::addColumn<QByteArray>("expected");
+
+ QTest::newRow("four-args-connect")
+ << QByteArray("conne@ct(this, SIGNAL(sigFoo(int)), this, SLOT(setProp(int)));")
+ << QByteArray("connect(this, &TestClass::sigFoo, this, &TestClass::setProp);");
+
+ QTest::newRow("four-args-disconnect")
+ << QByteArray("disconne@ct(this, SIGNAL(sigFoo(int)), this, SLOT(setProp(int)));")
+ << QByteArray("disconnect(this, &TestClass::sigFoo, this, &TestClass::setProp);");
+
+ QTest::newRow("three-args-connect")
+ << QByteArray("conne@ct(this, SIGNAL(sigFoo(int)), SLOT(setProp(int)));")
+ << QByteArray("connect(this, &TestClass::sigFoo, this, &TestClass::setProp);");
+
+ QTest::newRow("template-value")
+ << QByteArray("Pointer<TestClass> p;\n"
+ "conne@ct(p.t, SIGNAL(sigFoo(int)), p.t, SLOT(setProp(int)));")
+ << QByteArray("Pointer<TestClass> p;\n"
+ "connect(p.t, &TestClass::sigFoo, p.t, &TestClass::setProp);");
+
+ QTest::newRow("implicit-pointer")
+ << QByteArray("Pointer<TestClass> p;\n"
+ "conne@ct(p, SIGNAL(sigFoo(int)), p, SLOT(setProp(int)));")
+ << QByteArray("Pointer<TestClass> p;\n"
+ "connect(p.data(), &TestClass::sigFoo, p.data(), &TestClass::setProp);");
+ }
+
+ void testWithinClass()
+ {
+ QFETCH(QByteArray, original);
+ QFETCH(QByteArray, expected);
+
+ QByteArray prefix =
+ "template<class T>\n"
+ "struct Pointer\n"
+ "{\n"
+ " T *t;\n"
+ " operator T*() const { return t; }\n"
+ " T *data() const { return t; }\n"
+ "};\n"
+ "class QObject {};\n"
+ "class TestClass : public QObject\n"
+ "{\n"
+ "public:\n"
+ " void setProp(int) {}\n"
+ " void sigFoo(int) {}\n"
+ " void setupSignals();\n"
+ "};\n"
+ "\n"
+ "int TestClass::setupSignals()\n"
+ "{\n";
+
+ QByteArray suffix = "\n}\n";
+
+ QList<TestDocumentPtr> testDocuments;
+ testDocuments << CppTestDocument::create("file.cpp",
+ prefix + original + suffix,
+ prefix + expected + suffix);
+
+ ConvertQt4Connect factory;
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+
+ void testDifferentNamespace()
+ {
+ const QByteArray prefix =
+ "namespace NsA {\n"
+ "class ClassA : public QObject\n"
+ "{\n"
+ " static ClassA *instance();\n"
+ "signals:\n"
+ " void sig();\n"
+ "};\n"
+ "}\n"
+ "\n"
+ "namespace NsB {\n"
+ "class ClassB : public QObject\n"
+ "{\n"
+ " void slot();\n"
+ " void connector() {\n";
+
+ const QByteArray suffix = " }\n};\n}";
+
+ const QByteArray original = "co@nnect(NsA::ClassA::instance(), SIGNAL(sig()),\n"
+ " this, SLOT(slot()));\n";
+ const QByteArray expected = "connect(NsA::ClassA::instance(), &NsA::ClassA::sig,\n"
+ " this, &ClassB::slot);\n";
+ QList<TestDocumentPtr> testDocuments;
+ testDocuments << CppTestDocument::create("file.cpp",
+ prefix + original + suffix,
+ prefix + expected + suffix);
+
+ ConvertQt4Connect factory;
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+};
+
+QObject *ConvertQt4Connect::createTest()
+{
+ return new ConvertQt4ConnectTest;
+}
+#endif // WITH_TESTS
+
+} // namespace
+
+void registerConvertQt4ConnectQuickfix()
+{
+ CppQuickFixFactory::registerFactory<ConvertQt4Connect>();
+}
+
+} // namespace CppEditor::Internal
+
+#ifdef WITH_TESTS
+#include <convertqt4connect.moc>
+#endif
diff --git a/src/plugins/cppeditor/quickfixes/convertqt4connect.h b/src/plugins/cppeditor/quickfixes/convertqt4connect.h
new file mode 100644
index 0000000000..e96bd8f106
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/convertqt4connect.h
@@ -0,0 +1,8 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+namespace CppEditor::Internal {
+void registerConvertQt4ConnectQuickfix();
+} // namespace CppEditor::Internal
diff --git a/src/plugins/cppeditor/quickfixes/convertstringliteral.cpp b/src/plugins/cppeditor/quickfixes/convertstringliteral.cpp
new file mode 100644
index 0000000000..eff4b3a9ae
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/convertstringliteral.cpp
@@ -0,0 +1,746 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "convertstringliteral.h"
+
+#include "../cppeditordocument.h"
+#include "../cppeditortr.h"
+#include "../cppeditorwidget.h"
+#include "../cpprefactoringchanges.h"
+#include "cppquickfix.h"
+
+#include <QTextDecoder>
+
+#ifdef WITH_TESTS
+#include "cppquickfix_test.h"
+#include <QtTest>
+#endif
+
+using namespace CPlusPlus;
+using namespace Utils;
+
+namespace CppEditor::Internal {
+namespace {
+
+enum StringLiteralType { TypeString, TypeObjCString, TypeChar, TypeNone };
+
+enum ActionFlags {
+ EncloseInQLatin1CharAction = 0x1,
+ EncloseInQLatin1StringAction = 0x2,
+ EncloseInQStringLiteralAction = 0x4,
+ EncloseInQByteArrayLiteralAction = 0x8,
+ EncloseActionMask = EncloseInQLatin1CharAction | EncloseInQLatin1StringAction
+ | EncloseInQStringLiteralAction | EncloseInQByteArrayLiteralAction,
+ TranslateTrAction = 0x10,
+ TranslateQCoreApplicationAction = 0x20,
+ TranslateNoopAction = 0x40,
+ TranslationMask = TranslateTrAction | TranslateQCoreApplicationAction | TranslateNoopAction,
+ RemoveObjectiveCAction = 0x100,
+ ConvertEscapeSequencesToCharAction = 0x200,
+ ConvertEscapeSequencesToStringAction = 0x400,
+ SingleQuoteAction = 0x800,
+ DoubleQuoteAction = 0x1000
+};
+
+static bool isQtStringLiteral(const QByteArray &id)
+{
+ return id == "QLatin1String" || id == "QLatin1Literal" || id == "QStringLiteral"
+ || id == "QByteArrayLiteral";
+}
+
+static bool isQtStringTranslation(const QByteArray &id)
+{
+ return id == "tr" || id == "trUtf8" || id == "translate" || id == "QT_TRANSLATE_NOOP";
+}
+
+/* Convert single-character string literals into character literals with some
+ * special cases "a" --> 'a', "'" --> '\'', "\n" --> '\n', "\"" --> '"'. */
+static QByteArray stringToCharEscapeSequences(const QByteArray &content)
+{
+ if (content.size() == 1)
+ return content.at(0) == '\'' ? QByteArray("\\'") : content;
+ if (content.size() == 2 && content.at(0) == '\\')
+ return content == "\\\"" ? QByteArray(1, '"') : content;
+ return QByteArray();
+}
+
+/* Convert character literal into a string literal with some special cases
+ * 'a' -> "a", '\n' -> "\n", '\'' --> "'", '"' --> "\"". */
+static QByteArray charToStringEscapeSequences(const QByteArray &content)
+{
+ if (content.size() == 1)
+ return content.at(0) == '"' ? QByteArray("\\\"") : content;
+ if (content.size() == 2)
+ return content == "\\'" ? QByteArray("'") : content;
+ return QByteArray();
+}
+
+static QString msgQtStringLiteralDescription(const QString &replacement)
+{
+ return Tr::tr("Enclose in %1(...)").arg(replacement);
+}
+
+static QString stringLiteralReplacement(unsigned actions)
+{
+ if (actions & EncloseInQLatin1CharAction)
+ return QLatin1String("QLatin1Char");
+ if (actions & EncloseInQLatin1StringAction)
+ return QLatin1String("QLatin1String");
+ if (actions & EncloseInQStringLiteralAction)
+ return QLatin1String("QStringLiteral");
+ if (actions & EncloseInQByteArrayLiteralAction)
+ return QLatin1String("QByteArrayLiteral");
+ if (actions & TranslateTrAction)
+ return QLatin1String("tr");
+ if (actions & TranslateQCoreApplicationAction)
+ return QLatin1String("QCoreApplication::translate");
+ if (actions & TranslateNoopAction)
+ return QLatin1String("QT_TRANSLATE_NOOP");
+ return QString();
+}
+
+static ExpressionAST *analyzeStringLiteral(const QList<AST *> &path,
+ const CppRefactoringFilePtr &file, StringLiteralType *type,
+ QByteArray *enclosingFunction = nullptr,
+ CallAST **enclosingFunctionCall = nullptr)
+{
+ *type = TypeNone;
+ if (enclosingFunction)
+ enclosingFunction->clear();
+ if (enclosingFunctionCall)
+ *enclosingFunctionCall = nullptr;
+
+ if (path.isEmpty())
+ return nullptr;
+
+ ExpressionAST *literal = path.last()->asExpression();
+ if (literal) {
+ if (literal->asStringLiteral()) {
+ // Check for Objective C string (@"bla")
+ const QChar firstChar = file->charAt(file->startOf(literal));
+ *type = firstChar == QLatin1Char('@') ? TypeObjCString : TypeString;
+ } else if (NumericLiteralAST *numericLiteral = literal->asNumericLiteral()) {
+ // character ('c') constants are numeric.
+ if (file->tokenAt(numericLiteral->literal_token).is(T_CHAR_LITERAL))
+ *type = TypeChar;
+ }
+ }
+
+ if (*type != TypeNone && enclosingFunction && path.size() > 1) {
+ if (CallAST *call = path.at(path.size() - 2)->asCall()) {
+ if (call->base_expression) {
+ if (IdExpressionAST *idExpr = call->base_expression->asIdExpression()) {
+ if (SimpleNameAST *functionName = idExpr->name->asSimpleName()) {
+ *enclosingFunction = file->tokenAt(functionName->identifier_token).identifier->chars();
+ if (enclosingFunctionCall)
+ *enclosingFunctionCall = call;
+ }
+ }
+ }
+ }
+ }
+ return literal;
+}
+
+class EscapeStringLiteralOperation: public CppQuickFixOperation
+{
+public:
+ EscapeStringLiteralOperation(const CppQuickFixInterface &interface,
+ ExpressionAST *literal, bool escape)
+ : CppQuickFixOperation(interface)
+ , m_literal(literal)
+ , m_escape(escape)
+ {
+ if (m_escape) {
+ setDescription(Tr::tr("Escape String Literal as UTF-8"));
+ } else {
+ setDescription(Tr::tr("Unescape String Literal as UTF-8"));
+ }
+ }
+
+private:
+ static inline bool isDigit(quint8 ch, int base)
+ {
+ if (base == 8)
+ return ch >= '0' && ch < '8';
+ if (base == 16)
+ return isxdigit(ch);
+ return false;
+ }
+
+ static QByteArrayList escapeString(const QByteArray &contents)
+ {
+ QByteArrayList newContents;
+ QByteArray chunk;
+ bool wasEscaped = false;
+ for (const quint8 c : contents) {
+ const bool needsEscape = !isascii(c) || !isprint(c);
+ if (!needsEscape && wasEscaped && std::isxdigit(c) && !chunk.isEmpty()) {
+ newContents << chunk;
+ chunk.clear();
+ }
+ if (needsEscape)
+ chunk += QByteArray("\\x") + QByteArray::number(c, 16).rightJustified(2, '0');
+ else
+ chunk += c;
+ wasEscaped = needsEscape;
+ }
+ if (!chunk.isEmpty())
+ newContents << chunk;
+ return newContents;
+ }
+
+ static QByteArray unescapeString(const QByteArray &contents)
+ {
+ QByteArray newContents;
+ const int len = contents.length();
+ for (int i = 0; i < len; ++i) {
+ quint8 c = contents.at(i);
+ if (c == '\\' && i < len - 1) {
+ int idx = i + 1;
+ quint8 ch = contents.at(idx);
+ int base = 0;
+ int maxlen = 0;
+ if (isDigit(ch, 8)) {
+ base = 8;
+ maxlen = 3;
+ } else if ((ch == 'x' || ch == 'X') && idx < len - 1) {
+ base = 16;
+ maxlen = 2;
+ ch = contents.at(++idx);
+ }
+ if (base > 0) {
+ QByteArray buf;
+ while (isDigit(ch, base) && idx < len && buf.length() < maxlen) {
+ buf += ch;
+ ++idx;
+ if (idx == len)
+ break;
+ ch = contents.at(idx);
+ }
+ if (!buf.isEmpty()) {
+ bool ok;
+ uint value = buf.toUInt(&ok, base);
+ // Don't unescape isascii() && !isprint()
+ if (ok && (!isascii(value) || isprint(value))) {
+ newContents += value;
+ i = idx - 1;
+ continue;
+ }
+ }
+ }
+ newContents += c;
+ c = contents.at(++i);
+ }
+ newContents += c;
+ }
+ return newContents;
+ }
+
+ // QuickFixOperation interface
+public:
+ void perform() override
+ {
+ const int startPos = currentFile()->startOf(m_literal);
+ const int endPos = currentFile()->endOf(m_literal);
+
+ StringLiteralAST *stringLiteral = m_literal->asStringLiteral();
+ QTC_ASSERT(stringLiteral, return);
+ const QByteArray oldContents(currentFile()->tokenAt(stringLiteral->literal_token).
+ identifier->chars());
+ QByteArrayList newContents;
+ if (m_escape)
+ newContents = escapeString(oldContents);
+ else
+ newContents = {unescapeString(oldContents)};
+
+ if (newContents.isEmpty()
+ || (newContents.size() == 1 && newContents.first() == oldContents)) {
+ return;
+ }
+
+ QTextCodec *utf8codec = QTextCodec::codecForName("UTF-8");
+ QScopedPointer<QTextDecoder> decoder(utf8codec->makeDecoder());
+ ChangeSet changes;
+
+ bool replace = true;
+ for (const QByteArray &chunk : std::as_const(newContents)) {
+ const QString str = decoder->toUnicode(chunk);
+ const QByteArray utf8buf = str.toUtf8();
+ if (!utf8codec->canEncode(str) || chunk != utf8buf)
+ return;
+ if (replace)
+ changes.replace(startPos + 1, endPos - 1, str);
+ else
+ changes.insert(endPos, "\"" + str + "\"");
+ replace = false;
+ }
+ currentFile()->apply(changes);
+ }
+
+private:
+ ExpressionAST *m_literal;
+ bool m_escape;
+};
+
+/// Operation performs the operations of type ActionFlags passed in as actions.
+class WrapStringLiteralOp : public CppQuickFixOperation
+{
+public:
+ WrapStringLiteralOp(const CppQuickFixInterface &interface, int priority,
+ unsigned actions, const QString &description, ExpressionAST *literal,
+ const QString &translationContext = QString())
+ : CppQuickFixOperation(interface, priority), m_actions(actions), m_literal(literal),
+ m_translationContext(translationContext)
+ {
+ setDescription(description);
+ }
+
+ void perform() override
+ {
+ ChangeSet changes;
+
+ const int startPos = currentFile()->startOf(m_literal);
+ const int endPos = currentFile()->endOf(m_literal);
+
+ // kill leading '@'. No need to adapt endPos, that is done by ChangeSet
+ if (m_actions & RemoveObjectiveCAction)
+ changes.remove(startPos, startPos + 1);
+
+ // Fix quotes
+ if (m_actions & (SingleQuoteAction | DoubleQuoteAction)) {
+ const QString newQuote((m_actions & SingleQuoteAction)
+ ? QLatin1Char('\'') : QLatin1Char('"'));
+ changes.replace(startPos, startPos + 1, newQuote);
+ changes.replace(endPos - 1, endPos, newQuote);
+ }
+
+ // Convert single character strings into character constants
+ if (m_actions & ConvertEscapeSequencesToCharAction) {
+ StringLiteralAST *stringLiteral = m_literal->asStringLiteral();
+ QTC_ASSERT(stringLiteral, return ;);
+ const QByteArray oldContents(currentFile()->tokenAt(stringLiteral->literal_token).identifier->chars());
+ const QByteArray newContents = stringToCharEscapeSequences(oldContents);
+ QTC_ASSERT(!newContents.isEmpty(), return ;);
+ if (oldContents != newContents)
+ changes.replace(startPos + 1, endPos -1, QString::fromLatin1(newContents));
+ }
+
+ // Convert character constants into strings constants
+ if (m_actions & ConvertEscapeSequencesToStringAction) {
+ NumericLiteralAST *charLiteral = m_literal->asNumericLiteral(); // char 'c' constants are numerical.
+ QTC_ASSERT(charLiteral, return ;);
+ const QByteArray oldContents(currentFile()->tokenAt(charLiteral->literal_token).identifier->chars());
+ const QByteArray newContents = charToStringEscapeSequences(oldContents);
+ QTC_ASSERT(!newContents.isEmpty(), return ;);
+ if (oldContents != newContents)
+ changes.replace(startPos + 1, endPos -1, QString::fromLatin1(newContents));
+ }
+
+ // Enclose in literal or translation function, macro.
+ if (m_actions & (EncloseActionMask | TranslationMask)) {
+ changes.insert(endPos, QString(QLatin1Char(')')));
+ QString leading = stringLiteralReplacement(m_actions);
+ leading += QLatin1Char('(');
+ if (m_actions
+ & (TranslateQCoreApplicationAction | TranslateNoopAction)) {
+ leading += QLatin1Char('"');
+ leading += m_translationContext;
+ leading += QLatin1String("\", ");
+ }
+ changes.insert(startPos, leading);
+ }
+
+ currentFile()->apply(changes);
+ }
+
+private:
+ const unsigned m_actions;
+ ExpressionAST *m_literal;
+ const QString m_translationContext;
+};
+
+class ConvertCStringToNSStringOp: public CppQuickFixOperation
+{
+public:
+ ConvertCStringToNSStringOp(const CppQuickFixInterface &interface, int priority,
+ StringLiteralAST *stringLiteral, CallAST *qlatin1Call)
+ : CppQuickFixOperation(interface, priority)
+ , stringLiteral(stringLiteral)
+ , qlatin1Call(qlatin1Call)
+ {
+ setDescription(Tr::tr("Convert to Objective-C String Literal"));
+ }
+
+ void perform() override
+ {
+ ChangeSet changes;
+
+ if (qlatin1Call) {
+ changes.replace(currentFile()->startOf(qlatin1Call), currentFile()->startOf(stringLiteral),
+ QLatin1String("@"));
+ changes.remove(currentFile()->endOf(stringLiteral), currentFile()->endOf(qlatin1Call));
+ } else {
+ changes.insert(currentFile()->startOf(stringLiteral), QLatin1String("@"));
+ }
+
+ currentFile()->apply(changes);
+ }
+
+private:
+ StringLiteralAST *stringLiteral;
+ CallAST *qlatin1Call;
+};
+
+/*!
+ Replace
+ "abcd"
+ QLatin1String("abcd")
+ QLatin1Literal("abcd")
+
+ With
+ @"abcd"
+
+ Activates on: the string literal, if the file type is a Objective-C(++) file.
+*/
+class ConvertCStringToNSString: public CppQuickFixFactory
+{
+#ifdef WITH_TESTS
+public:
+ static QObject *createTest();
+#endif
+
+private:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ CppRefactoringFilePtr file = interface.currentFile();
+
+ if (!interface.editor()->cppEditorDocument()->isObjCEnabled())
+ return;
+
+ StringLiteralType type = TypeNone;
+ QByteArray enclosingFunction;
+ CallAST *qlatin1Call;
+ const QList<AST *> &path = interface.path();
+ ExpressionAST *literal = analyzeStringLiteral(path, file, &type, &enclosingFunction,
+ &qlatin1Call);
+ if (!literal || type != TypeString)
+ return;
+ if (!isQtStringLiteral(enclosingFunction))
+ qlatin1Call = nullptr;
+
+ result << new ConvertCStringToNSStringOp(interface, path.size() - 1, literal->asStringLiteral(),
+ qlatin1Call);
+ }
+};
+
+/*!
+ Replace
+ "abcd"
+
+ With
+ tr("abcd") or
+ QCoreApplication::translate("CONTEXT", "abcd") or
+ QT_TRANSLATE_NOOP("GLOBAL", "abcd")
+
+ depending on what is available.
+
+ Activates on: the string literal
+*/
+class TranslateStringLiteral: public CppQuickFixFactory
+{
+#ifdef WITH_TESTS
+public:
+ static QObject *createTest();
+#endif
+
+private:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ // Initialize
+ StringLiteralType type = TypeNone;
+ QByteArray enclosingFunction;
+ const QList<AST *> &path = interface.path();
+ CppRefactoringFilePtr file = interface.currentFile();
+ ExpressionAST *literal = analyzeStringLiteral(path, file, &type, &enclosingFunction);
+ if (!literal || type != TypeString
+ || isQtStringLiteral(enclosingFunction) || isQtStringTranslation(enclosingFunction))
+ return;
+
+ QString trContext;
+
+ std::shared_ptr<Control> control = interface.context().bindings()->control();
+ const Name *trName = control->identifier("tr");
+
+ // Check whether we are in a function:
+ const QString description = Tr::tr("Mark as Translatable");
+ for (int i = path.size() - 1; i >= 0; --i) {
+ if (FunctionDefinitionAST *definition = path.at(i)->asFunctionDefinition()) {
+ Function *function = definition->symbol;
+ ClassOrNamespace *b = interface.context().lookupType(function);
+ if (b) {
+ // Do we have a tr function?
+ const QList<LookupItem> items = b->find(trName);
+ for (const LookupItem &r : items) {
+ Symbol *s = r.declaration();
+ if (s->type()->asFunctionType()) {
+ // no context required for tr
+ result << new WrapStringLiteralOp(interface, path.size() - 1,
+ TranslateTrAction,
+ description, literal);
+ return;
+ }
+ }
+ }
+ // We need to do a QCA::translate, so we need a context.
+ // Use fully qualified class name:
+ Overview oo;
+ const QList<const Name *> names = LookupContext::path(function);
+ for (const Name *n : names) {
+ if (!trContext.isEmpty())
+ trContext.append(QLatin1String("::"));
+ trContext.append(oo.prettyName(n));
+ }
+ // ... or global if none available!
+ if (trContext.isEmpty())
+ trContext = QLatin1String("GLOBAL");
+ result << new WrapStringLiteralOp(interface, path.size() - 1,
+ TranslateQCoreApplicationAction,
+ description, literal, trContext);
+ return;
+ }
+ }
+
+ // We need to use Q_TRANSLATE_NOOP
+ result << new WrapStringLiteralOp(interface, path.size() - 1,
+ TranslateNoopAction,
+ description, literal, trContext);
+ }
+};
+
+/*!
+ Replace
+ "abcd" -> QLatin1String("abcd")
+ @"abcd" -> QLatin1String("abcd") (Objective C)
+ 'a' -> QLatin1Char('a')
+ 'a' -> "a"
+ "a" -> 'a' or QLatin1Char('a') (Single character string constants)
+ "\n" -> '\n', QLatin1Char('\n')
+
+ Except if they are already enclosed in
+ QLatin1Char, QT_TRANSLATE_NOOP, tr,
+ trUtf8, QLatin1Literal, QLatin1String
+
+ Activates on: the string or character literal
+*/
+
+class WrapStringLiteral: public CppQuickFixFactory
+{
+#ifdef WITH_TESTS
+public:
+ static QObject *createTest();
+#endif
+
+private:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ StringLiteralType type = TypeNone;
+ QByteArray enclosingFunction;
+ const QList<AST *> &path = interface.path();
+ CppRefactoringFilePtr file = interface.currentFile();
+ ExpressionAST *literal = analyzeStringLiteral(path, file, &type, &enclosingFunction);
+ if (!literal || type == TypeNone)
+ return;
+ if ((type == TypeChar && enclosingFunction == "QLatin1Char")
+ || isQtStringLiteral(enclosingFunction)
+ || isQtStringTranslation(enclosingFunction))
+ return;
+
+ const int priority = path.size() - 1; // very high priority
+ if (type == TypeChar) {
+ unsigned actions = EncloseInQLatin1CharAction;
+ QString description = msgQtStringLiteralDescription(stringLiteralReplacement(actions));
+ result << new WrapStringLiteralOp(interface, priority, actions, description, literal);
+ if (NumericLiteralAST *charLiteral = literal->asNumericLiteral()) {
+ const QByteArray contents(file->tokenAt(charLiteral->literal_token).identifier->chars());
+ if (!charToStringEscapeSequences(contents).isEmpty()) {
+ actions = DoubleQuoteAction | ConvertEscapeSequencesToStringAction;
+ description = Tr::tr("Convert to String Literal");
+ result << new WrapStringLiteralOp(interface, priority, actions,
+ description, literal);
+ }
+ }
+ } else {
+ const unsigned objectiveCActions = type == TypeObjCString ?
+ unsigned(RemoveObjectiveCAction) : 0u;
+ unsigned actions = 0;
+ if (StringLiteralAST *stringLiteral = literal->asStringLiteral()) {
+ const QByteArray contents(file->tokenAt(stringLiteral->literal_token).identifier->chars());
+ if (!stringToCharEscapeSequences(contents).isEmpty()) {
+ actions = EncloseInQLatin1CharAction | SingleQuoteAction
+ | ConvertEscapeSequencesToCharAction | objectiveCActions;
+ QString description =
+ Tr::tr("Convert to Character Literal and Enclose in QLatin1Char(...)");
+ result << new WrapStringLiteralOp(interface, priority, actions,
+ description, literal);
+ actions &= ~EncloseInQLatin1CharAction;
+ description = Tr::tr("Convert to Character Literal");
+ result << new WrapStringLiteralOp(interface, priority, actions,
+ description, literal);
+ }
+ }
+ actions = EncloseInQLatin1StringAction | objectiveCActions;
+ result << new WrapStringLiteralOp(interface, priority, actions,
+ msgQtStringLiteralDescription(stringLiteralReplacement(actions)), literal);
+ actions = EncloseInQStringLiteralAction | objectiveCActions;
+ result << new WrapStringLiteralOp(interface, priority, actions,
+ msgQtStringLiteralDescription(stringLiteralReplacement(actions)), literal);
+ actions = EncloseInQByteArrayLiteralAction | objectiveCActions;
+ result << new WrapStringLiteralOp(interface, priority, actions,
+ msgQtStringLiteralDescription(stringLiteralReplacement(actions)), literal);
+ }
+ }
+};
+
+/*!
+ Escapes or unescapes a string literal as UTF-8.
+
+ Escapes non-ASCII characters in a string literal to hexadecimal escape sequences.
+ Unescapes octal or hexadecimal escape sequences in a string literal.
+ String literals are handled as UTF-8 even if file's encoding is not UTF-8.
+ */
+class EscapeStringLiteral : public CppQuickFixFactory
+{
+#ifdef WITH_TESTS
+public:
+ static QObject *createTest();
+#endif
+
+private:
+ void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override
+ {
+ const QList<AST *> &path = interface.path();
+ if (path.isEmpty())
+ return;
+
+ AST * const lastAst = path.last();
+ ExpressionAST *literal = lastAst->asStringLiteral();
+ if (!literal)
+ return;
+
+ StringLiteralAST *stringLiteral = literal->asStringLiteral();
+ CppRefactoringFilePtr file = interface.currentFile();
+ const QByteArray contents(file->tokenAt(stringLiteral->literal_token).identifier->chars());
+
+ bool canEscape = false;
+ bool canUnescape = false;
+ for (int i = 0; i < contents.length(); ++i) {
+ quint8 c = contents.at(i);
+ if (!isascii(c) || !isprint(c)) {
+ canEscape = true;
+ } else if (c == '\\' && i < contents.length() - 1) {
+ c = contents.at(++i);
+ if ((c >= '0' && c < '8') || c == 'x' || c == 'X')
+ canUnescape = true;
+ }
+ }
+
+ if (canEscape)
+ result << new EscapeStringLiteralOperation(interface, literal, true);
+
+ if (canUnescape)
+ result << new EscapeStringLiteralOperation(interface, literal, false);
+ }
+};
+
+#ifdef WITH_TESTS
+using namespace Tests;
+
+class EscapeStringLiteralTest : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void test_data()
+ {
+ QTest::addColumn<QByteArray>("original");
+ QTest::addColumn<QByteArray>("expected");
+
+ // Escape String Literal as UTF-8 (no-trigger)
+ QTest::newRow("EscapeStringLiteral_notrigger")
+ << QByteArray("const char *notrigger = \"@abcdef \\a\\n\\\\\";\n")
+ << QByteArray();
+
+ // Escape String Literal as UTF-8
+ QTest::newRow("EscapeStringLiteral")
+ << QByteArray("const char *utf8 = \"@\xe3\x81\x82\xe3\x81\x84\";\n")
+ << QByteArray("const char *utf8 = \"\\xe3\\x81\\x82\\xe3\\x81\\x84\";\n");
+
+ // Unescape String Literal as UTF-8 (from hexdecimal escape sequences)
+ QTest::newRow("UnescapeStringLiteral_hex")
+ << QByteArray("const char *hex_escaped = \"@\\xe3\\x81\\x82\\xe3\\x81\\x84\";\n")
+ << QByteArray("const char *hex_escaped = \"\xe3\x81\x82\xe3\x81\x84\";\n");
+
+ // Unescape String Literal as UTF-8 (from octal escape sequences)
+ QTest::newRow("UnescapeStringLiteral_oct")
+ << QByteArray("const char *oct_escaped = \"@\\343\\201\\202\\343\\201\\204\";\n")
+ << QByteArray("const char *oct_escaped = \"\xe3\x81\x82\xe3\x81\x84\";\n");
+
+ // Unescape String Literal as UTF-8 (triggered but no change)
+ QTest::newRow("UnescapeStringLiteral_noconv")
+ << QByteArray("const char *escaped_ascii = \"@\\x1b\";\n")
+ << QByteArray("const char *escaped_ascii = \"\\x1b\";\n");
+
+ // Unescape String Literal as UTF-8 (no conversion because of invalid utf-8)
+ QTest::newRow("UnescapeStringLiteral_invalid")
+ << QByteArray("const char *escaped = \"@\\xe3\\x81\";\n")
+ << QByteArray("const char *escaped = \"\\xe3\\x81\";\n");
+
+ QTest::newRow("escape string literal: simple case")
+ << QByteArray(R"(const char *str = @"àxyz";)")
+ << QByteArray(R"(const char *str = "\xc3\xa0xyz";)");
+ QTest::newRow("escape string literal: simple case reverse")
+ << QByteArray(R"(const char *str = @"\xc3\xa0xyz";)")
+ << QByteArray(R"(const char *str = "àxyz";)");
+ QTest::newRow("escape string literal: raw string literal")
+ << QByteArray(R"x(const char *str = @R"(àxyz)";)x")
+ << QByteArray(R"x(const char *str = R"(\xc3\xa0xyz)";)x");
+ QTest::newRow("escape string literal: splitting required")
+ << QByteArray(R"(const char *str = @"àf23бgб1";)")
+ << QByteArray(R"(const char *str = "\xc3\xa0""f23\xd0\xb1g\xd0\xb1""1";)");
+ QTest::newRow("escape string literal: unescape adjacent literals")
+ << QByteArray(R"(const char *str = @"\xc3\xa0""f23\xd0\xb1g\xd0\xb1""1";)")
+ << QByteArray(R"(const char *str = "àf23бgб1";)");
+ }
+
+ void test()
+ {
+ QFETCH(QByteArray, original);
+ QFETCH(QByteArray, expected);
+
+ EscapeStringLiteral factory;
+ QuickFixOperationTest(singleDocument(original, expected), &factory);
+ }
+};
+
+QObject *EscapeStringLiteral::createTest() { return new EscapeStringLiteralTest; }
+QObject *ConvertCStringToNSString::createTest() { return new QObject; }
+QObject *WrapStringLiteral::createTest() { return new QObject; }
+QObject *TranslateStringLiteral::createTest() { return new QObject; }
+
+#endif // WITH_TESTS
+} // namespace
+
+void registerConvertStringLiteralQuickfixes()
+{
+ CppQuickFixFactory::registerFactory<ConvertCStringToNSString>();
+ CppQuickFixFactory::registerFactory<EscapeStringLiteral>();
+ CppQuickFixFactory::registerFactory<TranslateStringLiteral>();
+ CppQuickFixFactory::registerFactory<WrapStringLiteral>();
+}
+
+} // namespace CppEditor::Internal
+
+#ifdef WITH_TESTS
+#include <convertstringliteral.moc>
+#endif
diff --git a/src/plugins/cppeditor/quickfixes/convertstringliteral.h b/src/plugins/cppeditor/quickfixes/convertstringliteral.h
new file mode 100644
index 0000000000..16cefacae0
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/convertstringliteral.h
@@ -0,0 +1,8 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+namespace CppEditor::Internal {
+void registerConvertStringLiteralQuickfixes();
+} // namespace CppEditor::Internal
diff --git a/src/plugins/cppeditor/quickfixes/converttocamelcase.cpp b/src/plugins/cppeditor/quickfixes/converttocamelcase.cpp
new file mode 100644
index 0000000000..c597a29c11
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/converttocamelcase.cpp
@@ -0,0 +1,184 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "converttocamelcase.h"
+
+#include "../cppeditortr.h"
+#include "../cppeditorwidget.h"
+#include "../cpprefactoringchanges.h"
+#include "cppquickfix.h"
+
+#ifdef WITH_TESTS
+#include "cppquickfix_test.h"
+#include <QtTest>
+#endif
+
+using namespace CPlusPlus;
+using namespace Utils;
+
+namespace CppEditor::Internal {
+namespace {
+
+class ConvertToCamelCaseOp: public CppQuickFixOperation
+{
+public:
+ ConvertToCamelCaseOp(const CppQuickFixInterface &interface, const QString &name,
+ const AST *nameAst, bool test)
+ : CppQuickFixOperation(interface, -1)
+ , m_name(name)
+ , m_nameAst(nameAst)
+ , m_isAllUpper(name.isUpper())
+ , m_test(test)
+ {
+ setDescription(Tr::tr("Convert to Camel Case"));
+ }
+
+ static bool isConvertibleUnderscore(const QString &name, int pos)
+ {
+ return name.at(pos) == QLatin1Char('_') && name.at(pos+1).isLetter()
+ && !(pos == 1 && name.at(0) == QLatin1Char('m'));
+ }
+
+private:
+ void perform() override
+ {
+ QString newName = m_isAllUpper ? m_name.toLower() : m_name;
+ for (int i = 1; i < newName.length(); ++i) {
+ const QChar c = newName.at(i);
+ if (c.isUpper() && m_isAllUpper) {
+ newName[i] = c.toLower();
+ } else if (i < newName.length() - 1 && isConvertibleUnderscore(newName, i)) {
+ newName.remove(i, 1);
+ newName[i] = newName.at(i).toUpper();
+ }
+ }
+ if (m_test)
+ currentFile()->apply(ChangeSet::makeReplace(currentFile()->range(m_nameAst), newName));
+ else
+ editor()->renameUsages(newName);
+ }
+
+ const QString m_name;
+ const AST * const m_nameAst;
+ const bool m_isAllUpper;
+ const bool m_test;
+};
+
+/*!
+ Turns "an_example_symbol" into "anExampleSymbol" and
+ "AN_EXAMPLE_SYMBOL" into "AnExampleSymbol".
+
+ Activates on: identifiers
+*/
+class ConvertToCamelCase : public CppQuickFixFactory
+{
+public:
+ ConvertToCamelCase(bool test = false) : m_test(test) {}
+
+#ifdef WITH_TESTS
+ static QObject *createTest();
+#endif
+
+private:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ const QList<AST *> &path = interface.path();
+
+ if (path.isEmpty())
+ return;
+
+ AST * const ast = path.last();
+ const Name *name = nullptr;
+ const AST *astForName = nullptr;
+ if (const NameAST * const nameAst = ast->asName()) {
+ if (nameAst->name && nameAst->name->asNameId()) {
+ astForName = nameAst;
+ name = nameAst->name;
+ }
+ } else if (const NamespaceAST * const namespaceAst = ast->asNamespace()) {
+ astForName = namespaceAst;
+ name = namespaceAst->symbol->name();
+ }
+
+ if (!name)
+ return;
+
+ QString nameString = QString::fromUtf8(name->identifier()->chars());
+ if (nameString.length() < 3)
+ return;
+ for (int i = 1; i < nameString.length() - 1; ++i) {
+ if (ConvertToCamelCaseOp::isConvertibleUnderscore(nameString, i)) {
+ result << new ConvertToCamelCaseOp(interface, nameString, astForName, m_test);
+ return;
+ }
+ }
+ }
+
+ const bool m_test;
+};
+
+#ifdef WITH_TESTS
+using namespace Tests;
+
+class ConvertToCamelCaseTest : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void test_data()
+ {
+ QTest::addColumn<QByteArray>("original");
+ QTest::addColumn<QByteArray>("expected");
+
+ using QByteArray = QByteArray;
+
+ QTest::newRow("convert to camel case: normal")
+ << QByteArray("void @lower_case_function();\n")
+ << QByteArray("void lowerCaseFunction();\n");
+ QTest::newRow("convert to camel case: already camel case")
+ << QByteArray("void @camelCaseFunction();\n")
+ << QByteArray();
+ QTest::newRow("convert to camel case: no underscores (lower case)")
+ << QByteArray("void @lowercasefunction();\n")
+ << QByteArray();
+ QTest::newRow("convert to camel case: no underscores (upper case)")
+ << QByteArray("void @UPPERCASEFUNCTION();\n")
+ << QByteArray();
+ QTest::newRow("convert to camel case: non-applicable underscore")
+ << QByteArray("void @m_a_member;\n")
+ << QByteArray("void m_aMember;\n");
+ QTest::newRow("convert to camel case: upper case")
+ << QByteArray("void @UPPER_CASE_FUNCTION();\n")
+ << QByteArray("void upperCaseFunction();\n");
+ QTest::newRow("convert to camel case: partially camel case already")
+ << QByteArray("void mixed@_andCamelCase();\n")
+ << QByteArray("void mixedAndCamelCase();\n");
+ QTest::newRow("convert to camel case: wild mix")
+ << QByteArray("void @WhAt_TODO_hErE();\n")
+ << QByteArray("void WhAtTODOHErE();\n");
+ }
+
+ void test()
+ {
+ QFETCH(QByteArray, original);
+ QFETCH(QByteArray, expected);
+ ConvertToCamelCase factory(true);
+ QuickFixOperationTest(singleDocument(original, expected), &factory);
+ }
+};
+
+QObject *ConvertToCamelCase::createTest() { return new ConvertToCamelCaseTest; }
+
+#endif // WITH_TESTS
+} // namespace
+
+void registerConvertToCamelCaseQuickfix()
+{
+ CppQuickFixFactory::registerFactory<ConvertToCamelCase>();
+}
+
+} // namespace CppEditor::Internal
+
+#ifdef WITH_TESTS
+#include <converttocamelcase.moc>
+#endif
diff --git a/src/plugins/cppeditor/quickfixes/converttocamelcase.h b/src/plugins/cppeditor/quickfixes/converttocamelcase.h
new file mode 100644
index 0000000000..2d7acb08f7
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/converttocamelcase.h
@@ -0,0 +1,8 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+namespace CppEditor::Internal {
+void registerConvertToCamelCaseQuickfix();
+} // namespace CppEditor::Internal
diff --git a/src/plugins/cppeditor/quickfixes/converttometamethodcall.cpp b/src/plugins/cppeditor/quickfixes/converttometamethodcall.cpp
new file mode 100644
index 0000000000..9d94808fbf
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/converttometamethodcall.cpp
@@ -0,0 +1,273 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "converttometamethodcall.h"
+
+#include "../cppeditortr.h"
+#include "../cpprefactoringchanges.h"
+#include "cppquickfix.h"
+#include "cppquickfixhelpers.h"
+
+#include <cplusplus/ASTPath.h>
+#include <cplusplus/Overview.h>
+#include <cplusplus/TypeOfExpression.h>
+
+#ifdef WITH_TESTS
+#include "cppquickfix_test.h"
+#include <QtTest>
+#endif
+
+using namespace CPlusPlus;
+using namespace Utils;
+
+namespace CppEditor::Internal {
+namespace {
+
+class ConvertToMetaMethodCallOp : public CppQuickFixOperation
+{
+public:
+ ConvertToMetaMethodCallOp(const CppQuickFixInterface &interface, CallAST *callAst)
+ : CppQuickFixOperation(interface), m_callAst(callAst)
+ {
+ setDescription(Tr::tr("Convert Function Call to Qt Meta-Method Invocation"));
+ }
+
+private:
+ void perform() override
+ {
+ // Construct the argument list.
+ Overview ov;
+ QStringList arguments;
+ for (ExpressionListAST *it = m_callAst->expression_list; it; it = it->next) {
+ if (!it->value)
+ return;
+ const FullySpecifiedType argType
+ = typeOfExpr(it->value, currentFile(), snapshot(), context());
+ if (!argType.isValid())
+ return;
+ arguments << QString::fromUtf8("Q_ARG(%1, %2)")
+ .arg(ov.prettyType(argType), currentFile()->textOf(it->value));
+ }
+ QString argsString = arguments.join(", ");
+ if (!argsString.isEmpty())
+ argsString.prepend(", ");
+
+ // Construct the replace string.
+ const auto memberAccessAst = m_callAst->base_expression->asMemberAccess();
+ QTC_ASSERT(memberAccessAst, return);
+ QString baseExpr = currentFile()->textOf(memberAccessAst->base_expression);
+ const FullySpecifiedType baseExprType
+ = typeOfExpr(memberAccessAst->base_expression, currentFile(), snapshot(), context());
+ if (!baseExprType.isValid())
+ return;
+ if (!baseExprType->asPointerType())
+ baseExpr.prepend('&');
+ const QString functionName = currentFile()->textOf(memberAccessAst->member_name);
+ const QString qMetaObject = "QMetaObject";
+ const QString newCall = QString::fromUtf8("%1::invokeMethod(%2, \"%3\"%4)")
+ .arg(qMetaObject, baseExpr, functionName, argsString);
+
+ // Determine the start and end positions of the replace operation.
+ // If the call is preceded by an "emit" keyword, that one has to be removed as well.
+ int firstToken = m_callAst->firstToken();
+ if (firstToken > 0)
+ switch (semanticInfo().doc->translationUnit()->tokenKind(firstToken - 1)) {
+ case T_EMIT: case T_Q_EMIT: --firstToken; break;
+ default: break;
+ }
+ const TranslationUnit *const tu = semanticInfo().doc->translationUnit();
+ const int startPos = tu->getTokenPositionInDocument(firstToken, textDocument());
+ const int endPos = tu->getTokenPositionInDocument(m_callAst->lastToken(), textDocument());
+
+ // Replace the old call with the new one.
+ ChangeSet changes;
+ changes.replace(startPos, endPos, newCall);
+
+ // Insert include for QMetaObject, if necessary.
+ const Identifier qMetaObjectId(qPrintable(qMetaObject), qMetaObject.size());
+ Scope * const scope = currentFile()->scopeAt(firstToken);
+ const QList<LookupItem> results = context().lookup(&qMetaObjectId, scope);
+ bool isDeclared = false;
+ for (const LookupItem &item : results) {
+ if (Symbol *declaration = item.declaration(); declaration && declaration->asClass()) {
+ isDeclared = true;
+ break;
+ }
+ }
+ if (!isDeclared) {
+ insertNewIncludeDirective('<' + qMetaObject + '>', currentFile(), semanticInfo().doc,
+ changes);
+ }
+
+ // Apply the changes.
+ currentFile()->apply(changes);
+ }
+
+ const CallAST * const m_callAst;
+};
+
+//! Converts a normal function call into a meta method invocation, if the functions is
+//! marked as invokable.
+class ConvertToMetaMethodCall : public CppQuickFixFactory
+{
+#ifdef WITH_TESTS
+public:
+ static QObject *createTest();
+#endif
+private:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ const Document::Ptr &cppDoc = interface.currentFile()->cppDocument();
+ const QList<AST *> path = ASTPath(cppDoc)(interface.cursor());
+ if (path.isEmpty())
+ return;
+
+ // Are we on a member function call?
+ CallAST *callAst = nullptr;
+ for (auto it = path.crbegin(); it != path.crend(); ++it) {
+ if ((callAst = (*it)->asCall()))
+ break;
+ }
+ if (!callAst || !callAst->base_expression)
+ return;
+ ExpressionAST *baseExpr = nullptr;
+ const NameAST *nameAst = nullptr;
+ if (const MemberAccessAST * const ast = callAst->base_expression->asMemberAccess()) {
+ baseExpr = ast->base_expression;
+ nameAst = ast->member_name;
+ }
+ if (!baseExpr || !nameAst || !nameAst->name)
+ return;
+
+ // Locate called function and check whether it is invokable.
+ Scope *scope = cppDoc->globalNamespace();
+ for (auto it = path.crbegin(); it != path.crend(); ++it) {
+ if (const CompoundStatementAST * const stmtAst = (*it)->asCompoundStatement()) {
+ scope = stmtAst->symbol;
+ break;
+ }
+ }
+ const LookupContext context(cppDoc, interface.snapshot());
+ TypeOfExpression exprType;
+ exprType.setExpandTemplates(true);
+ exprType.init(cppDoc, interface.snapshot());
+ const QList<LookupItem> typeMatches = exprType(callAst->base_expression, cppDoc, scope);
+ for (const LookupItem &item : typeMatches) {
+ if (const auto func = item.type()->asFunctionType(); func && func->methodKey()) {
+ result << new ConvertToMetaMethodCallOp(interface, callAst);
+ return;
+ }
+ }
+ }
+};
+
+#ifdef WITH_TESTS
+using namespace Tests;
+
+class ConvertToMetaMethodCallTest : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void test_data()
+ {
+ QTest::addColumn<QByteArray>("input");
+ QTest::addColumn<QByteArray>("expected");
+
+ // ^ marks the cursor locations.
+ // $ marks the replacement regions.
+ // The quoted string in the comment is the data tag.
+ // The rest of the comment is the replacement string.
+ const QByteArray allCases = R"(
+class C {
+public:
+ C() {
+ $this->^aSignal()$; // "signal from region on pointer to object" QMetaObject::invokeMethod(this, "aSignal")
+ C c;
+ $c.^aSignal()$; // "signal from region on object value" QMetaObject::invokeMethod(&c, "aSignal")
+ $(new C)->^aSignal()$; // "signal from region on expression" QMetaObject::invokeMethod((new C), "aSignal")
+ $emit this->^aSignal()$; // "signal from region, with emit" QMetaObject::invokeMethod(this, "aSignal")
+ $Q_EMIT this->^aSignal()$; // "signal from region, with Q_EMIT" QMetaObject::invokeMethod(this, "aSignal")
+ $this->^aSlot()$; // "slot from region" QMetaObject::invokeMethod(this, "aSlot")
+ $this->^noArgs()$; // "Q_SIGNAL, no arguments" QMetaObject::invokeMethod(this, "noArgs")
+ $this->^oneArg(0)$; // "Q_SLOT, one argument" QMetaObject::invokeMethod(this, "oneArg", Q_ARG(int, 0))
+ $this->^twoArgs(0, c)$; // "Q_INVOKABLE, two arguments" QMetaObject::invokeMethod(this, "twoArgs", Q_ARG(int, 0), Q_ARG(C, c))
+ this->^notInvokable(); // "not invokable"
+ }
+
+signals:
+ void aSignal();
+
+private slots:
+ void aSlot();
+
+private:
+ Q_SIGNAL void noArgs();
+ Q_SLOT void oneArg(int index);
+ Q_INVOKABLE void twoArgs(int index, const C &value);
+ void notInvokable();
+};
+)";
+
+ qsizetype nextCursor = allCases.indexOf('^');
+ while (nextCursor != -1) {
+ const int commentStart = allCases.indexOf("//", nextCursor);
+ QVERIFY(commentStart != -1);
+ const int tagStart = allCases.indexOf('"', commentStart + 2);
+ QVERIFY(tagStart != -1);
+ const int tagEnd = allCases.indexOf('"', tagStart + 1);
+ QVERIFY(tagEnd != -1);
+ QByteArray input = allCases;
+ QByteArray output = allCases;
+ input.replace(nextCursor, 1, "@");
+ const QByteArray tag = allCases.mid(tagStart + 1, tagEnd - tagStart - 1);
+ const int prevNewline = allCases.lastIndexOf('\n', nextCursor);
+ const int regionStart = allCases.lastIndexOf('$', nextCursor);
+ bool hasReplacement = false;
+ if (regionStart != -1 && regionStart > prevNewline) {
+ const int regionEnd = allCases.indexOf('$', regionStart + 1);
+ QVERIFY(regionEnd != -1);
+ const int nextNewline = allCases.indexOf('\n', tagEnd);
+ QVERIFY(nextNewline != -1);
+ const QByteArray replacement
+ = allCases.mid(tagEnd + 1, nextNewline - tagEnd - 1).trimmed();
+ output.replace(regionStart, regionEnd - regionStart, replacement);
+ hasReplacement = true;
+ }
+ static const auto matcher = [](char c) { return c == '^' || c == '$'; };
+ input.removeIf(matcher);
+ if (hasReplacement) {
+ output.removeIf(matcher);
+ output.prepend("#include <QMetaObject>\n\n");
+ } else {
+ output.clear();
+ }
+ QTest::newRow(tag.data()) << input << output;
+ nextCursor = allCases.indexOf('^', nextCursor + 1);
+ }
+ }
+
+ void test()
+ {
+ QFETCH(QByteArray, input);
+ QFETCH(QByteArray, expected);
+ ConvertToMetaMethodCall factory;
+ QuickFixOperationTest({CppTestDocument::create("file.cpp", input, expected)}, &factory);
+ }
+};
+
+QObject *ConvertToMetaMethodCall::createTest() { return new ConvertToMetaMethodCallTest; }
+
+#endif // WITH_TESTS
+} // namespace
+
+void registerConvertToMetaMethodCallQuickfix()
+{
+ CppQuickFixFactory::registerFactory<ConvertToMetaMethodCall>();
+}
+
+} // namespace CppEditor::Internal
+
+#ifdef WITH_TESTS
+#include <converttometamethodcall.moc>
+#endif
diff --git a/src/plugins/cppeditor/quickfixes/converttometamethodcall.h b/src/plugins/cppeditor/quickfixes/converttometamethodcall.h
new file mode 100644
index 0000000000..e6c309ad13
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/converttometamethodcall.h
@@ -0,0 +1,8 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+namespace CppEditor::Internal {
+void registerConvertToMetaMethodCallQuickfix();
+} // namespace CppEditor::Internal
diff --git a/src/plugins/cppeditor/quickfixes/cppcodegenerationquickfixes.cpp b/src/plugins/cppeditor/quickfixes/cppcodegenerationquickfixes.cpp
new file mode 100644
index 0000000000..7adf62cb81
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/cppcodegenerationquickfixes.cpp
@@ -0,0 +1,5067 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "cppcodegenerationquickfixes.h"
+
+#include "../cppcodestylesettings.h"
+#include "../cppeditortr.h"
+#include "../cpprefactoringchanges.h"
+#include "../insertionpointlocator.h"
+#include "cppquickfix.h"
+#include "cppquickfixhelpers.h"
+#include "cppquickfixprojectsettings.h"
+#include "cppquickfixsettings.h"
+
+#include <cplusplus/Overview.h>
+#include <cplusplus/CppRewriter.h>
+#include <projectexplorer/projecttree.h>
+#include <utils/basetreeview.h>
+#include <utils/treemodel.h>
+
+#include <QApplication>
+#include <QCheckBox>
+#include <QDialog>
+#include <QDialogButtonBox>
+#include <QHBoxLayout>
+#include <QHeaderView>
+#include <QMimeData>
+#include <QPushButton>
+#include <QProxyStyle>
+#include <QStyledItemDelegate>
+#include <QTableView>
+#include <QTreeView>
+
+#ifdef WITH_TESTS
+#include "cppquickfix_test.h"
+#include <QAbstractItemModelTester>
+#include <QtTest>
+#endif
+
+using namespace CPlusPlus;
+using namespace ProjectExplorer;
+using namespace TextEditor;
+using namespace Utils;
+
+namespace CppEditor::Internal {
+namespace {
+
+static QStringList toStringList(const QList<Symbol *> names)
+{
+ QStringList list;
+ list.reserve(names.size());
+ for (const auto symbol : names) {
+ const Identifier *const id = symbol->identifier();
+ list << QString::fromUtf8(id->chars(), id->size());
+ }
+ return list;
+}
+
+static QList<Symbol *> getMemberFunctions(const Class *clazz)
+{
+ QList<Symbol *> memberFunctions;
+ for (auto it = clazz->memberBegin(); it != clazz->memberEnd(); ++it) {
+ Symbol *const s = *it;
+ if (!s->identifier() || !s->type())
+ continue;
+ if ((s->asDeclaration() && s->type()->asFunctionType()) || s->asFunction())
+ memberFunctions << s;
+ }
+ return memberFunctions;
+}
+
+static QString symbolAtDifferentLocation(
+ const CppQuickFixInterface &interface,
+ Symbol *symbol,
+ const CppRefactoringFilePtr &targetFile,
+ InsertionLocation targetLocation)
+{
+ QTC_ASSERT(symbol, return QString());
+ Scope *scopeAtInsertPos = targetFile->cppDocument()->scopeAt(targetLocation.line(),
+ targetLocation.column());
+
+ LookupContext cppContext(targetFile->cppDocument(), interface.snapshot());
+ ClassOrNamespace *cppCoN = cppContext.lookupType(scopeAtInsertPos);
+ if (!cppCoN)
+ cppCoN = cppContext.globalNamespace();
+ SubstitutionEnvironment env;
+ env.setContext(interface.context());
+ env.switchScope(symbol->enclosingScope());
+ UseMinimalNames q(cppCoN);
+ env.enter(&q);
+ Control *control = interface.context().bindings()->control().get();
+ Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview();
+ return oo.prettyName(LookupContext::minimalName(symbol, cppCoN, control));
+}
+
+static FullySpecifiedType typeAtDifferentLocation(
+ const CppQuickFixInterface &interface,
+ FullySpecifiedType type,
+ Scope *originalScope,
+ const CppRefactoringFilePtr &targetFile,
+ InsertionLocation targetLocation,
+ const QStringList &newNamespaceNamesAtLoc = {})
+{
+ Scope *scopeAtInsertPos = targetFile->cppDocument()->scopeAt(targetLocation.line(),
+ targetLocation.column());
+ for (const QString &nsName : newNamespaceNamesAtLoc) {
+ const QByteArray utf8Name = nsName.toUtf8();
+ Control *control = targetFile->cppDocument()->control();
+ const Name *name = control->identifier(utf8Name.data(), utf8Name.size());
+ Namespace *ns = control->newNamespace(0, name);
+ ns->setEnclosingScope(scopeAtInsertPos);
+ scopeAtInsertPos = ns;
+ }
+ LookupContext cppContext(targetFile->cppDocument(), interface.snapshot());
+ ClassOrNamespace *cppCoN = cppContext.lookupType(scopeAtInsertPos);
+ if (!cppCoN)
+ cppCoN = cppContext.globalNamespace();
+ SubstitutionEnvironment env;
+ env.setContext(interface.context());
+ env.switchScope(originalScope);
+ UseMinimalNames q(cppCoN);
+ env.enter(&q);
+ Control *control = interface.context().bindings()->control().get();
+ return rewriteType(type, &env, control);
+}
+
+static QString memberBaseName(const QString &name)
+{
+ const auto validName = [](const QString &name) {
+ return !name.isEmpty() && !name.at(0).isDigit();
+ };
+ QString baseName = name;
+
+ CppQuickFixSettings *settings = CppQuickFixProjectsSettings::getQuickFixSettings(
+ ProjectExplorer::ProjectTree::currentProject());
+ const QString &nameTemplate = settings->memberVariableNameTemplate;
+ const QString prefix = nameTemplate.left(nameTemplate.indexOf('<'));
+ const QString postfix = nameTemplate.mid(nameTemplate.lastIndexOf('>') + 1);
+ if (name.startsWith(prefix) && name.endsWith(postfix)) {
+ const QString base = name.mid(prefix.length(), name.length() - postfix.length());
+ if (validName(base))
+ return base;
+ }
+
+ // Remove leading and trailing "_"
+ while (baseName.startsWith(QLatin1Char('_')))
+ baseName.remove(0, 1);
+ while (baseName.endsWith(QLatin1Char('_')))
+ baseName.chop(1);
+ if (baseName != name && validName(baseName))
+ return baseName;
+
+ // If no leading/trailing "_": remove "m_" and "m" prefix
+ if (baseName.startsWith(QLatin1String("m_"))) {
+ baseName.remove(0, 2);
+ } else if (baseName.startsWith(QLatin1Char('m')) && baseName.length() > 1
+ && baseName.at(1).isUpper()) {
+ baseName.remove(0, 1);
+ baseName[0] = baseName.at(0).toLower();
+ }
+
+ return validName(baseName) ? baseName : name;
+}
+
+static std::optional<FullySpecifiedType> getFirstTemplateParameter(const Name *name)
+{
+ if (const QualifiedNameId *qualifiedName = name->asQualifiedNameId())
+ return getFirstTemplateParameter(qualifiedName->name());
+
+ if (const TemplateNameId *templateName = name->asTemplateNameId()) {
+ if (templateName->templateArgumentCount() > 0)
+ return templateName->templateArgumentAt(0).type();
+ }
+ return {};
+}
+
+static std::optional<FullySpecifiedType> getFirstTemplateParameter(Type *type)
+{
+ if (NamedType *namedType = type->asNamedType())
+ return getFirstTemplateParameter(namedType->name());
+
+ return {};
+}
+
+static std::optional<FullySpecifiedType> getFirstTemplateParameter(FullySpecifiedType type)
+{
+ return getFirstTemplateParameter(type.type());
+}
+
+struct ExistingGetterSetterData
+{
+ Class *clazz = nullptr;
+ Declaration *declarationSymbol = nullptr;
+ QString getterName;
+ QString setterName;
+ QString resetName;
+ QString signalName;
+ QString qPropertyName;
+ QString memberVariableName;
+ Document::Ptr doc;
+
+ int computePossibleFlags() const;
+};
+
+// FIXME: Should be a member?
+static void findExistingFunctions(ExistingGetterSetterData &existing, QStringList memberFunctionNames)
+{
+ const CppQuickFixSettings *settings = CppQuickFixProjectsSettings::getQuickFixSettings(
+ ProjectExplorer::ProjectTree::currentProject());
+ const QString lowerBaseName = memberBaseName(existing.memberVariableName).toLower();
+ const QStringList getterNames{lowerBaseName,
+ "get_" + lowerBaseName,
+ "get" + lowerBaseName,
+ "is_" + lowerBaseName,
+ "is" + lowerBaseName,
+ settings->getGetterName(lowerBaseName)};
+ const QStringList setterNames{"set_" + lowerBaseName,
+ "set" + lowerBaseName,
+ settings->getSetterName(lowerBaseName)};
+ const QStringList resetNames{"reset_" + lowerBaseName,
+ "reset" + lowerBaseName,
+ settings->getResetName(lowerBaseName)};
+ const QStringList signalNames{lowerBaseName + "_changed",
+ lowerBaseName + "changed",
+ settings->getSignalName(lowerBaseName)};
+ for (const auto &memberFunctionName : memberFunctionNames) {
+ const QString lowerName = memberFunctionName.toLower();
+ if (getterNames.contains(lowerName))
+ existing.getterName = memberFunctionName;
+ else if (setterNames.contains(lowerName))
+ existing.setterName = memberFunctionName;
+ else if (resetNames.contains(lowerName))
+ existing.resetName = memberFunctionName;
+ else if (signalNames.contains(lowerName))
+ existing.signalName = memberFunctionName;
+ }
+}
+
+static void extractNames(const CppRefactoringFilePtr &file,
+ QtPropertyDeclarationAST *qtPropertyDeclaration,
+ ExistingGetterSetterData &data)
+{
+ QtPropertyDeclarationItemListAST *it = qtPropertyDeclaration->property_declaration_item_list;
+ for (; it; it = it->next) {
+ const char *tokenString = file->tokenAt(it->value->item_name_token).spell();
+ if (!qstrcmp(tokenString, "READ")) {
+ data.getterName = file->textOf(it->value->expression);
+ } else if (!qstrcmp(tokenString, "WRITE")) {
+ data.setterName = file->textOf(it->value->expression);
+ } else if (!qstrcmp(tokenString, "RESET")) {
+ data.resetName = file->textOf(it->value->expression);
+ } else if (!qstrcmp(tokenString, "NOTIFY")) {
+ data.signalName = file->textOf(it->value->expression);
+ } else if (!qstrcmp(tokenString, "MEMBER")) {
+ data.memberVariableName = file->textOf(it->value->expression);
+ }
+ }
+}
+
+class GetterSetterRefactoringHelper
+{
+public:
+ GetterSetterRefactoringHelper(CppQuickFixOperation *operation, Class *clazz)
+ : m_operation(operation)
+ , m_changes(m_operation->snapshot())
+ , m_locator(m_changes)
+ , m_headerFile(operation->currentFile())
+ , m_sourceFile([&] {
+ FilePath cppFilePath = correspondingHeaderOrSource(m_headerFile->filePath(),
+ &m_isHeaderHeaderFile);
+ if (!m_isHeaderHeaderFile || !cppFilePath.exists()) {
+ // there is no "source" file
+ return m_headerFile;
+ } else {
+ return m_changes.cppFile(cppFilePath);
+ }
+ }())
+ , m_class(clazz)
+ {}
+
+ void performGeneration(ExistingGetterSetterData data, int generationFlags);
+
+ void applyChanges()
+ {
+ const auto classLayout = {
+ InsertionPointLocator::Public,
+ InsertionPointLocator::PublicSlot,
+ InsertionPointLocator::Signals,
+ InsertionPointLocator::Protected,
+ InsertionPointLocator::ProtectedSlot,
+ InsertionPointLocator::PrivateSlot,
+ InsertionPointLocator::Private,
+ };
+ for (auto spec : classLayout) {
+ const auto iter = m_headerFileCode.find(spec);
+ if (iter != m_headerFileCode.end()) {
+ const InsertionLocation loc = headerLocationFor(spec);
+ m_headerFile->setOpenEditor(true, m_headerFile->position(loc.line(), loc.column()));
+ insertAndIndent(m_headerFile, loc, *iter);
+ }
+ }
+ if (!m_sourceFileCode.isEmpty() && m_sourceFileInsertionPoint.isValid()) {
+ m_sourceFile->setOpenEditor(true, m_sourceFile->position(
+ m_sourceFileInsertionPoint.line(),
+ m_sourceFileInsertionPoint.column()));
+ insertAndIndent(m_sourceFile, m_sourceFileInsertionPoint, m_sourceFileCode);
+ }
+
+ m_headerFile->apply(m_headerFileChangeSet);
+ m_sourceFile->apply(m_sourceFileChangeSet);
+ }
+
+ bool hasSourceFile() const { return m_headerFile != m_sourceFile; }
+
+protected:
+ void insertAndIndent(const RefactoringFilePtr &file,
+ const InsertionLocation &loc,
+ const QString &text)
+ {
+ int targetPosition = file->position(loc.line(), loc.column());
+ ChangeSet &changeSet = file == m_headerFile ? m_headerFileChangeSet : m_sourceFileChangeSet;
+ changeSet.insert(targetPosition, loc.prefix() + text + loc.suffix());
+ }
+
+ FullySpecifiedType makeConstRef(FullySpecifiedType type)
+ {
+ type.setConst(true);
+ return m_operation->currentFile()->cppDocument()->control()->referenceType(type, false);
+ }
+
+ FullySpecifiedType addConstToReference(FullySpecifiedType type)
+ {
+ if (ReferenceType *ref = type.type()->asReferenceType()) {
+ FullySpecifiedType elemType = ref->elementType();
+ if (elemType.isConst())
+ return type;
+ elemType.setConst(true);
+ return m_operation->currentFile()->cppDocument()->control()->referenceType(elemType,
+ false);
+ }
+ return type;
+ }
+
+ QString symbolAt(Symbol *symbol,
+ const CppRefactoringFilePtr &targetFile,
+ InsertionLocation targetLocation)
+ {
+ return symbolAtDifferentLocation(*m_operation, symbol, targetFile, targetLocation);
+ }
+
+ FullySpecifiedType typeAt(FullySpecifiedType type,
+ Scope *originalScope,
+ const CppRefactoringFilePtr &targetFile,
+ InsertionLocation targetLocation,
+ const QStringList &newNamespaceNamesAtLoc = {})
+ {
+ return typeAtDifferentLocation(*m_operation,
+ type,
+ originalScope,
+ targetFile,
+ targetLocation,
+ newNamespaceNamesAtLoc);
+ }
+
+ /**
+ * @brief checks if the type in the enclosing scope in the header is a value type
+ * @param type a type in the m_headerFile
+ * @param enclosingScope the enclosing scope
+ * @param customValueType if not nullptr set to true when value type comes
+ * from CppQuickFixSettings::isValueType
+ * @return true if it is a pointer, enum, integer, floating point, reference, custom value type
+ */
+ bool isValueType(FullySpecifiedType type, Scope *enclosingScope, bool *customValueType = nullptr)
+ {
+ if (customValueType)
+ *customValueType = false;
+ // a type is a value type if it is one of the following
+ const auto isTypeValueType = [](const FullySpecifiedType &t) {
+ return t->asPointerType() || t->asEnumType() || t->asIntegerType() || t->asFloatType()
+ || t->asReferenceType();
+ };
+ if (type->asNamedType()) {
+ // we need a recursive search and a lookup context
+ LookupContext context(m_headerFile->cppDocument(), m_changes.snapshot());
+ auto isValueType = [settings = m_settings,
+ &customValueType,
+ &context,
+ &isTypeValueType](const Name *name,
+ Scope *scope,
+ auto &isValueType) {
+ // maybe the type is a custom value type by name
+ if (const Identifier *id = name->identifier()) {
+ if (settings->isValueType(QString::fromUtf8(id->chars(), id->size()))) {
+ if (customValueType)
+ *customValueType = true;
+ return true;
+ }
+ }
+ // search for the type declaration
+ QList<LookupItem> localLookup = context.lookup(name, scope);
+ for (auto &&i : localLookup) {
+ if (isTypeValueType(i.type()))
+ return true;
+ if (i.type()->asNamedType()) { // check if we have to search recursively
+ const Name *newName = i.type()->asNamedType()->name();
+ Scope *newScope = i.declaration()->enclosingScope();
+ if (Matcher::match(newName, name)
+ && Matcher::match(newScope->name(), scope->name())) {
+ continue; // we have found the start location of the search
+ }
+ return isValueType(newName, newScope, isValueType);
+ }
+ return false;
+ }
+ return false;
+ };
+ // start recursion
+ return isValueType(type->asNamedType()->name(), enclosingScope, isValueType);
+ }
+ return isTypeValueType(type);
+ }
+
+ bool isValueType(Symbol *symbol, bool *customValueType = nullptr)
+ {
+ return isValueType(symbol->type(), symbol->enclosingScope(), customValueType);
+ }
+
+ void addHeaderCode(InsertionPointLocator::AccessSpec spec, QString code)
+ {
+ QString &existing = m_headerFileCode[spec];
+ existing += code;
+ if (!existing.endsWith('\n'))
+ existing += '\n';
+ }
+
+ InsertionLocation headerLocationFor(InsertionPointLocator::AccessSpec spec)
+ {
+ const auto insertionPoint = m_headerInsertionPoints.find(spec);
+ if (insertionPoint != m_headerInsertionPoints.end())
+ return *insertionPoint;
+ const InsertionLocation loc = m_locator.methodDeclarationInClass(
+ m_headerFile->filePath(), m_class, spec,
+ InsertionPointLocator::ForceAccessSpec::Yes);
+ m_headerInsertionPoints.insert(spec, loc);
+ return loc;
+ }
+
+ InsertionLocation sourceLocationFor(Symbol *symbol, QStringList *insertedNamespaces = nullptr)
+ {
+ if (m_sourceFileInsertionPoint.isValid())
+ return m_sourceFileInsertionPoint;
+ m_sourceFileInsertionPoint
+ = insertLocationForMethodDefinition(symbol,
+ false,
+ m_settings->createMissingNamespacesinCppFile()
+ ? NamespaceHandling::CreateMissing
+ : NamespaceHandling::Ignore,
+ m_changes,
+ m_sourceFile->filePath(),
+ insertedNamespaces);
+ if (m_settings->addUsingNamespaceinCppFile()) {
+ // check if we have to insert a using namespace ...
+ auto requiredNamespaces = getNamespaceNames(
+ symbol->asClass() ? symbol : symbol->enclosingClass());
+ NSCheckerVisitor visitor(m_sourceFile.get(),
+ requiredNamespaces,
+ m_sourceFile->position(m_sourceFileInsertionPoint.line(),
+ m_sourceFileInsertionPoint.column()));
+ visitor.accept(m_sourceFile->cppDocument()->translationUnit()->ast());
+ if (insertedNamespaces)
+ insertedNamespaces->clear();
+ if (auto rns = visitor.remainingNamespaces(); !rns.empty()) {
+ QString ns = "using namespace ";
+ for (auto &n : rns) {
+ if (!n.isEmpty()) { // we have to ignore unnamed namespaces
+ ns += n;
+ ns += "::";
+ if (insertedNamespaces)
+ insertedNamespaces->append(n);
+ }
+ }
+ ns.resize(ns.size() - 2); // remove last '::'
+ ns += ";\n";
+ const auto &loc = m_sourceFileInsertionPoint;
+ m_sourceFileInsertionPoint = InsertionLocation(loc.filePath(),
+ loc.prefix() + ns,
+ loc.suffix(),
+ loc.line(),
+ loc.column());
+ }
+ }
+ return m_sourceFileInsertionPoint;
+ }
+
+ void addSourceFileCode(QString code)
+ {
+ while (!m_sourceFileCode.isEmpty() && !m_sourceFileCode.endsWith("\n\n"))
+ m_sourceFileCode += '\n';
+ m_sourceFileCode += code;
+ }
+
+protected:
+ CppQuickFixOperation *const m_operation;
+ const CppRefactoringChanges m_changes;
+ const InsertionPointLocator m_locator;
+ const CppRefactoringFilePtr m_headerFile;
+ bool m_isHeaderHeaderFile = false; // the "header" (where the class is defined) can be a source file
+ const CppRefactoringFilePtr m_sourceFile;
+ CppQuickFixSettings *const m_settings = CppQuickFixProjectsSettings::getQuickFixSettings(
+ ProjectTree::currentProject());
+ Class *const m_class;
+
+private:
+ ChangeSet m_headerFileChangeSet;
+ ChangeSet m_sourceFileChangeSet;
+ QMap<InsertionPointLocator::AccessSpec, InsertionLocation> m_headerInsertionPoints;
+ InsertionLocation m_sourceFileInsertionPoint;
+ QString m_sourceFileCode;
+ QMap<InsertionPointLocator::AccessSpec, QString> m_headerFileCode;
+};
+
+struct ParentClassConstructorInfo;
+
+class ConstructorMemberInfo
+{
+public:
+ ConstructorMemberInfo(const QString &name, Symbol *symbol, int numberOfMember)
+ : memberVariableName(name)
+ , parameterName(memberBaseName(name))
+ , symbol(symbol)
+ , type(symbol->type())
+ , numberOfMember(numberOfMember)
+ {}
+ ConstructorMemberInfo(const QString &memberName,
+ const QString &paramName,
+ const QString &defaultValue,
+ Symbol *symbol,
+ const ParentClassConstructorInfo *parentClassConstructor)
+ : parentClassConstructor(parentClassConstructor)
+ , memberVariableName(memberName)
+ , parameterName(paramName)
+ , defaultValue(defaultValue)
+ , init(defaultValue.isEmpty())
+ , symbol(symbol)
+ , type(symbol->type())
+ {}
+ const ParentClassConstructorInfo *parentClassConstructor = nullptr;
+ QString memberVariableName;
+ QString parameterName;
+ QString defaultValue;
+ bool init = true;
+ bool customValueType; // for the generation later
+ Symbol *symbol; // for the right type later
+ FullySpecifiedType type;
+ int numberOfMember; // first member, second member, ...
+};
+
+class ConstructorParams : public QAbstractTableModel
+{
+ Q_OBJECT
+ std::list<ConstructorMemberInfo> candidates;
+ std::vector<ConstructorMemberInfo *> infos;
+
+ void validateOrder()
+ {
+ // parameters with default values must be at the end
+ bool foundWithDefault = false;
+ for (auto info : infos) {
+ if (info->init) {
+ if (foundWithDefault && info->defaultValue.isEmpty()) {
+ emit validOrder(false);
+ return;
+ }
+ foundWithDefault |= !info->defaultValue.isEmpty();
+ }
+ }
+ emit validOrder(true);
+ }
+
+public:
+ enum Column { ShouldInitColumn, MemberNameColumn, ParameterNameColumn, DefaultValueColumn };
+ template<typename... _Args>
+ void emplaceBackParameter(_Args &&...__args)
+ {
+ candidates.emplace_back(std::forward<_Args>(__args)...);
+ infos.push_back(&candidates.back());
+ }
+ const std::vector<ConstructorMemberInfo *> &getInfos() const { return infos; }
+ void addRow(ConstructorMemberInfo *info)
+ {
+ beginInsertRows({}, rowCount(), rowCount());
+ infos.push_back(info);
+ endInsertRows();
+ validateOrder();
+ }
+ void removeRow(ConstructorMemberInfo *info)
+ {
+ for (auto iter = infos.begin(); iter != infos.end(); ++iter) {
+ if (*iter == info) {
+ const auto index = iter - infos.begin();
+ beginRemoveRows({}, index, index);
+ infos.erase(iter);
+ endRemoveRows();
+ validateOrder();
+ return;
+ }
+ }
+ }
+
+ int selectedCount() const
+ {
+ return Utils::count(infos, [](const ConstructorMemberInfo *mi) {
+ return mi->init && !mi->parentClassConstructor;
+ });
+ }
+ int memberCount() const
+ {
+ return Utils::count(infos, [](const ConstructorMemberInfo *mi) {
+ return !mi->parentClassConstructor;
+ });
+ }
+
+ int rowCount(const QModelIndex & /*parent*/ = {}) const override { return int(infos.size()); }
+ int columnCount(const QModelIndex & /*parent*/ = {}) const override { return 4; }
+ QVariant data(const QModelIndex &index, int role) const override
+ {
+ if (index.row() < 0 || index.row() >= rowCount())
+ return {};
+ if (role == Qt::CheckStateRole && index.column() == ShouldInitColumn
+ && !infos[index.row()]->parentClassConstructor)
+ return infos[index.row()]->init ? Qt::Checked : Qt::Unchecked;
+ if (role == Qt::DisplayRole && index.column() == MemberNameColumn)
+ return infos[index.row()]->memberVariableName;
+ if ((role == Qt::DisplayRole || role == Qt::EditRole)
+ && index.column() == ParameterNameColumn)
+ return infos[index.row()]->parameterName;
+ if ((role == Qt::DisplayRole || role == Qt::EditRole)
+ && index.column() == DefaultValueColumn)
+ return infos[index.row()]->defaultValue;
+ if ((role == Qt::ToolTipRole) && index.column() > 0)
+ return Overview{}.prettyType(infos[index.row()]->symbol->type());
+ return {};
+ }
+ bool setData(const QModelIndex &index, const QVariant &value, int role) override
+ {
+ if (index.column() == ShouldInitColumn && role == Qt::CheckStateRole) {
+ if (infos[index.row()]->parentClassConstructor)
+ return false;
+ infos[index.row()]->init = value.toInt() == Qt::Checked;
+ emit dataChanged(this->index(index.row(), 0), this->index(index.row(), columnCount()));
+ validateOrder();
+ return true;
+ }
+ if (index.column() == ParameterNameColumn && role == Qt::EditRole) {
+ infos[index.row()]->parameterName = value.toString();
+ return true;
+ }
+ if (index.column() == DefaultValueColumn && role == Qt::EditRole) {
+ infos[index.row()]->defaultValue = value.toString();
+ validateOrder();
+ return true;
+ }
+ return false;
+ }
+ Qt::DropActions supportedDropActions() const override { return Qt::MoveAction; }
+ Qt::ItemFlags flags(const QModelIndex &index) const override
+ {
+ if (!index.isValid())
+ return Qt::ItemIsSelectable | Qt::ItemIsDropEnabled;
+
+ Qt::ItemFlags f{};
+ if (infos[index.row()]->init) {
+ f |= Qt::ItemIsDragEnabled;
+ f |= Qt::ItemIsSelectable;
+ }
+
+ if (index.column() == ShouldInitColumn && !infos[index.row()]->parentClassConstructor)
+ return f | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable;
+ if (!infos[index.row()]->init)
+ return f;
+ if (index.column() == MemberNameColumn)
+ return f | Qt::ItemIsEnabled;
+ if (index.column() == ParameterNameColumn || index.column() == DefaultValueColumn)
+ return f | Qt::ItemIsEnabled | Qt::ItemIsEditable;
+ return {};
+ }
+
+ QVariant headerData(int section, Qt::Orientation orientation, int role) const override
+ {
+ if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
+ switch (section) {
+ case ShouldInitColumn:
+ return Tr::tr("Initialize in Constructor");
+ case MemberNameColumn:
+ return Tr::tr("Member Name");
+ case ParameterNameColumn:
+ return Tr::tr("Parameter Name");
+ case DefaultValueColumn:
+ return Tr::tr("Default Value");
+ }
+ }
+ return {};
+ }
+ bool dropMimeData(const QMimeData *data,
+ Qt::DropAction /*action*/,
+ int row,
+ int /*column*/,
+ const QModelIndex & /*parent*/) override
+ {
+ if (row == -1)
+ row = rowCount();
+ bool ok;
+ int sourceRow = data->data("application/x-qabstractitemmodeldatalist").toInt(&ok);
+ if (ok) {
+ if (sourceRow == row || row == sourceRow + 1)
+ return false;
+ beginMoveRows({}, sourceRow, sourceRow, {}, row);
+ infos.insert(infos.begin() + row, infos.at(sourceRow));
+ if (row < sourceRow)
+ ++sourceRow;
+ infos.erase(infos.begin() + sourceRow);
+ validateOrder();
+ return true;
+ }
+ return false;
+ }
+
+ QMimeData *mimeData(const QModelIndexList &indexes) const override
+ {
+ for (const auto &i : indexes) {
+ if (!i.isValid())
+ continue;
+ auto data = new QMimeData();
+ data->setData("application/x-qabstractitemmodeldatalist",
+ QString::number(i.row()).toLatin1());
+ return data;
+ }
+ return nullptr;
+ }
+
+ class TableViewStyle : public QProxyStyle
+ {
+ public:
+ TableViewStyle(QStyle *style)
+ : QProxyStyle(style)
+ {}
+
+ void drawPrimitive(PrimitiveElement element,
+ const QStyleOption *option,
+ QPainter *painter,
+ const QWidget *widget) const override
+ {
+ if (element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull()) {
+ QStyleOption opt(*option);
+ opt.rect.setLeft(0);
+ if (widget)
+ opt.rect.setRight(widget->width());
+ QProxyStyle::drawPrimitive(element, &opt, painter, widget);
+ return;
+ }
+ QProxyStyle::drawPrimitive(element, option, painter, widget);
+ }
+ };
+signals:
+ void validOrder(bool valid);
+};
+
+class TopMarginDelegate : public QStyledItemDelegate
+{
+public:
+ TopMarginDelegate(QObject *parent = nullptr)
+ : QStyledItemDelegate(parent)
+ {}
+
+ void paint(QPainter *painter,
+ const QStyleOptionViewItem &option,
+ const QModelIndex &index) const override
+ {
+ Q_ASSERT(index.isValid());
+ QStyleOptionViewItem opt = option;
+ initStyleOption(&opt, index);
+ const QWidget *widget = option.widget;
+ QStyle *style = widget ? widget->style() : QApplication::style();
+ if (opt.rect.height() > 20)
+ opt.rect.adjust(0, 5, 0, 0);
+ style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, widget);
+ }
+};
+
+struct ParentClassConstructorParameter : public ConstructorMemberInfo
+{
+ QString originalDefaultValue;
+ QString declaration; // displayed in the treeView
+ ParentClassConstructorParameter(const QString &name,
+ const QString &defaultValue,
+ Symbol *symbol,
+ const ParentClassConstructorInfo *parentClassConstructor);
+
+ ParentClassConstructorParameter(const ParentClassConstructorParameter &) = delete;
+ ParentClassConstructorParameter(ParentClassConstructorParameter &&) = default;
+};
+
+struct ParentClassConstructorInfo
+{
+ ParentClassConstructorInfo(const QString &name, ConstructorParams &model)
+ : className(name)
+ , model(model)
+ {}
+ bool useInConstructor = false;
+ const QString className;
+ QString declaration;
+ std::vector<ParentClassConstructorParameter> parameters;
+ ConstructorParams &model;
+
+ ParentClassConstructorInfo(const ParentClassConstructorInfo &) = delete;
+ ParentClassConstructorInfo(ParentClassConstructorInfo &&) = default;
+
+ void addParameter(ParentClassConstructorParameter &param) { model.addRow(&param); }
+ void removeParameter(ParentClassConstructorParameter &param) { model.removeRow(&param); }
+ void removeAllParameters()
+ {
+ for (auto &param : parameters)
+ model.removeRow(&param);
+ }
+};
+
+ParentClassConstructorParameter::ParentClassConstructorParameter(
+ const QString &name,
+ const QString &defaultValue,
+ Symbol *symbol,
+ const ParentClassConstructorInfo *parentClassConstructor)
+ : ConstructorMemberInfo(parentClassConstructor->className + "::" + name,
+ name,
+ defaultValue,
+ symbol,
+ parentClassConstructor)
+ , originalDefaultValue(defaultValue)
+ , declaration(Overview{}.prettyType(symbol->type(), name)
+ + (defaultValue.isEmpty() ? QString{} : " = " + defaultValue))
+{}
+
+using ParentClassConstructors = std::vector<ParentClassConstructorInfo>;
+
+class ParentClassesModel : public QAbstractItemModel
+{
+ ParentClassConstructors &constructors;
+
+public:
+ ParentClassesModel(QObject *parent, ParentClassConstructors &constructors)
+ : QAbstractItemModel(parent)
+ , constructors(constructors)
+ {}
+ QModelIndex index(int row, int column, const QModelIndex &parent = {}) const override
+ {
+ if (!parent.isValid())
+ return createIndex(row, column, nullptr);
+ if (parent.internalPointer())
+ return {};
+ auto index = createIndex(row, column, &constructors.at(parent.row()));
+ return index;
+ }
+ QModelIndex parent(const QModelIndex &index) const override
+ {
+ if (!index.isValid())
+ return {};
+ auto *parent = static_cast<ParentClassConstructorInfo *>(index.internalPointer());
+ if (!parent)
+ return {};
+ int i = 0;
+ for (const auto &info : constructors) {
+ if (&info == parent)
+ return createIndex(i, 0, nullptr);
+ ++i;
+ }
+ return {};
+ }
+ int rowCount(const QModelIndex &parent = {}) const override
+ {
+ if (!parent.isValid())
+ return static_cast<int>(constructors.size());
+ auto info = static_cast<ParentClassConstructorInfo *>(parent.internalPointer());
+ if (!info)
+ return static_cast<int>(constructors.at(parent.row()).parameters.size());
+ return 0;
+ }
+ int columnCount(const QModelIndex & /*parent*/ = {}) const override { return 1; }
+ QVariant data(const QModelIndex &index, int role) const override
+ {
+ if (!index.isValid())
+ return {};
+ auto info = static_cast<ParentClassConstructorInfo *>(index.internalPointer());
+
+ if (info) {
+ const auto &parameter = info->parameters.at(index.row());
+ if (role == Qt::CheckStateRole)
+ return parameter.init ? Qt::Checked : Qt::Unchecked;
+ if (role == Qt::DisplayRole)
+ return parameter.declaration;
+ return {};
+ }
+ const auto &constructor = constructors.at(index.row());
+ if (role == Qt::CheckStateRole)
+ return constructor.useInConstructor ? Qt::PartiallyChecked : Qt::Unchecked;
+ if (role == Qt::DisplayRole)
+ return constructor.declaration;
+
+ // Highlight the selected items
+ if (role == Qt::FontRole && constructor.useInConstructor) {
+ QFont font = QApplication::font();
+ font.setBold(true);
+ return font;
+ }
+ // Create a margin between sets of constructors for base classes
+ if (role == Qt::SizeHintRole && index.row() > 0
+ && constructor.className != constructors.at(index.row() - 1).className) {
+ return QSize(-1, 25);
+ }
+ return {};
+ }
+ bool setData(const QModelIndex &index, const QVariant &value, int /*role*/) override
+ {
+ if (index.isValid() && index.column() == 0) {
+ auto info = static_cast<ParentClassConstructorInfo *>(index.internalPointer());
+ if (info) {
+ const bool nowUse = value.toBool();
+ auto &param = info->parameters.at(index.row());
+ param.init = nowUse;
+ if (nowUse)
+ info->addParameter(param);
+ else
+ info->removeParameter(param);
+ return true;
+ }
+ auto &newConstructor = constructors.at(index.row());
+ // You have to select a base class constructor
+ if (newConstructor.useInConstructor)
+ return false;
+ auto c = std::find_if(constructors.begin(), constructors.end(), [&](const auto &c) {
+ return c.className == newConstructor.className && c.useInConstructor;
+ });
+ QTC_ASSERT(c == constructors.end(), return false;);
+ c->useInConstructor = false;
+ newConstructor.useInConstructor = true;
+ emit dataChanged(this->index(index.row(), 0), this->index(index.row(), columnCount()));
+ auto parentIndex = this->index(index.row(), 0);
+ emit dataChanged(this->index(0, 0, parentIndex),
+ this->index(rowCount(parentIndex), columnCount()));
+ const int oldIndex = c - constructors.begin();
+ emit dataChanged(this->index(oldIndex, 0), this->index(oldIndex, columnCount()));
+ parentIndex = this->index(oldIndex, 0);
+ emit dataChanged(this->index(0, 0, parentIndex),
+ this->index(rowCount(parentIndex), columnCount()));
+ // update other table
+ c->removeAllParameters();
+ for (auto &p : newConstructor.parameters)
+ if (p.init)
+ newConstructor.addParameter(p);
+ return true;
+ }
+ return false;
+ }
+ QVariant headerData(int section, Qt::Orientation orientation, int role) const override
+ {
+ if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
+ switch (section) {
+ case 0:
+ return Tr::tr("Base Class Constructors");
+ }
+ }
+ return {};
+ }
+ Qt::ItemFlags flags(const QModelIndex &index) const override
+ {
+ if (index.isValid()) {
+ Qt::ItemFlags f;
+ auto info = static_cast<ParentClassConstructorInfo *>(index.internalPointer());
+ if (!info || info->useInConstructor) {
+ f |= Qt::ItemIsEnabled;
+ }
+ f |= Qt::ItemIsUserCheckable;
+
+ return f;
+ }
+ return {};
+ }
+};
+
+class GenerateConstructorDialog : public QDialog
+{
+public:
+ GenerateConstructorDialog(ConstructorParams *constructorParamsModel,
+ ParentClassConstructors &constructors)
+ {
+ setWindowTitle(Tr::tr("Constructor"));
+
+ const auto treeModel = new ParentClassesModel(this, constructors);
+ const auto treeView = new QTreeView(this);
+ treeView->setModel(treeModel);
+ treeView->setItemDelegate(new TopMarginDelegate(this));
+ treeView->expandAll();
+
+ const auto view = new QTableView(this);
+ view->setModel(constructorParamsModel);
+ int optimalWidth = 0;
+ for (int i = 0; i < constructorParamsModel->columnCount(QModelIndex{}); ++i) {
+ view->resizeColumnToContents(i);
+ optimalWidth += view->columnWidth(i);
+ }
+ view->resizeRowsToContents();
+ view->verticalHeader()->setDefaultSectionSize(view->rowHeight(0));
+ view->setSelectionBehavior(QAbstractItemView::SelectRows);
+ view->setSelectionMode(QAbstractItemView::SingleSelection);
+ view->setDragEnabled(true);
+ view->setDropIndicatorShown(true);
+ view->setDefaultDropAction(Qt::MoveAction);
+ view->setDragDropMode(QAbstractItemView::InternalMove);
+ view->setDragDropOverwriteMode(false);
+ view->horizontalHeader()->setStretchLastSection(true);
+ view->setStyle(new ConstructorParams::TableViewStyle(view->style()));
+
+ const auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
+ connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
+ connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
+
+ const auto errorLabel = new QLabel(
+ Tr::tr("Parameters without default value must come before parameters with default value."));
+ errorLabel->setStyleSheet("color: #ff0000");
+ errorLabel->setVisible(false);
+ QSizePolicy labelSizePolicy = errorLabel->sizePolicy();
+ labelSizePolicy.setRetainSizeWhenHidden(true);
+ errorLabel->setSizePolicy(labelSizePolicy);
+ connect(constructorParamsModel, &ConstructorParams::validOrder, this,
+ [errorLabel, button = buttonBox->button(QDialogButtonBox::Ok)](bool valid) {
+ button->setEnabled(valid);
+ errorLabel->setVisible(!valid);
+ });
+
+ // setup select all/none checkbox
+ QCheckBox *const checkBox = new QCheckBox(Tr::tr("Initialize all members"));
+ checkBox->setChecked(true);
+ connect(checkBox, &QCheckBox::stateChanged, this,
+ [model = constructorParamsModel](int state) {
+ if (state != Qt::PartiallyChecked) {
+ for (int i = 0; i < model->rowCount(); ++i)
+ model->setData(model->index(i, ConstructorParams::ShouldInitColumn),
+ state,
+ Qt::CheckStateRole);
+ }
+ });
+ connect(checkBox, &QCheckBox::clicked, this, [checkBox] {
+ if (checkBox->checkState() == Qt::PartiallyChecked)
+ checkBox->setCheckState(Qt::Checked);
+ });
+ connect(constructorParamsModel,
+ &QAbstractItemModel::dataChanged,
+ this,
+ [model = constructorParamsModel, checkBox] {
+ const auto state = [model, selectedCount = model->selectedCount()]() {
+ if (selectedCount == 0)
+ return Qt::Unchecked;
+ if (static_cast<int>(model->memberCount() == selectedCount))
+ return Qt::Checked;
+ return Qt::PartiallyChecked;
+ }();
+ checkBox->setCheckState(state);
+ });
+
+ using A = InsertionPointLocator::AccessSpec;
+ auto accessCombo = new QComboBox;
+ connect(accessCombo, &QComboBox::currentIndexChanged, this, [this, accessCombo] {
+ const auto data = accessCombo->currentData();
+ m_accessSpec = static_cast<A>(data.toInt());
+ });
+ for (auto a : {A::Public, A::Protected, A::Private})
+ accessCombo->addItem(InsertionPointLocator::accessSpecToString(a), a);
+ const auto row = new QHBoxLayout();
+ row->addWidget(new QLabel(Tr::tr("Access") + ":"));
+ row->addWidget(accessCombo);
+ row->addSpacerItem(new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum));
+
+ const auto mainLayout = new QVBoxLayout(this);
+ mainLayout->addWidget(
+ new QLabel(Tr::tr("Select the members to be initialized in the constructor.\n"
+ "Use drag and drop to change the order of the parameters.")));
+ mainLayout->addLayout(row);
+ mainLayout->addWidget(checkBox);
+ mainLayout->addWidget(view);
+ mainLayout->addWidget(treeView);
+ mainLayout->addWidget(errorLabel);
+ mainLayout->addWidget(buttonBox);
+ int left, right;
+ mainLayout->getContentsMargins(&left, nullptr, &right, nullptr);
+ optimalWidth += left + right;
+ resize(optimalWidth, mainLayout->sizeHint().height());
+ }
+
+ InsertionPointLocator::AccessSpec accessSpec() const { return m_accessSpec; }
+
+private:
+ InsertionPointLocator::AccessSpec m_accessSpec;
+};
+
+class MemberInfo
+{
+public:
+ MemberInfo(ExistingGetterSetterData data, int possibleFlags)
+ : data(data)
+ , possibleFlags(possibleFlags)
+ {}
+
+ ExistingGetterSetterData data;
+ int possibleFlags;
+ int requestedFlags = 0;
+};
+using GetterSetterCandidates = std::vector<MemberInfo>;
+
+class GenerateConstructorOperation : public CppQuickFixOperation
+{
+public:
+ GenerateConstructorOperation(const CppQuickFixInterface &interface)
+ : CppQuickFixOperation(interface)
+ {
+ setDescription(Tr::tr("Generate Constructor"));
+
+ m_classAST = astForClassOperations(interface);
+ if (!m_classAST)
+ return;
+ Class *const theClass = m_classAST->symbol;
+ if (!theClass)
+ return;
+
+ // Go through all members and find member variable declarations
+ int memberCounter = 0;
+ for (auto it = theClass->memberBegin(); it != theClass->memberEnd(); ++it) {
+ Symbol *const s = *it;
+ if (!s->identifier() || !s->type() || s->type().isTypedef())
+ continue;
+ if ((s->asDeclaration() && s->type()->asFunctionType()) || s->asFunction())
+ continue;
+ if (s->asDeclaration() && (s->isPrivate() || s->isProtected()) && !s->isStatic()) {
+ const auto name = QString::fromUtf8(s->identifier()->chars(),
+ s->identifier()->size());
+ parameterModel.emplaceBackParameter(name, s, memberCounter++);
+ }
+ }
+ Overview o = CppCodeStyleSettings::currentProjectCodeStyleOverview();
+ o.showArgumentNames = true;
+ o.showReturnTypes = true;
+ o.showDefaultArguments = true;
+ o.showTemplateParameters = true;
+ o.showFunctionSignatures = true;
+ LookupContext context(currentFile()->cppDocument(), interface.snapshot());
+ for (BaseClass *bc : theClass->baseClasses()) {
+ const QString className = o.prettyName(bc->name());
+
+ ClassOrNamespace *localLookupType = context.lookupType(bc);
+ QList<LookupItem> localLookup = localLookupType->lookup(bc->name());
+ for (auto &li : localLookup) {
+ Symbol *d = li.declaration();
+ if (!d->asClass())
+ continue;
+ for (auto i = d->asClass()->memberBegin(); i != d->asClass()->memberEnd(); ++i) {
+ Symbol *s = *i;
+ if (s->isProtected() || s->isPublic()) {
+ if (s->name()->match(d->name())) {
+ // we have found a constructor
+ Function *func = s->type().type()->asFunctionType();
+ if (!func)
+ continue;
+ const bool isFirst = parentClassConstructors.empty()
+ || parentClassConstructors.back().className
+ != className;
+ parentClassConstructors.emplace_back(className, parameterModel);
+ ParentClassConstructorInfo &constructor = parentClassConstructors.back();
+ constructor.declaration = className + o.prettyType(func->type());
+ constructor.declaration.replace("std::__1::__get_nullptr_t()",
+ "nullptr");
+ constructor.useInConstructor = isFirst;
+ for (auto arg = func->memberBegin(); arg != func->memberEnd(); ++arg) {
+ Symbol *param = *arg;
+ Argument *argument = param->asArgument();
+ if (!argument) // can also be a block
+ continue;
+ const QString name = o.prettyName(param->name());
+ const StringLiteral *ini = argument->initializer();
+ QString defaultValue;
+ if (ini)
+ defaultValue = QString::fromUtf8(ini->chars(), ini->size())
+ .replace("std::__1::__get_nullptr_t()",
+ "nullptr");
+ constructor.parameters.emplace_back(name,
+ defaultValue,
+ param,
+ &constructor);
+ // do not show constructors like QObject(QObjectPrivate & dd, ...)
+ ReferenceType *ref = param->type()->asReferenceType();
+ if (ref && name == "dd") {
+ auto type = o.prettyType(ref->elementType());
+ if (type.startsWith("Q") && type.endsWith("Private")) {
+ parentClassConstructors.pop_back();
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // add params to parameter lists
+ for (auto &c : parentClassConstructors)
+ if (c.useInConstructor)
+ for (auto &p : c.parameters)
+ if (p.init)
+ c.addParameter(p);
+ }
+
+ bool isApplicable() const
+ {
+ return parameterModel.rowCount() > 0
+ || Utils::anyOf(parentClassConstructors,
+ [](const auto &parent) { return !parent.parameters.empty(); });
+ }
+
+ void setTest(bool isTest = true) { m_test = isTest; }
+
+private:
+ void perform() override
+ {
+ auto infos = parameterModel.getInfos();
+
+ InsertionPointLocator::AccessSpec accessSpec = InsertionPointLocator::Public;
+ if (!m_test) {
+ GenerateConstructorDialog dlg(&parameterModel, parentClassConstructors);
+ if (dlg.exec() == QDialog::Rejected)
+ return;
+ accessSpec = dlg.accessSpec();
+ infos = parameterModel.getInfos();
+ } else {
+#ifdef WITH_TESTS
+ ParentClassesModel model(nullptr, parentClassConstructors);
+ QAbstractItemModelTester tester(&model);
+#endif
+ if (infos.size() >= 3) {
+ // if we are testing and have 3 or more members => change the order
+ // move first element to the back
+ infos.push_back(infos[0]);
+ infos.erase(infos.begin());
+ }
+ for (auto info : infos) {
+ if (info->memberVariableName.startsWith("di_"))
+ info->defaultValue = "42";
+ }
+ for (auto &c : parentClassConstructors) {
+ if (c.useInConstructor) {
+ for (auto &p : c.parameters) {
+ if (!p.init && p.parameterName.startsWith("use_")) {
+ infos.push_back(&p);
+ p.init = true;
+ }
+ }
+ }
+ }
+ }
+ if (infos.empty())
+ return;
+ struct GenerateConstructorRefactoringHelper : public GetterSetterRefactoringHelper
+ {
+ const ClassSpecifierAST *m_classAST;
+ InsertionPointLocator::AccessSpec m_accessSpec;
+ GenerateConstructorRefactoringHelper(CppQuickFixOperation *operation,
+ Class *clazz,
+ const ClassSpecifierAST *classAST,
+ InsertionPointLocator::AccessSpec accessSpec)
+ : GetterSetterRefactoringHelper(operation, clazz)
+ , m_classAST(classAST)
+ , m_accessSpec(accessSpec)
+ {}
+ void generateConstructor(std::vector<ConstructorMemberInfo *> members,
+ const ParentClassConstructors &parentClassConstructors)
+ {
+ auto constructorLocation = m_settings->determineSetterLocation(int(members.size()));
+ if (constructorLocation == CppQuickFixSettings::FunctionLocation::CppFile
+ && !hasSourceFile())
+ constructorLocation = CppQuickFixSettings::FunctionLocation::OutsideClass;
+
+ Overview overview = CppCodeStyleSettings::currentProjectCodeStyleOverview();
+ overview.showTemplateParameters = true;
+
+ InsertionLocation implLoc;
+ QString implCode;
+ CppRefactoringFilePtr implFile;
+ QString className = overview.prettyName(m_class->name());
+ QStringList insertedNamespaces;
+ if (constructorLocation == CppQuickFixSettings::FunctionLocation::CppFile) {
+ implLoc = sourceLocationFor(m_class, &insertedNamespaces);
+ implFile = m_sourceFile;
+ if (m_settings->rewriteTypesinCppFile())
+ implCode = symbolAt(m_class, m_sourceFile, implLoc);
+ else
+ implCode = className;
+ implCode += "::" + className + "(";
+ } else if (constructorLocation
+ == CppQuickFixSettings::FunctionLocation::OutsideClass) {
+ implLoc = insertLocationForMethodDefinition(m_class,
+ false,
+ NamespaceHandling::Ignore,
+ m_changes,
+ m_headerFile->filePath(),
+ &insertedNamespaces);
+ implFile = m_headerFile;
+ implCode = symbolAt(m_class, m_headerFile, implLoc);
+ implCode += "::" + className + "(";
+ }
+
+ QString inClassDeclaration = overview.prettyName(m_class->name()) + "(";
+ QString constructorBody = members.empty() ? QString(") {}") : QString(") : ");
+ for (auto &member : members) {
+ if (isValueType(member->symbol, &member->customValueType))
+ member->type.setConst(false);
+ else
+ member->type = makeConstRef(member->type);
+
+ inClassDeclaration += overview.prettyType(member->type, member->parameterName);
+ if (!member->defaultValue.isEmpty())
+ inClassDeclaration += " = " + member->defaultValue;
+ inClassDeclaration += ", ";
+ if (implFile) {
+ FullySpecifiedType type = typeAt(member->type,
+ m_class,
+ implFile,
+ implLoc,
+ insertedNamespaces);
+ implCode += overview.prettyType(type, member->parameterName) + ", ";
+ }
+ }
+ Utils::sort(members, &ConstructorMemberInfo::numberOfMember);
+ // first, do the base classes
+ for (const auto &parent : parentClassConstructors) {
+ if (!parent.useInConstructor)
+ continue;
+ // Check if we really need a constructor
+ if (Utils::anyOf(parent.parameters, [](const auto &param) {
+ return param.init || param.originalDefaultValue.isEmpty();
+ })) {
+ int defaultAtEndCount = 0;
+ for (auto i = parent.parameters.crbegin(); i != parent.parameters.crend();
+ ++i) {
+ if (i->init || i->originalDefaultValue.isEmpty())
+ break;
+ ++defaultAtEndCount;
+ }
+ const int numberOfParameters = static_cast<int>(parent.parameters.size())
+ - defaultAtEndCount;
+ constructorBody += parent.className + "(";
+ int counter = 0;
+ for (const auto &param : parent.parameters) {
+ if (++counter > numberOfParameters)
+ break;
+ if (param.init) {
+ if (param.customValueType)
+ constructorBody += "std::move(" + param.parameterName + ')';
+ else
+ constructorBody += param.parameterName;
+ } else if (!param.originalDefaultValue.isEmpty())
+ constructorBody += param.originalDefaultValue;
+ else
+ constructorBody += "/* insert value */";
+ constructorBody += ", ";
+ }
+ constructorBody.resize(constructorBody.length() - 2);
+ constructorBody += "),\n";
+ }
+ }
+ for (auto &member : members) {
+ if (member->parentClassConstructor)
+ continue;
+ QString param = member->parameterName;
+ if (member->customValueType)
+ param = "std::move(" + member->parameterName + ')';
+ constructorBody += member->memberVariableName + '(' + param + "),\n";
+ }
+ if (!members.empty()) {
+ inClassDeclaration.resize(inClassDeclaration.length() - 2);
+ constructorBody.remove(constructorBody.length() - 2, 1); // ..),\n => ..)\n
+ constructorBody += "{}";
+ if (!implCode.isEmpty())
+ implCode.resize(implCode.length() - 2);
+ }
+ implCode += constructorBody;
+
+ if (constructorLocation == CppQuickFixSettings::FunctionLocation::InsideClass)
+ inClassDeclaration += constructorBody;
+ else
+ inClassDeclaration += QLatin1String(");");
+
+ TranslationUnit *tu = m_headerFile->cppDocument()->translationUnit();
+ insertAndIndent(m_headerFile,
+ m_locator.constructorDeclarationInClass(tu,
+ m_classAST,
+ m_accessSpec,
+ int(members.size())),
+ inClassDeclaration);
+
+ if (constructorLocation == CppQuickFixSettings::FunctionLocation::CppFile) {
+ addSourceFileCode(implCode);
+ } else if (constructorLocation
+ == CppQuickFixSettings::FunctionLocation::OutsideClass) {
+ if (m_isHeaderHeaderFile)
+ implCode.prepend("inline ");
+ insertAndIndent(m_headerFile, implLoc, implCode);
+ }
+ }
+ };
+ GenerateConstructorRefactoringHelper helper(this,
+ m_classAST->symbol,
+ m_classAST,
+ accessSpec);
+
+ auto members = Utils::filtered(infos, [](const auto mi) {
+ return mi->init || mi->parentClassConstructor;
+ });
+ helper.generateConstructor(std::move(members), parentClassConstructors);
+ helper.applyChanges();
+ }
+
+ ConstructorParams parameterModel;
+ ParentClassConstructors parentClassConstructors;
+ const ClassSpecifierAST *m_classAST = nullptr;
+ bool m_test = false;
+};
+
+class GenerateGetterSetterOp : public CppQuickFixOperation
+{
+public:
+ enum GenerateFlag {
+ GenerateGetter = 1 << 0,
+ GenerateSetter = 1 << 1,
+ GenerateSignal = 1 << 2,
+ GenerateMemberVariable = 1 << 3,
+ GenerateReset = 1 << 4,
+ GenerateProperty = 1 << 5,
+ GenerateConstantProperty = 1 << 6,
+ HaveExistingQProperty = 1 << 7,
+ Invalid = -1,
+ };
+
+ GenerateGetterSetterOp(const CppQuickFixInterface &interface,
+ ExistingGetterSetterData data,
+ int generateFlags,
+ int priority,
+ const QString &description)
+ : CppQuickFixOperation(interface)
+ , m_generateFlags(generateFlags)
+ , m_data(data)
+ {
+ setDescription(description);
+ setPriority(priority);
+ }
+
+ static void generateQuickFixes(QuickFixOperations &results,
+ const CppQuickFixInterface &interface,
+ const ExistingGetterSetterData &data,
+ const int possibleFlags)
+ {
+ // flags can have the value HaveExistingQProperty or a combination of all other values
+ // of the enum 'GenerateFlag'
+ int p = 0;
+ if (possibleFlags & HaveExistingQProperty) {
+ const QString desc = Tr::tr("Generate Missing Q_PROPERTY Members");
+ results << new GenerateGetterSetterOp(interface, data, possibleFlags, ++p, desc);
+ } else {
+ if (possibleFlags & GenerateSetter) {
+ const QString desc = Tr::tr("Generate Setter");
+ results << new GenerateGetterSetterOp(interface, data, GenerateSetter, ++p, desc);
+ }
+ if (possibleFlags & GenerateGetter) {
+ const QString desc = Tr::tr("Generate Getter");
+ results << new GenerateGetterSetterOp(interface, data, GenerateGetter, ++p, desc);
+ }
+ if (possibleFlags & GenerateGetter && possibleFlags & GenerateSetter) {
+ const QString desc = Tr::tr("Generate Getter and Setter");
+ const int flags = GenerateGetter | GenerateSetter;
+ results << new GenerateGetterSetterOp(interface, data, flags, ++p, desc);
+ }
+
+ if (possibleFlags & GenerateConstantProperty) {
+ const QString desc = Tr::tr("Generate Constant Q_PROPERTY and Missing Members");
+ const int flags = possibleFlags & ~(GenerateSetter | GenerateSignal | GenerateReset);
+ results << new GenerateGetterSetterOp(interface, data, flags, ++p, desc);
+ }
+ if (possibleFlags & GenerateProperty) {
+ if (possibleFlags & GenerateReset) {
+ const QString desc = Tr::tr(
+ "Generate Q_PROPERTY and Missing Members with Reset Function");
+ const int flags = possibleFlags & ~GenerateConstantProperty;
+ results << new GenerateGetterSetterOp(interface, data, flags, ++p, desc);
+ }
+ const QString desc = Tr::tr("Generate Q_PROPERTY and Missing Members");
+ const int flags = possibleFlags & ~GenerateConstantProperty & ~GenerateReset;
+ results << new GenerateGetterSetterOp(interface, data, flags, ++p, desc);
+ }
+ }
+ }
+
+ void perform() override
+ {
+ GetterSetterRefactoringHelper helper(this, m_data.clazz);
+ helper.performGeneration(m_data, m_generateFlags);
+ helper.applyChanges();
+ }
+
+private:
+ int m_generateFlags;
+ ExistingGetterSetterData m_data;
+};
+
+class CandidateTreeItem : public TreeItem
+{
+public:
+ enum Column {
+ NameColumn,
+ GetterColumn,
+ SetterColumn,
+ SignalColumn,
+ ResetColumn,
+ QPropertyColumn,
+ ConstantQPropertyColumn
+ };
+ using Flag = GenerateGetterSetterOp::GenerateFlag;
+ constexpr static Flag ColumnFlag[] = {
+ Flag::Invalid,
+ Flag::GenerateGetter,
+ Flag::GenerateSetter,
+ Flag::GenerateSignal,
+ Flag::GenerateReset,
+ Flag::GenerateProperty,
+ Flag::GenerateConstantProperty,
+ };
+
+ CandidateTreeItem(MemberInfo *memberInfo)
+ : m_memberInfo(memberInfo)
+ {}
+
+private:
+ QVariant data(int column, int role) const override
+ {
+ if (role == Qt::DisplayRole && column == NameColumn)
+ return m_memberInfo->data.memberVariableName;
+ if (role == Qt::CheckStateRole && column > 0
+ && column <= static_cast<int>(std::size(ColumnFlag))) {
+ return m_memberInfo->requestedFlags & ColumnFlag[column] ? Qt::Checked : Qt::Unchecked;
+ }
+ return {};
+ }
+
+ bool setData(int column, const QVariant &data, int role) override
+ {
+ if (column < 1 || column > static_cast<int>(std::size(ColumnFlag)))
+ return false;
+ if (role != Qt::CheckStateRole)
+ return false;
+ if (!(m_memberInfo->possibleFlags & ColumnFlag[column]))
+ return false;
+ const bool nowChecked = data.toInt() == Qt::Checked;
+ if (nowChecked)
+ m_memberInfo->requestedFlags |= ColumnFlag[column];
+ else
+ m_memberInfo->requestedFlags &= ~ColumnFlag[column];
+
+ if (nowChecked) {
+ if (column == QPropertyColumn) {
+ m_memberInfo->requestedFlags |= Flag::GenerateGetter;
+ m_memberInfo->requestedFlags |= Flag::GenerateSetter;
+ m_memberInfo->requestedFlags |= Flag::GenerateSignal;
+ m_memberInfo->requestedFlags &= ~Flag::GenerateConstantProperty;
+ } else if (column == ConstantQPropertyColumn) {
+ m_memberInfo->requestedFlags |= Flag::GenerateGetter;
+ m_memberInfo->requestedFlags &= ~Flag::GenerateSetter;
+ m_memberInfo->requestedFlags &= ~Flag::GenerateSignal;
+ m_memberInfo->requestedFlags &= ~Flag::GenerateReset;
+ m_memberInfo->requestedFlags &= ~Flag::GenerateProperty;
+ } else if (column == SetterColumn || column == SignalColumn || column == ResetColumn) {
+ m_memberInfo->requestedFlags &= ~Flag::GenerateConstantProperty;
+ }
+ } else {
+ if (column == SignalColumn)
+ m_memberInfo->requestedFlags &= ~Flag::GenerateProperty;
+ }
+ for (int i = 0; i < 16; ++i) {
+ const bool allowed = m_memberInfo->possibleFlags & (1 << i);
+ if (!allowed)
+ m_memberInfo->requestedFlags &= ~(1 << i); // clear bit
+ }
+ update();
+ return true;
+ }
+
+ Qt::ItemFlags flags(int column) const override
+ {
+ if (column == NameColumn)
+ return Qt::ItemIsEnabled;
+ if (column < 1 || column > static_cast<int>(std::size(ColumnFlag)))
+ return {};
+ if (m_memberInfo->possibleFlags & ColumnFlag[column])
+ return Qt::ItemIsEnabled | Qt::ItemIsUserCheckable;
+ return {};
+ }
+
+ MemberInfo *const m_memberInfo;
+};
+
+class GenerateGettersSettersDialog : public QDialog
+{
+ static constexpr CandidateTreeItem::Column CheckBoxColumn[4]
+ = {CandidateTreeItem::Column::GetterColumn,
+ CandidateTreeItem::Column::SetterColumn,
+ CandidateTreeItem::Column::SignalColumn,
+ CandidateTreeItem::Column::QPropertyColumn};
+
+public:
+ GenerateGettersSettersDialog(const GetterSetterCandidates &candidates)
+ : QDialog()
+ , m_candidates(candidates)
+ {
+ using Flags = GenerateGetterSetterOp::GenerateFlag;
+ setWindowTitle(Tr::tr("Getters and Setters"));
+ const auto model = new TreeModel<TreeItem, CandidateTreeItem>(this);
+ model->setHeader(QStringList({
+ Tr::tr("Member"),
+ Tr::tr("Getter"),
+ Tr::tr("Setter"),
+ Tr::tr("Signal"),
+ Tr::tr("Reset"),
+ Tr::tr("QProperty"),
+ Tr::tr("Constant QProperty"),
+ }));
+ for (MemberInfo &candidate : m_candidates)
+ model->rootItem()->appendChild(new CandidateTreeItem(&candidate));
+ const auto view = new BaseTreeView(this);
+ view->setModel(model);
+ int optimalWidth = 0;
+ for (int i = 0; i < model->columnCount(QModelIndex{}); ++i) {
+ view->resizeColumnToContents(i);
+ optimalWidth += view->columnWidth(i);
+ }
+
+ const auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
+ connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
+ connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
+
+ const auto setCheckStateForAll = [model](int column, int checkState) {
+ for (int i = 0; i < model->rowCount(); ++i)
+ model->setData(model->index(i, column), checkState, Qt::CheckStateRole);
+ };
+ const auto preventPartiallyChecked = [](QCheckBox *checkbox) {
+ if (checkbox->checkState() == Qt::PartiallyChecked)
+ checkbox->setCheckState(Qt::Checked);
+ };
+ using Column = CandidateTreeItem::Column;
+ const auto createConnections = [this, setCheckStateForAll, preventPartiallyChecked](
+ QCheckBox *checkbox, Column column) {
+ connect(checkbox, &QCheckBox::stateChanged, this, [setCheckStateForAll, column](int state) {
+ if (state != Qt::PartiallyChecked)
+ setCheckStateForAll(column, state);
+ });
+ connect(checkbox, &QCheckBox::clicked, this, [checkbox, preventPartiallyChecked] {
+ preventPartiallyChecked(checkbox);
+ });
+ };
+ std::array<QCheckBox *, 4> checkBoxes = {};
+
+ static_assert(std::size(CheckBoxColumn) == checkBoxes.size(),
+ "Must contain the same number of elements");
+ for (std::size_t i = 0; i < checkBoxes.size(); ++i) {
+ if (Utils::anyOf(candidates, [i](const MemberInfo &mi) {
+ return mi.possibleFlags & CandidateTreeItem::ColumnFlag[CheckBoxColumn[i]];
+ })) {
+ const Column column = CheckBoxColumn[i];
+ if (column == Column::GetterColumn)
+ checkBoxes[i] = new QCheckBox(Tr::tr("Create getters for all members"));
+ else if (column == Column::SetterColumn)
+ checkBoxes[i] = new QCheckBox(Tr::tr("Create setters for all members"));
+ else if (column == Column::SignalColumn)
+ checkBoxes[i] = new QCheckBox(Tr::tr("Create signals for all members"));
+ else if (column == Column::QPropertyColumn)
+ checkBoxes[i] = new QCheckBox(Tr::tr("Create Q_PROPERTY for all members"));
+
+ createConnections(checkBoxes[i], column);
+ }
+ }
+ connect(model, &QAbstractItemModel::dataChanged, this, [this, checkBoxes] {
+ const auto countExisting = [this](Flags flag) {
+ return Utils::count(m_candidates, [flag](const MemberInfo &mi) {
+ return !(mi.possibleFlags & flag);
+ });
+ };
+ const auto countRequested = [this](Flags flag) {
+ return Utils::count(m_candidates, [flag](const MemberInfo &mi) {
+ return mi.requestedFlags & flag;
+ });
+ };
+ const auto countToState = [this](int requestedCount, int alreadyExistsCount) {
+ if (requestedCount == 0)
+ return Qt::Unchecked;
+ if (int(m_candidates.size()) - requestedCount == alreadyExistsCount)
+ return Qt::Checked;
+ return Qt::PartiallyChecked;
+ };
+ for (std::size_t i = 0; i < checkBoxes.size(); ++i) {
+ if (checkBoxes[i]) {
+ const Flags flag = CandidateTreeItem::ColumnFlag[CheckBoxColumn[i]];
+ checkBoxes[i]->setCheckState(
+ countToState(countRequested(flag), countExisting(flag)));
+ }
+ }
+ });
+
+ const auto mainLayout = new QVBoxLayout(this);
+ mainLayout->addWidget(new QLabel(Tr::tr("Select the getters and setters "
+ "to be created.")));
+ for (auto checkBox : checkBoxes) {
+ if (checkBox)
+ mainLayout->addWidget(checkBox);
+ }
+ mainLayout->addWidget(view);
+ mainLayout->addWidget(buttonBox);
+ int left, right;
+ mainLayout->getContentsMargins(&left, nullptr, &right, nullptr);
+ optimalWidth += left + right;
+ resize(optimalWidth, mainLayout->sizeHint().height());
+ }
+
+ GetterSetterCandidates candidates() const { return m_candidates; }
+
+private:
+ GetterSetterCandidates m_candidates;
+};
+
+class GenerateGettersSettersOperation : public CppQuickFixOperation
+{
+public:
+ GenerateGettersSettersOperation(const CppQuickFixInterface &interface)
+ : CppQuickFixOperation(interface)
+ {
+ setDescription(Tr::tr("Create Getter and Setter Member Functions"));
+
+ m_classAST = astForClassOperations(interface);
+ if (!m_classAST)
+ return;
+ Class * const theClass = m_classAST->symbol;
+ if (!theClass)
+ return;
+
+ // Go through all data members and try to find out whether they have getters and/or setters.
+ QList<Symbol *> dataMembers;
+ QList<Symbol *> memberFunctions;
+ for (auto it = theClass->memberBegin(); it != theClass->memberEnd(); ++it) {
+ Symbol *const s = *it;
+ if (!s->identifier() || !s->type() || s->type().isTypedef())
+ continue;
+ if ((s->asDeclaration() && s->type()->asFunctionType()) || s->asFunction())
+ memberFunctions << s;
+ else if (s->asDeclaration() && (s->isPrivate() || s->isProtected()))
+ dataMembers << s;
+ }
+
+ auto file = interface.currentFile();
+ QStringList qPropertyNames; // name after MEMBER or name of the property
+ for (auto it = m_classAST->member_specifier_list; it; it = it->next) {
+ if (it->value->asQtPropertyDeclaration()) {
+ auto propDecl = it->value->asQtPropertyDeclaration();
+ // iterator over 'READ ...', ... and check if we have a MEMBER
+ for (auto p = propDecl->property_declaration_item_list; p; p = p->next) {
+ const char *tokenString = file->tokenAt(p->value->item_name_token).spell();
+ if (!qstrcmp(tokenString, "MEMBER"))
+ qPropertyNames << file->textOf(p->value->expression);
+ }
+ // no MEMBER, but maybe the property name is the same
+ qPropertyNames << file->textOf(propDecl->property_name);
+ }
+ }
+ const QStringList memberFunctionsAsStrings = toStringList(memberFunctions);
+
+ for (Symbol *const member : std::as_const(dataMembers)) {
+ ExistingGetterSetterData existing;
+ existing.memberVariableName = QString::fromUtf8(member->identifier()->chars(),
+ member->identifier()->size());
+ existing.declarationSymbol = member->asDeclaration();
+ existing.clazz = theClass;
+
+ // check if a Q_PROPERTY exist
+ const QString baseName = memberBaseName(existing.memberVariableName);
+ if (qPropertyNames.contains(baseName)
+ || qPropertyNames.contains(existing.memberVariableName))
+ continue;
+
+ findExistingFunctions(existing, memberFunctionsAsStrings);
+ existing.qPropertyName = baseName;
+
+ int possibleFlags = existing.computePossibleFlags();
+ if (possibleFlags == 0)
+ continue;
+ m_candidates.emplace_back(existing, possibleFlags);
+ }
+ }
+
+ GetterSetterCandidates candidates() const { return m_candidates; }
+ bool isApplicable() const { return !m_candidates.empty(); }
+
+ void setGetterSetterData(const GetterSetterCandidates &data)
+ {
+ m_candidates = data;
+ m_hasData = true;
+ }
+
+private:
+ void perform() override
+ {
+ if (!m_hasData) {
+ GenerateGettersSettersDialog dlg(m_candidates);
+ if (dlg.exec() == QDialog::Rejected)
+ return;
+ m_candidates = dlg.candidates();
+ }
+ if (m_candidates.empty())
+ return;
+ GetterSetterRefactoringHelper helper(this, m_candidates.front().data.clazz);
+ for (MemberInfo &mi : m_candidates) {
+ if (mi.requestedFlags != 0) {
+ helper.performGeneration(mi.data, mi.requestedFlags);
+ }
+ }
+ helper.applyChanges();
+ }
+
+ GetterSetterCandidates m_candidates;
+ const ClassSpecifierAST *m_classAST = nullptr;
+ bool m_hasData = false;
+};
+
+int ExistingGetterSetterData::computePossibleFlags() const
+{
+ const bool isConst = declarationSymbol->type().isConst();
+ const bool isStatic = declarationSymbol->type().isStatic();
+ using Flag = GenerateGetterSetterOp::GenerateFlag;
+ int generateFlags = 0;
+ if (getterName.isEmpty())
+ generateFlags |= Flag::GenerateGetter;
+ if (!isConst) {
+ if (resetName.isEmpty())
+ generateFlags |= Flag::GenerateReset;
+ if (!isStatic && signalName.isEmpty() && setterName.isEmpty())
+ generateFlags |= Flag::GenerateSignal;
+ if (setterName.isEmpty())
+ generateFlags |= Flag::GenerateSetter;
+ }
+ if (!isStatic) {
+ const bool hasSignal = !signalName.isEmpty() || generateFlags & Flag::GenerateSignal;
+ if (!isConst && hasSignal)
+ generateFlags |= Flag::GenerateProperty;
+ }
+ if (setterName.isEmpty() && signalName.isEmpty())
+ generateFlags |= Flag::GenerateConstantProperty;
+ return generateFlags;
+}
+
+void GetterSetterRefactoringHelper::performGeneration(ExistingGetterSetterData data, int generateFlags)
+{
+ using Flag = GenerateGetterSetterOp::GenerateFlag;
+
+ if (generateFlags & Flag::GenerateGetter && data.getterName.isEmpty()) {
+ data.getterName = m_settings->getGetterName(data.qPropertyName);
+ if (data.getterName == data.memberVariableName) {
+ data.getterName = "get" + data.memberVariableName.left(1).toUpper()
+ + data.memberVariableName.mid(1);
+ }
+ }
+ if (generateFlags & Flag::GenerateSetter && data.setterName.isEmpty())
+ data.setterName = m_settings->getSetterName(data.qPropertyName);
+ if (generateFlags & Flag::GenerateSignal && data.signalName.isEmpty())
+ data.signalName = m_settings->getSignalName(data.qPropertyName);
+ if (generateFlags & Flag::GenerateReset && data.resetName.isEmpty())
+ data.resetName = m_settings->getResetName(data.qPropertyName);
+
+ FullySpecifiedType memberVariableType = data.declarationSymbol->type();
+ memberVariableType.setConst(false);
+ const bool isMemberVariableStatic = memberVariableType.isStatic();
+ memberVariableType.setStatic(false);
+ Overview overview = CppCodeStyleSettings::currentProjectCodeStyleOverview();
+ overview.showTemplateParameters = false;
+ // TODO does not work with using. e.g. 'using foo = std::unique_ptr<int>'
+ // TODO must be fully qualified
+ auto getSetTemplate = m_settings->findGetterSetterTemplate(overview.prettyType(memberVariableType));
+ overview.showTemplateParameters = true;
+
+ // Ok... - If a type is a Named type we have to search recusive for the real type
+ const bool isValueType = this->isValueType(memberVariableType,
+ data.declarationSymbol->enclosingScope());
+ const FullySpecifiedType parameterType = isValueType ? memberVariableType
+ : makeConstRef(memberVariableType);
+
+ QString baseName = memberBaseName(data.memberVariableName);
+ if (baseName.isEmpty())
+ baseName = data.memberVariableName;
+
+ const QString parameterName = m_settings->getSetterParameterName(baseName);
+ if (parameterName == data.memberVariableName)
+ data.memberVariableName = "this->" + data.memberVariableName;
+
+ getSetTemplate.replacePlaceholders(data.memberVariableName, parameterName);
+
+ using Pattern = CppQuickFixSettings::GetterSetterTemplate;
+ std::optional<FullySpecifiedType> returnTypeTemplateParameter;
+ if (getSetTemplate.returnTypeTemplate.has_value()) {
+ QString returnTypeTemplate = getSetTemplate.returnTypeTemplate.value();
+ if (returnTypeTemplate.contains(Pattern::TEMPLATE_PARAMETER_PATTERN)) {
+ returnTypeTemplateParameter = getFirstTemplateParameter(data.declarationSymbol->type());
+ if (!returnTypeTemplateParameter.has_value())
+ return; // Maybe report error to the user
+ }
+ }
+ const FullySpecifiedType returnTypeHeader = [&] {
+ if (!getSetTemplate.returnTypeTemplate.has_value())
+ return m_settings->returnByConstRef ? parameterType : memberVariableType;
+ QString typeTemplate = getSetTemplate.returnTypeTemplate.value();
+ if (returnTypeTemplateParameter.has_value())
+ typeTemplate.replace(Pattern::TEMPLATE_PARAMETER_PATTERN,
+ overview.prettyType(returnTypeTemplateParameter.value()));
+ if (typeTemplate.contains(Pattern::TYPE_PATTERN))
+ typeTemplate.replace(Pattern::TYPE_PATTERN,
+ overview.prettyType(data.declarationSymbol->type()));
+ Control *control = m_operation->currentFile()->cppDocument()->control();
+ std::string utf8TypeName = typeTemplate.toUtf8().toStdString();
+ return FullySpecifiedType(control->namedType(control->identifier(utf8TypeName.c_str())));
+ }();
+
+ // getter declaration
+ if (generateFlags & Flag::GenerateGetter) {
+ // maybe we added 'this->' to memberVariableName because of a collision with parameterName
+ // but here the 'this->' is not needed
+ const QString returnExpression = QString{getSetTemplate.returnExpression}.replace("this->",
+ "");
+ QString getterInClassDeclaration = overview.prettyType(returnTypeHeader, data.getterName)
+ + QLatin1String("()");
+ if (isMemberVariableStatic)
+ getterInClassDeclaration.prepend(QLatin1String("static "));
+ else
+ getterInClassDeclaration += QLatin1String(" const");
+ getterInClassDeclaration.prepend(m_settings->getterAttributes + QLatin1Char(' '));
+
+ auto getterLocation = m_settings->determineGetterLocation(1);
+ // if we have an anonymous class we must add code inside the class
+ if (data.clazz->name()->asAnonymousNameId())
+ getterLocation = CppQuickFixSettings::FunctionLocation::InsideClass;
+
+ if (getterLocation == CppQuickFixSettings::FunctionLocation::InsideClass) {
+ getterInClassDeclaration += QLatin1String("\n{\nreturn ") + returnExpression
+ + QLatin1String(";\n}\n");
+ } else {
+ getterInClassDeclaration += QLatin1String(";\n");
+ }
+ addHeaderCode(InsertionPointLocator::Public, getterInClassDeclaration);
+ if (getterLocation == CppQuickFixSettings::FunctionLocation::CppFile && !hasSourceFile())
+ getterLocation = CppQuickFixSettings::FunctionLocation::OutsideClass;
+
+ if (getterLocation != CppQuickFixSettings::FunctionLocation::InsideClass) {
+ const auto getReturnTypeAt = [&](CppRefactoringFilePtr targetFile,
+ InsertionLocation targetLoc) {
+ if (getSetTemplate.returnTypeTemplate.has_value()) {
+ QString returnType = getSetTemplate.returnTypeTemplate.value();
+ if (returnTypeTemplateParameter.has_value()) {
+ const QString templateTypeName = overview.prettyType(typeAt(
+ returnTypeTemplateParameter.value(), data.clazz, targetFile, targetLoc));
+ returnType.replace(Pattern::TEMPLATE_PARAMETER_PATTERN, templateTypeName);
+ }
+ if (returnType.contains(Pattern::TYPE_PATTERN)) {
+ const QString declarationType = overview.prettyType(
+ typeAt(memberVariableType, data.clazz, targetFile, targetLoc));
+ returnType.replace(Pattern::TYPE_PATTERN, declarationType);
+ }
+ Control *control = m_operation->currentFile()->cppDocument()->control();
+ std::string utf8String = returnType.toUtf8().toStdString();
+ return FullySpecifiedType(
+ control->namedType(control->identifier(utf8String.c_str())));
+ } else {
+ FullySpecifiedType returnType = typeAt(memberVariableType,
+ data.clazz,
+ targetFile,
+ targetLoc);
+ if (m_settings->returnByConstRef && !isValueType)
+ return makeConstRef(returnType);
+ return returnType;
+ }
+ };
+ const QString constSpec = isMemberVariableStatic ? QLatin1String("")
+ : QLatin1String(" const");
+ if (getterLocation == CppQuickFixSettings::FunctionLocation::CppFile) {
+ InsertionLocation loc = sourceLocationFor(data.declarationSymbol);
+ FullySpecifiedType returnType;
+ QString clazz;
+ if (m_settings->rewriteTypesinCppFile()) {
+ returnType = getReturnTypeAt(m_sourceFile, loc);
+ clazz = symbolAt(data.clazz, m_sourceFile, loc);
+ } else {
+ returnType = returnTypeHeader;
+ const Identifier *identifier = data.clazz->name()->identifier();
+ clazz = QString::fromUtf8(identifier->chars(), identifier->size());
+ }
+ const QString code = overview.prettyType(returnType, clazz + "::" + data.getterName)
+ + "()" + constSpec + "\n{\nreturn " + returnExpression + ";\n}";
+ addSourceFileCode(code);
+ } else if (getterLocation == CppQuickFixSettings::FunctionLocation::OutsideClass) {
+ InsertionLocation loc
+ = insertLocationForMethodDefinition(data.declarationSymbol,
+ false,
+ NamespaceHandling::Ignore,
+ m_changes,
+ m_headerFile->filePath());
+ const FullySpecifiedType returnType = getReturnTypeAt(m_headerFile, loc);
+ const QString clazz = symbolAt(data.clazz, m_headerFile, loc);
+ QString code = overview.prettyType(returnType, clazz + "::" + data.getterName)
+ + "()" + constSpec + "\n{\nreturn " + returnExpression + ";\n}";
+ if (m_isHeaderHeaderFile)
+ code.prepend("inline ");
+ insertAndIndent(m_headerFile, loc, code);
+ }
+ }
+ }
+
+ // setter declaration
+ InsertionPointLocator::AccessSpec setterAccessSpec = InsertionPointLocator::Public;
+ if (m_settings->setterAsSlot) {
+ const QByteArray connectName = "connect";
+ const Identifier connectId(connectName.data(), connectName.size());
+ const QList<LookupItem> items = m_operation->context().lookup(&connectId, data.clazz);
+ for (const LookupItem &item : items) {
+ if (item.declaration() && item.declaration()->enclosingClass()
+ && overview.prettyName(item.declaration()->enclosingClass()->name())
+ == "QObject") {
+ setterAccessSpec = InsertionPointLocator::PublicSlot;
+ break;
+ }
+ }
+ }
+ const auto createSetterBodyWithSignal = [this, &getSetTemplate, &data] {
+ QString body;
+ QTextStream setter(&body);
+ setter << "if (" << getSetTemplate.equalComparison << ")\nreturn;\n";
+
+ setter << getSetTemplate.assignment << ";\n";
+ if (m_settings->signalWithNewValue)
+ setter << "emit " << data.signalName << "(" << getSetTemplate.returnExpression << ");\n";
+ else
+ setter << "emit " << data.signalName << "();\n";
+
+ return body;
+ };
+ if (generateFlags & Flag::GenerateSetter) {
+ QString headerDeclaration = "void " + data.setterName + '('
+ + overview.prettyType(addConstToReference(parameterType),
+ parameterName)
+ + ")";
+ if (isMemberVariableStatic)
+ headerDeclaration.prepend("static ");
+ QString body = "\n{\n";
+ if (data.signalName.isEmpty())
+ body += getSetTemplate.assignment + ";\n";
+ else
+ body += createSetterBodyWithSignal();
+
+ body += "}";
+
+ auto setterLocation = m_settings->determineSetterLocation(body.count('\n') - 2);
+ // if we have an anonymous class we must add code inside the class
+ if (data.clazz->name()->asAnonymousNameId())
+ setterLocation = CppQuickFixSettings::FunctionLocation::InsideClass;
+
+ if (setterLocation == CppQuickFixSettings::FunctionLocation::CppFile && !hasSourceFile())
+ setterLocation = CppQuickFixSettings::FunctionLocation::OutsideClass;
+
+ if (setterLocation == CppQuickFixSettings::FunctionLocation::InsideClass) {
+ headerDeclaration += body;
+ } else {
+ headerDeclaration += ";\n";
+ if (setterLocation == CppQuickFixSettings::FunctionLocation::CppFile) {
+ InsertionLocation loc = sourceLocationFor(data.declarationSymbol);
+ QString clazz;
+ FullySpecifiedType newParameterType = parameterType;
+ if (m_settings->rewriteTypesinCppFile()) {
+ newParameterType = typeAt(memberVariableType, data.clazz, m_sourceFile, loc);
+ if (!isValueType)
+ newParameterType = makeConstRef(newParameterType);
+ clazz = symbolAt(data.clazz, m_sourceFile, loc);
+ } else {
+ const Identifier *identifier = data.clazz->name()->identifier();
+ clazz = QString::fromUtf8(identifier->chars(), identifier->size());
+ }
+ newParameterType = addConstToReference(newParameterType);
+ const QString code = "void " + clazz + "::" + data.setterName + '('
+ + overview.prettyType(newParameterType, parameterName) + ')'
+ + body;
+ addSourceFileCode(code);
+ } else if (setterLocation == CppQuickFixSettings::FunctionLocation::OutsideClass) {
+ InsertionLocation loc
+ = insertLocationForMethodDefinition(data.declarationSymbol,
+ false,
+ NamespaceHandling::Ignore,
+ m_changes,
+ m_headerFile->filePath());
+
+ FullySpecifiedType newParameterType = typeAt(data.declarationSymbol->type(),
+ data.clazz,
+ m_headerFile,
+ loc);
+ if (!isValueType)
+ newParameterType = makeConstRef(newParameterType);
+ newParameterType = addConstToReference(newParameterType);
+ QString clazz = symbolAt(data.clazz, m_headerFile, loc);
+
+ QString code = "void " + clazz + "::" + data.setterName + '('
+ + overview.prettyType(newParameterType, parameterName) + ')' + body;
+ if (m_isHeaderHeaderFile)
+ code.prepend("inline ");
+ insertAndIndent(m_headerFile, loc, code);
+ }
+ }
+ addHeaderCode(setterAccessSpec, headerDeclaration);
+ }
+
+ // reset declaration
+ if (generateFlags & Flag::GenerateReset) {
+ QString headerDeclaration = "void " + data.resetName + "()";
+ if (isMemberVariableStatic)
+ headerDeclaration.prepend("static ");
+ QString body = "\n{\n";
+ if (!data.setterName.isEmpty()) {
+ body += data.setterName + "({}); // TODO: Adapt to use your actual default value\n";
+ } else {
+ body += "static $TYPE defaultValue{}; "
+ "// TODO: Adapt to use your actual default value\n";
+ if (data.signalName.isEmpty())
+ body += getSetTemplate.assignment + ";\n";
+ else
+ body += createSetterBodyWithSignal();
+ }
+ body += "}";
+
+ // the template use <parameterName> as new value name, but we want to use 'defaultValue'
+ body.replace(QRegularExpression("\\b" + parameterName + "\\b"), "defaultValue");
+ // body.count('\n') - 2 : do not count the 2 at start
+ auto resetLocation = m_settings->determineSetterLocation(body.count('\n') - 2);
+ // if we have an anonymous class we must add code inside the class
+ if (data.clazz->name()->asAnonymousNameId())
+ resetLocation = CppQuickFixSettings::FunctionLocation::InsideClass;
+
+ if (resetLocation == CppQuickFixSettings::FunctionLocation::CppFile && !hasSourceFile())
+ resetLocation = CppQuickFixSettings::FunctionLocation::OutsideClass;
+
+ if (resetLocation == CppQuickFixSettings::FunctionLocation::InsideClass) {
+ headerDeclaration += body.replace("$TYPE", overview.prettyType(memberVariableType));
+ } else {
+ headerDeclaration += ";\n";
+ if (resetLocation == CppQuickFixSettings::FunctionLocation::CppFile) {
+ const InsertionLocation loc = sourceLocationFor(data.declarationSymbol);
+ QString clazz;
+ FullySpecifiedType type = memberVariableType;
+ if (m_settings->rewriteTypesinCppFile()) {
+ type = typeAt(memberVariableType, data.clazz, m_sourceFile, loc);
+ clazz = symbolAt(data.clazz, m_sourceFile, loc);
+ } else {
+ const Identifier *identifier = data.clazz->name()->identifier();
+ clazz = QString::fromUtf8(identifier->chars(), identifier->size());
+ }
+ const QString code = "void " + clazz + "::" + data.resetName + "()"
+ + body.replace("$TYPE", overview.prettyType(type));
+ addSourceFileCode(code);
+ } else if (resetLocation == CppQuickFixSettings::FunctionLocation::OutsideClass) {
+ const InsertionLocation loc = insertLocationForMethodDefinition(
+ data.declarationSymbol,
+ false,
+ NamespaceHandling::Ignore,
+ m_changes,
+ m_headerFile->filePath());
+ const FullySpecifiedType type = typeAt(data.declarationSymbol->type(),
+ data.clazz,
+ m_headerFile,
+ loc);
+ const QString clazz = symbolAt(data.clazz, m_headerFile, loc);
+ QString code = "void " + clazz + "::" + data.resetName + "()"
+ + body.replace("$TYPE", overview.prettyType(type));
+ if (m_isHeaderHeaderFile)
+ code.prepend("inline ");
+ insertAndIndent(m_headerFile, loc, code);
+ }
+ }
+ addHeaderCode(setterAccessSpec, headerDeclaration);
+ }
+
+ // signal declaration
+ if (generateFlags & Flag::GenerateSignal) {
+ const auto &parameter = overview.prettyType(returnTypeHeader, data.qPropertyName);
+ const QString newValue = m_settings->signalWithNewValue ? parameter : QString();
+ const QString declaration = QString("void %1(%2);\n").arg(data.signalName, newValue);
+ addHeaderCode(InsertionPointLocator::Signals, declaration);
+ }
+
+ // member variable
+ if (generateFlags & Flag::GenerateMemberVariable) {
+ QString storageDeclaration = overview.prettyType(memberVariableType, data.memberVariableName);
+ if (memberVariableType->asPointerType()
+ && m_operation->semanticInfo().doc->translationUnit()->languageFeatures().cxx11Enabled) {
+ storageDeclaration.append(" = nullptr");
+ }
+ storageDeclaration.append(";\n");
+ addHeaderCode(InsertionPointLocator::Private, storageDeclaration);
+ }
+
+ // Q_PROPERTY
+ if (generateFlags & Flag::GenerateProperty || generateFlags & Flag::GenerateConstantProperty) {
+ // Use the returnTypeHeader as base because of custom types in getSetTemplates.
+ // Remove const reference from type.
+ FullySpecifiedType type = returnTypeHeader;
+ if (ReferenceType *ref = type.type()->asReferenceType())
+ type = ref->elementType();
+ type.setConst(false);
+
+ QString propertyDeclaration = QLatin1String("Q_PROPERTY(")
+ + overview.prettyType(type,
+ memberBaseName(data.memberVariableName));
+ bool needMember = false;
+ if (data.getterName.isEmpty())
+ needMember = true;
+ else
+ propertyDeclaration += QLatin1String(" READ ") + data.getterName;
+ if (generateFlags & Flag::GenerateConstantProperty) {
+ if (needMember)
+ propertyDeclaration += QLatin1String(" MEMBER ") + data.memberVariableName;
+ propertyDeclaration.append(QLatin1String(" CONSTANT"));
+ } else {
+ if (data.setterName.isEmpty()) {
+ needMember = true;
+ } else if (!getSetTemplate.returnTypeTemplate.has_value()) {
+ // if the return type of the getter and then Q_PROPERTY is different than
+ // the setter type, we should not add WRITE to the Q_PROPERTY
+ propertyDeclaration.append(QLatin1String(" WRITE ")).append(data.setterName);
+ }
+ if (needMember)
+ propertyDeclaration += QLatin1String(" MEMBER ") + data.memberVariableName;
+ if (!data.resetName.isEmpty())
+ propertyDeclaration += QLatin1String(" RESET ") + data.resetName;
+ propertyDeclaration.append(QLatin1String(" NOTIFY ")).append(data.signalName);
+ }
+
+ propertyDeclaration.append(QLatin1String(" FINAL)\n"));
+ addHeaderCode(InsertionPointLocator::Private, propertyDeclaration);
+ }
+}
+
+//! Generate constructor
+class GenerateConstructor : public CppQuickFixFactory
+{
+public:
+#ifdef WITH_TESTS
+ static QObject* createTest();
+#endif
+
+protected:
+ void setTest() { m_test = true; }
+
+private:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ const auto op = QSharedPointer<GenerateConstructorOperation>::create(interface);
+ if (!op->isApplicable())
+ return;
+ op->setTest(m_test);
+ result << op;
+ }
+
+ bool m_test = false;
+};
+
+//! Adds getter and setter functions for a member variable
+class GenerateGetterSetter : public CppQuickFixFactory
+{
+public:
+#ifdef WITH_TESTS
+ static QObject* createTest();
+#endif
+
+private:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ ExistingGetterSetterData existing;
+
+ const QList<AST *> &path = interface.path();
+ // We expect something like
+ // [0] TranslationUnitAST
+ // [1] NamespaceAST
+ // [2] LinkageBodyAST
+ // [3] SimpleDeclarationAST
+ // [4] ClassSpecifierAST
+ // [5] SimpleDeclarationAST
+ // [6] DeclaratorAST
+ // [7] DeclaratorIdAST
+ // [8] SimpleNameAST
+
+ const int n = path.size();
+ if (n < 6)
+ return;
+
+ int i = 1;
+ const auto variableNameAST = path.at(n - i++)->asSimpleName();
+ const auto declaratorId = path.at(n - i++)->asDeclaratorId();
+ // DeclaratorAST might be preceded by PointerAST, e.g. for the case
+ // "class C { char *@s; };", where '@' denotes the text cursor position.
+ auto declarator = path.at(n - i++)->asDeclarator();
+ if (!declarator) {
+ --i;
+ if (path.at(n - i++)->asPointer()) {
+ if (n < 7)
+ return;
+ declarator = path.at(n - i++)->asDeclarator();
+ }
+ if (!declarator)
+ return;
+ }
+ const auto variableDecl = path.at(n - i++)->asSimpleDeclaration();
+ const auto classSpecifier = path.at(n - i++)->asClassSpecifier();
+ const auto classDecl = path.at(n - i++)->asSimpleDeclaration();
+
+ if (!(variableNameAST && declaratorId && variableDecl && classSpecifier && classDecl))
+ return;
+
+ // Do not get triggered on member functconstions and arrays
+ if (declarator->postfix_declarator_list) {
+ return;
+ }
+
+ // Construct getter and setter names
+ const Name *variableName = variableNameAST->name;
+ if (!variableName) {
+ return;
+ }
+ const Identifier *variableId = variableName->identifier();
+ if (!variableId) {
+ return;
+ }
+ existing.memberVariableName = QString::fromUtf8(variableId->chars(), variableId->size());
+
+ // Find the right symbol (for typeName) in the simple declaration
+ Symbol *symbol = nullptr;
+ const List<Symbol *> *symbols = variableDecl->symbols;
+ QTC_ASSERT(symbols, return );
+ for (; symbols; symbols = symbols->next) {
+ Symbol *s = symbols->value;
+ if (const Name *name = s->name()) {
+ if (const Identifier *id = name->identifier()) {
+ const QString symbolName = QString::fromUtf8(id->chars(), id->size());
+ if (symbolName == existing.memberVariableName) {
+ symbol = s;
+ break;
+ }
+ }
+ }
+ }
+ if (!symbol) {
+ // no type can be determined
+ return;
+ }
+ if (!symbol->asDeclaration()) {
+ return;
+ }
+ existing.declarationSymbol = symbol->asDeclaration();
+
+ existing.clazz = classSpecifier->symbol;
+ if (!existing.clazz)
+ return;
+
+ auto file = interface.currentFile();
+ // check if a Q_PROPERTY exist
+ const QString baseName = memberBaseName(existing.memberVariableName);
+ // eg: we have 'int m_test' and now 'Q_PROPERTY(int foo WRITE setTest MEMBER m_test NOTIFY tChanged)'
+ for (auto it = classSpecifier->member_specifier_list; it; it = it->next) {
+ if (it->value->asQtPropertyDeclaration()) {
+ auto propDecl = it->value->asQtPropertyDeclaration();
+ // iterator over 'READ ...', ...
+ auto p = propDecl->property_declaration_item_list;
+ // first check, if we have a MEMBER and the member is equal to the baseName
+ for (; p; p = p->next) {
+ const char *tokenString = file->tokenAt(p->value->item_name_token).spell();
+ if (!qstrcmp(tokenString, "MEMBER")) {
+ if (baseName == file->textOf(p->value->expression))
+ return;
+ }
+ }
+ // no MEMBER, but maybe the property name is the same
+ const QString propertyName = file->textOf(propDecl->property_name);
+ // we compare the baseName. e.g. 'test' instead of 'm_test'
+ if (propertyName == baseName)
+ return; // TODO Maybe offer quick fix "Add missing Q_PROPERTY Members"
+ }
+ }
+
+ findExistingFunctions(existing, toStringList(getMemberFunctions(existing.clazz)));
+ existing.qPropertyName = memberBaseName(existing.memberVariableName);
+
+ const int possibleFlags = existing.computePossibleFlags();
+ GenerateGetterSetterOp::generateQuickFixes(result, interface, existing, possibleFlags);
+ }
+};
+
+//! Adds getter and setter functions for several member variables
+class GenerateGettersSettersForClass : public CppQuickFixFactory
+{
+public:
+#ifdef WITH_TESTS
+ static QObject* createTest();
+#endif
+
+protected:
+ void setTest() { m_test = true; }
+
+private:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ const auto op = QSharedPointer<GenerateGettersSettersOperation>::create(interface);
+ if (!op->isApplicable())
+ return;
+ if (m_test) {
+ GetterSetterCandidates candidates = op->candidates();
+ for (MemberInfo &mi : candidates) {
+ mi.requestedFlags = mi.possibleFlags;
+ using Flag = GenerateGetterSetterOp::GenerateFlag;
+ mi.requestedFlags &= ~Flag::GenerateConstantProperty;
+ }
+ op->setGetterSetterData(candidates);
+ }
+ result << op;
+ }
+
+ bool m_test = false;
+};
+
+//! Adds missing members for a Q_PROPERTY
+class InsertQtPropertyMembers : public CppQuickFixFactory
+{
+public:
+#ifdef WITH_TESTS
+ static QObject* createTest();
+#endif
+
+private:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ using Flag = GenerateGetterSetterOp::GenerateFlag;
+ ExistingGetterSetterData existing;
+ // check for Q_PROPERTY
+
+ const QList<AST *> &path = interface.path();
+ if (path.isEmpty())
+ return;
+
+ AST *const ast = path.last();
+ QtPropertyDeclarationAST *qtPropertyDeclaration = ast->asQtPropertyDeclaration();
+ if (!qtPropertyDeclaration || !qtPropertyDeclaration->type_id)
+ return;
+
+ ClassSpecifierAST *klass = nullptr;
+ for (int i = path.size() - 2; i >= 0; --i) {
+ klass = path.at(i)->asClassSpecifier();
+ if (klass)
+ break;
+ }
+ if (!klass)
+ return;
+ existing.clazz = klass->symbol;
+
+ CppRefactoringFilePtr file = interface.currentFile();
+ const QString propertyName = file->textOf(qtPropertyDeclaration->property_name);
+ existing.qPropertyName = propertyName;
+ extractNames(file, qtPropertyDeclaration, existing);
+
+ Control *control = interface.currentFile()->cppDocument()->control();
+
+ existing.declarationSymbol = control->newDeclaration(ast->firstToken(),
+ qtPropertyDeclaration->property_name->name);
+ existing.declarationSymbol->setVisibility(Symbol::Private);
+ existing.declarationSymbol->setEnclosingScope(existing.clazz);
+
+ {
+ // create a 'right' Type Object
+ // if we have Q_PROPERTY(int test ...) then we only get a NamedType for 'int', but we want
+ // a IntegerType. So create a new dummy file with a dummy declaration to get the right
+ // object
+ QByteArray type = file->textOf(qtPropertyDeclaration->type_id).toUtf8();
+ QByteArray newSource = file->document()
+ ->toPlainText()
+ .insert(file->startOf(qtPropertyDeclaration),
+ QString::fromUtf8(type + " __dummy;\n"))
+ .toUtf8();
+
+ Document::Ptr doc = interface.snapshot().preprocessedDocument(newSource, "___quickfix.h");
+ if (!doc->parse(Document::ParseTranlationUnit))
+ return;
+ doc->check();
+ class TypeFinder : public ASTVisitor
+ {
+ public:
+ FullySpecifiedType type;
+ TypeFinder(TranslationUnit *u)
+ : ASTVisitor(u)
+ {}
+ bool visit(SimpleDeclarationAST *ast) override
+ {
+ if (ast->symbols && !ast->symbols->next) {
+ const Name *name = ast->symbols->value->name();
+ if (name && name->asNameId() && name->asNameId()->identifier()) {
+ const Identifier *id = name->asNameId()->identifier();
+ if (QString::fromUtf8(id->chars(), id->size()) == "__dummy")
+ type = ast->symbols->value->type();
+ }
+ }
+ return true;
+ }
+ };
+ TypeFinder finder(doc->translationUnit());
+ finder.accept(doc->translationUnit()->ast());
+ if (finder.type.type()->isUndefinedType())
+ return;
+ existing.declarationSymbol->setType(finder.type);
+ existing.doc = doc; // to hold type
+ }
+ // check which methods are already there
+ const bool haveFixMemberVariableName = !existing.memberVariableName.isEmpty();
+ int generateFlags = Flag::GenerateMemberVariable;
+ if (!existing.resetName.isEmpty())
+ generateFlags |= Flag::GenerateReset;
+ if (!existing.setterName.isEmpty())
+ generateFlags |= Flag::GenerateSetter;
+ if (!existing.getterName.isEmpty())
+ generateFlags |= Flag::GenerateGetter;
+ if (!existing.signalName.isEmpty())
+ generateFlags |= Flag::GenerateSignal;
+ Overview overview;
+ for (int i = 0; i < existing.clazz->memberCount(); ++i) {
+ Symbol *member = existing.clazz->memberAt(i);
+ FullySpecifiedType type = member->type();
+ if (member->asFunction() || (type.isValid() && type->asFunctionType())) {
+ const QString name = overview.prettyName(member->name());
+ if (name == existing.getterName)
+ generateFlags &= ~Flag::GenerateGetter;
+ else if (name == existing.setterName)
+ generateFlags &= ~Flag::GenerateSetter;
+ else if (name == existing.resetName)
+ generateFlags &= ~Flag::GenerateReset;
+ else if (name == existing.signalName)
+ generateFlags &= ~Flag::GenerateSignal;
+ } else if (member->asDeclaration()) {
+ const QString name = overview.prettyName(member->name());
+ if (haveFixMemberVariableName) {
+ if (name == existing.memberVariableName) {
+ generateFlags &= ~Flag::GenerateMemberVariable;
+ }
+ } else {
+ const QString baseName = memberBaseName(name);
+ if (existing.qPropertyName == baseName) {
+ existing.memberVariableName = name;
+ generateFlags &= ~Flag::GenerateMemberVariable;
+ }
+ }
+ }
+ }
+ if (generateFlags & Flag::GenerateMemberVariable) {
+ CppQuickFixSettings *settings = CppQuickFixProjectsSettings::getQuickFixSettings(
+ ProjectExplorer::ProjectTree::currentProject());
+ existing.memberVariableName = settings->getMemberVariableName(existing.qPropertyName);
+ }
+ if (generateFlags == 0) {
+ // everything is already there
+ return;
+ }
+ generateFlags |= Flag::HaveExistingQProperty;
+ GenerateGetterSetterOp::generateQuickFixes(result, interface, existing, generateFlags);
+ }
+};
+
+
+#ifdef WITH_TESTS
+using namespace Tests;
+
+class GenerateGetterSetterTest : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void testNamespaceHandlingCreate_data()
+ {
+ QTest::addColumn<QByteArrayList>("headers");
+ QTest::addColumn<QByteArrayList>("sources");
+
+ QByteArray originalSource;
+ QByteArray expectedSource;
+
+ const QByteArray originalHeader =
+ "namespace N1 {\n"
+ "namespace N2 {\n"
+ "class Something\n"
+ "{\n"
+ " int @it;\n"
+ "};\n"
+ "}\n"
+ "}\n";
+ const QByteArray expectedHeader =
+ "namespace N1 {\n"
+ "namespace N2 {\n"
+ "class Something\n"
+ "{\n"
+ " int it;\n"
+ "\n"
+ "public:\n"
+ " int getIt() const;\n"
+ " void setIt(int value);\n"
+ "};\n"
+ "}\n"
+ "}\n";
+
+ originalSource = "#include \"file.h\"\n";
+ expectedSource =
+ "#include \"file.h\"\n\n\n"
+ "namespace N1 {\n"
+ "namespace N2 {\n"
+ "int Something::getIt() const\n"
+ "{\n"
+ " return it;\n"
+ "}\n"
+ "\n"
+ "void Something::setIt(int value)\n"
+ "{\n"
+ " it = value;\n"
+ "}\n\n"
+ "}\n"
+ "}\n";
+ QTest::addRow("insert new namespaces")
+ << QByteArrayList{originalHeader, expectedHeader}
+ << QByteArrayList{originalSource, expectedSource};
+
+ originalSource =
+ "#include \"file.h\"\n"
+ "namespace N2 {} // decoy\n";
+ expectedSource =
+ "#include \"file.h\"\n"
+ "namespace N2 {} // decoy\n\n\n"
+ "namespace N1 {\n"
+ "namespace N2 {\n"
+ "int Something::getIt() const\n"
+ "{\n"
+ " return it;\n"
+ "}\n"
+ "\n"
+ "void Something::setIt(int value)\n"
+ "{\n"
+ " it = value;\n"
+ "}\n\n"
+ "}\n"
+ "}\n";
+ QTest::addRow("insert new namespaces (with decoy)")
+ << QByteArrayList{originalHeader, expectedHeader}
+ << QByteArrayList{originalSource, expectedSource};
+
+ originalSource = "#include \"file.h\"\n"
+ "namespace N2 {} // decoy\n"
+ "namespace {\n"
+ "namespace N1 {\n"
+ "namespace {\n"
+ "}\n"
+ "}\n"
+ "}\n";
+ expectedSource = "#include \"file.h\"\n"
+ "namespace N2 {} // decoy\n"
+ "namespace {\n"
+ "namespace N1 {\n"
+ "namespace {\n"
+ "}\n"
+ "}\n"
+ "}\n"
+ "\n"
+ "\n"
+ "namespace N1 {\n"
+ "namespace N2 {\n"
+ "int Something::getIt() const\n"
+ "{\n"
+ " return it;\n"
+ "}\n"
+ "\n"
+ "void Something::setIt(int value)\n"
+ "{\n"
+ " it = value;\n"
+ "}\n"
+ "\n"
+ "}\n"
+ "}\n";
+ QTest::addRow("insert inner namespace (with decoy and unnamed)")
+ << QByteArrayList{originalHeader, expectedHeader}
+ << QByteArrayList{originalSource, expectedSource};
+
+ const QByteArray unnamedOriginalHeader = "namespace {\n" + originalHeader + "}\n";
+ const QByteArray unnamedExpectedHeader = "namespace {\n" + expectedHeader + "}\n";
+
+ originalSource = "#include \"file.h\"\n"
+ "namespace N2 {} // decoy\n"
+ "namespace {\n"
+ "namespace N1 {\n"
+ "namespace {\n"
+ "}\n"
+ "}\n"
+ "}\n";
+ expectedSource = "#include \"file.h\"\n"
+ "namespace N2 {} // decoy\n"
+ "namespace {\n"
+ "namespace N1 {\n"
+ "\n"
+ "namespace N2 {\n"
+ "int Something::getIt() const\n"
+ "{\n"
+ " return it;\n"
+ "}\n"
+ "\n"
+ "void Something::setIt(int value)\n"
+ "{\n"
+ " it = value;\n"
+ "}\n"
+ "\n"
+ "}\n"
+ "\n"
+ "namespace {\n"
+ "}\n"
+ "}\n"
+ "}\n";
+ QTest::addRow("insert inner namespace in unnamed (with decoy)")
+ << QByteArrayList{unnamedOriginalHeader, unnamedExpectedHeader}
+ << QByteArrayList{originalSource, expectedSource};
+
+ originalSource =
+ "#include \"file.h\"\n"
+ "namespace N1 {\n"
+ "namespace N2 {\n"
+ "namespace N3 {\n"
+ "}\n"
+ "}\n"
+ "}\n";
+ expectedSource =
+ "#include \"file.h\"\n"
+ "namespace N1 {\n"
+ "namespace N2 {\n"
+ "namespace N3 {\n"
+ "}\n\n"
+ "int Something::getIt() const\n"
+ "{\n"
+ " return it;\n"
+ "}\n"
+ "\n"
+ "void Something::setIt(int value)\n"
+ "{\n"
+ " it = value;\n"
+ "}\n\n"
+ "}\n"
+ "}\n";
+ QTest::addRow("all namespaces already present")
+ << QByteArrayList{originalHeader, expectedHeader}
+ << QByteArrayList{originalSource, expectedSource};
+
+ originalSource = "#include \"file.h\"\n"
+ "namespace N1 {\n"
+ "using namespace N2::N3;\n"
+ "using namespace N2;\n"
+ "using namespace N2;\n"
+ "using namespace N3;\n"
+ "}\n";
+ expectedSource = "#include \"file.h\"\n"
+ "namespace N1 {\n"
+ "using namespace N2::N3;\n"
+ "using namespace N2;\n"
+ "using namespace N2;\n"
+ "using namespace N3;\n"
+ "\n"
+ "int Something::getIt() const\n"
+ "{\n"
+ " return it;\n"
+ "}\n"
+ "\n"
+ "void Something::setIt(int value)\n"
+ "{\n"
+ " it = value;\n"
+ "}\n\n"
+ "}\n";
+ QTest::addRow("namespaces present and using namespace")
+ << QByteArrayList{originalHeader, expectedHeader}
+ << QByteArrayList{originalSource, expectedSource};
+
+ originalSource = "#include \"file.h\"\n"
+ "using namespace N1::N2::N3;\n"
+ "using namespace N1::N2;\n"
+ "namespace N1 {\n"
+ "using namespace N3;\n"
+ "}\n";
+ expectedSource = "#include \"file.h\"\n"
+ "using namespace N1::N2::N3;\n"
+ "using namespace N1::N2;\n"
+ "namespace N1 {\n"
+ "using namespace N3;\n"
+ "\n"
+ "int Something::getIt() const\n"
+ "{\n"
+ " return it;\n"
+ "}\n"
+ "\n"
+ "void Something::setIt(int value)\n"
+ "{\n"
+ " it = value;\n"
+ "}\n"
+ "\n"
+ "}\n";
+ QTest::addRow("namespaces present and outer using namespace")
+ << QByteArrayList{originalHeader, expectedHeader}
+ << QByteArrayList{originalSource, expectedSource};
+
+ originalSource = "#include \"file.h\"\n"
+ "using namespace N1;\n"
+ "using namespace N2;\n"
+ "namespace N3 {\n"
+ "}\n";
+ expectedSource = "#include \"file.h\"\n"
+ "using namespace N1;\n"
+ "using namespace N2;\n"
+ "namespace N3 {\n"
+ "}\n"
+ "\n"
+ "int Something::getIt() const\n"
+ "{\n"
+ " return it;\n"
+ "}\n"
+ "\n"
+ "void Something::setIt(int value)\n"
+ "{\n"
+ " it = value;\n"
+ "}\n";
+ QTest::addRow("namespaces present and outer using namespace")
+ << QByteArrayList{originalHeader, expectedHeader}
+ << QByteArrayList{originalSource, expectedSource};
+ }
+
+ void testNamespaceHandlingCreate()
+ {
+ QFETCH(QByteArrayList, headers);
+ QFETCH(QByteArrayList, sources);
+
+ QList<TestDocumentPtr> testDocuments(
+ {CppTestDocument::create("file.h", headers.at(0), headers.at(1)),
+ CppTestDocument::create("file.cpp", sources.at(0), sources.at(1))});
+
+ QuickFixSettings s;
+ s->cppFileNamespaceHandling = CppQuickFixSettings::MissingNamespaceHandling::CreateMissing;
+ s->setterParameterNameTemplate = "value";
+ s->getterNameTemplate = "get<Name>";
+ s->setterInCppFileFrom = 1;
+ s->getterInCppFileFrom = 1;
+ GenerateGetterSetter factory;
+ QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 2);
+ }
+
+ void testNamespaceHandlingAddUsing_data()
+ {
+ QTest::addColumn<QByteArrayList>("headers");
+ QTest::addColumn<QByteArrayList>("sources");
+
+ QByteArray originalSource;
+ QByteArray expectedSource;
+
+ const QByteArray originalHeader = "namespace N1 {\n"
+ "namespace N2 {\n"
+ "class Something\n"
+ "{\n"
+ " int @it;\n"
+ "};\n"
+ "}\n"
+ "}\n";
+ const QByteArray expectedHeader = "namespace N1 {\n"
+ "namespace N2 {\n"
+ "class Something\n"
+ "{\n"
+ " int it;\n"
+ "\n"
+ "public:\n"
+ " void setIt(int value);\n"
+ "};\n"
+ "}\n"
+ "}\n";
+
+ originalSource = "#include \"file.h\"\n";
+ expectedSource = "#include \"file.h\"\n\n"
+ "using namespace N1::N2;\n"
+ "void Something::setIt(int value)\n"
+ "{\n"
+ " it = value;\n"
+ "}\n";
+ QTest::addRow("add using namespaces") << QByteArrayList{originalHeader, expectedHeader}
+ << QByteArrayList{originalSource, expectedSource};
+
+ const QByteArray unnamedOriginalHeader = "namespace {\n" + originalHeader + "}\n";
+ const QByteArray unnamedExpectedHeader = "namespace {\n" + expectedHeader + "}\n";
+
+ originalSource = "#include \"file.h\"\n"
+ "namespace N2 {} // decoy\n"
+ "namespace {\n"
+ "namespace N1 {\n"
+ "namespace {\n"
+ "}\n"
+ "}\n"
+ "}\n";
+ expectedSource = "#include \"file.h\"\n"
+ "namespace N2 {} // decoy\n"
+ "namespace {\n"
+ "namespace N1 {\n"
+ "using namespace N2;\n"
+ "void Something::setIt(int value)\n"
+ "{\n"
+ " it = value;\n"
+ "}\n"
+ "namespace {\n"
+ "}\n"
+ "}\n"
+ "}\n";
+ QTest::addRow("insert using namespace into unnamed nested (with decoy)")
+ << QByteArrayList{unnamedOriginalHeader, unnamedExpectedHeader}
+ << QByteArrayList{originalSource, expectedSource};
+
+ originalSource = "#include \"file.h\"\n";
+ expectedSource = "#include \"file.h\"\n\n"
+ "using namespace N1::N2;\n"
+ "void Something::setIt(int value)\n"
+ "{\n"
+ " it = value;\n"
+ "}\n";
+ QTest::addRow("insert using namespace into unnamed")
+ << QByteArrayList{unnamedOriginalHeader, unnamedExpectedHeader}
+ << QByteArrayList{originalSource, expectedSource};
+
+ originalSource = "#include \"file.h\"\n"
+ "namespace N2 {} // decoy\n"
+ "namespace {\n"
+ "namespace N1 {\n"
+ "namespace {\n"
+ "}\n"
+ "}\n"
+ "}\n";
+ expectedSource = "#include \"file.h\"\n"
+ "namespace N2 {} // decoy\n"
+ "namespace {\n"
+ "namespace N1 {\n"
+ "namespace {\n"
+ "}\n"
+ "}\n"
+ "}\n"
+ "\n"
+ "using namespace N1::N2;\n"
+ "void Something::setIt(int value)\n"
+ "{\n"
+ " it = value;\n"
+ "}\n";
+ QTest::addRow("insert using namespace (with decoy)")
+ << QByteArrayList{originalHeader, expectedHeader}
+ << QByteArrayList{originalSource, expectedSource};
+ }
+
+ void testNamespaceHandlingAddUsing()
+ {
+ QFETCH(QByteArrayList, headers);
+ QFETCH(QByteArrayList, sources);
+
+ QList<TestDocumentPtr> testDocuments(
+ {CppTestDocument::create("file.h", headers.at(0), headers.at(1)),
+ CppTestDocument::create("file.cpp", sources.at(0), sources.at(1))});
+
+ QuickFixSettings s;
+ s->cppFileNamespaceHandling = CppQuickFixSettings::MissingNamespaceHandling::AddUsingDirective;
+ s->setterParameterNameTemplate = "value";
+ s->setterInCppFileFrom = 1;
+
+ if (std::strstr(QTest::currentDataTag(), "unnamed nested") != nullptr)
+ QSKIP("TODO"); // FIXME
+ GenerateGetterSetter factory;
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+
+ void testNamespaceHandlingFullyQualify_data()
+ {
+ QTest::addColumn<QByteArrayList>("headers");
+ QTest::addColumn<QByteArrayList>("sources");
+
+ QByteArray originalSource;
+ QByteArray expectedSource;
+
+ const QByteArray originalHeader = "namespace N1 {\n"
+ "namespace N2 {\n"
+ "class Something\n"
+ "{\n"
+ " int @it;\n"
+ "};\n"
+ "}\n"
+ "}\n";
+ const QByteArray expectedHeader = "namespace N1 {\n"
+ "namespace N2 {\n"
+ "class Something\n"
+ "{\n"
+ " int it;\n"
+ "\n"
+ "public:\n"
+ " void setIt(int value);\n"
+ "};\n"
+ "}\n"
+ "}\n";
+
+ originalSource = "#include \"file.h\"\n";
+ expectedSource = "#include \"file.h\"\n\n"
+ "void N1::N2::Something::setIt(int value)\n"
+ "{\n"
+ " it = value;\n"
+ "}\n";
+ QTest::addRow("fully qualify") << QByteArrayList{originalHeader, expectedHeader}
+ << QByteArrayList{originalSource, expectedSource};
+
+ originalSource = "#include \"file.h\"\n"
+ "namespace N2 {} // decoy\n";
+ expectedSource = "#include \"file.h\"\n"
+ "namespace N2 {} // decoy\n"
+ "\n"
+ "void N1::N2::Something::setIt(int value)\n"
+ "{\n"
+ " it = value;\n"
+ "}\n";
+ QTest::addRow("fully qualify (with decoy)") << QByteArrayList{originalHeader, expectedHeader}
+ << QByteArrayList{originalSource, expectedSource};
+
+ originalSource = "#include \"file.h\"\n"
+ "namespace N2 {} // decoy\n"
+ "namespace {\n"
+ "namespace N1 {\n"
+ "namespace {\n"
+ "}\n"
+ "}\n"
+ "}\n";
+ expectedSource = "#include \"file.h\"\n"
+ "namespace N2 {} // decoy\n"
+ "namespace {\n"
+ "namespace N1 {\n"
+ "namespace {\n"
+ "}\n"
+ "}\n"
+ "}\n"
+ "\n"
+ "void N1::N2::Something::setIt(int value)\n"
+ "{\n"
+ " it = value;\n"
+ "}\n";
+ QTest::addRow("qualify in inner namespace (with decoy)")
+ << QByteArrayList{originalHeader, expectedHeader}
+ << QByteArrayList{originalSource, expectedSource};
+
+ const QByteArray unnamedOriginalHeader = "namespace {\n" + originalHeader + "}\n";
+ const QByteArray unnamedExpectedHeader = "namespace {\n" + expectedHeader + "}\n";
+
+ originalSource = "#include \"file.h\"\n"
+ "namespace N2 {} // decoy\n"
+ "namespace {\n"
+ "namespace N1 {\n"
+ "namespace {\n"
+ "}\n"
+ "}\n"
+ "}\n";
+ expectedSource = "#include \"file.h\"\n"
+ "namespace N2 {} // decoy\n"
+ "namespace {\n"
+ "namespace N1 {\n"
+ "void N2::Something::setIt(int value)\n"
+ "{\n"
+ " it = value;\n"
+ "}\n"
+ "namespace {\n"
+ "}\n"
+ "}\n"
+ "}\n";
+ QTest::addRow("qualify in inner namespace unnamed nested (with decoy)")
+ << QByteArrayList{unnamedOriginalHeader, unnamedExpectedHeader}
+ << QByteArrayList{originalSource, expectedSource};
+
+ originalSource = "#include \"file.h\"\n";
+ expectedSource = "#include \"file.h\"\n\n"
+ "void N1::N2::Something::setIt(int value)\n"
+ "{\n"
+ " it = value;\n"
+ "}\n";
+ QTest::addRow("qualify in unnamed namespace")
+ << QByteArrayList{unnamedOriginalHeader, unnamedExpectedHeader}
+ << QByteArrayList{originalSource, expectedSource};
+ }
+
+ void testNamespaceHandlingFullyQualify()
+ {
+ QFETCH(QByteArrayList, headers);
+ QFETCH(QByteArrayList, sources);
+
+ QList<TestDocumentPtr> testDocuments(
+ {CppTestDocument::create("file.h", headers.at(0), headers.at(1)),
+ CppTestDocument::create("file.cpp", sources.at(0), sources.at(1))});
+
+ QuickFixSettings s;
+ s->cppFileNamespaceHandling = CppQuickFixSettings::MissingNamespaceHandling::RewriteType;
+ s->setterParameterNameTemplate = "value";
+ s->setterInCppFileFrom = 1;
+
+ if (std::strstr(QTest::currentDataTag(), "unnamed nested") != nullptr)
+ QSKIP("TODO"); // FIXME
+ GenerateGetterSetter factory;
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+
+ void testCustomNames_data()
+ {
+ QTest::addColumn<QByteArrayList>("headers");
+ QTest::addColumn<int>("operation");
+
+ QByteArray originalSource;
+ QByteArray expectedSource;
+
+ // Check if right names are created
+ originalSource = R"-(
+ class Test {
+ int m_fooBar_test@;
+ };
+)-";
+ expectedSource = R"-(
+ class Test {
+ int m_fooBar_test;
+
+ public:
+ int give_me_foo_bar_test() const
+ {
+ return m_fooBar_test;
+ }
+ void Seet_FooBar_test(int New_Foo_Bar_Test)
+ {
+ if (m_fooBar_test == New_Foo_Bar_Test)
+ return;
+ m_fooBar_test = New_Foo_Bar_Test;
+ emit newFooBarTestValue();
+ }
+ void set_fooBarTest_toDefault()
+ {
+ Seet_FooBar_test({}); // TODO: Adapt to use your actual default value
+ }
+
+ signals:
+ void newFooBarTestValue();
+
+ private:
+ Q_PROPERTY(int fooBar_test READ give_me_foo_bar_test WRITE Seet_FooBar_test RESET set_fooBarTest_toDefault NOTIFY newFooBarTestValue FINAL)
+ };
+)-";
+ QTest::addRow("create right names") << QByteArrayList{originalSource, expectedSource} << 4;
+
+ // Check if not triggered with custom names
+ originalSource = R"-(
+ class Test {
+ int m_fooBar_test@;
+
+ public:
+ int give_me_foo_bar_test() const
+ {
+ return m_fooBar_test;
+ }
+ void Seet_FooBar_test(int New_Foo_Bar_Test)
+ {
+ if (m_fooBar_test == New_Foo_Bar_Test)
+ return;
+ m_fooBar_test = New_Foo_Bar_Test;
+ emit newFooBarTestValue();
+ }
+ void set_fooBarTest_toDefault()
+ {
+ Seet_FooBar_test({}); // TODO: Adapt to use your actual default value
+ }
+
+ signals:
+ void newFooBarTestValue();
+
+ private:
+ Q_PROPERTY(int fooBar_test READ give_me_foo_bar_test WRITE Seet_FooBar_test RESET set_fooBarTest_toDefault NOTIFY newFooBarTestValue FINAL)
+ };
+)-";
+ expectedSource = "";
+ QTest::addRow("everything already exists") << QByteArrayList{originalSource, expectedSource} << 4;
+
+ // create from Q_PROPERTY with custom names
+ originalSource = R"-(
+ class Test {
+ Q_PROPER@TY(int fooBar_test READ give_me_foo_bar_test WRITE Seet_FooBar_test RESET set_fooBarTest_toDefault NOTIFY newFooBarTestValue FINAL)
+
+ public:
+ int give_me_foo_bar_test() const
+ {
+ return mem_fooBar_test;
+ }
+ void Seet_FooBar_test(int New_Foo_Bar_Test)
+ {
+ if (mem_fooBar_test == New_Foo_Bar_Test)
+ return;
+ mem_fooBar_test = New_Foo_Bar_Test;
+ emit newFooBarTestValue();
+ }
+ void set_fooBarTest_toDefault()
+ {
+ Seet_FooBar_test({}); // TODO: Adapt to use your actual default value
+ }
+
+ signals:
+ void newFooBarTestValue();
+ };
+)-";
+ expectedSource = R"-(
+ class Test {
+ Q_PROPERTY(int fooBar_test READ give_me_foo_bar_test WRITE Seet_FooBar_test RESET set_fooBarTest_toDefault NOTIFY newFooBarTestValue FINAL)
+
+ public:
+ int give_me_foo_bar_test() const
+ {
+ return mem_fooBar_test;
+ }
+ void Seet_FooBar_test(int New_Foo_Bar_Test)
+ {
+ if (mem_fooBar_test == New_Foo_Bar_Test)
+ return;
+ mem_fooBar_test = New_Foo_Bar_Test;
+ emit newFooBarTestValue();
+ }
+ void set_fooBarTest_toDefault()
+ {
+ Seet_FooBar_test({}); // TODO: Adapt to use your actual default value
+ }
+
+ signals:
+ void newFooBarTestValue();
+ private:
+ int mem_fooBar_test;
+ };
+)-";
+ QTest::addRow("create only member variable")
+ << QByteArrayList{originalSource, expectedSource} << 0;
+
+ // create from Q_PROPERTY with custom names
+ originalSource = R"-(
+ class Test {
+ Q_PROPE@RTY(int fooBar_test READ give_me_foo_bar_test WRITE Seet_FooBar_test RESET set_fooBarTest_toDefault NOTIFY newFooBarTestValue FINAL)
+ int mem_fooBar_test;
+ public:
+ };
+)-";
+ expectedSource = R"-(
+ class Test {
+ Q_PROPERTY(int fooBar_test READ give_me_foo_bar_test WRITE Seet_FooBar_test RESET set_fooBarTest_toDefault NOTIFY newFooBarTestValue FINAL)
+ int mem_fooBar_test;
+ public:
+ int give_me_foo_bar_test() const
+ {
+ return mem_fooBar_test;
+ }
+ void Seet_FooBar_test(int New_Foo_Bar_Test)
+ {
+ if (mem_fooBar_test == New_Foo_Bar_Test)
+ return;
+ mem_fooBar_test = New_Foo_Bar_Test;
+ emit newFooBarTestValue();
+ }
+ void set_fooBarTest_toDefault()
+ {
+ Seet_FooBar_test({}); // TODO: Adapt to use your actual default value
+ }
+ signals:
+ void newFooBarTestValue();
+ };
+)-";
+ QTest::addRow("create methods with given member variable")
+ << QByteArrayList{originalSource, expectedSource} << 0;
+ }
+
+ void testCustomNames()
+ {
+ QFETCH(QByteArrayList, headers);
+ QFETCH(int, operation);
+
+ QList<TestDocumentPtr> testDocuments(
+ {CppTestDocument::create("file.h", headers.at(0), headers.at(1))});
+
+ QuickFixSettings s;
+ s->setterInCppFileFrom = 0;
+ s->getterInCppFileFrom = 0;
+ s->setterNameTemplate = "Seet_<Name>";
+ s->getterNameTemplate = "give_me_<snake>";
+ s->signalNameTemplate = "new<Camel>Value";
+ s->setterParameterNameTemplate = "New_<Snake>";
+ s->resetNameTemplate = "set_<camel>_toDefault";
+ s->memberVariableNameTemplate = "mem_<name>";
+ if (operation == 0) {
+ InsertQtPropertyMembers factory;
+ QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), operation);
+ } else {
+ GenerateGetterSetter factory;
+ QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), operation);
+ }
+ }
+
+ void testValueTypes_data()
+ {
+ QTest::addColumn<QByteArrayList>("headers");
+ QTest::addColumn<int>("operation");
+
+ QByteArray originalSource;
+ QByteArray expectedSource;
+
+ // int should be a value type
+ originalSource = R"-(
+ class Test {
+ int i@;
+ };
+)-";
+ expectedSource = R"-(
+ class Test {
+ int i;
+
+ public:
+ int getI() const
+ {
+ return i;
+ }
+ };
+)-";
+ QTest::addRow("int") << QByteArrayList{originalSource, expectedSource} << 1;
+
+ // return type should be only int without const
+ originalSource = R"-(
+ class Test {
+ const int i@;
+ };
+)-";
+ expectedSource = R"-(
+ class Test {
+ const int i;
+
+ public:
+ int getI() const
+ {
+ return i;
+ }
+ };
+)-";
+ QTest::addRow("const int") << QByteArrayList{originalSource, expectedSource} << 0;
+
+ // float should be a value type
+ originalSource = R"-(
+ class Test {
+ float f@;
+ };
+)-";
+ expectedSource = R"-(
+ class Test {
+ float f;
+
+ public:
+ float getF() const
+ {
+ return f;
+ }
+ };
+)-";
+ QTest::addRow("float") << QByteArrayList{originalSource, expectedSource} << 1;
+
+ // pointer should be a value type
+ originalSource = R"-(
+ class Test {
+ void* v@;
+ };
+)-";
+ expectedSource = R"-(
+ class Test {
+ void* v;
+
+ public:
+ void *getV() const
+ {
+ return v;
+ }
+ };
+)-";
+ QTest::addRow("pointer") << QByteArrayList{originalSource, expectedSource} << 1;
+
+ // reference should be a value type (setter is const ref)
+ originalSource = R"-(
+ class Test {
+ int& r@;
+ };
+)-";
+ expectedSource = R"-(
+ class Test {
+ int& r;
+
+ public:
+ int &getR() const
+ {
+ return r;
+ }
+ void setR(const int &newR)
+ {
+ r = newR;
+ }
+ };
+)-";
+ QTest::addRow("reference to value type") << QByteArrayList{originalSource, expectedSource} << 2;
+
+ // reference should be a value type
+ originalSource = R"-(
+ using bar = int;
+ class Test {
+ bar i@;
+ };
+)-";
+ expectedSource = R"-(
+ using bar = int;
+ class Test {
+ bar i;
+
+ public:
+ bar getI() const
+ {
+ return i;
+ }
+ };
+)-";
+ QTest::addRow("value type through using") << QByteArrayList{originalSource, expectedSource} << 1;
+
+ // enum should be a value type
+ originalSource = R"-(
+ enum Foo{V1, V2};
+ class Test {
+ Foo e@;
+ };
+)-";
+ expectedSource = R"-(
+ enum Foo{V1, V2};
+ class Test {
+ Foo e;
+
+ public:
+ Foo getE() const
+ {
+ return e;
+ }
+ };
+)-";
+ QTest::addRow("enum") << QByteArrayList{originalSource, expectedSource} << 1;
+
+ // class should not be a value type
+ originalSource = R"-(
+ class NoVal{};
+ class Test {
+ NoVal n@;
+ };
+)-";
+ expectedSource = R"-(
+ class NoVal{};
+ class Test {
+ NoVal n;
+
+ public:
+ const NoVal &getN() const
+ {
+ return n;
+ }
+ };
+)-";
+ QTest::addRow("class") << QByteArrayList{originalSource, expectedSource} << 1;
+
+ // custom classes can be a value type
+ originalSource = R"-(
+ class Value{};
+ class Test {
+ Value v@;
+ };
+)-";
+ expectedSource = R"-(
+ class Value{};
+ class Test {
+ Value v;
+
+ public:
+ Value getV() const
+ {
+ return v;
+ }
+ };
+)-";
+ QTest::addRow("value class") << QByteArrayList{originalSource, expectedSource} << 1;
+
+ // custom classes (in namespace) can be a value type
+ originalSource = R"-(
+ namespace N1{
+ class Value{};
+ }
+ class Test {
+ N1::Value v@;
+ };
+)-";
+ expectedSource = R"-(
+ namespace N1{
+ class Value{};
+ }
+ class Test {
+ N1::Value v;
+
+ public:
+ N1::Value getV() const
+ {
+ return v;
+ }
+ };
+)-";
+ QTest::addRow("value class in namespace") << QByteArrayList{originalSource, expectedSource} << 1;
+
+ // custom template class can be a value type
+ originalSource = R"-(
+ template<typename T>
+ class Value{};
+ class Test {
+ Value<int> v@;
+ };
+)-";
+ expectedSource = R"-(
+ template<typename T>
+ class Value{};
+ class Test {
+ Value<int> v;
+
+ public:
+ Value<int> getV() const
+ {
+ return v;
+ }
+ };
+)-";
+ QTest::addRow("value template class") << QByteArrayList{originalSource, expectedSource} << 1;
+ }
+
+ void testValueTypes()
+ {
+ QFETCH(QByteArrayList, headers);
+ QFETCH(int, operation);
+
+ QList<TestDocumentPtr> testDocuments(
+ {CppTestDocument::create("file.h", headers.at(0), headers.at(1))});
+
+ QuickFixSettings s;
+ s->setterInCppFileFrom = 0;
+ s->getterInCppFileFrom = 0;
+ s->getterNameTemplate = "get<Name>";
+ s->valueTypes << "Value";
+ s->returnByConstRef = true;
+
+ GenerateGetterSetter factory;
+ QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), operation);
+ }
+
+ /// Checks: Use template for a custom type
+ void testCustomTemplate()
+ {
+ QList<TestDocumentPtr> testDocuments;
+ QByteArray original;
+ QByteArray expected;
+
+ const QByteArray customTypeDecl = R"--(
+namespace N1 {
+namespace N2 {
+struct test{};
+}
+template<typename T>
+struct custom {
+ void assign(const custom<T>&);
+ bool equals(const custom<T>&);
+ T* get();
+};
+)--";
+ // Header File
+ original = customTypeDecl + R"--(
+class Foo
+{
+public:
+ custom<N2::test> bar@;
+};
+})--";
+ expected = customTypeDecl + R"--(
+class Foo
+{
+public:
+ custom<N2::test> bar@;
+ N2::test *getBar() const;
+ void setBar(const custom<N2::test> &newBar);
+signals:
+ void barChanged(N2::test *bar);
+private:
+ Q_PROPERTY(N2::test *bar READ getBar NOTIFY barChanged FINAL)
+};
+})--";
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File
+ original = "";
+ expected = R"-(
+using namespace N1;
+N2::test *Foo::getBar() const
+{
+ return bar.get();
+}
+
+void Foo::setBar(const custom<N2::test> &newBar)
+{
+ if (bar.equals(newBar))
+ return;
+ bar.assign(newBar);
+ emit barChanged(bar.get());
+}
+)-";
+
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+ QuickFixSettings s;
+ s->cppFileNamespaceHandling = CppQuickFixSettings::MissingNamespaceHandling::AddUsingDirective;
+ s->getterNameTemplate = "get<Name>";
+ s->getterInCppFileFrom = 1;
+ s->signalWithNewValue = true;
+ CppQuickFixSettings::CustomTemplate t;
+ t.types.append("custom");
+ t.equalComparison = "<cur>.equals(<new>)";
+ t.returnExpression = "<cur>.get()";
+ t.returnType = "<T> *";
+ t.assignment = "<cur>.assign(<new>)";
+ s->customTemplates.push_back(t);
+
+ GenerateGetterSetter factory;
+ QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 5);
+ }
+
+ /// Checks: if the setter parameter name is the same as the member variable name, this-> is needed
+ void testNeedThis()
+ {
+ QList<TestDocumentPtr> testDocuments;
+
+ // Header File
+ const QByteArray original = R"-(
+ class Foo {
+ int bar@;
+ public:
+ };
+)-";
+ const QByteArray expected = R"-(
+ class Foo {
+ int bar@;
+ public:
+ void setBar(int bar)
+ {
+ this->bar = bar;
+ }
+ };
+)-";
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ QuickFixSettings s;
+ s->setterParameterNameTemplate = "<name>";
+ s->setterInCppFileFrom = 0;
+
+ GenerateGetterSetter factory;
+ QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 0);
+ }
+
+ void testOfferedFixes_data()
+ {
+ QTest::addColumn<QByteArray>("header");
+ QTest::addColumn<QStringList>("offered");
+
+ QByteArray header;
+ QStringList offered;
+ const QString setter = QStringLiteral("Generate Setter");
+ const QString getter = QStringLiteral("Generate Getter");
+ const QString getset = QStringLiteral("Generate Getter and Setter");
+ const QString constQandMissing = QStringLiteral(
+ "Generate Constant Q_PROPERTY and Missing Members");
+ const QString qAndResetAndMissing = QStringLiteral(
+ "Generate Q_PROPERTY and Missing Members with Reset Function");
+ const QString qAndMissing = QStringLiteral("Generate Q_PROPERTY and Missing Members");
+ const QStringList all{setter, getter, getset, constQandMissing, qAndResetAndMissing, qAndMissing};
+
+ header = R"-(
+ class Foo {
+ static int bar@;
+ };
+)-";
+ offered = QStringList{setter, getter, getset, constQandMissing};
+ QTest::addRow("static") << header << offered;
+
+ header = R"-(
+ class Foo {
+ static const int bar@;
+ };
+)-";
+ offered = QStringList{getter, constQandMissing};
+ QTest::addRow("const static") << header << offered;
+
+ header = R"-(
+ class Foo {
+ const int bar@;
+ };
+)-";
+ offered = QStringList{getter, constQandMissing};
+ QTest::addRow("const") << header << offered;
+
+ header = R"-(
+ class Foo {
+ const int bar@;
+ int getBar() const;
+ };
+)-";
+ offered = QStringList{constQandMissing};
+ QTest::addRow("const + getter") << header << offered;
+
+ header = R"-(
+ class Foo {
+ const int bar@;
+ int getBar() const;
+ void setBar(int value);
+ };
+)-";
+ offered = QStringList{};
+ QTest::addRow("const + getter + setter") << header << offered;
+
+ header = R"-(
+ class Foo {
+ const int* bar@;
+ };
+)-";
+ offered = all;
+ QTest::addRow("pointer to const") << header << offered;
+
+ header = R"-(
+ class Foo {
+ int bar@;
+ public:
+ int bar();
+ };
+)-";
+ offered = QStringList{setter, constQandMissing, qAndResetAndMissing, qAndMissing};
+ QTest::addRow("existing getter") << header << offered;
+
+ header = R"-(
+ class Foo {
+ int bar@;
+ public:
+ set setBar(int);
+ };
+)-";
+ offered = QStringList{getter};
+ QTest::addRow("existing setter") << header << offered;
+
+ header = R"-(
+ class Foo {
+ int bar@;
+ signals:
+ void barChanged(int);
+ };
+)-";
+ offered = QStringList{setter, getter, getset, qAndResetAndMissing, qAndMissing};
+ QTest::addRow("existing signal (no const Q_PROPERTY)") << header << offered;
+
+ header = R"-(
+ class Foo {
+ int m_bar@;
+ Q_PROPERTY(int bar)
+ };
+)-";
+ offered = QStringList{}; // user should use "InsertQPropertyMembers", no duplicated code
+ QTest::addRow("existing Q_PROPERTY") << header << offered;
+ }
+
+ void testOfferedFixes()
+ {
+ QFETCH(QByteArray, header);
+ QFETCH(QStringList, offered);
+
+ QList<TestDocumentPtr> testDocuments(
+ {CppTestDocument::create("file.h", header, header)});
+
+ GenerateGetterSetter factory;
+ QuickFixOfferedOperationsTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), offered);
+ }
+
+ void testGeneral_data()
+ {
+ QTest::addColumn<int>("operation");
+ QTest::addColumn<QByteArray>("original");
+ QTest::addColumn<QByteArray>("expected");
+
+ QTest::newRow("GenerateGetterSetter_referenceToNonConst")
+ << 2
+ << QByteArray("\n"
+ "class Something\n"
+ "{\n"
+ " int &it@;\n"
+ "};\n")
+ << QByteArray("\n"
+ "class Something\n"
+ "{\n"
+ " int &it;\n"
+ "\n"
+ "public:\n"
+ " int &getIt() const;\n"
+ " void setIt(const int &it);\n"
+ "};\n"
+ "\n"
+ "int &Something::getIt() const\n"
+ "{\n"
+ " return it;\n"
+ "}\n"
+ "\n"
+ "void Something::setIt(const int &it)\n"
+ "{\n"
+ " this->it = it;\n"
+ "}\n");
+
+ // Checks: No special treatment for reference to const.
+ QTest::newRow("GenerateGetterSetter_referenceToConst")
+ << 2
+ << QByteArray("\n"
+ "class Something\n"
+ "{\n"
+ " const int &it@;\n"
+ "};\n")
+ << QByteArray("\n"
+ "class Something\n"
+ "{\n"
+ " const int &it;\n"
+ "\n"
+ "public:\n"
+ " const int &getIt() const;\n"
+ " void setIt(const int &it);\n"
+ "};\n"
+ "\n"
+ "const int &Something::getIt() const\n"
+ "{\n"
+ " return it;\n"
+ "}\n"
+ "\n"
+ "void Something::setIt(const int &it)\n"
+ "{\n"
+ " this->it = it;\n"
+ "}\n");
+
+ // Checks:
+ // 1. Setter: Setter is a static function.
+ // 2. Getter: Getter is a static, non const function.
+ QTest::newRow("GenerateGetterSetter_staticMember")
+ << 2
+ << QByteArray("\n"
+ "class Something\n"
+ "{\n"
+ " static int @m_member;\n"
+ "};\n")
+ << QByteArray("\n"
+ "class Something\n"
+ "{\n"
+ " static int m_member;\n"
+ "\n"
+ "public:\n"
+ " static int member();\n"
+ " static void setMember(int member);\n"
+ "};\n"
+ "\n"
+ "int Something::member()\n"
+ "{\n"
+ " return m_member;\n"
+ "}\n"
+ "\n"
+ "void Something::setMember(int member)\n"
+ "{\n"
+ " m_member = member;\n"
+ "}\n");
+
+ // Check: Check if it works on the second declarator
+ // clang-format off
+ QTest::newRow("GenerateGetterSetter_secondDeclarator") << 2
+ << QByteArray("\n"
+ "class Something\n"
+ "{\n"
+ " int *foo, @it;\n"
+ "};\n")
+ << QByteArray("\n"
+ "class Something\n"
+ "{\n"
+ " int *foo, it;\n"
+ "\n"
+ "public:\n"
+ " int getIt() const;\n"
+ " void setIt(int it);\n"
+ "};\n"
+ "\n"
+ "int Something::getIt() const\n"
+ "{\n"
+ " return it;\n"
+ "}\n"
+ "\n"
+ "void Something::setIt(int it)\n"
+ "{\n"
+ " this->it = it;\n"
+ "}\n");
+ // clang-format on
+
+ // Check: Quick fix is offered for "int *@it;" ('@' denotes the text cursor position)
+ QTest::newRow("GenerateGetterSetter_triggeringRightAfterPointerSign")
+ << 2
+ << QByteArray("\n"
+ "class Something\n"
+ "{\n"
+ " int *@it;\n"
+ "};\n")
+ << QByteArray("\n"
+ "class Something\n"
+ "{\n"
+ " int *it;\n"
+ "\n"
+ "public:\n"
+ " int *getIt() const;\n"
+ " void setIt(int *it);\n"
+ "};\n"
+ "\n"
+ "int *Something::getIt() const\n"
+ "{\n"
+ " return it;\n"
+ "}\n"
+ "\n"
+ "void Something::setIt(int *it)\n"
+ "{\n"
+ " this->it = it;\n"
+ "}\n");
+
+ // Checks if "m_" is recognized as "m" with the postfix "_" and not simply as "m_" prefix.
+ QTest::newRow("GenerateGetterSetter_recognizeMasVariableName")
+ << 2
+ << QByteArray("\n"
+ "class Something\n"
+ "{\n"
+ " int @m_;\n"
+ "};\n")
+ << QByteArray("\n"
+ "class Something\n"
+ "{\n"
+ " int m_;\n"
+ "\n"
+ "public:\n"
+ " int m() const;\n"
+ " void setM(int m);\n"
+ "};\n"
+ "\n"
+ "int Something::m() const\n"
+ "{\n"
+ " return m_;\n"
+ "}\n"
+ "\n"
+ "void Something::setM(int m)\n"
+ "{\n"
+ " m_ = m;\n"
+ "}\n");
+
+ // Checks if "m" followed by an upper character is recognized as a prefix
+ QTest::newRow("GenerateGetterSetter_recognizeMFollowedByCapital")
+ << 2
+ << QByteArray("\n"
+ "class Something\n"
+ "{\n"
+ " int @mFoo;\n"
+ "};\n")
+ << QByteArray("\n"
+ "class Something\n"
+ "{\n"
+ " int mFoo;\n"
+ "\n"
+ "public:\n"
+ " int foo() const;\n"
+ " void setFoo(int foo);\n"
+ "};\n"
+ "\n"
+ "int Something::foo() const\n"
+ "{\n"
+ " return mFoo;\n"
+ "}\n"
+ "\n"
+ "void Something::setFoo(int foo)\n"
+ "{\n"
+ " mFoo = foo;\n"
+ "}\n");
+ }
+
+ void testGeneral()
+ {
+ QFETCH(int, operation);
+ QFETCH(QByteArray, original);
+ QFETCH(QByteArray, expected);
+
+ QuickFixSettings s;
+ s->setterParameterNameTemplate = "<name>";
+ s->getterInCppFileFrom = 1;
+ s->setterInCppFileFrom = 1;
+
+ GenerateGetterSetter factory;
+ QuickFixOperationTest(singleDocument(original, expected),
+ &factory,
+ ProjectExplorer::HeaderPaths(),
+ operation);
+ }
+
+ /// Checks: Only generate getter
+ void testOnlyGetter()
+ {
+ QList<TestDocumentPtr> testDocuments;
+ QByteArray original;
+ QByteArray expected;
+
+ // Header File
+ original =
+ "class Foo\n"
+ "{\n"
+ "public:\n"
+ " int bar@;\n"
+ "};\n";
+ expected =
+ "class Foo\n"
+ "{\n"
+ "public:\n"
+ " int bar@;\n"
+ " int getBar() const;\n"
+ "};\n";
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File
+ original.resize(0);
+ expected =
+ "\n"
+ "int Foo::getBar() const\n"
+ "{\n"
+ " return bar;\n"
+ "}\n";
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+ QuickFixSettings s;
+ s->getterInCppFileFrom = 1;
+ s->getterNameTemplate = "get<Name>";
+ GenerateGetterSetter factory;
+ QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 1);
+ }
+
+ /// Checks: Only generate setter
+ void testOnlySetter()
+ {
+ QList<TestDocumentPtr> testDocuments;
+ QByteArray original;
+ QByteArray expected;
+ QuickFixSettings s;
+ s->setterAsSlot = true; // To be ignored, as we don't have QObjects here.
+
+ // Header File
+ original =
+ "class Foo\n"
+ "{\n"
+ "public:\n"
+ " int bar@;\n"
+ "};\n";
+ expected =
+ "class Foo\n"
+ "{\n"
+ "public:\n"
+ " int bar@;\n"
+ " void setBar(int value);\n"
+ "};\n";
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File
+ original.resize(0);
+ expected =
+ "\n"
+ "void Foo::setBar(int value)\n"
+ "{\n"
+ " bar = value;\n"
+ "}\n";
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+ s->setterInCppFileFrom = 1;
+ s->setterParameterNameTemplate = "value";
+
+ GenerateGetterSetter factory;
+ QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 0);
+ }
+
+ void testAnonymousClass()
+ {
+ QList<TestDocumentPtr> testDocuments;
+ QByteArray original;
+ QByteArray expected;
+ QuickFixSettings s;
+ s->setterInCppFileFrom = 1;
+ s->setterParameterNameTemplate = "value";
+
+ // Header File
+ original = R"(
+ class {
+ int @m_foo;
+ } bar;
+)";
+ expected = R"(
+ class {
+ int m_foo;
+
+ public:
+ int foo() const
+ {
+ return m_foo;
+ }
+ void setFoo(int value)
+ {
+ if (m_foo == value)
+ return;
+ m_foo = value;
+ emit fooChanged();
+ }
+ void resetFoo()
+ {
+ setFoo({}); // TODO: Adapt to use your actual default defaultValue
+ }
+
+ signals:
+ void fooChanged();
+
+ private:
+ Q_PROPERTY(int foo READ foo WRITE setFoo RESET resetFoo NOTIFY fooChanged FINAL)
+ } bar;
+)";
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File
+ testDocuments << CppTestDocument::create("file.cpp", {}, {});
+
+ GenerateGetterSetter factory;
+ QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 4);
+ }
+
+ void testInlineInHeaderFile()
+ {
+ QList<TestDocumentPtr> testDocuments;
+ const QByteArray original = R"-(
+ class Foo {
+ public:
+ int bar@;
+ };
+)-";
+ const QByteArray expected = R"-(
+ class Foo {
+ public:
+ int bar;
+ int getBar() const;
+ void setBar(int value);
+ void resetBar();
+ signals:
+ void barChanged();
+ private:
+ Q_PROPERTY(int bar READ getBar WRITE setBar RESET resetBar NOTIFY barChanged FINAL)
+ };
+
+ inline int Foo::getBar() const
+ {
+ return bar;
+ }
+
+ inline void Foo::setBar(int value)
+ {
+ if (bar == value)
+ return;
+ bar = value;
+ emit barChanged();
+ }
+
+ inline void Foo::resetBar()
+ {
+ setBar({}); // TODO: Adapt to use your actual default defaultValue
+ }
+)-";
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ QuickFixSettings s;
+ s->setterOutsideClassFrom = 1;
+ s->getterOutsideClassFrom = 1;
+ s->setterParameterNameTemplate = "value";
+ s->getterNameTemplate = "get<Name>";
+
+ GenerateGetterSetter factory;
+ QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 4);
+ }
+
+ void testOnlySetterHeaderFileWithIncludeGuard()
+ {
+ QList<TestDocumentPtr> testDocuments;
+ const QByteArray original =
+ "#ifndef FILE__H__DECLARED\n"
+ "#define FILE__H__DECLARED\n"
+ "class Foo\n"
+ "{\n"
+ "public:\n"
+ " int bar@;\n"
+ "};\n"
+ "#endif\n";
+ const QByteArray expected =
+ "#ifndef FILE__H__DECLARED\n"
+ "#define FILE__H__DECLARED\n"
+ "class Foo\n"
+ "{\n"
+ "public:\n"
+ " int bar@;\n"
+ " void setBar(int value);\n"
+ "};\n\n"
+ "inline void Foo::setBar(int value)\n"
+ "{\n"
+ " bar = value;\n"
+ "}\n"
+ "#endif\n";
+
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ QuickFixSettings s;
+ s->setterOutsideClassFrom = 1;
+ s->setterParameterNameTemplate = "value";
+
+ GenerateGetterSetter factory;
+ QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 0);
+ }
+
+ void testFunctionAsTemplateArg()
+ {
+ QList<TestDocumentPtr> testDocuments;
+ const QByteArray original = R"(
+ template<typename T> class TS {};
+ template<typename T, typename U> class TS<T(U)> {};
+
+ class S2 {
+ TS<int(int)> @member;
+ };
+)";
+ const QByteArray expected = R"(
+ template<typename T> class TS {};
+ template<typename T, typename U> class TS<T(U)> {};
+
+ class S2 {
+ TS<int(int)> member;
+
+ public:
+ const TS<int (int)> &getMember() const
+ {
+ return member;
+ }
+ };
+)";
+
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ QuickFixSettings s;
+ s->getterOutsideClassFrom = 0;
+ s->getterInCppFileFrom = 0;
+ s->getterNameTemplate = "get<Name>";
+ s->returnByConstRef = true;
+
+ GenerateGetterSetter factory;
+ QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 1);
+ }
+
+ void testNotTriggeringOnMemberFunction()
+ {
+ const QByteArray input = "class Something { void @f(); };\n";
+ GenerateGetterSetter factory;
+ QuickFixOperationTest({CppTestDocument::create("file.h", input, {})}, &factory);
+ }
+
+ void testNotTriggeringOnMemberArray()
+ {
+ const QByteArray input = "class Something { void @a[10]; };\n";
+ GenerateGetterSetter factory;
+ QuickFixOperationTest({CppTestDocument::create("file.h", input, {})}, &factory);
+ }
+};
+
+class GenerateGettersSettersTest : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void test_data()
+ {
+ QTest::addColumn<QByteArray>("original");
+ QTest::addColumn<QByteArray>("expected");
+
+ const QByteArray onlyReset = R"(
+ class Foo {
+ public:
+ int bar() const;
+ void setBar(int bar);
+ private:
+ int m_bar;
+ @};)";
+
+ const QByteArray onlyResetAfter = R"(
+ class @Foo {
+ public:
+ int bar() const;
+ void setBar(int bar);
+ void resetBar();
+
+ private:
+ int m_bar;
+ };
+ inline void Foo::resetBar()
+ {
+ setBar({}); // TODO: Adapt to use your actual default defaultValue
+ }
+)";
+ QTest::addRow("only reset") << onlyReset << onlyResetAfter;
+
+ const QByteArray withCandidates = R"(
+ class @Foo {
+ public:
+ int bar() const;
+ void setBar(int bar) { m_bar = bar; }
+
+ int getBar2() const;
+
+ int m_alreadyPublic;
+
+ private:
+ friend void distraction();
+ class AnotherDistraction {};
+ enum EvenMoreDistraction { val1, val2 };
+
+ int m_bar;
+ int bar2_;
+ QString bar3;
+ };)";
+ const QByteArray after = R"(
+ class Foo {
+ public:
+ int bar() const;
+ void setBar(int bar) { m_bar = bar; }
+
+ int getBar2() const;
+
+ int m_alreadyPublic;
+
+ void resetBar();
+ void setBar2(int value);
+ void resetBar2();
+ const QString &getBar3() const;
+ void setBar3(const QString &value);
+ void resetBar3();
+
+ signals:
+ void bar2Changed();
+ void bar3Changed();
+
+ private:
+ friend void distraction();
+ class AnotherDistraction {};
+ enum EvenMoreDistraction { val1, val2 };
+
+ int m_bar;
+ int bar2_;
+ QString bar3;
+ Q_PROPERTY(int bar2 READ getBar2 WRITE setBar2 RESET resetBar2 NOTIFY bar2Changed FINAL)
+ Q_PROPERTY(QString bar3 READ getBar3 WRITE setBar3 RESET resetBar3 NOTIFY bar3Changed FINAL)
+ };
+ inline void Foo::resetBar()
+ {
+ setBar({}); // TODO: Adapt to use your actual default defaultValue
+ }
+
+ inline void Foo::setBar2(int value)
+ {
+ if (bar2_ == value)
+ return;
+ bar2_ = value;
+ emit bar2Changed();
+ }
+
+ inline void Foo::resetBar2()
+ {
+ setBar2({}); // TODO: Adapt to use your actual default defaultValue
+ }
+
+ inline const QString &Foo::getBar3() const
+ {
+ return bar3;
+ }
+
+ inline void Foo::setBar3(const QString &value)
+ {
+ if (bar3 == value)
+ return;
+ bar3 = value;
+ emit bar3Changed();
+ }
+
+ inline void Foo::resetBar3()
+ {
+ setBar3({}); // TODO: Adapt to use your actual default defaultValue
+ }
+)";
+ QTest::addRow("with candidates") << withCandidates << after;
+ }
+
+ void test()
+ {
+ class TestFactory : public GenerateGettersSettersForClass
+ {
+ public:
+ TestFactory() { setTest(); }
+ };
+
+ QFETCH(QByteArray, original);
+ QFETCH(QByteArray, expected);
+
+ QuickFixSettings s;
+ s->getterNameTemplate = "get<Name>";
+ s->setterParameterNameTemplate = "value";
+ s->setterOutsideClassFrom = 1;
+ s->getterOutsideClassFrom = 1;
+ s->returnByConstRef = true;
+
+ TestFactory factory;
+ QuickFixOperationTest({CppTestDocument::create("file.h", original, expected)}, &factory);
+ }
+};
+
+class InsertQtPropertyMembersTest : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void test_data()
+ {
+ QTest::addColumn<QByteArray>("original");
+ QTest::addColumn<QByteArray>("expected");
+
+ QTest::newRow("InsertQtPropertyMembers")
+ << QByteArray("struct QObject { void connect(); }\n"
+ "struct XmarksTheSpot : public QObject {\n"
+ " @Q_PROPERTY(int it READ getIt WRITE setIt RESET resetIt NOTIFY itChanged)\n"
+ "};\n")
+ << QByteArray("struct QObject { void connect(); }\n"
+ "struct XmarksTheSpot : public QObject {\n"
+ " Q_PROPERTY(int it READ getIt WRITE setIt RESET resetIt NOTIFY itChanged)\n"
+ "\n"
+ "public:\n"
+ " int getIt() const;\n"
+ "\n"
+ "public slots:\n"
+ " void setIt(int it)\n"
+ " {\n"
+ " if (m_it == it)\n"
+ " return;\n"
+ " m_it = it;\n"
+ " emit itChanged(m_it);\n"
+ " }\n"
+ " void resetIt()\n"
+ " {\n"
+ " setIt({}); // TODO: Adapt to use your actual default value\n"
+ " }\n"
+ "\n"
+ "signals:\n"
+ " void itChanged(int it);\n"
+ "\n"
+ "private:\n"
+ " int m_it;\n"
+ "};\n"
+ "\n"
+ "int XmarksTheSpot::getIt() const\n"
+ "{\n"
+ " return m_it;\n"
+ "}\n");
+
+ QTest::newRow("InsertQtPropertyMembersResetWithoutSet")
+ << QByteArray("struct QObject { void connect(); }\n"
+ "struct XmarksTheSpot : public QObject {\n"
+ " @Q_PROPERTY(int it READ getIt RESET resetIt NOTIFY itChanged)\n"
+ "};\n")
+ << QByteArray("struct QObject { void connect(); }\n"
+ "struct XmarksTheSpot : public QObject {\n"
+ " Q_PROPERTY(int it READ getIt RESET resetIt NOTIFY itChanged)\n"
+ "\n"
+ "public:\n"
+ " int getIt() const;\n"
+ "\n"
+ "public slots:\n"
+ " void resetIt()\n"
+ " {\n"
+ " static int defaultValue{}; // TODO: Adapt to use your actual default "
+ "value\n"
+ " if (m_it == defaultValue)\n"
+ " return;\n"
+ " m_it = defaultValue;\n"
+ " emit itChanged(m_it);\n"
+ " }\n"
+ "\n"
+ "signals:\n"
+ " void itChanged(int it);\n"
+ "\n"
+ "private:\n"
+ " int m_it;\n"
+ "};\n"
+ "\n"
+ "int XmarksTheSpot::getIt() const\n"
+ "{\n"
+ " return m_it;\n"
+ "}\n");
+
+ QTest::newRow("InsertQtPropertyMembersResetWithoutSetAndNotify")
+ << QByteArray("struct QObject { void connect(); }\n"
+ "struct XmarksTheSpot : public QObject {\n"
+ " @Q_PROPERTY(int it READ getIt RESET resetIt)\n"
+ "};\n")
+ << QByteArray("struct QObject { void connect(); }\n"
+ "struct XmarksTheSpot : public QObject {\n"
+ " Q_PROPERTY(int it READ getIt RESET resetIt)\n"
+ "\n"
+ "public:\n"
+ " int getIt() const;\n"
+ "\n"
+ "public slots:\n"
+ " void resetIt()\n"
+ " {\n"
+ " static int defaultValue{}; // TODO: Adapt to use your actual default "
+ "value\n"
+ " m_it = defaultValue;\n"
+ " }\n"
+ "\n"
+ "private:\n"
+ " int m_it;\n"
+ "};\n"
+ "\n"
+ "int XmarksTheSpot::getIt() const\n"
+ "{\n"
+ " return m_it;\n"
+ "}\n");
+
+ QTest::newRow("InsertQtPropertyMembersPrivateBeforePublic")
+ << QByteArray("struct QObject { void connect(); }\n"
+ "class XmarksTheSpot : public QObject {\n"
+ "private:\n"
+ " @Q_PROPERTY(int it READ getIt WRITE setIt NOTIFY itChanged)\n"
+ "public:\n"
+ " void find();\n"
+ "};\n")
+ << QByteArray("struct QObject { void connect(); }\n"
+ "class XmarksTheSpot : public QObject {\n"
+ "private:\n"
+ " Q_PROPERTY(int it READ getIt WRITE setIt NOTIFY itChanged)\n"
+ " int m_it;\n"
+ "\n"
+ "public:\n"
+ " void find();\n"
+ " int getIt() const;\n"
+ "public slots:\n"
+ " void setIt(int it)\n"
+ " {\n"
+ " if (m_it == it)\n"
+ " return;\n"
+ " m_it = it;\n"
+ " emit itChanged(m_it);\n"
+ " }\n"
+ "signals:\n"
+ " void itChanged(int it);\n"
+ "};\n"
+ "\n"
+ "int XmarksTheSpot::getIt() const\n"
+ "{\n"
+ " return m_it;\n"
+ "}\n");
+ }
+
+ void test()
+ {
+ QFETCH(QByteArray, original);
+ QFETCH(QByteArray, expected);
+
+ QuickFixSettings s;
+ s->setterAsSlot = true;
+ s->setterInCppFileFrom = 0;
+ s->setterParameterNameTemplate = "<name>";
+ s->signalWithNewValue = true;
+
+ InsertQtPropertyMembers factory;
+ QuickFixOperationTest({CppTestDocument::create("file.cpp", original, expected)}, &factory);
+ }
+
+ void testNotTriggeringOnInvalidCode()
+ {
+ const QByteArray input = "class C { @Q_PROPERTY(typeid foo READ foo) };\n";
+ InsertQtPropertyMembers factory;
+ QuickFixOperationTest({CppTestDocument::create("file.h", input, {})}, &factory);
+ }
+};
+
+class GenerateConstructorTest : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void test_data()
+ {
+ QTest::addColumn<QByteArray>("original_header");
+ QTest::addColumn<QByteArray>("expected_header");
+ QTest::addColumn<QByteArray>("original_source");
+ QTest::addColumn<QByteArray>("expected_source");
+ QTest::addColumn<int>("location");
+ const int Inside = ConstructorLocation::Inside;
+ const int Outside = ConstructorLocation::Outside;
+ const int CppGenNamespace = ConstructorLocation::CppGenNamespace;
+ const int CppGenUsingDirective = ConstructorLocation::CppGenUsingDirective;
+ const int CppRewriteType = ConstructorLocation::CppRewriteType;
+
+ QByteArray header = R"--(
+class@ Foo{
+ int test;
+ static int s;
+};
+)--";
+ QByteArray expected = R"--(
+class Foo{
+ int test;
+ static int s;
+public:
+ Foo(int test) : test(test)
+ {}
+};
+)--";
+ QTest::newRow("ignore static") << header << expected << QByteArray() << QByteArray() << Inside;
+
+ header = R"--(
+class@ Foo{
+ CustomType test;
+};
+)--";
+ expected = R"--(
+class Foo{
+ CustomType test;
+public:
+ Foo(CustomType test) : test(std::move(test))
+ {}
+};
+)--";
+ QTest::newRow("Move custom value types")
+ << header << expected << QByteArray() << QByteArray() << Inside;
+
+ header = R"--(
+class@ Foo{
+ int test;
+protected:
+ Foo() = default;
+};
+)--";
+ expected = R"--(
+class Foo{
+ int test;
+public:
+ Foo(int test) : test(test)
+ {}
+
+protected:
+ Foo() = default;
+};
+)--";
+
+ QTest::newRow("new section before existing")
+ << header << expected << QByteArray() << QByteArray() << Inside;
+
+ header = R"--(
+class@ Foo{
+ int test;
+};
+)--";
+ expected = R"--(
+class Foo{
+ int test;
+public:
+ Foo(int test) : test(test)
+ {}
+};
+)--";
+ QTest::newRow("new section at end")
+ << header << expected << QByteArray() << QByteArray() << Inside;
+
+ header = R"--(
+class@ Foo{
+ int test;
+public:
+ /**
+ * Random comment
+ */
+ Foo(int i, int i2);
+};
+)--";
+ expected = R"--(
+class Foo{
+ int test;
+public:
+ Foo(int test) : test(test)
+ {}
+ /**
+ * Random comment
+ */
+ Foo(int i, int i2);
+};
+)--";
+ QTest::newRow("in section before")
+ << header << expected << QByteArray() << QByteArray() << Inside;
+
+ header = R"--(
+class@ Foo{
+ int test;
+public:
+ Foo() = default;
+};
+)--";
+ expected = R"--(
+class Foo{
+ int test;
+public:
+ Foo() = default;
+ Foo(int test) : test(test)
+ {}
+};
+)--";
+ QTest::newRow("in section after")
+ << header << expected << QByteArray() << QByteArray() << Inside;
+
+ header = R"--(
+class@ Foo{
+ int test1;
+ int test2;
+ int test3;
+public:
+};
+)--";
+ expected = R"--(
+class Foo{
+ int test1;
+ int test2;
+ int test3;
+public:
+ Foo(int test2, int test3, int test1) : test1(test1),
+ test2(test2),
+ test3(test3)
+ {}
+};
+)--";
+ // No worry, that is not the default behavior.
+ // Move first member to the back when testing with 3 or more members
+ QTest::newRow("changed parameter order")
+ << header << expected << QByteArray() << QByteArray() << Inside;
+
+ header = R"--(
+class@ Foo{
+ int test;
+ int di_test;
+public:
+};
+)--";
+ expected = R"--(
+class Foo{
+ int test;
+ int di_test;
+public:
+ Foo(int test, int di_test = 42) : test(test),
+ di_test(di_test)
+ {}
+};
+)--";
+ QTest::newRow("default parameters")
+ << header << expected << QByteArray() << QByteArray() << Inside;
+
+ header = R"--(
+struct Bar{
+ Bar(int i);
+};
+class@ Foo : public Bar{
+ int test;
+public:
+};
+)--";
+ expected = R"--(
+struct Bar{
+ Bar(int i);
+};
+class Foo : public Bar{
+ int test;
+public:
+ Foo(int test, int i) : Bar(i),
+ test(test)
+ {}
+};
+)--";
+ QTest::newRow("parent constructor")
+ << header << expected << QByteArray() << QByteArray() << Inside;
+
+ header = R"--(
+struct Bar{
+ Bar(int use_i = 6);
+};
+class@ Foo : public Bar{
+ int test;
+public:
+};
+)--";
+ expected = R"--(
+struct Bar{
+ Bar(int use_i = 6);
+};
+class Foo : public Bar{
+ int test;
+public:
+ Foo(int test, int use_i = 6) : Bar(use_i),
+ test(test)
+ {}
+};
+)--";
+ QTest::newRow("parent constructor with default")
+ << header << expected << QByteArray() << QByteArray() << Inside;
+
+ header = R"--(
+struct Bar{
+ Bar(int use_i = L'A', int use_i2 = u8"B");
+};
+class@ Foo : public Bar{
+public:
+};
+)--";
+ expected = R"--(
+struct Bar{
+ Bar(int use_i = L'A', int use_i2 = u8"B");
+};
+class Foo : public Bar{
+public:
+ Foo(int use_i = L'A', int use_i2 = u8"B") : Bar(use_i, use_i2)
+ {}
+};
+)--";
+ QTest::newRow("parent constructor with char/string default value")
+ << header << expected << QByteArray() << QByteArray() << Inside;
+
+ const QByteArray common = R"--(
+namespace N{
+ template<typename T>
+ struct vector{
+ };
+}
+)--";
+ header = common + R"--(
+namespace M{
+enum G{g};
+class@ Foo{
+ N::vector<G> g;
+ enum E{e}e;
+public:
+};
+}
+)--";
+
+ expected = common + R"--(
+namespace M{
+enum G{g};
+class@ Foo{
+ N::vector<G> g;
+ enum E{e}e;
+public:
+ Foo(const N::vector<G> &g, E e);
+};
+
+Foo::Foo(const N::vector<G> &g, Foo::E e) : g(g),
+ e(e)
+{}
+
+}
+)--";
+ QTest::newRow("source: right type outside class ")
+ << QByteArray() << QByteArray() << header << expected << Outside;
+ expected = common + R"--(
+namespace M{
+enum G{g};
+class@ Foo{
+ N::vector<G> g;
+ enum E{e}e;
+public:
+ Foo(const N::vector<G> &g, E e);
+};
+}
+
+
+inline M::Foo::Foo(const N::vector<M::G> &g, M::Foo::E e) : g(g),
+ e(e)
+{}
+
+)--";
+ QTest::newRow("header: right type outside class ")
+ << header << expected << QByteArray() << QByteArray() << Outside;
+
+ expected = common + R"--(
+namespace M{
+enum G{g};
+class@ Foo{
+ N::vector<G> g;
+ enum E{e}e;
+public:
+ Foo(const N::vector<G> &g, E e);
+};
+}
+)--";
+ const QByteArray source = R"--(
+#include "file.h"
+)--";
+ QByteArray expected_source = R"--(
+#include "file.h"
+
+
+namespace M {
+Foo::Foo(const N::vector<G> &g, Foo::E e) : g(g),
+ e(e)
+{}
+
+}
+)--";
+ QTest::newRow("source: right type inside namespace")
+ << header << expected << source << expected_source << CppGenNamespace;
+
+ expected_source = R"--(
+#include "file.h"
+
+using namespace M;
+Foo::Foo(const N::vector<G> &g, Foo::E e) : g(g),
+ e(e)
+{}
+)--";
+ QTest::newRow("source: right type with using directive")
+ << header << expected << source << expected_source << CppGenUsingDirective;
+
+ expected_source = R"--(
+#include "file.h"
+
+M::Foo::Foo(const N::vector<M::G> &g, M::Foo::E e) : g(g),
+ e(e)
+{}
+)--";
+ QTest::newRow("source: right type while rewritung types")
+ << header << expected << source << expected_source << CppRewriteType;
+
+ }
+
+ void test()
+ {
+ class TestFactory : public GenerateConstructor
+ {
+ public:
+ TestFactory() { setTest(); }
+ };
+
+ QFETCH(QByteArray, original_header);
+ QFETCH(QByteArray, expected_header);
+ QFETCH(QByteArray, original_source);
+ QFETCH(QByteArray, expected_source);
+ QFETCH(int, location);
+
+ QuickFixSettings s;
+ s->valueTypes << "CustomType";
+ using L = ConstructorLocation;
+ if (location == L::Inside) {
+ s->setterInCppFileFrom = -1;
+ s->setterOutsideClassFrom = -1;
+ } else if (location == L::Outside) {
+ s->setterInCppFileFrom = -1;
+ s->setterOutsideClassFrom = 1;
+ } else if (location >= L::CppGenNamespace && location <= L::CppRewriteType) {
+ s->setterInCppFileFrom = 1;
+ s->setterOutsideClassFrom = -1;
+ using Handling = CppQuickFixSettings::MissingNamespaceHandling;
+ if (location == L::CppGenNamespace)
+ s->cppFileNamespaceHandling = Handling::CreateMissing;
+ else if (location == L::CppGenUsingDirective)
+ s->cppFileNamespaceHandling = Handling::AddUsingDirective;
+ else if (location == L::CppRewriteType)
+ s->cppFileNamespaceHandling = Handling::RewriteType;
+ } else {
+ QFAIL("location is none of the values of the ConstructorLocation enum");
+ }
+
+ QList<TestDocumentPtr> testDocuments;
+ testDocuments << CppTestDocument::create("file.h", original_header, expected_header);
+ testDocuments << CppTestDocument::create("file.cpp", original_source, expected_source);
+ TestFactory factory;
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+
+private:
+ enum ConstructorLocation { Inside, Outside, CppGenNamespace, CppGenUsingDirective, CppRewriteType };
+};
+
+QObject *GenerateGetterSetter::createTest()
+{
+ return new GenerateGetterSetterTest;
+}
+
+QObject *GenerateGettersSettersForClass::createTest()
+{
+ return new GenerateGettersSettersTest;
+}
+
+QObject *InsertQtPropertyMembers::createTest()
+{
+ return new InsertQtPropertyMembersTest;
+}
+
+QObject *GenerateConstructor::createTest()
+{
+ return new GenerateConstructorTest;
+}
+
+#endif // WITH_TESTS
+
+} // namespace
+
+void registerCodeGenerationQuickfixes()
+{
+ CppQuickFixFactory::registerFactory<GenerateGetterSetter>();
+ CppQuickFixFactory::registerFactory<GenerateGettersSettersForClass>();
+ CppQuickFixFactory::registerFactory<GenerateConstructor>();
+ CppQuickFixFactory::registerFactory<InsertQtPropertyMembers>();
+}
+
+} // namespace CppEditor::Internal
+
+#include <cppcodegenerationquickfixes.moc>
diff --git a/src/plugins/cppeditor/quickfixes/cppcodegenerationquickfixes.h b/src/plugins/cppeditor/quickfixes/cppcodegenerationquickfixes.h
new file mode 100644
index 0000000000..3cef95c7c1
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/cppcodegenerationquickfixes.h
@@ -0,0 +1,8 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+namespace CppEditor::Internal {
+void registerCodeGenerationQuickfixes();
+} // namespace CppEditor::Internal
diff --git a/src/plugins/cppeditor/cppinsertvirtualmethods.cpp b/src/plugins/cppeditor/quickfixes/cppinsertvirtualmethods.cpp
index b34977cfd8..1a2ee5ab87 100644
--- a/src/plugins/cppeditor/cppinsertvirtualmethods.cpp
+++ b/src/plugins/cppeditor/quickfixes/cppinsertvirtualmethods.cpp
@@ -3,12 +3,13 @@
#include "cppinsertvirtualmethods.h"
-#include "cppcodestylesettings.h"
-#include "cppeditortr.h"
+#include "../cppcodestylesettings.h"
+#include "../cppeditortr.h"
+#include "../cpptoolsreuse.h"
+#include "../functionutils.h"
+#include "../insertionpointlocator.h"
#include "cppquickfixassistant.h"
-#include "cpptoolsreuse.h"
-#include "functionutils.h"
-#include "insertionpointlocator.h"
+#include "cppquickfixhelpers.h"
#include <coreplugin/icore.h>
#include <texteditor/fontsettings.h>
@@ -116,6 +117,11 @@ public:
QSortFilterProxyModel *classFunctionFilterModel;
};
+void registerInsertVirtualMethodsQuickfix()
+{
+ CppQuickFixFactory::registerFactory<InsertVirtualMethods>();
+}
+
} // namespace Internal
} // namespace CppEditor
@@ -761,8 +767,7 @@ public:
printer.showTemplateParameters = true;
Utils::ChangeSet headerChangeSet;
const CppRefactoringChanges refactoring(snapshot());
- const Utils::FilePath filePath = currentFile()->filePath();
- const CppRefactoringFilePtr headerFile = refactoring.cppFile(filePath);
+ const CppRefactoringFilePtr headerFile = currentFile();
const LookupContext targetContext(headerFile->cppDocument(), snapshot());
const Class *targetClass = m_classAST->symbol;
@@ -862,9 +867,8 @@ public:
// Write header file
if (!headerChangeSet.isEmpty()) {
- headerFile->setChangeSet(headerChangeSet);
headerFile->setOpenEditor(true, m_insertPosDecl);
- headerFile->apply();
+ headerFile->apply(headerChangeSet);
}
// Insert in implementation file
@@ -915,10 +919,7 @@ public:
implementationChangeSet.insert(insertPos, QLatin1String("\n\n") + defText);
}
- if (!implementationChangeSet.isEmpty()) {
- implementationFile->setChangeSet(implementationChangeSet);
- implementationFile->apply();
- }
+ implementationFile->apply(implementationChangeSet);
}
}
@@ -1269,6 +1270,17 @@ public:
void saveSettings() override { }
};
+class InsertVirtualMethodsTest : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void test_data();
+ void test();
+ void testImplementationFile();
+ void testBaseClassInNamespace();
+};
+
void InsertVirtualMethodsTest::test_data()
{
QTest::addColumn<InsertVirtualMethodsDialog::ImplementationMode>("implementationMode");
@@ -1969,6 +1981,11 @@ InsertVirtualMethods *InsertVirtualMethods::createTestFactory()
InsertVirtualMethodsDialog::ModeOutsideClass, true, false));
}
+QObject *InsertVirtualMethods::createTest()
+{
+ return new Tests::InsertVirtualMethodsTest;
+}
+
#endif // WITH_TESTS
} // namespace Internal
diff --git a/src/plugins/cppeditor/cppinsertvirtualmethods.h b/src/plugins/cppeditor/quickfixes/cppinsertvirtualmethods.h
index 7116efde1d..ffb749a71e 100644
--- a/src/plugins/cppeditor/cppinsertvirtualmethods.h
+++ b/src/plugins/cppeditor/quickfixes/cppinsertvirtualmethods.h
@@ -19,6 +19,7 @@ public:
void doMatch(const CppQuickFixInterface &interface,
TextEditor::QuickFixOperations &result) override;
#ifdef WITH_TESTS
+ static QObject *createTest();
static InsertVirtualMethods *createTestFactory();
#endif
@@ -26,20 +27,7 @@ private:
InsertVirtualMethodsDialog *m_dialog;
};
-#ifdef WITH_TESTS
-namespace Tests {
-class InsertVirtualMethodsTest : public QObject
-{
- Q_OBJECT
-
-private slots:
- void test_data();
- void test();
- void testImplementationFile();
- void testBaseClassInNamespace();
-};
-} // namespace Tests
-#endif // WITH_TESTS
+void registerInsertVirtualMethodsQuickfix();
} // namespace Internal
} // namespace CppEditor
diff --git a/src/plugins/cppeditor/quickfixes/cppquickfix.cpp b/src/plugins/cppeditor/quickfixes/cppquickfix.cpp
new file mode 100644
index 0000000000..a4052db83e
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/cppquickfix.cpp
@@ -0,0 +1,187 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "cppquickfix.h"
+
+#include "../baseeditordocumentprocessor.h"
+#include "../cppeditortr.h"
+#include "../cppeditorwidget.h"
+#include "../cppfunctiondecldeflink.h"
+#include "../cpprefactoringchanges.h"
+#include "assigntolocalvariable.h"
+#include "bringidentifierintoscope.h"
+#include "completeswitchstatement.h"
+#include "convertfromandtopointer.h"
+#include "convertnumericliteral.h"
+#include "convertqt4connect.h"
+#include "convertstringliteral.h"
+#include "converttocamelcase.h"
+#include "converttometamethodcall.h"
+#include "cppcodegenerationquickfixes.h"
+#include "cppinsertvirtualmethods.h"
+#include "cppquickfixassistant.h"
+#include "createdeclarationfromuse.h"
+#include "extractfunction.h"
+#include "extractliteralasparameter.h"
+#include "insertfunctiondefinition.h"
+#include "logicaloperationquickfixes.h"
+#include "moveclasstoownfile.h"
+#include "movefunctiondefinition.h"
+#include "rearrangeparamdeclarationlist.h"
+#include "reformatpointerdeclaration.h"
+#include "removeusingnamespace.h"
+#include "rewritecomment.h"
+#include "rewritecontrolstatements.h"
+#include "splitsimpledeclaration.h"
+#include "synchronizememberfunctionorder.h"
+
+#include <extensionsystem/pluginmanager.h>
+#include <extensionsystem/pluginspec.h>
+
+using namespace CPlusPlus;
+using namespace TextEditor;
+
+namespace CppEditor {
+namespace Internal {
+namespace {
+
+class ApplyDeclDefLinkOperation : public CppQuickFixOperation
+{
+public:
+ explicit ApplyDeclDefLinkOperation(const CppQuickFixInterface &interface,
+ const std::shared_ptr<FunctionDeclDefLink> &link)
+ : CppQuickFixOperation(interface, 100)
+ , m_link(link)
+ {}
+
+ void perform() override
+ {
+ if (editor()->declDefLink() == m_link)
+ editor()->applyDeclDefLinkChanges(/*don't jump*/false);
+ }
+
+private:
+ std::shared_ptr<FunctionDeclDefLink> m_link;
+};
+
+class ExtraRefactoringOperations : public CppQuickFixFactory
+{
+public:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ const auto processor = CppModelManager::cppEditorDocumentProcessor(interface.filePath());
+ if (processor) {
+ const auto clangFixItOperations = processor->extraRefactoringOperations(interface);
+ result.append(clangFixItOperations);
+ }
+ }
+};
+
+//! Applies function signature changes
+class ApplyDeclDefLinkChanges: public CppQuickFixFactory
+{
+public:
+ void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override
+ {
+ std::shared_ptr<FunctionDeclDefLink> link = interface.editor()->declDefLink();
+ if (!link || !link->isMarkerVisible())
+ return;
+
+ auto op = new ApplyDeclDefLinkOperation(interface, link);
+ op->setDescription(Tr::tr("Apply Function Signature Changes"));
+ result << op;
+ }
+};
+
+} // namespace
+
+static ExtensionSystem::IPlugin *getCppEditor()
+{
+ using namespace ExtensionSystem;
+ for (PluginSpec * const spec : PluginManager::plugins()) {
+ if (spec->name() == "CppEditor")
+ return spec->plugin();
+ }
+ QTC_ASSERT(false, return nullptr);
+}
+
+CppQuickFixOperation::~CppQuickFixOperation() = default;
+
+void createCppQuickFixFactories()
+{
+ new ApplyDeclDefLinkChanges;
+ new ExtraRefactoringOperations;
+
+ registerAssignToLocalVariableQuickfix();
+ registerBringIdentifierIntoScopeQuickfixes();
+ registerCodeGenerationQuickfixes();
+ registerCompleteSwitchStatementQuickfix();
+ registerConvertFromAndToPointerQuickfix();
+ registerConvertNumericLiteralQuickfix();
+ registerConvertQt4ConnectQuickfix();
+ registerConvertStringLiteralQuickfixes();
+ registerConvertToCamelCaseQuickfix();
+ registerConvertToMetaMethodCallQuickfix();
+ registerCreateDeclarationFromUseQuickfixes();
+ registerExtractFunctionQuickfix();
+ registerExtractLiteralAsParameterQuickfix();
+ registerInsertFunctionDefinitionQuickfixes();
+ registerInsertVirtualMethodsQuickfix();
+ registerLogicalOperationQuickfixes();
+ registerMoveClassToOwnFileQuickfix();
+ registerMoveFunctionDefinitionQuickfixes();
+ registerRearrangeParamDeclarationListQuickfix();
+ registerReformatPointerDeclarationQuickfix();
+ registerRemoveUsingNamespaceQuickfix();
+ registerRewriteCommentQuickfixes();
+ registerRewriteControlStatementQuickfixes();
+ registerSplitSimpleDeclarationQuickfix();
+ registerSynchronizeMemberFunctionOrderQuickfix();
+}
+
+static QList<CppQuickFixFactory *> g_cppQuickFixFactories;
+
+void destroyCppQuickFixFactories()
+{
+ for (int i = g_cppQuickFixFactories.size(); --i >= 0; )
+ delete g_cppQuickFixFactories.at(i);
+}
+
+} // namespace Internal
+
+CppQuickFixFactory::CppQuickFixFactory()
+{
+ Internal::g_cppQuickFixFactories.append(this);
+}
+
+CppQuickFixFactory::~CppQuickFixFactory()
+{
+ Internal::g_cppQuickFixFactories.removeOne(this);
+}
+
+ExtensionSystem::IPlugin *CppQuickFixFactory::cppEditor()
+{
+ static ExtensionSystem::IPlugin * const plugin = Internal::getCppEditor();
+ return plugin;
+}
+
+void CppQuickFixFactory::match(const Internal::CppQuickFixInterface &interface,
+ QuickFixOperations &result)
+{
+ if (m_clangdReplacement) {
+ if (const auto clangdVersion = CppModelManager::usesClangd(
+ interface.currentFile()->editor()->textDocument());
+ clangdVersion && clangdVersion >= m_clangdReplacement) {
+ return;
+ }
+ }
+
+ doMatch(interface, result);
+}
+
+const QList<CppQuickFixFactory *> &CppQuickFixFactory::cppQuickFixFactories()
+{
+ return Internal::g_cppQuickFixFactories;
+}
+
+} // namespace CppEditor
diff --git a/src/plugins/cppeditor/cppquickfix.h b/src/plugins/cppeditor/quickfixes/cppquickfix.h
index e901b2bd30..3d654661bc 100644
--- a/src/plugins/cppeditor/cppquickfix.h
+++ b/src/plugins/cppeditor/quickfixes/cppquickfix.h
@@ -3,9 +3,10 @@
#pragma once
-#include "cppeditor_global.h"
+#include "../cppeditor_global.h"
#include "cppquickfixassistant.h"
+#include <extensionsystem/iplugin.h>
#include <texteditor/quickfix.h>
#include <QVersionNumber>
@@ -16,18 +17,20 @@ namespace CppEditor {
namespace Internal {
class CppQuickFixInterface;
-// These are generated functions that should not be offered in quickfixes.
-const QStringList magicQObjectFunctions();
-
class CppQuickFixOperation
: public TextEditor::QuickFixOperation,
public Internal::CppQuickFixInterface
{
public:
- explicit CppQuickFixOperation(const CppQuickFixInterface &interface, int priority = -1);
+ explicit CppQuickFixOperation(const CppQuickFixInterface &interface, int priority = -1)
+ : QuickFixOperation(priority), CppQuickFixInterface(interface)
+ {}
~CppQuickFixOperation() override;
};
+void createCppQuickFixFactories();
+void destroyCppQuickFixFactories();
+
} // namespace Internal
/*!
@@ -59,14 +62,27 @@ public:
std::optional<QVersionNumber> clangdReplacement() const { return m_clangdReplacement; }
void setClangdReplacement(const QVersionNumber &version) { m_clangdReplacement = version; }
+ template<class Factory> static void registerFactory()
+ {
+ new Factory;
+#ifdef WITH_TESTS
+ cppEditor()->addTestCreator(Factory::createTest);
+#endif
+ }
+
private:
/*!
- Implement this function to doMatch and create the appropriate
+ Implement this function to match and create the appropriate
CppQuickFixOperation objects.
+ Make sure that the function is "cheap". Otherwise, since the match()
+ functions are also called to generate context menu entries,
+ the user might experience a delay opening the context menu.
*/
virtual void doMatch(const Internal::CppQuickFixInterface &interface,
QuickFixOperations &result) = 0;
+ static ExtensionSystem::IPlugin *cppEditor();
+
std::optional<QVersionNumber> m_clangdReplacement;
};
diff --git a/src/plugins/cppeditor/quickfixes/cppquickfix_test.cpp b/src/plugins/cppeditor/quickfixes/cppquickfix_test.cpp
new file mode 100644
index 0000000000..2bde0a1021
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/cppquickfix_test.cpp
@@ -0,0 +1,251 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "cppquickfix_test.h"
+
+#include "../cppcodestylepreferences.h"
+#include "../cppeditorwidget.h"
+#include "../cppmodelmanager.h"
+#include "../cppsourceprocessertesthelper.h"
+#include "../cpptoolssettings.h"
+#include "cppquickfixassistant.h"
+
+#include <projectexplorer/kitmanager.h>
+#include <projectexplorer/projectexplorer.h>
+#include <texteditor/textdocument.h>
+#include <utils/fileutils.h>
+
+#include <QDebug>
+#include <QDir>
+#include <QtTest>
+
+/*!
+ Tests for quick-fixes.
+ */
+using namespace Core;
+using namespace CPlusPlus;
+using namespace ProjectExplorer;
+using namespace TextEditor;
+using namespace Utils;
+
+using CppEditor::Tests::TemporaryDir;
+using CppEditor::Tests::Internal::TestIncludePaths;
+
+namespace CppEditor {
+namespace Internal {
+namespace Tests {
+
+QList<TestDocumentPtr> singleDocument(const QByteArray &original,
+ const QByteArray &expected)
+{
+ return {CppTestDocument::create("file.cpp", original, expected)};
+}
+
+BaseQuickFixTestCase::BaseQuickFixTestCase(const QList<TestDocumentPtr> &testDocuments,
+ const ProjectExplorer::HeaderPaths &headerPaths,
+ const QByteArray &clangFormatSettings)
+ : m_testDocuments(testDocuments)
+ , m_cppCodeStylePreferences(0)
+ , m_restoreHeaderPaths(false)
+{
+ QVERIFY(succeededSoFar());
+ m_succeededSoFar = false;
+
+ // Check if there is exactly one cursor marker
+ unsigned cursorMarkersCount = 0;
+ for (const TestDocumentPtr &document : std::as_const(m_testDocuments)) {
+ if (document->hasCursorMarker())
+ ++cursorMarkersCount;
+ }
+ QVERIFY2(cursorMarkersCount == 1, "Exactly one cursor marker is allowed.");
+
+ // Write documents to disk
+ m_temporaryDirectory.reset(new TemporaryDir);
+ QVERIFY(m_temporaryDirectory->isValid());
+ for (const TestDocumentPtr &document : std::as_const(m_testDocuments)) {
+ if (QFileInfo(document->m_fileName).isRelative())
+ document->setBaseDirectory(m_temporaryDirectory->path());
+ document->writeToDisk();
+ }
+
+ // Create .clang-format file
+ if (!clangFormatSettings.isEmpty())
+ m_temporaryDirectory->createFile(".clang-format", clangFormatSettings);
+
+ // Set appropriate include paths
+ if (!headerPaths.isEmpty()) {
+ m_restoreHeaderPaths = true;
+ m_headerPathsToRestore = CppModelManager::headerPaths();
+ CppModelManager::setHeaderPaths(headerPaths);
+ }
+
+ // Update Code Model
+ QSet<FilePath> filePaths;
+ for (const TestDocumentPtr &document : std::as_const(m_testDocuments))
+ filePaths << document->filePath();
+ QVERIFY(parseFiles(filePaths));
+
+ // Open Files
+ for (const TestDocumentPtr &document : std::as_const(m_testDocuments)) {
+ QVERIFY(openCppEditor(document->filePath(), &document->m_editor,
+ &document->m_editorWidget));
+ closeEditorAtEndOfTestCase(document->m_editor);
+
+ // Set cursor position
+ if (document->hasCursorMarker()) {
+ if (document->hasAnchorMarker()) {
+ document->m_editor->setCursorPosition(document->m_anchorPosition);
+ document->m_editor->select(document->m_cursorPosition);
+ } else {
+ document->m_editor->setCursorPosition(document->m_cursorPosition);
+ }
+ } else {
+ document->m_editor->setCursorPosition(0);
+ }
+
+ // Rehighlight
+ waitForRehighlightedSemanticDocument(document->m_editorWidget);
+ }
+
+ // Enforce the default cpp code style, so we are independent of config file settings.
+ // This is needed by e.g. the GenerateGetterSetter quick fix.
+ m_cppCodeStylePreferences = CppToolsSettings::cppCodeStyle();
+ QVERIFY(m_cppCodeStylePreferences);
+ m_cppCodeStylePreferencesOriginalDelegateId = m_cppCodeStylePreferences->currentDelegateId();
+ m_cppCodeStylePreferences->setCurrentDelegate("qt");
+
+ // Find the document having the cursor marker
+ for (const TestDocumentPtr &document : std::as_const(m_testDocuments)) {
+ if (document->hasCursorMarker()){
+ m_documentWithMarker = document;
+ break;
+ }
+ }
+
+ QVERIFY(m_documentWithMarker);
+ m_succeededSoFar = true;
+}
+
+BaseQuickFixTestCase::~BaseQuickFixTestCase()
+{
+ // Restore default cpp code style
+ if (m_cppCodeStylePreferences)
+ m_cppCodeStylePreferences->setCurrentDelegate(m_cppCodeStylePreferencesOriginalDelegateId);
+
+ // Restore include paths
+ if (m_restoreHeaderPaths)
+ CppModelManager::setHeaderPaths(m_headerPathsToRestore);
+
+ // Remove created files from file system
+ for (const TestDocumentPtr &testDocument : std::as_const(m_testDocuments))
+ QVERIFY(testDocument->filePath().removeFile());
+}
+
+QuickFixOfferedOperationsTest::QuickFixOfferedOperationsTest(
+ const QList<TestDocumentPtr> &testDocuments,
+ CppQuickFixFactory *factory,
+ const ProjectExplorer::HeaderPaths &headerPaths,
+ const QStringList &expectedOperations)
+ : BaseQuickFixTestCase(testDocuments, headerPaths)
+{
+ // Get operations
+ CppQuickFixInterface quickFixInterface(m_documentWithMarker->m_editorWidget, ExplicitlyInvoked);
+ QuickFixOperations actualOperations;
+ factory->match(quickFixInterface, actualOperations);
+
+ // Convert to QStringList
+ QStringList actualOperationsAsStringList;
+ for (const QuickFixOperation::Ptr &operation : std::as_const(actualOperations))
+ actualOperationsAsStringList << operation->description();
+
+ QCOMPARE(actualOperationsAsStringList, expectedOperations);
+}
+
+/// Leading whitespace is not removed, so we can check if the indetation ranges
+/// have been set correctly by the quick-fix.
+static QString &removeTrailingWhitespace(QString &input)
+{
+ const QStringList lines = input.split(QLatin1Char('\n'));
+ input.resize(0);
+ for (int i = 0, total = lines.size(); i < total; ++i) {
+ QString line = lines.at(i);
+ while (line.length() > 0) {
+ QChar lastChar = line[line.length() - 1];
+ if (lastChar == QLatin1Char(' ') || lastChar == QLatin1Char('\t'))
+ line.chop(1);
+ else
+ break;
+ }
+ input.append(line);
+
+ const bool isLastLine = i == lines.size() - 1;
+ if (!isLastLine)
+ input.append(QLatin1Char('\n'));
+ }
+ return input;
+}
+
+QuickFixOperationTest::QuickFixOperationTest(const QList<TestDocumentPtr> &testDocuments,
+ CppQuickFixFactory *factory,
+ const ProjectExplorer::HeaderPaths &headerPaths,
+ int operationIndex,
+ const QByteArray &expectedFailMessage,
+ const QByteArray &clangFormatSettings)
+ : BaseQuickFixTestCase(testDocuments, headerPaths, clangFormatSettings)
+{
+ if (factory->clangdReplacement() && CppModelManager::isClangCodeModelActive())
+ return;
+
+ QVERIFY(succeededSoFar());
+
+ // Perform operation if there is one
+ CppQuickFixInterface quickFixInterface(m_documentWithMarker->m_editorWidget, ExplicitlyInvoked);
+ QuickFixOperations operations;
+ factory->match(quickFixInterface, operations);
+ if (operations.isEmpty()) {
+ QEXPECT_FAIL("QTCREATORBUG-25998", "FIXME", Abort);
+ QVERIFY(testDocuments.first()->m_expectedSource.isEmpty());
+ return;
+ }
+
+ QVERIFY(operationIndex < operations.size());
+ const QuickFixOperation::Ptr operation = operations.at(operationIndex);
+ operation->perform();
+
+ // Compare all files
+ for (const TestDocumentPtr &testDocument : std::as_const(m_testDocuments)) {
+ // Check
+ QString result = testDocument->m_editorWidget->document()->toPlainText();
+ removeTrailingWhitespace(result);
+ QEXPECT_FAIL("escape string literal: raw string literal", "FIXME", Continue);
+ QEXPECT_FAIL("escape string literal: unescape adjacent literals", "FIXME", Continue);
+ if (!expectedFailMessage.isEmpty())
+ QEXPECT_FAIL("", expectedFailMessage.data(), Continue);
+ else if (result != testDocument->m_expectedSource) {
+ qDebug() << "---" << testDocument->m_expectedSource;
+ qDebug() << "+++" << result;
+ }
+ QCOMPARE(result, testDocument->m_expectedSource);
+
+ // Undo the change
+ for (int i = 0; i < 100; ++i)
+ testDocument->m_editorWidget->undo();
+ result = testDocument->m_editorWidget->document()->toPlainText();
+ QCOMPARE(result, testDocument->m_source);
+ }
+}
+
+void QuickFixOperationTest::run(const QList<TestDocumentPtr> &testDocuments,
+ CppQuickFixFactory *factory,
+ const QString &headerPath,
+ int operationIndex)
+{
+ ProjectExplorer::HeaderPaths headerPaths;
+ headerPaths.push_back(ProjectExplorer::HeaderPath::makeUser(headerPath));
+ QuickFixOperationTest(testDocuments, factory, headerPaths, operationIndex);
+}
+
+} // namespace Tests
+} // namespace Internal
+} // namespace CppEditor
+
diff --git a/src/plugins/cppeditor/quickfixes/cppquickfix_test.h b/src/plugins/cppeditor/quickfixes/cppquickfix_test.h
new file mode 100644
index 0000000000..b199062970
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/cppquickfix_test.h
@@ -0,0 +1,94 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "../cpptoolstestcase.h"
+#include "cppquickfix.h"
+#include "cppquickfixsettings.h"
+
+#include <projectexplorer/headerpath.h>
+
+#include <QByteArray>
+#include <QList>
+#include <QObject>
+#include <QSharedPointer>
+#include <QStringList>
+
+namespace TextEditor { class QuickFixOperation; }
+
+namespace CppEditor {
+class CppCodeStylePreferences;
+
+namespace Internal {
+namespace Tests {
+
+class QuickFixSettings
+{
+ const CppQuickFixSettings original = *CppQuickFixSettings::instance();
+
+public:
+ CppQuickFixSettings *operator->() { return CppQuickFixSettings::instance(); }
+ ~QuickFixSettings() { *CppQuickFixSettings::instance() = original; }
+};
+
+class BaseQuickFixTestCase : public CppEditor::Tests::TestCase
+{
+public:
+ /// Exactly one QuickFixTestDocument must contain the cursor position marker '@'
+ /// or "@{start}" and "@{end}"
+ BaseQuickFixTestCase(const QList<TestDocumentPtr> &testDocuments,
+ const ProjectExplorer::HeaderPaths &headerPaths,
+ const QByteArray &clangFormatSettings = {});
+
+ ~BaseQuickFixTestCase();
+
+protected:
+ TestDocumentPtr m_documentWithMarker;
+ QList<TestDocumentPtr> m_testDocuments;
+
+private:
+ QScopedPointer<CppEditor::Tests::TemporaryDir> m_temporaryDirectory;
+
+ CppCodeStylePreferences *m_cppCodeStylePreferences;
+ QByteArray m_cppCodeStylePreferencesOriginalDelegateId;
+
+ ProjectExplorer::HeaderPaths m_headerPathsToRestore;
+ bool m_restoreHeaderPaths;
+};
+
+/// Tests the offered operations provided by a given CppQuickFixFactory
+class QuickFixOfferedOperationsTest : public BaseQuickFixTestCase
+{
+public:
+ QuickFixOfferedOperationsTest(const QList<TestDocumentPtr> &testDocuments,
+ CppQuickFixFactory *factory,
+ const ProjectExplorer::HeaderPaths &headerPaths
+ = ProjectExplorer::HeaderPaths(),
+ const QStringList &expectedOperations = QStringList());
+};
+
+/// Tests a concrete QuickFixOperation of a given CppQuickFixFactory
+class QuickFixOperationTest : public BaseQuickFixTestCase
+{
+public:
+ QuickFixOperationTest(const QList<TestDocumentPtr> &testDocuments,
+ CppQuickFixFactory *factory,
+ const ProjectExplorer::HeaderPaths &headerPaths
+ = ProjectExplorer::HeaderPaths(),
+ int operationIndex = 0,
+ const QByteArray &expectedFailMessage = {},
+ const QByteArray &clangFormatSettings = {});
+
+ static void run(const QList<TestDocumentPtr> &testDocuments,
+ CppQuickFixFactory *factory,
+ const QString &headerPath,
+ int operationIndex = 0);
+};
+
+QList<TestDocumentPtr> singleDocument(const QByteArray &original,
+ const QByteArray &expected);
+
+} // namespace Tests
+} // namespace Internal
+} // namespace CppEditor
diff --git a/src/plugins/cppeditor/cppquickfixassistant.cpp b/src/plugins/cppeditor/quickfixes/cppquickfixassistant.cpp
index 1456ce5604..834abaed8a 100644
--- a/src/plugins/cppeditor/cppquickfixassistant.cpp
+++ b/src/plugins/cppeditor/quickfixes/cppquickfixassistant.cpp
@@ -3,10 +3,10 @@
#include "cppquickfixassistant.h"
-#include "cppeditorwidget.h"
-#include "cppmodelmanager.h"
+#include "../cppeditorwidget.h"
+#include "../cppmodelmanager.h"
+#include "../cpprefactoringchanges.h"
#include "cppquickfix.h"
-#include "cpprefactoringchanges.h"
#include <texteditor/codeassist/genericproposal.h>
#include <texteditor/codeassist/iassistprocessor.h>
diff --git a/src/plugins/cppeditor/cppquickfixassistant.h b/src/plugins/cppeditor/quickfixes/cppquickfixassistant.h
index 63a37f715a..db818c923c 100644
--- a/src/plugins/cppeditor/cppquickfixassistant.h
+++ b/src/plugins/cppeditor/quickfixes/cppquickfixassistant.h
@@ -3,7 +3,7 @@
#pragma once
-#include "cppsemanticinfo.h"
+#include "../cppsemanticinfo.h"
#include <texteditor/codeassist/assistinterface.h>
#include <texteditor/codeassist/iassistprovider.h>
diff --git a/src/plugins/cppeditor/quickfixes/cppquickfixhelpers.cpp b/src/plugins/cppeditor/quickfixes/cppquickfixhelpers.cpp
new file mode 100644
index 0000000000..eb67b928db
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/cppquickfixhelpers.cpp
@@ -0,0 +1,199 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "cppquickfixhelpers.h"
+
+#include "../cppcodestylesettings.h"
+#include "../cppprojectfile.h"
+#include "../includeutils.h"
+#include "cppquickfixassistant.h"
+
+#include <cplusplus/CppRewriter.h>
+#include <cplusplus/Overview.h>
+#include <cplusplus/TypeOfExpression.h>
+
+using namespace CPlusPlus;
+using namespace Utils;
+
+namespace CppEditor::Internal {
+
+void insertNewIncludeDirective(
+ const QString &include,
+ CppRefactoringFilePtr file,
+ const Document::Ptr &cppDocument,
+ ChangeSet &changes)
+{
+ // Find optimal position
+ unsigned newLinesToPrepend = 0;
+ unsigned newLinesToAppend = 0;
+ const int insertLine = lineForNewIncludeDirective(
+ file->filePath(),
+ file->document(),
+ cppDocument,
+ IgnoreMocIncludes,
+ AutoDetect,
+ include,
+ &newLinesToPrepend,
+ &newLinesToAppend);
+ QTC_ASSERT(insertLine >= 1, return);
+ const int insertPosition = file->position(insertLine, 1);
+ QTC_ASSERT(insertPosition >= 0, return);
+
+ // Construct text to insert
+ const QString includeLine = QLatin1String("#include ") + include + QLatin1Char('\n');
+ QString prependedNewLines, appendedNewLines;
+ while (newLinesToAppend--)
+ appendedNewLines += QLatin1String("\n");
+ while (newLinesToPrepend--)
+ prependedNewLines += QLatin1String("\n");
+ const QString textToInsert = prependedNewLines + includeLine + appendedNewLines;
+
+ // Insert
+ changes.insert(insertPosition, textToInsert);
+}
+
+ClassSpecifierAST *astForClassOperations(const CppQuickFixInterface &interface)
+{
+ const QList<AST *> &path = interface.path();
+ if (path.isEmpty())
+ return nullptr;
+ if (const auto classSpec = path.last()->asClassSpecifier()) // Cursor inside class decl?
+ return classSpec;
+
+ // Cursor on a class name?
+ if (path.size() < 2)
+ return nullptr;
+ const SimpleNameAST * const nameAST = path.at(path.size() - 1)->asSimpleName();
+ if (!nameAST || !interface.isCursorOn(nameAST))
+ return nullptr;
+ if (const auto classSpec = path.at(path.size() - 2)->asClassSpecifier())
+ return classSpec;
+ return nullptr;
+}
+
+bool nameIncludesOperatorName(const Name *name)
+{
+ return name->asOperatorNameId()
+ || (name->asQualifiedNameId() && name->asQualifiedNameId()->name()->asOperatorNameId());
+}
+
+QString inlinePrefix(const FilePath &targetFile, const std::function<bool()> &extraCondition)
+{
+ if (ProjectFile::isHeader(ProjectFile::classify(targetFile.path()))
+ && (!extraCondition || extraCondition())) {
+ return "inline ";
+ }
+ return {};
+}
+
+Class *isMemberFunction(const CPlusPlus::LookupContext &context, CPlusPlus::Function *function)
+{
+ QTC_ASSERT(function, return nullptr);
+
+ Scope *enclosingScope = function->enclosingScope();
+ while (!(enclosingScope->asNamespace() || enclosingScope->asClass()))
+ enclosingScope = enclosingScope->enclosingScope();
+ QTC_ASSERT(enclosingScope != nullptr, return nullptr);
+
+ const Name *functionName = function->name();
+ if (!functionName)
+ return nullptr;
+
+ if (!functionName->asQualifiedNameId())
+ return nullptr; // trying to add a declaration for a global function
+
+ const QualifiedNameId *q = functionName->asQualifiedNameId();
+ if (!q->base())
+ return nullptr;
+
+ if (ClassOrNamespace *binding = context.lookupType(q->base(), enclosingScope)) {
+ const QList<Symbol *> symbols = binding->symbols();
+ for (Symbol *s : symbols) {
+ if (Class *matchingClass = s->asClass())
+ return matchingClass;
+ }
+ }
+
+ return nullptr;
+}
+
+CPlusPlus::Namespace *isNamespaceFunction(
+ const CPlusPlus::LookupContext &context, CPlusPlus::Function *function)
+{
+ QTC_ASSERT(function, return nullptr);
+ if (isMemberFunction(context, function))
+ return nullptr;
+
+ Scope *enclosingScope = function->enclosingScope();
+ while (!(enclosingScope->asNamespace() || enclosingScope->asClass()))
+ enclosingScope = enclosingScope->enclosingScope();
+ QTC_ASSERT(enclosingScope != nullptr, return nullptr);
+
+ const Name *functionName = function->name();
+ if (!functionName)
+ return nullptr;
+
+ // global namespace
+ if (!functionName->asQualifiedNameId()) {
+ const QList<Symbol *> symbols = context.globalNamespace()->symbols();
+ for (Symbol *s : symbols) {
+ if (Namespace *matchingNamespace = s->asNamespace())
+ return matchingNamespace;
+ }
+ return nullptr;
+ }
+
+ const QualifiedNameId *q = functionName->asQualifiedNameId();
+ if (!q->base())
+ return nullptr;
+
+ if (ClassOrNamespace *binding = context.lookupType(q->base(), enclosingScope)) {
+ const QList<Symbol *> symbols = binding->symbols();
+ for (Symbol *s : symbols) {
+ if (Namespace *matchingNamespace = s->asNamespace())
+ return matchingNamespace;
+ }
+ }
+
+ return nullptr;
+}
+
+QString nameString(const CPlusPlus::NameAST *name)
+{
+ return CppCodeStyleSettings::currentProjectCodeStyleOverview().prettyName(name->name);
+}
+
+CPlusPlus::FullySpecifiedType typeOfExpr(
+ const ExpressionAST *expr,
+ const CppRefactoringFilePtr &file,
+ const Snapshot &snapshot,
+ const LookupContext &context)
+{
+ TypeOfExpression typeOfExpression;
+ typeOfExpression.init(file->cppDocument(), snapshot, context.bindings());
+ Scope *scope = file->scopeAt(expr->firstToken());
+ const QList<LookupItem> result
+ = typeOfExpression(file->textOf(expr).toUtf8(), scope, TypeOfExpression::Preprocess);
+ if (result.isEmpty())
+ return {};
+
+ SubstitutionEnvironment env;
+ env.setContext(context);
+ env.switchScope(result.first().scope());
+ ClassOrNamespace *con = typeOfExpression.context().lookupType(scope);
+ if (!con)
+ con = typeOfExpression.context().globalNamespace();
+ UseMinimalNames q(con);
+ env.enter(&q);
+
+ Control *control = context.bindings()->control().get();
+ return rewriteType(result.first().type(), &env, control);
+}
+
+const QStringList magicQObjectFunctions()
+{
+ static QStringList list{"metaObject", "qt_metacast", "qt_metacall", "qt_static_metacall"};
+ return list;
+}
+
+} // namespace CppEditor::Internal
diff --git a/src/plugins/cppeditor/quickfixes/cppquickfixhelpers.h b/src/plugins/cppeditor/quickfixes/cppquickfixhelpers.h
new file mode 100644
index 0000000000..b083fdb0d8
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/cppquickfixhelpers.h
@@ -0,0 +1,47 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "../cpprefactoringchanges.h"
+
+#include <QStringList>
+
+namespace CppEditor::Internal {
+class CppQuickFixInterface;
+
+// These are generated functions that should not be offered in quickfixes.
+const QStringList magicQObjectFunctions();
+
+// Given include is e.g. "afile.h" or <afile.h> (quotes/angle brackets included!).
+void insertNewIncludeDirective(
+ const QString &include,
+ CppRefactoringFilePtr file,
+ const CPlusPlus::Document::Ptr &cppDocument,
+ Utils::ChangeSet &changes);
+
+// Returns a non-null value if and only if the cursor is on the name of a (proper) class
+// declaration or at some place inside the body of a class declaration that does not
+// correspond to an AST of its own, i.e. on "empty space".
+CPlusPlus::ClassSpecifierAST *astForClassOperations(const CppQuickFixInterface &interface);
+
+bool nameIncludesOperatorName(const CPlusPlus::Name *name);
+
+QString inlinePrefix(const Utils::FilePath &targetFile,
+ const std::function<bool()> &extraCondition = {});
+
+CPlusPlus::Class *isMemberFunction(
+ const CPlusPlus::LookupContext &context, CPlusPlus::Function *function);
+
+CPlusPlus::Namespace *isNamespaceFunction(
+ const CPlusPlus::LookupContext &context, CPlusPlus::Function *function);
+
+QString nameString(const CPlusPlus::NameAST *name);
+
+CPlusPlus::FullySpecifiedType typeOfExpr(
+ const CPlusPlus::ExpressionAST *expr,
+ const CppRefactoringFilePtr &file,
+ const CPlusPlus::Snapshot &snapshot,
+ const CPlusPlus::LookupContext &context);
+
+} // namespace CppEditor::Internal
diff --git a/src/plugins/cppeditor/cppquickfixprojectsettings.cpp b/src/plugins/cppeditor/quickfixes/cppquickfixprojectsettings.cpp
index 3f3f4bc366..0313103433 100644
--- a/src/plugins/cppeditor/cppquickfixprojectsettings.cpp
+++ b/src/plugins/cppeditor/quickfixes/cppquickfixprojectsettings.cpp
@@ -3,8 +3,8 @@
#include "cppquickfixprojectsettings.h"
-#include "cppeditorconstants.h"
-#include "cppeditortr.h"
+#include "../cppeditorconstants.h"
+#include "../cppeditortr.h"
#include <coreplugin/icore.h>
diff --git a/src/plugins/cppeditor/cppquickfixprojectsettings.h b/src/plugins/cppeditor/quickfixes/cppquickfixprojectsettings.h
index d8e871515f..d8e871515f 100644
--- a/src/plugins/cppeditor/cppquickfixprojectsettings.h
+++ b/src/plugins/cppeditor/quickfixes/cppquickfixprojectsettings.h
diff --git a/src/plugins/cppeditor/cppquickfixprojectsettingswidget.cpp b/src/plugins/cppeditor/quickfixes/cppquickfixprojectsettingswidget.cpp
index feb9777e45..8046d80a60 100644
--- a/src/plugins/cppeditor/cppquickfixprojectsettingswidget.cpp
+++ b/src/plugins/cppeditor/quickfixes/cppquickfixprojectsettingswidget.cpp
@@ -3,8 +3,8 @@
#include "cppquickfixprojectsettingswidget.h"
-#include "cppeditorconstants.h"
-#include "cppeditortr.h"
+#include "../cppeditorconstants.h"
+#include "../cppeditortr.h"
#include "cppquickfixprojectsettings.h"
#include "cppquickfixsettingswidget.h"
diff --git a/src/plugins/cppeditor/cppquickfixprojectsettingswidget.h b/src/plugins/cppeditor/quickfixes/cppquickfixprojectsettingswidget.h
index a88395cc73..a88395cc73 100644
--- a/src/plugins/cppeditor/cppquickfixprojectsettingswidget.h
+++ b/src/plugins/cppeditor/quickfixes/cppquickfixprojectsettingswidget.h
diff --git a/src/plugins/cppeditor/cppquickfixsettings.cpp b/src/plugins/cppeditor/quickfixes/cppquickfixsettings.cpp
index 4ce1082267..e08de44675 100644
--- a/src/plugins/cppeditor/cppquickfixsettings.cpp
+++ b/src/plugins/cppeditor/quickfixes/cppquickfixsettings.cpp
@@ -3,8 +3,8 @@
#include "cppquickfixsettings.h"
-#include "cppcodestylesettings.h"
-#include "cppeditorconstants.h"
+#include "../cppcodestylesettings.h"
+#include "../cppeditorconstants.h"
#include <coreplugin/icore.h>
diff --git a/src/plugins/cppeditor/cppquickfixsettings.h b/src/plugins/cppeditor/quickfixes/cppquickfixsettings.h
index d033516276..d033516276 100644
--- a/src/plugins/cppeditor/cppquickfixsettings.h
+++ b/src/plugins/cppeditor/quickfixes/cppquickfixsettings.h
diff --git a/src/plugins/cppeditor/cppquickfixsettingspage.cpp b/src/plugins/cppeditor/quickfixes/cppquickfixsettingspage.cpp
index 4e1297f5c2..5933694bbc 100644
--- a/src/plugins/cppeditor/cppquickfixsettingspage.cpp
+++ b/src/plugins/cppeditor/quickfixes/cppquickfixsettingspage.cpp
@@ -3,8 +3,8 @@
#include "cppquickfixsettingspage.h"
-#include "cppeditorconstants.h"
-#include "cppeditortr.h"
+#include "../cppeditorconstants.h"
+#include "../cppeditortr.h"
#include "cppquickfixsettingswidget.h"
#include <coreplugin/dialogs/ioptionspage.h>
diff --git a/src/plugins/cppeditor/cppquickfixsettingspage.h b/src/plugins/cppeditor/quickfixes/cppquickfixsettingspage.h
index 6288c9f9f2..6288c9f9f2 100644
--- a/src/plugins/cppeditor/cppquickfixsettingspage.h
+++ b/src/plugins/cppeditor/quickfixes/cppquickfixsettingspage.h
diff --git a/src/plugins/cppeditor/cppquickfixsettingswidget.cpp b/src/plugins/cppeditor/quickfixes/cppquickfixsettingswidget.cpp
index c589a26b9d..67213105bc 100644
--- a/src/plugins/cppeditor/cppquickfixsettingswidget.cpp
+++ b/src/plugins/cppeditor/quickfixes/cppquickfixsettingswidget.cpp
@@ -3,7 +3,7 @@
#include "cppquickfixsettingswidget.h"
-#include "cppeditortr.h"
+#include "../cppeditortr.h"
#include "cppquickfixsettings.h"
#include <utils/layoutbuilder.h>
@@ -215,6 +215,7 @@ e.g. name = "m_test_foo_":
using namespace Layouting;
+ // clang-format off
Grid {
empty, ulLabel(Tr::tr("Generate Setters")), ulLabel(Tr::tr("Generate Getters")), br,
Tr::tr("Inside class:"), Tr::tr("Default"), Tr::tr("Default"), br,
@@ -274,7 +275,7 @@ e.g. name = "m_test_foo_":
},
},
Group {
- title(Tr::tr("Value types:")),
+ title(Tr::tr("Value Types")),
Row {
m_valueTypes,
Column { pushButton_addValueType, pushButton_removeValueType, st, },
@@ -282,6 +283,7 @@ e.g. name = "m_test_foo_":
},
m_returnByConstRefCheckBox,
}.attachTo(this);
+ // clang-format on
// connect controls to settingsChanged signal
auto then = [this] {
diff --git a/src/plugins/cppeditor/cppquickfixsettingswidget.h b/src/plugins/cppeditor/quickfixes/cppquickfixsettingswidget.h
index e11d81a9f2..e11d81a9f2 100644
--- a/src/plugins/cppeditor/cppquickfixsettingswidget.h
+++ b/src/plugins/cppeditor/quickfixes/cppquickfixsettingswidget.h
diff --git a/src/plugins/cppeditor/quickfixes/createdeclarationfromuse.cpp b/src/plugins/cppeditor/quickfixes/createdeclarationfromuse.cpp
new file mode 100644
index 0000000000..a0e8ad2141
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/createdeclarationfromuse.cpp
@@ -0,0 +1,1208 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "createdeclarationfromuse.h"
+
+#include "../cppcodestylesettings.h"
+#include "../cppeditortr.h"
+#include "../cppeditorwidget.h"
+#include "../cpprefactoringchanges.h"
+#include "../insertionpointlocator.h"
+#include "../symbolfinder.h"
+#include "cppquickfix.h"
+#include "cppquickfixhelpers.h"
+#include "cppquickfixprojectsettings.h"
+
+#include <coreplugin/icore.h>
+#include <cplusplus/Overview.h>
+#include <cplusplus/TypeOfExpression.h>
+#include <projectexplorer/projecttree.h>
+
+#include <QInputDialog>
+
+#ifdef WITH_TESTS
+#include "cppquickfix_test.h"
+#include <QtTest>
+#endif
+
+#include <variant>
+
+using namespace CPlusPlus;
+using namespace ProjectExplorer;
+using namespace TextEditor;
+using namespace Utils;
+
+namespace CppEditor::Internal {
+namespace {
+
+using TypeOrExpr = std::variant<const CPlusPlus::ExpressionAST *, CPlusPlus::FullySpecifiedType>;
+
+// FIXME: Needs to consider the scope at the insertion site.
+static QString declFromExpr(
+ const TypeOrExpr &typeOrExpr,
+ const CallAST *call,
+ const NameAST *varName,
+ const Snapshot &snapshot,
+ const LookupContext &context,
+ const CppRefactoringFilePtr &file,
+ bool makeConst)
+{
+ const auto getTypeFromUser = [varName, call]() -> QString {
+ if (call)
+ return {};
+ const QString typeFromUser = QInputDialog::getText(
+ Core::ICore::dialogParent(),
+ Tr::tr("Provide the type"),
+ Tr::tr("Data type:"),
+ QLineEdit::Normal);
+ if (!typeFromUser.isEmpty())
+ return typeFromUser + ' ' + nameString(varName);
+ return {};
+ };
+ const auto getTypeOfExpr = [&](const ExpressionAST *expr) -> FullySpecifiedType {
+ return typeOfExpr(expr, file, snapshot, context);
+ };
+
+ const Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview();
+ const FullySpecifiedType type = std::holds_alternative<FullySpecifiedType>(typeOrExpr)
+ ? std::get<FullySpecifiedType>(typeOrExpr)
+ : getTypeOfExpr(std::get<const ExpressionAST *>(typeOrExpr));
+ if (!call)
+ return type.isValid() ? oo.prettyType(type, varName->name) : getTypeFromUser();
+
+ Function func(file->cppDocument()->translationUnit(), 0, varName->name);
+ func.setConst(makeConst);
+ for (ExpressionListAST *it = call->expression_list; it; it = it->next) {
+ Argument *const arg = new Argument(nullptr, 0, nullptr);
+ arg->setType(getTypeOfExpr(it->value));
+ func.addMember(arg);
+ }
+ return oo.prettyType(type) + ' ' + oo.prettyType(func.type(), varName->name);
+}
+
+
+
+
+class InsertDeclOperation: public CppQuickFixOperation
+{
+public:
+ InsertDeclOperation(const CppQuickFixInterface &interface,
+ const FilePath &targetFilePath, const Class *targetSymbol,
+ InsertionPointLocator::AccessSpec xsSpec, const QString &decl, int priority)
+ : CppQuickFixOperation(interface, priority)
+ , m_targetFilePath(targetFilePath)
+ , m_targetSymbol(targetSymbol)
+ , m_xsSpec(xsSpec)
+ , m_decl(decl)
+ {
+ setDescription(Tr::tr("Add %1 Declaration")
+ .arg(InsertionPointLocator::accessSpecToString(xsSpec)));
+ }
+
+ void perform() override
+ {
+ CppRefactoringChanges refactoring(snapshot());
+
+ InsertionPointLocator locator(refactoring);
+ const InsertionLocation loc = locator.methodDeclarationInClass(
+ m_targetFilePath, m_targetSymbol, m_xsSpec);
+ QTC_ASSERT(loc.isValid(), return);
+
+ CppRefactoringFilePtr targetFile = refactoring.cppFile(m_targetFilePath);
+ int targetPosition = targetFile->position(loc.line(), loc.column());
+
+ ChangeSet target;
+ target.insert(targetPosition, loc.prefix() + m_decl);
+ targetFile->setOpenEditor(true, targetPosition);
+ targetFile->apply(target);
+ }
+
+ static QString generateDeclaration(const Function *function)
+ {
+ Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview();
+ oo.showFunctionSignatures = true;
+ oo.showReturnTypes = true;
+ oo.showArgumentNames = true;
+ oo.showEnclosingTemplate = true;
+
+ QString decl;
+ decl += oo.prettyType(function->type(), function->unqualifiedName());
+ decl += QLatin1String(";\n");
+
+ return decl;
+ }
+
+private:
+ FilePath m_targetFilePath;
+ const Class *m_targetSymbol;
+ InsertionPointLocator::AccessSpec m_xsSpec;
+ QString m_decl;
+};
+
+class DeclOperationFactory
+{
+public:
+ DeclOperationFactory(const CppQuickFixInterface &interface, const FilePath &filePath,
+ const Class *matchingClass, const QString &decl)
+ : m_interface(interface)
+ , m_filePath(filePath)
+ , m_matchingClass(matchingClass)
+ , m_decl(decl)
+ {}
+
+ QuickFixOperation *operator()(InsertionPointLocator::AccessSpec xsSpec, int priority)
+ {
+ return new InsertDeclOperation(m_interface, m_filePath, m_matchingClass, xsSpec, m_decl, priority);
+ }
+
+private:
+ const CppQuickFixInterface &m_interface;
+ const FilePath &m_filePath;
+ const Class *m_matchingClass;
+ const QString &m_decl;
+};
+
+class InsertMemberFromInitializationOp : public CppQuickFixOperation
+{
+public:
+ InsertMemberFromInitializationOp(
+ const CppQuickFixInterface &interface,
+ const Class *theClass,
+ const NameAST *memberName,
+ const TypeOrExpr &typeOrExpr,
+ const CallAST *call,
+ InsertionPointLocator::AccessSpec accessSpec,
+ bool makeStatic,
+ bool makeConst)
+ : CppQuickFixOperation(interface),
+ m_class(theClass), m_memberName(memberName), m_typeOrExpr(typeOrExpr), m_call(call),
+ m_accessSpec(accessSpec), m_makeStatic(makeStatic), m_makeConst(makeConst)
+ {
+ if (call)
+ setDescription(Tr::tr("Add Member Function \"%1\"").arg(nameString(memberName)));
+ else
+ setDescription(Tr::tr("Add Class Member \"%1\"").arg(nameString(memberName)));
+ }
+
+private:
+ void perform() override
+ {
+ QString decl = declFromExpr(m_typeOrExpr, m_call, m_memberName, snapshot(), context(),
+ currentFile(), m_makeConst);
+ if (decl.isEmpty())
+ return;
+ if (m_makeStatic)
+ decl.prepend("static ");
+
+ const CppRefactoringChanges refactoring(snapshot());
+ const InsertionPointLocator locator(refactoring);
+ const FilePath filePath = FilePath::fromUtf8(m_class->fileName());
+ const InsertionLocation loc = locator.methodDeclarationInClass(
+ filePath, m_class, m_accessSpec);
+ QTC_ASSERT(loc.isValid(), return);
+
+ CppRefactoringFilePtr targetFile = refactoring.cppFile(filePath);
+ targetFile->apply(ChangeSet::makeInsert(
+ targetFile->position(loc.line(), loc.column()), loc.prefix() + decl + ";\n"));
+ }
+
+ const Class * const m_class;
+ const NameAST * const m_memberName;
+ const TypeOrExpr m_typeOrExpr;
+ const CallAST * m_call;
+ const InsertionPointLocator::AccessSpec m_accessSpec;
+ const bool m_makeStatic;
+ const bool m_makeConst;
+};
+
+class AddLocalDeclarationOp: public CppQuickFixOperation
+{
+public:
+ AddLocalDeclarationOp(const CppQuickFixInterface &interface,
+ int priority,
+ const BinaryExpressionAST *binaryAST,
+ const SimpleNameAST *simpleNameAST)
+ : CppQuickFixOperation(interface, priority)
+ , binaryAST(binaryAST)
+ , simpleNameAST(simpleNameAST)
+ {
+ setDescription(Tr::tr("Add Local Declaration"));
+ }
+
+ void perform() override
+ {
+ QString declaration = getDeclaration();
+
+ if (!declaration.isEmpty()) {
+ currentFile()->apply(ChangeSet::makeReplace(
+ currentFile()->startOf(binaryAST),
+ currentFile()->endOf(simpleNameAST),
+ declaration));
+ }
+ }
+
+private:
+ QString getDeclaration()
+ {
+ Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview();
+ const auto settings = CppQuickFixProjectsSettings::getQuickFixSettings(
+ ProjectTree::currentProject());
+
+ if (currentFile()->cppDocument()->languageFeatures().cxx11Enabled && settings->useAuto)
+ return "auto " + oo.prettyName(simpleNameAST->name);
+ return declFromExpr(binaryAST->right_expression, nullptr, simpleNameAST, snapshot(),
+ context(), currentFile(), false);
+ }
+
+ const BinaryExpressionAST *binaryAST;
+ const SimpleNameAST *simpleNameAST;
+};
+
+//! Adds a declarations to a definition
+class InsertDeclFromDef: public CppQuickFixFactory
+{
+#ifdef WITH_TESTS
+public:
+ static QObject *createTest();
+#endif
+
+private:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ const QList<AST *> &path = interface.path();
+ CppRefactoringFilePtr file = interface.currentFile();
+
+ FunctionDefinitionAST *funDef = nullptr;
+ int idx = 0;
+ for (; idx < path.size(); ++idx) {
+ AST *node = path.at(idx);
+ if (idx > 1) {
+ if (DeclaratorIdAST *declId = node->asDeclaratorId()) {
+ if (file->isCursorOn(declId)) {
+ if (FunctionDefinitionAST *candidate = path.at(idx - 2)->asFunctionDefinition()) {
+ funDef = candidate;
+ break;
+ }
+ }
+ }
+ }
+
+ if (node->asClassSpecifier())
+ return;
+ }
+
+ if (!funDef || !funDef->symbol)
+ return;
+
+ Function *fun = funDef->symbol;
+ if (Class *matchingClass = isMemberFunction(interface.context(), fun)) {
+ const QualifiedNameId *qName = fun->name()->asQualifiedNameId();
+ for (Symbol *symbol = matchingClass->find(qName->identifier());
+ symbol; symbol = symbol->next()) {
+ Symbol *s = symbol;
+ if (fun->enclosingScope()->asTemplate()) {
+ if (const Template *templ = s->type()->asTemplateType()) {
+ if (Symbol *decl = templ->declaration()) {
+ if (decl->type()->asFunctionType())
+ s = decl;
+ }
+ }
+ }
+ if (!s->name()
+ || !qName->identifier()->match(s->identifier())
+ || !s->type()->asFunctionType())
+ continue;
+
+ if (s->type().match(fun->type())) {
+ // Declaration exists.
+ return;
+ }
+ }
+ const FilePath fileName = matchingClass->filePath();
+ const QString decl = InsertDeclOperation::generateDeclaration(fun);
+
+ // Add several possible insertion locations for declaration
+ DeclOperationFactory operation(interface, fileName, matchingClass, decl);
+
+ result << operation(InsertionPointLocator::Public, 5)
+ << operation(InsertionPointLocator::PublicSlot, 4)
+ << operation(InsertionPointLocator::Protected, 3)
+ << operation(InsertionPointLocator::ProtectedSlot, 2)
+ << operation(InsertionPointLocator::Private, 1)
+ << operation(InsertionPointLocator::PrivateSlot, 0);
+ }
+ }
+};
+
+class AddDeclarationForUndeclaredIdentifier : public CppQuickFixFactory
+{
+public:
+#ifdef WITH_TESTS
+ static QObject *createTest();
+ void setMembersOnly() { m_membersOnly = true; }
+#endif
+
+private:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ // Are we on a name?
+ const QList<AST *> &path = interface.path();
+ if (path.isEmpty())
+ return;
+ if (!path.last()->asSimpleName())
+ return;
+
+ // Special case: Member initializer.
+ if (!checkForMemberInitializer(interface, result))
+ return;
+
+ // Are we inside a function?
+ const FunctionDefinitionAST *func = nullptr;
+ for (auto it = path.rbegin(); !func && it != path.rend(); ++it)
+ func = (*it)->asFunctionDefinition();
+ if (!func)
+ return;
+
+ // Is this name declared somewhere already?
+ const CursorInEditor cursorInEditor(interface.cursor(), interface.filePath(),
+ interface.editor(), interface.editor()->textDocument());
+ const auto followSymbolFallback = [&](const Link &link) {
+ if (!link.hasValidTarget())
+ collectOperations(interface, result);
+ };
+ NonInteractiveFollowSymbolMarker niMarker;
+ CppModelManager::followSymbol(cursorInEditor, followSymbolFallback, false, false,
+ FollowSymbolMode::Exact,
+ CppModelManager::Backend::Builtin);
+ }
+
+ void collectOperations(const CppQuickFixInterface &interface,
+ QuickFixOperations &result)
+ {
+ const QList<AST *> &path = interface.path();
+ const CppRefactoringFilePtr &file = interface.currentFile();
+ for (int index = path.size() - 1; index != -1; --index) {
+ if (const auto call = path.at(index)->asCall())
+ return handleCall(call, interface, result);
+
+ // We only trigger if the identifier appears on the left-hand side of an
+ // assignment expression.
+ const auto binExpr = path.at(index)->asBinaryExpression();
+ if (!binExpr)
+ continue;
+ if (!binExpr->left_expression || !binExpr->right_expression
+ || file->tokenAt(binExpr->binary_op_token).kind() != T_EQUAL
+ || !interface.isCursorOn(binExpr->left_expression)) {
+ return;
+ }
+
+ // In the case of "a.|b = c", find out the type of a, locate the class declaration
+ // and add a member b there.
+ if (const auto memberAccess = binExpr->left_expression->asMemberAccess()) {
+ if (interface.isCursorOn(memberAccess->member_name)
+ && memberAccess->member_name == path.last()) {
+ maybeAddMember(interface, file->scopeAt(memberAccess->firstToken()),
+ file->textOf(memberAccess->base_expression).toUtf8(),
+ binExpr->right_expression, nullptr, result);
+ }
+ return;
+ }
+
+ const auto idExpr = binExpr->left_expression->asIdExpression();
+ if (!idExpr || !idExpr->name)
+ return;
+
+ // In the case of "A::|b = c", add a static member b to A.
+ if (const auto qualName = idExpr->name->asQualifiedName()) {
+ return maybeAddStaticMember(interface, qualName, binExpr->right_expression, nullptr,
+ result);
+ }
+
+ // For an unqualified access, offer a local declaration and, if we are
+ // in a member function, a member declaration.
+ if (const auto simpleName = idExpr->name->asSimpleName()) {
+ if (!m_membersOnly)
+ result << new AddLocalDeclarationOp(interface, index, binExpr, simpleName);
+ maybeAddMember(interface, file->scopeAt(idExpr->firstToken()), "this",
+ binExpr->right_expression, nullptr, result);
+ return;
+ }
+ }
+ }
+
+ void handleCall(const CPlusPlus::CallAST *call, const CppQuickFixInterface &interface,
+ QuickFixOperations &result)
+ {
+ if (!call->base_expression)
+ return;
+
+ // In order to find out the return type, we need to check the context of the call.
+ // If it is a statement expression, the type is void, if it's a binary expression,
+ // we assume the type of the other side of the expression, if it's a return statement,
+ // we use the return type of the surrounding function, and if it's a declaration,
+ // we use the type of the variable. Other cases are not supported.
+ const QList<AST *> &path = interface.path();
+ const CppRefactoringFilePtr &file = interface.currentFile();
+ TypeOrExpr returnTypeOrExpr;
+ for (auto it = path.rbegin(); it != path.rend(); ++it) {
+ if ((*it)->asCompoundStatement())
+ return;
+ if ((*it)->asExpressionStatement()) {
+ returnTypeOrExpr = FullySpecifiedType(new VoidType);
+ break;
+ }
+ if (const auto binExpr = (*it)->asBinaryExpression()) {
+ returnTypeOrExpr = interface.isCursorOn(binExpr->left_expression)
+ ? binExpr->right_expression : binExpr->left_expression;
+ break;
+ }
+ if ((*it)->asReturnStatement()) {
+ for (auto it2 = std::next(it); it2 != path.rend(); ++it2) {
+ if (const auto func = (*it2)->asFunctionDefinition()) {
+ if (!func->symbol)
+ return;
+ returnTypeOrExpr = func->symbol->returnType();
+ break;
+ }
+ }
+ break;
+ }
+ if (const auto declarator = (*it)->asDeclarator()) {
+ if (!interface.isCursorOn(declarator->initializer))
+ return;
+ const auto decl = (*std::next(it))->asSimpleDeclaration();
+ if (!decl || !decl->symbols)
+ return;
+ if (!decl->symbols->value->type().isValid())
+ return;
+ returnTypeOrExpr = decl->symbols->value->type();
+ break;
+ }
+ }
+
+ if (std::holds_alternative<const ExpressionAST *>(returnTypeOrExpr)
+ && !std::get<const ExpressionAST *>(returnTypeOrExpr)) {
+ return;
+ }
+
+ // a.f()
+ if (const auto memberAccess = call->base_expression->asMemberAccess()) {
+ if (!interface.isCursorOn(memberAccess->member_name))
+ return;
+ maybeAddMember(
+ interface, file->scopeAt(call->firstToken()),
+ file->textOf(memberAccess->base_expression).toUtf8(), returnTypeOrExpr, call, result);
+ }
+
+ const auto idExpr = call->base_expression->asIdExpression();
+ if (!idExpr || !idExpr->name)
+ return;
+
+ // A::f()
+ if (const auto qualName = idExpr->name->asQualifiedName())
+ return maybeAddStaticMember(interface, qualName, returnTypeOrExpr, call, result);
+
+ // f()
+ if (idExpr->name->asSimpleName()) {
+ maybeAddMember(interface, file->scopeAt(idExpr->firstToken()), "this",
+ returnTypeOrExpr, call, result);
+ }
+ }
+
+ // Returns whether to still do other checks.
+ bool checkForMemberInitializer(const CppQuickFixInterface &interface,
+ QuickFixOperations &result)
+ {
+ const QList<AST *> &path = interface.path();
+ const int size = path.size();
+ if (size < 4)
+ return true;
+ const MemInitializerAST * const memInitializer = path.at(size - 2)->asMemInitializer();
+ if (!memInitializer)
+ return true;
+ if (!path.at(size - 3)->asCtorInitializer())
+ return true;
+ const FunctionDefinitionAST * ctor = path.at(size - 4)->asFunctionDefinition();
+ if (!ctor)
+ return false;
+
+ // Now find the class.
+ const Class *theClass = nullptr;
+ if (size > 4) {
+ const ClassSpecifierAST * const classSpec = path.at(size - 5)->asClassSpecifier();
+ if (classSpec) // Inline constructor. We get the class directly.
+ theClass = classSpec->symbol;
+ }
+ if (!theClass) {
+ // Out-of-line constructor. We need to find the class.
+ SymbolFinder finder;
+ const QList<Declaration *> matches = finder.findMatchingDeclaration(
+ LookupContext(interface.currentFile()->cppDocument(), interface.snapshot()),
+ ctor->symbol);
+ if (!matches.isEmpty())
+ theClass = matches.first()->enclosingClass();
+ }
+
+ if (!theClass)
+ return false;
+
+ const SimpleNameAST * const name = path.at(size - 1)->asSimpleName();
+ QTC_ASSERT(name, return false);
+
+ // Check whether the member exists already.
+ if (theClass->find(interface.currentFile()->cppDocument()->translationUnit()->identifier(
+ name->identifier_token))) {
+ return false;
+ }
+
+ result << new InsertMemberFromInitializationOp(
+ interface, theClass, memInitializer->name->asSimpleName(), memInitializer->expression,
+ nullptr, InsertionPointLocator::Private, false, false);
+ return false;
+ }
+
+ void maybeAddMember(const CppQuickFixInterface &interface, CPlusPlus::Scope *scope,
+ const QByteArray &classTypeExpr, const TypeOrExpr &typeOrExpr,
+ const CPlusPlus::CallAST *call, QuickFixOperations &result)
+ {
+ const QList<AST *> &path = interface.path();
+
+ TypeOfExpression typeOfExpression;
+ typeOfExpression.init(interface.semanticInfo().doc, interface.snapshot(),
+ interface.context().bindings());
+ const QList<LookupItem> lhsTypes = typeOfExpression(
+ classTypeExpr, scope,
+ TypeOfExpression::Preprocess);
+ if (lhsTypes.isEmpty())
+ return;
+
+ const Type *type = lhsTypes.first().type().type();
+ if (!type)
+ return;
+ if (type->asPointerType()) {
+ type = type->asPointerType()->elementType().type();
+ if (!type)
+ return;
+ }
+ const auto namedType = type->asNamedType();
+ if (!namedType)
+ return;
+ const ClassOrNamespace * const classOrNamespace
+ = interface.context().lookupType(namedType->name(), scope);
+ if (!classOrNamespace || !classOrNamespace->rootClass())
+ return;
+
+ const Class * const theClass = classOrNamespace->rootClass();
+ bool needsStatic = lhsTypes.first().type().isStatic();
+
+ // If the base expression refers to the same class that the member function is in,
+ // then we want to insert a private member, otherwise a public one.
+ const FunctionDefinitionAST *func = nullptr;
+ for (auto it = path.rbegin(); !func && it != path.rend(); ++it)
+ func = (*it)->asFunctionDefinition();
+ QTC_ASSERT(func, return);
+ InsertionPointLocator::AccessSpec accessSpec = InsertionPointLocator::Public;
+ for (int i = 0; i < theClass->memberCount(); ++i) {
+ if (theClass->memberAt(i) == func->symbol) {
+ accessSpec = InsertionPointLocator::Private;
+ needsStatic = func->symbol->isStatic();
+ break;
+ }
+ }
+ if (accessSpec == InsertionPointLocator::Public) {
+ QList<Declaration *> decls;
+ QList<Declaration *> dummy;
+ SymbolFinder().findMatchingDeclaration(interface.context(), func->symbol, &decls,
+ &dummy, &dummy);
+ for (const Declaration * const decl : std::as_const(decls)) {
+ for (int i = 0; i < theClass->memberCount(); ++i) {
+ if (theClass->memberAt(i) == decl) {
+ accessSpec = InsertionPointLocator::Private;
+ needsStatic = decl->isStatic();
+ break;
+ }
+ }
+ if (accessSpec == InsertionPointLocator::Private)
+ break;
+ }
+ }
+ result << new InsertMemberFromInitializationOp(interface, theClass, path.last()->asName(),
+ typeOrExpr, call, accessSpec, needsStatic,
+ func->symbol->isConst());
+ }
+
+ void maybeAddStaticMember(
+ const CppQuickFixInterface &interface, const CPlusPlus::QualifiedNameAST *qualName,
+ const TypeOrExpr &typeOrExpr, const CPlusPlus::CallAST *call,
+ QuickFixOperations &result)
+ {
+ const QList<AST *> &path = interface.path();
+
+ if (!interface.isCursorOn(qualName->unqualified_name))
+ return;
+ if (qualName->unqualified_name != path.last())
+ return;
+ if (!qualName->nested_name_specifier_list)
+ return;
+
+ const NameAST * const topLevelName
+ = qualName->nested_name_specifier_list->value->class_or_namespace_name;
+ if (!topLevelName)
+ return;
+ ClassOrNamespace * const classOrNamespace = interface.context().lookupType(
+ topLevelName->name, interface.currentFile()->scopeAt(qualName->firstToken()));
+ if (!classOrNamespace)
+ return;
+ QList<const Name *> otherNames;
+ for (auto it = qualName->nested_name_specifier_list->next; it; it = it->next) {
+ if (!it->value || !it->value->class_or_namespace_name)
+ return;
+ otherNames << it->value->class_or_namespace_name->name;
+ }
+
+ const Class *theClass = nullptr;
+ if (!otherNames.isEmpty()) {
+ const Symbol * const symbol = classOrNamespace->lookupInScope(otherNames);
+ if (!symbol)
+ return;
+ theClass = symbol->asClass();
+ } else {
+ theClass = classOrNamespace->rootClass();
+ }
+ if (theClass) {
+ result << new InsertMemberFromInitializationOp(
+ interface, theClass, path.last()->asName(), typeOrExpr, call,
+ InsertionPointLocator::Public, true, false);
+ }
+ }
+
+ bool m_membersOnly = false;
+};
+
+#ifdef WITH_TESTS
+using namespace Tests;
+
+class AddDeclarationForUndeclaredIdentifierTest : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ // QTCREATORBUG-26004
+ void testLocalDeclFromUse()
+ {
+ const QByteArray original = "void func() {\n"
+ " QStringList list;\n"
+ " @it = list.cbegin();\n"
+ "}\n";
+ const QByteArray expected = "void func() {\n"
+ " QStringList list;\n"
+ " auto it = list.cbegin();\n"
+ "}\n";
+ AddDeclarationForUndeclaredIdentifier factory;
+ QuickFixOperationTest(singleDocument(original, expected), &factory);
+ }
+
+ void testInsertMemberFromUse_data()
+ {
+ QTest::addColumn<QByteArray>("original");
+ QTest::addColumn<QByteArray>("expected");
+
+ QByteArray original;
+ QByteArray expected;
+
+ original =
+ "class C {\n"
+ "public:\n"
+ " C(int x) : @m_x(x) {}\n"
+ "private:\n"
+ " int m_y;\n"
+ "};\n";
+ expected =
+ "class C {\n"
+ "public:\n"
+ " C(int x) : m_x(x) {}\n"
+ "private:\n"
+ " int m_y;\n"
+ " int m_x;\n"
+ "};\n";
+ QTest::addRow("inline constructor") << original << expected;
+
+ original =
+ "class C {\n"
+ "public:\n"
+ " C(int x, double d);\n"
+ "private:\n"
+ " int m_x;\n"
+ "};\n"
+ "C::C(int x, double d) : m_x(x), @m_d(d)\n";
+ expected =
+ "class C {\n"
+ "public:\n"
+ " C(int x, double d);\n"
+ "private:\n"
+ " int m_x;\n"
+ " double m_d;\n"
+ "};\n"
+ "C::C(int x, double d) : m_x(x), m_d(d)\n";
+ QTest::addRow("out-of-line constructor") << original << expected;
+
+ original =
+ "class C {\n"
+ "public:\n"
+ " C(int x) : @m_x(x) {}\n"
+ "private:\n"
+ " int m_x;\n"
+ "};\n";
+ expected = "";
+ QTest::addRow("member already present") << original << expected;
+
+ original =
+ "int func() { return 0; }\n"
+ "class C {\n"
+ "public:\n"
+ " C() : @m_x(func()) {}\n"
+ "private:\n"
+ " int m_y;\n"
+ "};\n";
+ expected =
+ "int func() { return 0; }\n"
+ "class C {\n"
+ "public:\n"
+ " C() : m_x(func()) {}\n"
+ "private:\n"
+ " int m_y;\n"
+ " int m_x;\n"
+ "};\n";
+ QTest::addRow("initialization via function call") << original << expected;
+
+ original =
+ "struct S {\n\n};\n"
+ "class C {\n"
+ "public:\n"
+ " void setValue(int v) { m_s.@value = v; }\n"
+ "private:\n"
+ " S m_s;\n"
+ "};\n";
+ expected =
+ "struct S {\n\n"
+ " int value;\n"
+ "};\n"
+ "class C {\n"
+ "public:\n"
+ " void setValue(int v) { m_s.value = v; }\n"
+ "private:\n"
+ " S m_s;\n"
+ "};\n";
+ QTest::addRow("add member to other struct") << original << expected;
+
+ original =
+ "struct S {\n\n};\n"
+ "class C {\n"
+ "public:\n"
+ " void setValue(int v) { S::@value = v; }\n"
+ "};\n";
+ expected =
+ "struct S {\n\n"
+ " static int value;\n"
+ "};\n"
+ "class C {\n"
+ "public:\n"
+ " void setValue(int v) { S::value = v; }\n"
+ "};\n";
+ QTest::addRow("add static member to other struct (explicit)") << original << expected;
+
+ original =
+ "struct S {\n\n};\n"
+ "class C {\n"
+ "public:\n"
+ " void setValue(int v) { m_s.@value = v; }\n"
+ "private:\n"
+ " static S m_s;\n"
+ "};\n";
+ expected =
+ "struct S {\n\n"
+ " static int value;\n"
+ "};\n"
+ "class C {\n"
+ "public:\n"
+ " void setValue(int v) { m_s.value = v; }\n"
+ "private:\n"
+ " static S m_s;\n"
+ "};\n";
+ QTest::addRow("add static member to other struct (implicit)") << original << expected;
+
+ original =
+ "class C {\n"
+ "public:\n"
+ " void setValue(int v);\n"
+ "};\n"
+ "void C::setValue(int v) { this->@m_value = v; }\n";
+ expected =
+ "class C {\n"
+ "public:\n"
+ " void setValue(int v);\n"
+ "private:\n"
+ " int m_value;\n"
+ "};\n"
+ "void C::setValue(int v) { this->@m_value = v; }\n";
+ QTest::addRow("add member to this (explicit)") << original << expected;
+
+ original =
+ "class C {\n"
+ "public:\n"
+ " void setValue(int v) { @m_value = v; }\n"
+ "};\n";
+ expected =
+ "class C {\n"
+ "public:\n"
+ " void setValue(int v) { m_value = v; }\n"
+ "private:\n"
+ " int m_value;\n"
+ "};\n";
+ QTest::addRow("add member to this (implicit)") << original << expected;
+
+ original =
+ "class C {\n"
+ "public:\n"
+ " static void setValue(int v) { @m_value = v; }\n"
+ "};\n";
+ expected =
+ "class C {\n"
+ "public:\n"
+ " static void setValue(int v) { m_value = v; }\n"
+ "private:\n"
+ " static int m_value;\n"
+ "};\n";
+ QTest::addRow("add static member to this (inline)") << original << expected;
+
+ original =
+ "class C {\n"
+ "public:\n"
+ " static void setValue(int v);\n"
+ "};\n"
+ "void C::setValue(int v) { @m_value = v; }\n";
+ expected =
+ "class C {\n"
+ "public:\n"
+ " static void setValue(int v);\n"
+ "private:\n"
+ " static int m_value;\n"
+ "};\n"
+ "void C::setValue(int v) { @m_value = v; }\n";
+ QTest::addRow("add static member to this (non-inline)") << original << expected;
+
+ original =
+ "struct S {\n\n};\n"
+ "class C {\n"
+ "public:\n"
+ " void setValue(int v) { m_s.@setValue(v); }\n"
+ "private:\n"
+ " S m_s;\n"
+ "};\n";
+ expected =
+ "struct S {\n\n"
+ " void setValue(int);\n"
+ "};\n"
+ "class C {\n"
+ "public:\n"
+ " void setValue(int v) { m_s.setValue(v); }\n"
+ "private:\n"
+ " S m_s;\n"
+ "};\n";
+ QTest::addRow("add member function to other struct") << original << expected;
+
+ original =
+ "struct S {\n\n};\n"
+ "class C {\n"
+ "public:\n"
+ " void setValue(int v) { S::@setValue(v); }\n"
+ "};\n";
+ expected =
+ "struct S {\n\n"
+ " static void setValue(int);\n"
+ "};\n"
+ "class C {\n"
+ "public:\n"
+ " void setValue(int v) { S::setValue(v); }\n"
+ "};\n";
+ QTest::addRow("add static member function to other struct (explicit)") << original << expected;
+
+ original =
+ "struct S {\n\n};\n"
+ "class C {\n"
+ "public:\n"
+ " void setValue(int v) { m_s.@setValue(v); }\n"
+ "private:\n"
+ " static S m_s;\n"
+ "};\n";
+ expected =
+ "struct S {\n\n"
+ " static void setValue(int);\n"
+ "};\n"
+ "class C {\n"
+ "public:\n"
+ " void setValue(int v) { m_s.setValue(v); }\n"
+ "private:\n"
+ " static S m_s;\n"
+ "};\n";
+ QTest::addRow("add static member function to other struct (implicit)") << original << expected;
+
+ original =
+ "class C {\n"
+ "public:\n"
+ " void setValue(int v);\n"
+ "};\n"
+ "void C::setValue(int v) { this->@setValueInternal(v); }\n";
+ expected =
+ "class C {\n"
+ "public:\n"
+ " void setValue(int v);\n"
+ "private:\n"
+ " void setValueInternal(int);\n"
+ "};\n"
+ "void C::setValue(int v) { this->setValueInternal(v); }\n";
+ QTest::addRow("add member function to this (explicit)") << original << expected;
+
+ original =
+ "class C {\n"
+ "public:\n"
+ " void setValue(int v) { @setValueInternal(v); }\n"
+ "};\n";
+ expected =
+ "class C {\n"
+ "public:\n"
+ " void setValue(int v) { setValueInternal(v); }\n"
+ "private:\n"
+ " void setValueInternal(int);\n"
+ "};\n";
+ QTest::addRow("add member function to this (implicit)") << original << expected;
+
+ original =
+ "class C {\n"
+ "public:\n"
+ " int value() const { return @valueInternal(); }\n"
+ "};\n";
+ expected =
+ "class C {\n"
+ "public:\n"
+ " int value() const { return valueInternal(); }\n"
+ "private:\n"
+ " int valueInternal() const;\n"
+ "};\n";
+ QTest::addRow("add const member function to this (implicit)") << original << expected;
+
+ original =
+ "class C {\n"
+ "public:\n"
+ " static int value() { int i = @valueInternal(); return i; }\n"
+ "};\n";
+ expected =
+ "class C {\n"
+ "public:\n"
+ " static int value() { int i = @valueInternal(); return i; }\n"
+ "private:\n"
+ " static int valueInternal();\n"
+ "};\n";
+ QTest::addRow("add static member function to this (inline)") << original << expected;
+
+ original =
+ "class C {\n"
+ "public:\n"
+ " static int value();\n"
+ "};\n"
+ "int C::value() { return @valueInternal(); }\n";
+ expected =
+ "class C {\n"
+ "public:\n"
+ " static int value();\n"
+ "private:\n"
+ " static int valueInternal();\n"
+ "};\n"
+ "int C::value() { return valueInternal(); }\n";
+ QTest::addRow("add static member function to this (non-inline)") << original << expected;
+ }
+
+ void testInsertMemberFromUse()
+ {
+ QFETCH(QByteArray, original);
+ QFETCH(QByteArray, expected);
+
+ QList<TestDocumentPtr> testDocuments({
+ CppTestDocument::create("file.h", original, expected)
+ });
+
+ AddDeclarationForUndeclaredIdentifier factory;
+ factory.setMembersOnly();
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+};
+
+class InsertDeclFromDefTest : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ /// Check from source file: Insert in header file.
+ void test()
+ {
+ insertToSectionDeclFromDef("public", 0);
+ insertToSectionDeclFromDef("public slots", 1);
+ insertToSectionDeclFromDef("protected", 2);
+ insertToSectionDeclFromDef("protected slots", 3);
+ insertToSectionDeclFromDef("private", 4);
+ insertToSectionDeclFromDef("private slots", 5);
+ }
+
+ void testTemplateFuncTypename()
+ {
+ QByteArray original =
+ "class Foo\n"
+ "{\n"
+ "};\n"
+ "\n"
+ "template<class T>\n"
+ "void Foo::fu@nc() {}\n";
+
+ QByteArray expected =
+ "class Foo\n"
+ "{\n"
+ "public:\n"
+ " template<class T>\n"
+ " void func();\n"
+ "};\n"
+ "\n"
+ "template<class T>\n"
+ "void Foo::fu@nc() {}\n";
+
+ InsertDeclFromDef factory;
+ QuickFixOperationTest(singleDocument(original, expected), &factory, {}, 0);
+ }
+
+ void testTemplateFuncInt()
+ {
+ QByteArray original =
+ "class Foo\n"
+ "{\n"
+ "};\n"
+ "\n"
+ "template<int N>\n"
+ "void Foo::fu@nc() {}\n";
+
+ QByteArray expected =
+ "class Foo\n"
+ "{\n"
+ "public:\n"
+ " template<int N>\n"
+ " void func();\n"
+ "};\n"
+ "\n"
+ "template<int N>\n"
+ "void Foo::fu@nc() {}\n";
+
+ InsertDeclFromDef factory;
+ QuickFixOperationTest(singleDocument(original, expected), &factory, {}, 0);
+ }
+
+ void testTemplateReturnType()
+ {
+ QByteArray original =
+ "class Foo\n"
+ "{\n"
+ "};\n"
+ "\n"
+ "std::vector<int> Foo::fu@nc() const {}\n";
+
+ QByteArray expected =
+ "class Foo\n"
+ "{\n"
+ "public:\n"
+ " std::vector<int> func() const;\n"
+ "};\n"
+ "\n"
+ "std::vector<int> Foo::func() const {}\n";
+
+ InsertDeclFromDef factory;
+ QuickFixOperationTest(singleDocument(original, expected), &factory, {}, 0);
+ }
+
+ void testNotTriggeredForTemplateFunc()
+ {
+ QByteArray contents =
+ "class Foo\n"
+ "{\n"
+ " template<class T>\n"
+ " void func();\n"
+ "};\n"
+ "\n"
+ "template<class T>\n"
+ "void Foo::fu@nc() {}\n";
+
+ InsertDeclFromDef factory;
+ QuickFixOperationTest(singleDocument(contents, ""), &factory);
+ }
+
+private:
+ // Function for one of InsertDeclDef section cases
+ void insertToSectionDeclFromDef(const QByteArray &section, int sectionIndex)
+ {
+ QList<TestDocumentPtr> testDocuments;
+
+ QByteArray original;
+ QByteArray expected;
+ QByteArray sectionString = section + ":\n";
+ if (sectionIndex == 4)
+ sectionString.clear();
+
+ // Header File
+ original =
+ "class Foo\n"
+ "{\n"
+ "};\n";
+ expected =
+ "class Foo\n"
+ "{\n"
+ + sectionString +
+ " Foo();\n"
+ "@};\n";
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File
+ original =
+ "#include \"file.h\"\n"
+ "\n"
+ "Foo::Foo@()\n"
+ "{\n"
+ "}\n"
+ ;
+ expected = original;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+ InsertDeclFromDef factory;
+ QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), sectionIndex);
+ }
+};
+
+QObject *AddDeclarationForUndeclaredIdentifier::createTest()
+{
+ return new AddDeclarationForUndeclaredIdentifierTest;
+}
+
+QObject *InsertDeclFromDef::createTest()
+{
+ return new InsertDeclFromDefTest;
+}
+
+#endif // WITH_TESTS
+} // namespace
+
+void registerCreateDeclarationFromUseQuickfixes()
+{
+ CppQuickFixFactory::registerFactory<InsertDeclFromDef>();
+ CppQuickFixFactory::registerFactory<AddDeclarationForUndeclaredIdentifier>();
+}
+
+} // namespace CppEditor::Internal
+
+#ifdef WITH_TESTS
+#include <createdeclarationfromuse.moc>
+#endif
diff --git a/src/plugins/cppeditor/quickfixes/createdeclarationfromuse.h b/src/plugins/cppeditor/quickfixes/createdeclarationfromuse.h
new file mode 100644
index 0000000000..d8452f5850
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/createdeclarationfromuse.h
@@ -0,0 +1,8 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+namespace CppEditor::Internal {
+void registerCreateDeclarationFromUseQuickfixes();
+} // namespace CppEditor::Internal
diff --git a/src/plugins/cppeditor/quickfixes/extractfunction.cpp b/src/plugins/cppeditor/quickfixes/extractfunction.cpp
new file mode 100644
index 0000000000..46be84fa78
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/extractfunction.cpp
@@ -0,0 +1,762 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "extractfunction.h"
+
+#include "../cppcodestylesettings.h"
+#include "../cppeditortr.h"
+#include "../cpprefactoringchanges.h"
+#include "../insertionpointlocator.h"
+#include "cppquickfix.h"
+#include "cppquickfixhelpers.h"
+
+#include <coreplugin/icore.h>
+#include <cplusplus/CppRewriter.h>
+#include <cplusplus/declarationcomments.h>
+#include <cplusplus/Overview.h>
+
+#include <QDialogButtonBox>
+#include <QFormLayout>
+#include <QPushButton>
+
+#include <functional>
+
+#ifdef WITH_TESTS
+#include "cppquickfix_test.h"
+#include <QTest>
+#endif
+
+using namespace CPlusPlus;
+using namespace Utils;
+
+namespace CppEditor::Internal {
+namespace {
+using FunctionNameGetter = std::function<QString()>;
+
+class ExtractFunctionOptions
+{
+public:
+ static bool isValidFunctionName(const QString &name)
+ {
+ return !name.isEmpty() && isValidIdentifier(name);
+ }
+
+ bool hasValidFunctionName() const
+ {
+ return isValidFunctionName(funcName);
+ }
+
+ QString funcName;
+ InsertionPointLocator::AccessSpec access = InsertionPointLocator::Public;
+};
+
+class ExtractFunctionOperation : public CppQuickFixOperation
+{
+public:
+ ExtractFunctionOperation(
+ const CppQuickFixInterface &interface,
+ int extractionStart,
+ int extractionEnd,
+ FunctionDefinitionAST *refFuncDef,
+ Symbol *funcReturn,
+ QList<QPair<QString, QString>> relevantDecls,
+ FunctionNameGetter functionNameGetter = {})
+ : CppQuickFixOperation(interface)
+ , m_extractionStart(extractionStart)
+ , m_extractionEnd(extractionEnd)
+ , m_refFuncDef(refFuncDef)
+ , m_funcReturn(funcReturn)
+ , m_relevantDecls(relevantDecls)
+ , m_functionNameGetter(functionNameGetter)
+ {
+ setDescription(Tr::tr("Extract Function"));
+ }
+
+ void perform() override
+ {
+ QTC_ASSERT(!m_funcReturn || !m_relevantDecls.isEmpty(), return);
+
+ CppRefactoringChanges refactoring(snapshot());
+ ExtractFunctionOptions options;
+ if (m_functionNameGetter)
+ options.funcName = m_functionNameGetter();
+ else
+ options = getOptions();
+
+ if (!options.hasValidFunctionName())
+ return;
+ const QString &funcName = options.funcName;
+
+ Function *refFunc = m_refFuncDef->symbol;
+
+ // We don't need to rewrite the type for declarations made inside the reference function,
+ // since their scope will remain the same. Then we preserve the original spelling style.
+ // However, we must do so for the return type in the definition.
+ SubstitutionEnvironment env;
+ env.setContext(context());
+ env.switchScope(refFunc);
+ ClassOrNamespace *targetCoN = context().lookupType(refFunc->enclosingScope());
+ if (!targetCoN)
+ targetCoN = context().globalNamespace();
+ UseMinimalNames subs(targetCoN);
+ env.enter(&subs);
+
+ Overview printer = CppCodeStyleSettings::currentProjectCodeStyleOverview();
+ Control *control = context().bindings()->control().get();
+ QString funcDef;
+ QString funcDecl; // We generate a declaration only in the case of a member function.
+ QString funcCall;
+
+ Class *matchingClass = isMemberFunction(context(), refFunc);
+
+ // Write return type.
+ if (!m_funcReturn) {
+ funcDef.append(QLatin1String("void "));
+ if (matchingClass)
+ funcDecl.append(QLatin1String("void "));
+ } else {
+ const FullySpecifiedType &fullType = rewriteType(m_funcReturn->type(), &env, control);
+ funcDef.append(printer.prettyType(fullType) + QLatin1Char(' '));
+ funcDecl.append(printer.prettyType(m_funcReturn->type()) + QLatin1Char(' '));
+ }
+
+ // Write class qualification, if any.
+ if (matchingClass) {
+ const Scope *current = matchingClass;
+ QVector<const Name *> classes{matchingClass->name()};
+ while (current->enclosingScope()->asClass()) {
+ current = current->enclosingScope()->asClass();
+ classes.prepend(current->name());
+ }
+ while (current->enclosingScope() && current->enclosingScope()->asNamespace()) {
+ current = current->enclosingScope()->asNamespace();
+ if (current->name())
+ classes.prepend(current->name());
+ }
+ for (const Name *n : classes) {
+ const Name *name = rewriteName(n, &env, control);
+ funcDef.append(printer.prettyName(name));
+ funcDef.append(QLatin1String("::"));
+ }
+ }
+
+ // Write the extracted function itself and its call.
+ funcDef.append(funcName);
+ if (matchingClass)
+ funcDecl.append(funcName);
+ funcCall.append(funcName);
+ funcDef.append(QLatin1Char('('));
+ if (matchingClass)
+ funcDecl.append(QLatin1Char('('));
+ funcCall.append(QLatin1Char('('));
+ for (int i = m_funcReturn ? 1 : 0; i < m_relevantDecls.length(); ++i) {
+ QPair<QString, QString> p = m_relevantDecls.at(i);
+ funcCall.append(p.first);
+ funcDef.append(p.second);
+ if (matchingClass)
+ funcDecl.append(p.second);
+ if (i < m_relevantDecls.length() - 1) {
+ funcCall.append(QLatin1String(", "));
+ funcDef.append(QLatin1String(", "));
+ if (matchingClass)
+ funcDecl.append(QLatin1String(", "));
+ }
+ }
+ funcDef.append(QLatin1Char(')'));
+ if (matchingClass)
+ funcDecl.append(QLatin1Char(')'));
+ funcCall.append(QLatin1Char(')'));
+ if (refFunc->isConst()) {
+ funcDef.append(QLatin1String(" const"));
+ funcDecl.append(QLatin1String(" const"));
+ }
+ funcDef.append(QLatin1String("\n{\n"));
+ QString extract = currentFile()->textOf(m_extractionStart, m_extractionEnd);
+ extract.replace(QChar::ParagraphSeparator, QLatin1String("\n"));
+ if (!extract.endsWith(QLatin1Char('\n')) && m_funcReturn)
+ extract.append(QLatin1Char('\n'));
+ funcDef.append(extract);
+ if (matchingClass)
+ funcDecl.append(QLatin1String(";\n"));
+ if (m_funcReturn) {
+ funcDef.append(QLatin1String("\nreturn ")
+ + m_relevantDecls.at(0).first
+ + QLatin1Char(';'));
+ funcCall.prepend(m_relevantDecls.at(0).second + QLatin1String(" = "));
+ }
+ funcDef.append(QLatin1String("\n}\n\n"));
+ funcDef.replace(QChar::ParagraphSeparator, QLatin1String("\n"));
+ funcDef.prepend(inlinePrefix(currentFile()->filePath()));
+ funcCall.append(QLatin1Char(';'));
+
+ // Do not insert right between the function and an associated comment.
+ int position = currentFile()->startOf(m_refFuncDef);
+ const QList<Token> functionDoc = commentsForDeclaration(
+ m_refFuncDef->symbol, m_refFuncDef, *currentFile()->document(),
+ currentFile()->cppDocument());
+ if (!functionDoc.isEmpty()) {
+ position = currentFile()->cppDocument()->translationUnit()->getTokenPositionInDocument(
+ functionDoc.first(), currentFile()->document());
+ }
+
+ ChangeSet change;
+ change.insert(position, funcDef);
+ change.replace(m_extractionStart, m_extractionEnd, funcCall);
+ currentFile()->apply(change);
+
+ // Write declaration, if necessary.
+ if (matchingClass) {
+ InsertionPointLocator locator(refactoring);
+ const FilePath filePath = FilePath::fromUtf8(matchingClass->fileName());
+ const InsertionLocation &location =
+ locator.methodDeclarationInClass(filePath, matchingClass, options.access);
+ CppRefactoringFilePtr declFile = refactoring.cppFile(filePath);
+ declFile->apply(ChangeSet::makeInsert(
+ declFile->position(location.line(), location.column()),
+ location.prefix() + funcDecl + location.suffix()));
+ }
+ }
+
+ ExtractFunctionOptions getOptions() const
+ {
+ QDialog dlg(Core::ICore::dialogParent());
+ dlg.setWindowTitle(Tr::tr("Extract Function Refactoring"));
+ auto layout = new QFormLayout(&dlg);
+
+ auto funcNameEdit = new FancyLineEdit;
+ funcNameEdit->setValidationFunction([](FancyLineEdit *edit, QString *) {
+ return ExtractFunctionOptions::isValidFunctionName(edit->text());
+ });
+ layout->addRow(Tr::tr("Function name"), funcNameEdit);
+
+ auto accessCombo = new QComboBox;
+ accessCombo->addItem(
+ InsertionPointLocator::accessSpecToString(InsertionPointLocator::Public),
+ InsertionPointLocator::Public);
+ accessCombo->addItem(
+ InsertionPointLocator::accessSpecToString(InsertionPointLocator::PublicSlot),
+ InsertionPointLocator::PublicSlot);
+ accessCombo->addItem(
+ InsertionPointLocator::accessSpecToString(InsertionPointLocator::Protected),
+ InsertionPointLocator::Protected);
+ accessCombo->addItem(
+ InsertionPointLocator::accessSpecToString(InsertionPointLocator::ProtectedSlot),
+ InsertionPointLocator::ProtectedSlot);
+ accessCombo->addItem(
+ InsertionPointLocator::accessSpecToString(InsertionPointLocator::Private),
+ InsertionPointLocator::Private);
+ accessCombo->addItem(
+ InsertionPointLocator::accessSpecToString(InsertionPointLocator::PrivateSlot),
+ InsertionPointLocator::PrivateSlot);
+ layout->addRow(Tr::tr("Access"), accessCombo);
+
+ auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
+ QObject::connect(buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept);
+ QObject::connect(buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject);
+ QPushButton *ok = buttonBox->button(QDialogButtonBox::Ok);
+ ok->setEnabled(false);
+ QObject::connect(funcNameEdit, &Utils::FancyLineEdit::validChanged,
+ ok, &QPushButton::setEnabled);
+ layout->addWidget(buttonBox);
+
+ if (dlg.exec() == QDialog::Accepted) {
+ ExtractFunctionOptions options;
+ options.funcName = funcNameEdit->text();
+ options.access = static_cast<InsertionPointLocator::AccessSpec>(accessCombo->
+ currentData().toInt());
+ return options;
+ }
+ return ExtractFunctionOptions();
+ }
+
+ int m_extractionStart;
+ int m_extractionEnd;
+ FunctionDefinitionAST *m_refFuncDef;
+ Symbol *m_funcReturn;
+ QList<QPair<QString, QString> > m_relevantDecls;
+ FunctionNameGetter m_functionNameGetter;
+};
+
+static QPair<QString, QString> assembleDeclarationData(
+ const QString &specifiers,
+ DeclaratorAST *decltr,
+ const CppRefactoringFilePtr &file,
+ const Overview &printer)
+{
+ QTC_ASSERT(decltr, return (QPair<QString, QString>()));
+ if (decltr->core_declarator
+ && decltr->core_declarator->asDeclaratorId()
+ && decltr->core_declarator->asDeclaratorId()->name) {
+ QString decltrText = file->textOf(file->startOf(decltr),
+ file->endOf(decltr->core_declarator));
+ if (!decltrText.isEmpty()) {
+ const QString &name = printer.prettyName(
+ decltr->core_declarator->asDeclaratorId()->name->name);
+ QString completeDecl = specifiers;
+ if (!decltrText.contains(QLatin1Char(' ')))
+ completeDecl.append(QLatin1Char(' ') + decltrText);
+ else
+ completeDecl.append(decltrText);
+ return {name, completeDecl};
+ }
+ }
+ return QPair<QString, QString>();
+}
+
+class FunctionExtractionAnalyser : public ASTVisitor
+{
+public:
+ FunctionExtractionAnalyser(TranslationUnit *unit,
+ const int selStart,
+ const int selEnd,
+ const CppRefactoringFilePtr &file,
+ const Overview &printer)
+ : ASTVisitor(unit)
+ , m_done(false)
+ , m_failed(false)
+ , m_selStart(selStart)
+ , m_selEnd(selEnd)
+ , m_extractionStart(0)
+ , m_extractionEnd(0)
+ , m_file(file)
+ , m_printer(printer)
+ {}
+
+ bool operator()(FunctionDefinitionAST *refFunDef)
+ {
+ accept(refFunDef);
+
+ if (!m_failed && m_extractionStart == m_extractionEnd)
+ m_failed = true;
+
+ return !m_failed;
+ }
+
+ bool preVisit(AST *) override
+ {
+ return !m_done;
+ }
+
+ void statement(StatementAST *stmt)
+ {
+ if (!stmt)
+ return;
+
+ const int stmtStart = m_file->startOf(stmt);
+ const int stmtEnd = m_file->endOf(stmt);
+
+ if (stmtStart >= m_selEnd
+ || (m_extractionStart && stmtEnd > m_selEnd)) {
+ m_done = true;
+ return;
+ }
+
+ if (stmtStart >= m_selStart && !m_extractionStart)
+ m_extractionStart = stmtStart;
+ if (stmtEnd > m_extractionEnd && m_extractionStart)
+ m_extractionEnd = stmtEnd;
+
+ accept(stmt);
+ }
+
+ bool visit(CaseStatementAST *stmt) override
+ {
+ statement(stmt->statement);
+ return false;
+ }
+
+ bool visit(CompoundStatementAST *stmt) override
+ {
+ for (StatementListAST *it = stmt->statement_list; it; it = it->next) {
+ statement(it->value);
+ if (m_done)
+ break;
+ }
+ return false;
+ }
+
+ bool visit(DoStatementAST *stmt) override
+ {
+ statement(stmt->statement);
+ return false;
+ }
+
+ bool visit(ForeachStatementAST *stmt) override
+ {
+ statement(stmt->statement);
+ return false;
+ }
+
+ bool visit(RangeBasedForStatementAST *stmt) override
+ {
+ statement(stmt->statement);
+ return false;
+ }
+
+ bool visit(ForStatementAST *stmt) override
+ {
+ statement(stmt->initializer);
+ if (!m_done)
+ statement(stmt->statement);
+ return false;
+ }
+
+ bool visit(IfStatementAST *stmt) override
+ {
+ statement(stmt->statement);
+ if (!m_done)
+ statement(stmt->else_statement);
+ return false;
+ }
+
+ bool visit(TryBlockStatementAST *stmt) override
+ {
+ statement(stmt->statement);
+ for (CatchClauseListAST *it = stmt->catch_clause_list; it; it = it->next) {
+ statement(it->value);
+ if (m_done)
+ break;
+ }
+ return false;
+ }
+
+ bool visit(WhileStatementAST *stmt) override
+ {
+ statement(stmt->statement);
+ return false;
+ }
+
+ bool visit(DeclarationStatementAST *declStmt) override
+ {
+ // We need to collect the declarations we see before the extraction or even inside it.
+ // They might need to be used as either a parameter or return value. Actually, we could
+ // still obtain their types from the local uses, but it's good to preserve the original
+ // typing style.
+ if (declStmt
+ && declStmt->declaration
+ && declStmt->declaration->asSimpleDeclaration()) {
+ SimpleDeclarationAST *simpleDecl = declStmt->declaration->asSimpleDeclaration();
+ if (simpleDecl->decl_specifier_list
+ && simpleDecl->declarator_list) {
+ const QString &specifiers =
+ m_file->textOf(m_file->startOf(simpleDecl),
+ m_file->endOf(simpleDecl->decl_specifier_list->lastValue()));
+ for (DeclaratorListAST *decltrList = simpleDecl->declarator_list;
+ decltrList;
+ decltrList = decltrList->next) {
+ const QPair<QString, QString> p =
+ assembleDeclarationData(specifiers, decltrList->value, m_file, m_printer);
+ if (!p.first.isEmpty())
+ m_knownDecls.insert(p.first, p.second);
+ }
+ }
+ }
+
+ return false;
+ }
+
+ bool visit(ReturnStatementAST *) override
+ {
+ if (m_extractionStart) {
+ m_done = true;
+ m_failed = true;
+ }
+
+ return false;
+ }
+
+ bool m_done;
+ bool m_failed;
+ const int m_selStart;
+ const int m_selEnd;
+ int m_extractionStart;
+ int m_extractionEnd;
+ QHash<QString, QString> m_knownDecls;
+ CppRefactoringFilePtr m_file;
+ const Overview &m_printer;
+};
+
+//! Extracts the selected code and puts it to a function
+class ExtractFunction : public CppQuickFixFactory
+{
+public:
+ ExtractFunction(FunctionNameGetter functionNameGetter = FunctionNameGetter())
+ : m_functionNameGetter(functionNameGetter)
+ {}
+
+#ifdef WITH_TESTS
+ static QObject *createTest();
+#endif
+
+private:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ const CppRefactoringFilePtr file = interface.currentFile();
+
+ // TODO: Fix upstream and uncomment; see QTCREATORBUG-28030.
+ // if (CppModelManager::usesClangd(file->editor()->textDocument())
+ // && file->cppDocument()->languageFeatures().cxxEnabled) {
+ // return;
+ // }
+
+ QTextCursor cursor = file->cursor();
+ if (!cursor.hasSelection())
+ return;
+
+ const QList<AST *> &path = interface.path();
+ FunctionDefinitionAST *refFuncDef = nullptr; // The "reference" function, which we will extract from.
+ for (int i = path.size() - 1; i >= 0; --i) {
+ refFuncDef = path.at(i)->asFunctionDefinition();
+ if (refFuncDef)
+ break;
+ }
+
+ if (!refFuncDef
+ || !refFuncDef->function_body
+ || !refFuncDef->function_body->asCompoundStatement()
+ || !refFuncDef->function_body->asCompoundStatement()->statement_list
+ || !refFuncDef->symbol
+ || !refFuncDef->symbol->name()
+ || refFuncDef->symbol->enclosingScope()->asTemplate() /* TODO: Templates... */) {
+ return;
+ }
+
+ // Adjust selection ends.
+ int selStart = cursor.selectionStart();
+ int selEnd = cursor.selectionEnd();
+ if (selStart > selEnd)
+ std::swap(selStart, selEnd);
+
+ Overview printer;
+
+ // Analyze the content to be extracted, which consists of determining the statements
+ // which are complete and collecting the declarations seen.
+ FunctionExtractionAnalyser analyser(interface.semanticInfo().doc->translationUnit(),
+ selStart, selEnd,
+ file,
+ printer);
+ if (!analyser(refFuncDef))
+ return;
+
+ // We also need to collect the declarations of the parameters from the reference function.
+ QSet<QString> refFuncParams;
+ if (refFuncDef->declarator->postfix_declarator_list
+ && refFuncDef->declarator->postfix_declarator_list->value
+ && refFuncDef->declarator->postfix_declarator_list->value->asFunctionDeclarator()) {
+ FunctionDeclaratorAST *funcDecltr =
+ refFuncDef->declarator->postfix_declarator_list->value->asFunctionDeclarator();
+ if (funcDecltr->parameter_declaration_clause
+ && funcDecltr->parameter_declaration_clause->parameter_declaration_list) {
+ for (ParameterDeclarationListAST *it =
+ funcDecltr->parameter_declaration_clause->parameter_declaration_list;
+ it;
+ it = it->next) {
+ ParameterDeclarationAST *paramDecl = it->value->asParameterDeclaration();
+ if (paramDecl->declarator) {
+ const QString &specifiers =
+ file->textOf(file->startOf(paramDecl),
+ file->endOf(paramDecl->type_specifier_list->lastValue()));
+ const QPair<QString, QString> &p =
+ assembleDeclarationData(specifiers, paramDecl->declarator,
+ file, printer);
+ if (!p.first.isEmpty()) {
+ analyser.m_knownDecls.insert(p.first, p.second);
+ refFuncParams.insert(p.first);
+ }
+ }
+ }
+ }
+ }
+
+ // Identify what would be parameters for the new function and its return value, if any.
+ Symbol *funcReturn = nullptr;
+ QList<QPair<QString, QString> > relevantDecls;
+ const SemanticInfo::LocalUseMap localUses = interface.semanticInfo().localUses;
+ for (auto it = localUses.cbegin(), end = localUses.cend(); it != end; ++it) {
+ bool usedBeforeExtraction = false;
+ bool usedAfterExtraction = false;
+ bool usedInsideExtraction = false;
+ const QList<SemanticInfo::Use> &uses = it.value();
+ for (const SemanticInfo::Use &use : uses) {
+ if (use.isInvalid())
+ continue;
+
+ const int position = file->position(use.line, use.column);
+ if (position < analyser.m_extractionStart)
+ usedBeforeExtraction = true;
+ else if (position >= analyser.m_extractionEnd)
+ usedAfterExtraction = true;
+ else
+ usedInsideExtraction = true;
+ }
+
+ const QString &name = printer.prettyName(it.key()->name());
+
+ if ((usedBeforeExtraction && usedInsideExtraction)
+ || (usedInsideExtraction && refFuncParams.contains(name))) {
+ QTC_ASSERT(analyser.m_knownDecls.contains(name), return);
+ relevantDecls.push_back({name, analyser.m_knownDecls.value(name)});
+ }
+
+ // We assume that the first use of a local corresponds to its declaration.
+ if (usedInsideExtraction && usedAfterExtraction && !usedBeforeExtraction) {
+ if (!funcReturn) {
+ QTC_ASSERT(analyser.m_knownDecls.contains(name), return);
+ // The return, if any, is stored as the first item in the list.
+ relevantDecls.push_front({name, analyser.m_knownDecls.value(name)});
+ funcReturn = it.key();
+ } else {
+ // Would require multiple returns. (Unless we do fancy things, as pointed below.)
+ return;
+ }
+ }
+ }
+
+ // The current implementation doesn't try to be too smart since it preserves the original form
+ // of the declarations. This might be or not the desired effect. An improvement would be to
+ // let the user somehow customize the function interface.
+ result << new ExtractFunctionOperation(interface,
+ analyser.m_extractionStart,
+ analyser.m_extractionEnd,
+ refFuncDef, funcReturn, relevantDecls,
+ m_functionNameGetter);
+ }
+
+private:
+ FunctionNameGetter m_functionNameGetter; // For tests to avoid GUI pop-up.
+};
+
+#ifdef WITH_TESTS
+using namespace Tests;
+
+class ExtractFunctionTest : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void test_data()
+ {
+ QTest::addColumn<QByteArray>("original");
+ QTest::addColumn<QByteArray>("expected");
+
+ QTest::newRow("basic")
+ << QByteArray("// Documentation for f\n"
+ "void f()\n"
+ "{\n"
+ " @{start}g();@{end}\n"
+ "}\n")
+ << QByteArray("inline void extracted()\n"
+ "{\n"
+ " g();\n"
+ "}\n"
+ "\n"
+ "// Documentation for f\n"
+ "void f()\n"
+ "{\n"
+ " extracted();\n"
+ "}\n");
+
+ QTest::newRow("class function")
+ << QByteArray("class Foo\n"
+ "{\n"
+ "private:\n"
+ " void bar();\n"
+ "};\n\n"
+ "void Foo::bar()\n"
+ "{\n"
+ " @{start}g();@{end}\n"
+ "}\n")
+ << QByteArray("class Foo\n"
+ "{\n"
+ "public:\n"
+ " void extracted();\n\n"
+ "private:\n"
+ " void bar();\n"
+ "};\n\n"
+ "inline void Foo::extracted()\n"
+ "{\n"
+ " g();\n"
+ "}\n\n"
+ "void Foo::bar()\n"
+ "{\n"
+ " extracted();\n"
+ "}\n");
+
+ QTest::newRow("class in namespace")
+ << QByteArray("namespace NS {\n"
+ "class C {\n"
+ " void f(C &c);\n"
+ "};\n"
+ "}\n"
+ "void NS::C::f(NS::C &c)\n"
+ "{\n"
+ " @{start}C *c2 = &c;@{end}\n"
+ "}\n")
+ << QByteArray("namespace NS {\n"
+ "class C {\n"
+ " void f(C &c);\n"
+ "\n"
+ "public:\n"
+ " void extracted(NS::C &c);\n" // TODO: Remove non-required qualification
+ "};\n"
+ "}\n"
+ "inline void NS::C::extracted(NS::C &c)\n"
+ "{\n"
+ " C *c2 = &c;\n"
+ "}\n"
+ "\n"
+ "void NS::C::f(NS::C &c)\n"
+ "{\n"
+ " extracted(c);\n"
+ "}\n");
+
+ QTest::newRow("if-block")
+ << QByteArray("inline void func()\n"
+ "{\n"
+ " int dummy = 0;\n"
+ " @{start}if@{end} (dummy < 10) {\n"
+ " ++dummy;\n"
+ " }\n"
+ "}\n")
+ << QByteArray("inline void extracted(int dummy)\n"
+ "{\n"
+ " if (dummy < 10) {\n"
+ " ++dummy;\n"
+ " }\n"
+ "}\n\n"
+ "inline void func()\n"
+ "{\n"
+ " int dummy = 0;\n"
+ " extracted(dummy);\n"
+ "}\n");
+ }
+
+ void test()
+ {
+ QFETCH(QByteArray, original);
+ QFETCH(QByteArray, expected);
+
+ QList<TestDocumentPtr> testDocuments;
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ ExtractFunction factory([]() { return QLatin1String("extracted"); });
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+};
+
+QObject *ExtractFunction::createTest() { return new ExtractFunctionTest; }
+
+#endif // WITH_TESTS
+
+} // namespace
+
+void registerExtractFunctionQuickfix()
+{
+ CppQuickFixFactory::registerFactory<ExtractFunction>();
+}
+
+} // namespace CppEditor::Internal
+
+#ifdef WITH_TESTS
+#include <extractfunction.moc>
+#endif
diff --git a/src/plugins/cppeditor/quickfixes/extractfunction.h b/src/plugins/cppeditor/quickfixes/extractfunction.h
new file mode 100644
index 0000000000..b41cef59c2
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/extractfunction.h
@@ -0,0 +1,7 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#pragma once
+
+namespace CppEditor::Internal {
+void registerExtractFunctionQuickfix();
+} // namespace CppEditor::Internal
diff --git a/src/plugins/cppeditor/quickfixes/extractliteralasparameter.cpp b/src/plugins/cppeditor/quickfixes/extractliteralasparameter.cpp
new file mode 100644
index 0000000000..b5b23906e0
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/extractliteralasparameter.cpp
@@ -0,0 +1,560 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "extractliteralasparameter.h"
+
+#include "../cppeditortr.h"
+#include "../cppeditorwidget.h"
+#include "../cpprefactoringchanges.h"
+#include "cppquickfix.h"
+#include "cppquickfixhelpers.h"
+
+#include <cplusplus/ASTPath.h>
+#include <cplusplus/Overview.h>
+#include <cplusplus/TypeOfExpression.h>
+
+#ifdef WITH_TESTS
+#include "cppquickfix_test.h"
+#include <QtTest>
+#endif
+
+using namespace CPlusPlus;
+using namespace Utils;
+
+namespace CppEditor::Internal {
+namespace {
+
+struct ReplaceLiteralsResult
+{
+ Token token;
+ QString literalText;
+};
+
+template <class T>
+class ReplaceLiterals : private ASTVisitor
+{
+public:
+ ReplaceLiterals(const CppRefactoringFilePtr &file, ChangeSet *changes, T *literal)
+ : ASTVisitor(file->cppDocument()->translationUnit()), m_file(file), m_changes(changes),
+ m_literal(literal)
+ {
+ m_result.token = m_file->tokenAt(literal->firstToken());
+ m_literalTokenText = m_result.token.spell();
+ m_result.literalText = QLatin1String(m_literalTokenText);
+ if (m_result.token.isCharLiteral()) {
+ m_result.literalText.prepend(QLatin1Char('\''));
+ m_result.literalText.append(QLatin1Char('\''));
+ if (m_result.token.kind() == T_WIDE_CHAR_LITERAL)
+ m_result.literalText.prepend(QLatin1Char('L'));
+ else if (m_result.token.kind() == T_UTF16_CHAR_LITERAL)
+ m_result.literalText.prepend(QLatin1Char('u'));
+ else if (m_result.token.kind() == T_UTF32_CHAR_LITERAL)
+ m_result.literalText.prepend(QLatin1Char('U'));
+ } else if (m_result.token.isStringLiteral()) {
+ m_result.literalText.prepend(QLatin1Char('"'));
+ m_result.literalText.append(QLatin1Char('"'));
+ if (m_result.token.kind() == T_WIDE_STRING_LITERAL)
+ m_result.literalText.prepend(QLatin1Char('L'));
+ else if (m_result.token.kind() == T_UTF16_STRING_LITERAL)
+ m_result.literalText.prepend(QLatin1Char('u'));
+ else if (m_result.token.kind() == T_UTF32_STRING_LITERAL)
+ m_result.literalText.prepend(QLatin1Char('U'));
+ }
+ }
+
+ ReplaceLiteralsResult apply(AST *ast)
+ {
+ ast->accept(this);
+ return m_result;
+ }
+
+private:
+ bool visit(T *ast) override
+ {
+ if (ast != m_literal
+ && strcmp(m_file->tokenAt(ast->firstToken()).spell(), m_literalTokenText) != 0) {
+ return true;
+ }
+ int start, end;
+ m_file->startAndEndOf(ast->firstToken(), &start, &end);
+ m_changes->replace(start, end, QLatin1String("newParameter"));
+ return true;
+ }
+
+ const CppRefactoringFilePtr &m_file;
+ ChangeSet *m_changes;
+ T *m_literal;
+ const char *m_literalTokenText;
+ ReplaceLiteralsResult m_result;
+};
+
+class ExtractLiteralAsParameterOp : public CppQuickFixOperation
+{
+public:
+ ExtractLiteralAsParameterOp(const CppQuickFixInterface &interface, int priority,
+ ExpressionAST *literal, FunctionDefinitionAST *function)
+ : CppQuickFixOperation(interface, priority),
+ m_literal(literal),
+ m_functionDefinition(function)
+ {
+ setDescription(Tr::tr("Extract Constant as Function Parameter"));
+ }
+
+ struct FoundDeclaration
+ {
+ FunctionDeclaratorAST *ast = nullptr;
+ CppRefactoringFilePtr file;
+ };
+
+ FoundDeclaration findDeclaration(const CppRefactoringChanges &refactoring,
+ FunctionDefinitionAST *ast)
+ {
+ FoundDeclaration result;
+ Function *func = ast->symbol;
+ if (Class *matchingClass = isMemberFunction(context(), func)) {
+ // Dealing with member functions
+ const QualifiedNameId *qName = func->name()->asQualifiedNameId();
+ for (Symbol *s = matchingClass->find(qName->identifier()); s; s = s->next()) {
+ if (!s->name()
+ || !qName->identifier()->match(s->identifier())
+ || !s->type()->asFunctionType()
+ || !s->type().match(func->type())
+ || s->asFunction()) {
+ continue;
+ }
+
+ const FilePath declFilePath = matchingClass->filePath();
+ result.file = refactoring.cppFile(declFilePath);
+ ASTPath astPath(result.file->cppDocument());
+ const QList<AST *> path = astPath(s->line(), s->column());
+ SimpleDeclarationAST *simpleDecl = nullptr;
+ for (AST *node : path) {
+ simpleDecl = node->asSimpleDeclaration();
+ if (simpleDecl) {
+ if (simpleDecl->symbols && !simpleDecl->symbols->next) {
+ result.ast = functionDeclarator(simpleDecl);
+ return result;
+ }
+ }
+ }
+
+ if (simpleDecl)
+ break;
+ }
+ } else if (Namespace *matchingNamespace = isNamespaceFunction(context(), func)) {
+ // Dealing with free functions and inline member functions.
+ bool isHeaderFile;
+ FilePath declFilePath = correspondingHeaderOrSource(filePath(), &isHeaderFile);
+ if (!declFilePath.exists())
+ return FoundDeclaration();
+ result.file = refactoring.cppFile(declFilePath);
+ if (!result.file)
+ return FoundDeclaration();
+ const LookupContext lc(result.file->cppDocument(), snapshot());
+ const QList<LookupItem> candidates = lc.lookup(func->name(), matchingNamespace);
+ for (const LookupItem &candidate : candidates) {
+ if (Symbol *s = candidate.declaration()) {
+ if (s->asDeclaration()) {
+ ASTPath astPath(result.file->cppDocument());
+ const QList<AST *> path = astPath(s->line(), s->column());
+ for (AST *node : path) {
+ SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration();
+ if (simpleDecl) {
+ result.ast = functionDeclarator(simpleDecl);
+ return result;
+ }
+ }
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ void perform() override
+ {
+ FunctionDeclaratorAST *functionDeclaratorOfDefinition
+ = functionDeclarator(m_functionDefinition);
+ const CppRefactoringChanges refactoring(snapshot());
+ deduceTypeNameOfLiteral(currentFile()->cppDocument());
+
+ ChangeSet changes;
+ if (NumericLiteralAST *concreteLiteral = m_literal->asNumericLiteral()) {
+ m_literalInfo = ReplaceLiterals<NumericLiteralAST>(currentFile(), &changes,
+ concreteLiteral)
+ .apply(m_functionDefinition->function_body);
+ } else if (StringLiteralAST *concreteLiteral = m_literal->asStringLiteral()) {
+ m_literalInfo = ReplaceLiterals<StringLiteralAST>(currentFile(), &changes,
+ concreteLiteral)
+ .apply(m_functionDefinition->function_body);
+ } else if (BoolLiteralAST *concreteLiteral = m_literal->asBoolLiteral()) {
+ m_literalInfo = ReplaceLiterals<BoolLiteralAST>(currentFile(), &changes,
+ concreteLiteral)
+ .apply(m_functionDefinition->function_body);
+ }
+ const FoundDeclaration functionDeclaration
+ = findDeclaration(refactoring, m_functionDefinition);
+ appendFunctionParameter(functionDeclaratorOfDefinition, currentFile(), &changes,
+ !functionDeclaration.ast);
+ if (functionDeclaration.ast) {
+ if (currentFile()->filePath() != functionDeclaration.file->filePath()) {
+ ChangeSet declChanges;
+ appendFunctionParameter(functionDeclaration.ast, functionDeclaration.file, &declChanges,
+ true);
+ functionDeclaration.file->apply(declChanges);
+ } else {
+ appendFunctionParameter(functionDeclaration.ast, currentFile(), &changes,
+ true);
+ }
+ }
+ currentFile()->apply(changes);
+ QTextCursor c = currentFile()->cursor();
+ c.setPosition(c.position() - parameterName().length());
+ editor()->setTextCursor(c);
+ editor()->renameSymbolUnderCursor();
+ }
+
+private:
+ bool hasParameters(FunctionDeclaratorAST *ast) const
+ {
+ return ast->parameter_declaration_clause
+ && ast->parameter_declaration_clause->parameter_declaration_list
+ && ast->parameter_declaration_clause->parameter_declaration_list->value;
+ }
+
+ void deduceTypeNameOfLiteral(const Document::Ptr &document)
+ {
+ TypeOfExpression typeOfExpression;
+ typeOfExpression.init(document, snapshot());
+ Overview overview;
+ Scope *scope = m_functionDefinition->symbol->enclosingScope();
+ const QList<LookupItem> items = typeOfExpression(m_literal, document, scope);
+ if (!items.isEmpty())
+ m_typeName = overview.prettyType(items.first().type());
+ }
+
+ static QString parameterName() { return QLatin1String("newParameter"); }
+
+ QString parameterDeclarationTextToInsert(FunctionDeclaratorAST *ast) const
+ {
+ QString str;
+ if (hasParameters(ast))
+ str = QLatin1String(", ");
+ str += m_typeName;
+ if (!m_typeName.endsWith(QLatin1Char('*')))
+ str += QLatin1Char(' ');
+ str += parameterName();
+ return str;
+ }
+
+ FunctionDeclaratorAST *functionDeclarator(SimpleDeclarationAST *ast) const
+ {
+ for (DeclaratorListAST *decls = ast->declarator_list; decls; decls = decls->next) {
+ FunctionDeclaratorAST * const functionDeclaratorAST = functionDeclarator(decls->value);
+ if (functionDeclaratorAST)
+ return functionDeclaratorAST;
+ }
+ return nullptr;
+ }
+
+ FunctionDeclaratorAST *functionDeclarator(DeclaratorAST *ast) const
+ {
+ for (PostfixDeclaratorListAST *pds = ast->postfix_declarator_list; pds; pds = pds->next) {
+ FunctionDeclaratorAST *funcdecl = pds->value->asFunctionDeclarator();
+ if (funcdecl)
+ return funcdecl;
+ }
+ return nullptr;
+ }
+
+ FunctionDeclaratorAST *functionDeclarator(FunctionDefinitionAST *ast) const
+ {
+ return functionDeclarator(ast->declarator);
+ }
+
+ void appendFunctionParameter(FunctionDeclaratorAST *ast, const CppRefactoringFileConstPtr &file,
+ ChangeSet *changes, bool addDefaultValue)
+ {
+ if (!ast)
+ return;
+ if (m_declarationInsertionString.isEmpty())
+ m_declarationInsertionString = parameterDeclarationTextToInsert(ast);
+ QString insertion = m_declarationInsertionString;
+ if (addDefaultValue)
+ insertion += QLatin1String(" = ") + m_literalInfo.literalText;
+ changes->insert(file->startOf(ast->rparen_token), insertion);
+ }
+
+ ExpressionAST *m_literal;
+ FunctionDefinitionAST *m_functionDefinition;
+ QString m_typeName;
+ QString m_declarationInsertionString;
+ ReplaceLiteralsResult m_literalInfo;
+};
+
+/*!
+ Extracts the selected constant and converts it to a parameter of the current function.
+ Activates on numeric, bool, character, or string literal in the function body.
+ */
+class ExtractLiteralAsParameter : public CppQuickFixFactory
+{
+#ifdef WITH_TESTS
+public:
+ static QObject *createTest();
+#endif
+
+private:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ const QList<AST *> &path = interface.path();
+ if (path.count() < 2)
+ return;
+
+ AST * const lastAst = path.last();
+ ExpressionAST *literal;
+ if (!((literal = lastAst->asNumericLiteral())
+ || (literal = lastAst->asStringLiteral())
+ || (literal = lastAst->asBoolLiteral()))) {
+ return;
+ }
+
+ FunctionDefinitionAST *function;
+ int i = path.count() - 2;
+ while (!(function = path.at(i)->asFunctionDefinition())) {
+ // Ignore literals in lambda expressions for now.
+ if (path.at(i)->asLambdaExpression())
+ return;
+ if (--i < 0)
+ return;
+ }
+
+ PostfixDeclaratorListAST * const declaratorList = function->declarator->postfix_declarator_list;
+ if (!declaratorList)
+ return;
+ if (FunctionDeclaratorAST *declarator = declaratorList->value->asFunctionDeclarator()) {
+ if (declarator->parameter_declaration_clause
+ && declarator->parameter_declaration_clause->dot_dot_dot_token) {
+ // Do not handle functions with ellipsis parameter.
+ return;
+ }
+ }
+
+ const int priority = path.size() - 1;
+ result << new ExtractLiteralAsParameterOp(interface, priority, literal, function);
+ }
+};
+
+#ifdef WITH_TESTS
+using namespace Tests;
+
+class ExtractLiteralAsParameterTest : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void testTypeDeduction_data()
+ {
+ QTest::addColumn<QByteArray>("typeString");
+ QTest::addColumn<QByteArray>("literal");
+ QTest::newRow("int")
+ << QByteArray("int ") << QByteArray("156");
+ QTest::newRow("unsigned int")
+ << QByteArray("unsigned int ") << QByteArray("156u");
+ QTest::newRow("long")
+ << QByteArray("long ") << QByteArray("156l");
+ QTest::newRow("unsigned long")
+ << QByteArray("unsigned long ") << QByteArray("156ul");
+ QTest::newRow("long long")
+ << QByteArray("long long ") << QByteArray("156ll");
+ QTest::newRow("unsigned long long")
+ << QByteArray("unsigned long long ") << QByteArray("156ull");
+ QTest::newRow("float")
+ << QByteArray("float ") << QByteArray("3.14159f");
+ QTest::newRow("double")
+ << QByteArray("double ") << QByteArray("3.14159");
+ QTest::newRow("long double")
+ << QByteArray("long double ") << QByteArray("3.14159L");
+ QTest::newRow("bool")
+ << QByteArray("bool ") << QByteArray("true");
+ QTest::newRow("bool")
+ << QByteArray("bool ") << QByteArray("false");
+ QTest::newRow("char")
+ << QByteArray("char ") << QByteArray("'X'");
+ QTest::newRow("wchar_t")
+ << QByteArray("wchar_t ") << QByteArray("L'X'");
+ QTest::newRow("char16_t")
+ << QByteArray("char16_t ") << QByteArray("u'X'");
+ QTest::newRow("char32_t")
+ << QByteArray("char32_t ") << QByteArray("U'X'");
+ QTest::newRow("const char *")
+ << QByteArray("const char *") << QByteArray("\"narf\"");
+ QTest::newRow("const wchar_t *")
+ << QByteArray("const wchar_t *") << QByteArray("L\"narf\"");
+ QTest::newRow("const char16_t *")
+ << QByteArray("const char16_t *") << QByteArray("u\"narf\"");
+ QTest::newRow("const char32_t *")
+ << QByteArray("const char32_t *") << QByteArray("U\"narf\"");
+ }
+
+ void testTypeDeduction()
+ {
+ QFETCH(QByteArray, typeString);
+ QFETCH(QByteArray, literal);
+ const QByteArray original = QByteArray("void foo() {return @") + literal + QByteArray(";}\n");
+ const QByteArray expected = QByteArray("void foo(") + typeString + QByteArray("newParameter = ")
+ + literal + QByteArray(") {return newParameter;}\n");
+
+ if (literal == "3.14159") {
+ qWarning("Literal 3.14159 is wrongly reported as int. Skipping.");
+ return;
+ } else if (literal == "3.14159L") {
+ qWarning("Literal 3.14159L is wrongly reported as long. Skipping.");
+ return;
+ }
+
+ ExtractLiteralAsParameter factory;
+ QuickFixOperationTest(singleDocument(original, expected), &factory);
+ }
+
+ void testFreeFunctionSeparateFiles()
+ {
+ QList<TestDocumentPtr> testDocuments;
+ QByteArray original;
+ QByteArray expected;
+
+ // Header File
+ original =
+ "void foo(const char *a, long b = 1);\n";
+ expected =
+ "void foo(const char *a, long b = 1, int newParameter = 156);\n";
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File
+ original =
+ "void foo(const char *a, long b)\n"
+ "{return 1@56 + 123 + 156;}\n";
+ expected =
+ "void foo(const char *a, long b, int newParameter)\n"
+ "{return newParameter + 123 + newParameter;}\n";
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+ ExtractLiteralAsParameter factory;
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+
+ void testMemberFunctionSeparateFiles()
+ {
+ QList<TestDocumentPtr> testDocuments;
+ QByteArray original;
+ QByteArray expected;
+
+ // Header File
+ original =
+ "class Narf {\n"
+ "public:\n"
+ " int zort();\n"
+ "};\n";
+ expected =
+ "class Narf {\n"
+ "public:\n"
+ " int zort(int newParameter = 155);\n"
+ "};\n";
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File
+ original =
+ "#include \"file.h\"\n\n"
+ "int Narf::zort()\n"
+ "{ return 15@5 + 1; }\n";
+ expected =
+ "#include \"file.h\"\n\n"
+ "int Narf::zort(int newParameter)\n"
+ "{ return newParameter + 1; }\n";
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+ ExtractLiteralAsParameter factory;
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+
+ void testNotTriggeringForInvalidCode()
+ {
+ QList<TestDocumentPtr> testDocuments;
+ QByteArray original;
+ original =
+ "T(\"test\")\n"
+ "{\n"
+ " const int i = @14;\n"
+ "}\n";
+ testDocuments << CppTestDocument::create("file.cpp", original, "");
+
+ ExtractLiteralAsParameter factory;
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+
+ void test_data()
+ {
+ QTest::addColumn<QByteArray>("original");
+ QTest::addColumn<QByteArray>("expected");
+
+ QTest::newRow("ExtractLiteralAsParameter_freeFunction")
+ << QByteArray(
+ "void foo(const char *a, long b = 1)\n"
+ "{return 1@56 + 123 + 156;}\n")
+ << QByteArray(
+ "void foo(const char *a, long b = 1, int newParameter = 156)\n"
+ "{return newParameter + 123 + newParameter;}\n");
+ QTest::newRow("ExtractLiteralAsParameter_memberFunction")
+ << QByteArray(
+ "class Narf {\n"
+ "public:\n"
+ " int zort();\n"
+ "};\n\n"
+ "int Narf::zort()\n"
+ "{ return 15@5 + 1; }\n")
+ << QByteArray(
+ "class Narf {\n"
+ "public:\n"
+ " int zort(int newParameter = 155);\n"
+ "};\n\n"
+ "int Narf::zort(int newParameter)\n"
+ "{ return newParameter + 1; }\n");
+ QTest::newRow("ExtractLiteralAsParameter_memberFunctionInline")
+ << QByteArray(
+ "class Narf {\n"
+ "public:\n"
+ " int zort()\n"
+ " { return 15@5 + 1; }\n"
+ "};\n")
+ << QByteArray(
+ "class Narf {\n"
+ "public:\n"
+ " int zort(int newParameter = 155)\n"
+ " { return newParameter + 1; }\n"
+ "};\n");
+ }
+
+ void test()
+ {
+ QFETCH(QByteArray, original);
+ QFETCH(QByteArray, expected);
+ ExtractLiteralAsParameter factory;
+ QuickFixOperationTest(singleDocument(original, expected), &factory);
+ }
+
+};
+
+QObject *ExtractLiteralAsParameter::createTest() { return new ExtractLiteralAsParameterTest; }
+
+#endif // WITH_TESTS
+} // namespace
+
+void registerExtractLiteralAsParameterQuickfix()
+{
+ CppQuickFixFactory::registerFactory<ExtractLiteralAsParameter>();
+}
+
+} // namespace CppEditor::Internal
+
+#ifdef WITH_TESTS
+#include <extractliteralasparameter.moc>
+#endif
diff --git a/src/plugins/cppeditor/quickfixes/extractliteralasparameter.h b/src/plugins/cppeditor/quickfixes/extractliteralasparameter.h
new file mode 100644
index 0000000000..e6359f1602
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/extractliteralasparameter.h
@@ -0,0 +1,8 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+namespace CppEditor::Internal {
+void registerExtractLiteralAsParameterQuickfix();
+} // namespace CppEditor::Internal
diff --git a/src/plugins/cppeditor/quickfixes/insertfunctiondefinition.cpp b/src/plugins/cppeditor/quickfixes/insertfunctiondefinition.cpp
new file mode 100644
index 0000000000..693b2c4a1d
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/insertfunctiondefinition.cpp
@@ -0,0 +1,2123 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "insertfunctiondefinition.h"
+
+#include "../cppcodestylepreferences.h"
+#include "../cppcodestylesettings.h"
+#include "../cppeditortr.h"
+#include "../cppeditorwidget.h"
+#include "../cpprefactoringchanges.h"
+#include "../cpptoolssettings.h"
+#include "../insertionpointlocator.h"
+#include "../symbolfinder.h"
+#include "cppquickfix.h"
+#include "cppquickfixhelpers.h"
+
+#include <coreplugin/icore.h>
+#include <cplusplus/CppRewriter.h>
+#include <cplusplus/Overview.h>
+#include <utils/layoutbuilder.h>
+
+#include <QDialogButtonBox>
+#include <QGridLayout>
+#include <QHBoxLayout>
+
+#ifdef WITH_TESTS
+#include "cppquickfix_test.h"
+#include <QtTest>
+#endif
+
+using namespace CPlusPlus;
+using namespace TextEditor;
+using namespace Utils;
+
+namespace CppEditor::Internal {
+namespace {
+
+enum DefPos {
+ DefPosInsideClass,
+ DefPosOutsideClass,
+ DefPosImplementationFile
+};
+
+enum class InsertDefsFromDeclsMode {
+ Off, // Testing: simulates user canceling the dialog
+ Impl, // Testing: simulates user choosing cpp file for every function
+ Alternating, // Testing: simulates user choosing a different DefPos for every function
+ User // Normal interactive mode
+};
+
+class InsertDefOperation: public CppQuickFixOperation
+{
+public:
+ // Make sure that either loc is valid or targetFileName is not empty.
+ InsertDefOperation(const CppQuickFixInterface &interface,
+ Declaration *decl, DeclaratorAST *declAST, const InsertionLocation &loc,
+ const DefPos defpos, const FilePath &targetFileName = {},
+ bool freeFunction = false)
+ : CppQuickFixOperation(interface, 0)
+ , m_decl(decl)
+ , m_declAST(declAST)
+ , m_loc(loc)
+ , m_defpos(defpos)
+ , m_targetFilePath(targetFileName)
+ {
+ if (m_defpos == DefPosImplementationFile) {
+ const FilePath declFile = decl->filePath();
+ const FilePath targetFile = m_loc.isValid() ? m_loc.filePath() : m_targetFilePath;
+ const FilePath resolved = targetFile.relativePathFrom(declFile.parentDir());
+ setPriority(2);
+ setDescription(Tr::tr("Add Definition in %1").arg(resolved.displayName()));
+ } else if (freeFunction) {
+ setDescription(Tr::tr("Add Definition Here"));
+ } else if (m_defpos == DefPosInsideClass) {
+ setDescription(Tr::tr("Add Definition Inside Class"));
+ } else if (m_defpos == DefPosOutsideClass) {
+ setPriority(1);
+ setDescription(Tr::tr("Add Definition Outside Class"));
+ }
+ }
+
+ static void insertDefinition(
+ const CppQuickFixOperation *op,
+ InsertionLocation loc,
+ DefPos defPos,
+ DeclaratorAST *declAST,
+ Declaration *decl,
+ const FilePath &targetFilePath,
+ ChangeSet *changeSet = nullptr)
+ {
+ CppRefactoringChanges refactoring(op->snapshot());
+ if (!loc.isValid())
+ loc = insertLocationForMethodDefinition(decl, true, NamespaceHandling::Ignore,
+ refactoring, targetFilePath);
+ QTC_ASSERT(loc.isValid(), return);
+
+ CppRefactoringFilePtr targetFile = refactoring.cppFile(loc.filePath());
+ Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview();
+ oo.showFunctionSignatures = true;
+ oo.showReturnTypes = true;
+ oo.showArgumentNames = true;
+ oo.showEnclosingTemplate = true;
+
+ // What we really want is to show template parameters for the class, but not for the
+ // function, but we cannot express that. This is an approximation that will work
+ // as long as either the surrounding class or the function is not a template.
+ oo.showTemplateParameters = decl->enclosingClass()
+ && decl->enclosingClass()->enclosingTemplate();
+
+ if (defPos == DefPosInsideClass) {
+ const int targetPos = targetFile->position(loc.line(), loc.column());
+ ChangeSet localChangeSet;
+ ChangeSet * const target = changeSet ? changeSet : &localChangeSet;
+ target->replace(targetPos - 1, targetPos, QLatin1String("\n {\n\n}")); // replace ';'
+
+ if (!changeSet) {
+ targetFile->setOpenEditor(true, targetPos);
+ targetFile->apply(*target);
+
+ // Move cursor inside definition
+ QTextCursor c = targetFile->cursor();
+ c.setPosition(targetPos);
+ c.movePosition(QTextCursor::Down);
+ c.movePosition(QTextCursor::EndOfLine);
+ op->editor()->setTextCursor(c);
+ }
+ } else {
+ // make target lookup context
+ Document::Ptr targetDoc = targetFile->cppDocument();
+ Scope *targetScope = targetDoc->scopeAt(loc.line(), loc.column());
+
+ // Correct scope in case of a function try-block. See QTCREATORBUG-14661.
+ if (targetScope && targetScope->asBlock()) {
+ if (Class * const enclosingClass = targetScope->enclosingClass())
+ targetScope = enclosingClass;
+ else
+ targetScope = targetScope->enclosingNamespace();
+ }
+
+ LookupContext targetContext(targetDoc, op->snapshot());
+ ClassOrNamespace *targetCoN = targetContext.lookupType(targetScope);
+ if (!targetCoN)
+ targetCoN = targetContext.globalNamespace();
+
+ // setup rewriting to get minimally qualified names
+ SubstitutionEnvironment env;
+ env.setContext(op->context());
+ env.switchScope(decl->enclosingScope());
+ UseMinimalNames q(targetCoN);
+ env.enter(&q);
+ Control *control = op->context().bindings()->control().get();
+
+ // rewrite the function type
+ const FullySpecifiedType tn = rewriteType(decl->type(), &env, control);
+
+ // rewrite the function name
+ if (nameIncludesOperatorName(decl->name())) {
+ const QString operatorNameText = op->currentFile()->textOf(declAST->core_declarator);
+ oo.includeWhiteSpaceInOperatorName = operatorNameText.contains(QLatin1Char(' '));
+ }
+ const QString name = oo.prettyName(LookupContext::minimalName(decl, targetCoN,
+ control));
+
+ const QString inlinePref = inlinePrefix(targetFilePath, [defPos] {
+ return defPos == DefPosOutsideClass;
+ });
+
+ const QString prettyType = oo.prettyType(tn, name);
+
+ QString input = prettyType;
+ int index = 0;
+ while (input.startsWith("template")) {
+ QRegularExpression templateRegex("template\\s*<[^>]*>");
+ QRegularExpressionMatch match = templateRegex.match(input);
+ if (match.hasMatch()) {
+ index += match.captured().size() + 1;
+ input = input.mid(match.captured().size() + 1);
+ }
+ }
+
+ QString defText = prettyType;
+ defText.insert(index, inlinePref);
+ defText += QLatin1String("\n{\n\n}");
+
+ ChangeSet localChangeSet;
+ ChangeSet * const target = changeSet ? changeSet : &localChangeSet;
+ const int targetPos = targetFile->position(loc.line(), loc.column());
+ target->insert(targetPos, loc.prefix() + defText + loc.suffix());
+
+ if (!changeSet) {
+ targetFile->setOpenEditor(true, targetPos);
+ targetFile->apply(*target);
+
+ // Move cursor inside definition
+ QTextCursor c = targetFile->cursor();
+ c.setPosition(targetPos);
+ c.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor,
+ loc.prefix().count(QLatin1String("\n")) + 2);
+ c.movePosition(QTextCursor::EndOfLine);
+ if (defPos == DefPosImplementationFile) {
+ if (targetFile->editor())
+ targetFile->editor()->setTextCursor(c);
+ } else {
+ op->editor()->setTextCursor(c);
+ }
+ }
+ }
+ }
+
+private:
+ void perform() override
+ {
+ insertDefinition(this, m_loc, m_defpos, m_declAST, m_decl, m_targetFilePath);
+ }
+
+ Declaration *m_decl;
+ DeclaratorAST *m_declAST;
+ InsertionLocation m_loc;
+ const DefPos m_defpos;
+ const FilePath m_targetFilePath;
+};
+
+class MemberFunctionImplSetting
+{
+public:
+ Symbol *func = nullptr;
+ DefPos defPos = DefPosImplementationFile;
+};
+using MemberFunctionImplSettings = QList<MemberFunctionImplSetting>;
+
+class AddImplementationsDialog : public QDialog
+{
+public:
+ AddImplementationsDialog(const QList<Symbol *> &candidates, const FilePath &implFile)
+ : QDialog(Core::ICore::dialogParent()), m_candidates(candidates)
+ {
+ setWindowTitle(Tr::tr("Member Function Implementations"));
+
+ const auto defaultImplTargetComboBox = new QComboBox;
+ QStringList implTargetStrings{Tr::tr("None"), Tr::tr("Inline"), Tr::tr("Outside Class")};
+ if (!implFile.isEmpty())
+ implTargetStrings.append(implFile.fileName());
+ defaultImplTargetComboBox->insertItems(0, implTargetStrings);
+ connect(defaultImplTargetComboBox, &QComboBox::currentIndexChanged, this,
+ [this](int index) {
+ for (int i = 0; i < m_implTargetBoxes.size(); ++i) {
+ if (!m_candidates.at(i)->type()->asFunctionType()->isPureVirtual())
+ static_cast<QComboBox *>(m_implTargetBoxes.at(i))->setCurrentIndex(index);
+ }
+ });
+ const auto defaultImplTargetLayout = new QHBoxLayout;
+ defaultImplTargetLayout->addWidget(new QLabel(Tr::tr("Default implementation location:")));
+ defaultImplTargetLayout->addWidget(defaultImplTargetComboBox);
+
+ const auto candidatesLayout = new QGridLayout;
+ Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview();
+ oo.showFunctionSignatures = true;
+ oo.showReturnTypes = true;
+ for (int i = 0; i < m_candidates.size(); ++i) {
+ const Function * const func = m_candidates.at(i)->type()->asFunctionType();
+ QTC_ASSERT(func, continue);
+ const auto implTargetComboBox = new QComboBox;
+ m_implTargetBoxes.append(implTargetComboBox);
+ implTargetComboBox->insertItems(0, implTargetStrings);
+ if (func->isPureVirtual())
+ implTargetComboBox->setCurrentIndex(0);
+ candidatesLayout->addWidget(new QLabel(oo.prettyType(func->type(), func->name())),
+ i, 0);
+ candidatesLayout->addWidget(implTargetComboBox, i, 1);
+ }
+
+ const auto buttonBox
+ = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
+ connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
+ connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
+
+ defaultImplTargetComboBox->setCurrentIndex(implTargetStrings.size() - 1);
+ const auto mainLayout = new QVBoxLayout(this);
+ mainLayout->addLayout(defaultImplTargetLayout);
+ mainLayout->addWidget(Layouting::createHr(this));
+ mainLayout->addLayout(candidatesLayout);
+ mainLayout->addWidget(buttonBox);
+ }
+
+ MemberFunctionImplSettings settings() const
+ {
+ QTC_ASSERT(m_candidates.size() == m_implTargetBoxes.size(), return {});
+ MemberFunctionImplSettings settings;
+ for (int i = 0; i < m_candidates.size(); ++i) {
+ MemberFunctionImplSetting setting;
+ const int index = m_implTargetBoxes.at(i)->currentIndex();
+ const bool addImplementation = index != 0;
+ if (!addImplementation)
+ continue;
+ setting.func = m_candidates.at(i);
+ setting.defPos = static_cast<DefPos>(index - 1);
+ settings << setting;
+ }
+ return settings;
+ }
+
+private:
+ const QList<Symbol *> m_candidates;
+ QList<QComboBox *> m_implTargetBoxes;
+};
+
+class InsertDefsOperation: public CppQuickFixOperation
+{
+public:
+ InsertDefsOperation(const CppQuickFixInterface &interface) : CppQuickFixOperation(interface)
+ {
+ setDescription(Tr::tr("Create Implementations for Member Functions"));
+
+ m_classAST = astForClassOperations(interface);
+ if (!m_classAST)
+ return;
+ const Class * const theClass = m_classAST->symbol;
+ if (!theClass)
+ return;
+
+ // Collect all member functions.
+ for (auto it = theClass->memberBegin(); it != theClass->memberEnd(); ++it) {
+ Symbol * const s = *it;
+ if (!s->identifier() || !s->type() || !s->asDeclaration() || s->asFunction())
+ continue;
+ Function * const func = s->type()->asFunctionType();
+ if (!func || func->isSignal() || func->isFriend())
+ continue;
+ Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview();
+ oo.showFunctionSignatures = true;
+ if (magicQObjectFunctions().contains(oo.prettyName(func->name())))
+ continue;
+ m_declarations << s;
+ }
+ }
+
+ bool isApplicable() const { return !m_declarations.isEmpty(); }
+ void setMode(InsertDefsFromDeclsMode mode) { m_mode = mode; }
+
+private:
+ void perform() override
+ {
+ QList<Symbol *> unimplemented;
+ SymbolFinder symbolFinder;
+ for (Symbol * const s : std::as_const(m_declarations)) {
+ if (!symbolFinder.findMatchingDefinition(s, snapshot()))
+ unimplemented << s;
+ }
+ if (unimplemented.isEmpty())
+ return;
+
+ CppRefactoringChanges refactoring(snapshot());
+ const bool isHeaderFile = ProjectFile::isHeader(ProjectFile::classify(filePath().toString()));
+ FilePath cppFile; // Only set if the class is defined in a header file.
+ if (isHeaderFile) {
+ InsertionPointLocator locator(refactoring);
+ for (const InsertionLocation &location
+ : locator.methodDefinition(unimplemented.first(), false, {})) {
+ if (!location.isValid())
+ continue;
+ const FilePath filePath = location.filePath();
+ if (ProjectFile::isHeader(ProjectFile::classify(filePath.path()))) {
+ const FilePath source = correspondingHeaderOrSource(filePath);
+ if (!source.isEmpty())
+ cppFile = source;
+ } else {
+ cppFile = filePath;
+ }
+ break;
+ }
+ }
+
+ MemberFunctionImplSettings settings;
+ switch (m_mode) {
+ case InsertDefsFromDeclsMode::User: {
+ AddImplementationsDialog dlg(unimplemented, cppFile);
+ if (dlg.exec() == QDialog::Accepted)
+ settings = dlg.settings();
+ break;
+ }
+ case InsertDefsFromDeclsMode::Impl: {
+ for (Symbol * const func : std::as_const(unimplemented)) {
+ MemberFunctionImplSetting setting;
+ setting.func = func;
+ setting.defPos = DefPosImplementationFile;
+ settings << setting;
+ }
+ break;
+ }
+ case InsertDefsFromDeclsMode::Alternating: {
+ int defPos = DefPosImplementationFile;
+ const auto incDefPos = [&defPos] {
+ defPos = (defPos + 1) % (DefPosImplementationFile + 2);
+ };
+ for (Symbol * const func : std::as_const(unimplemented)) {
+ incDefPos();
+ if (defPos > DefPosImplementationFile)
+ continue;
+ MemberFunctionImplSetting setting;
+ setting.func = func;
+ setting.defPos = static_cast<DefPos>(defPos);
+ settings << setting;
+ }
+ break;
+ }
+ case InsertDefsFromDeclsMode::Off:
+ break;
+ }
+
+ if (settings.isEmpty())
+ return;
+
+ class DeclFinder : public ASTVisitor
+ {
+ public:
+ DeclFinder(const CppRefactoringFile *file, const Symbol *func)
+ : ASTVisitor(file->cppDocument()->translationUnit()), m_func(func) {}
+
+ SimpleDeclarationAST *decl() const { return m_decl; }
+
+ private:
+ bool visit(SimpleDeclarationAST *decl) override
+ {
+ if (m_decl)
+ return false;
+ if (decl->symbols && decl->symbols->value == m_func)
+ m_decl = decl;
+ return !m_decl;
+ }
+
+ const Symbol * const m_func;
+ SimpleDeclarationAST *m_decl = nullptr;
+ };
+
+ QHash<FilePath, ChangeSet> changeSets;
+ for (const MemberFunctionImplSetting &setting : std::as_const(settings)) {
+ DeclFinder finder(currentFile().data(), setting.func);
+ finder.accept(m_classAST);
+ QTC_ASSERT(finder.decl(), continue);
+ InsertionLocation loc;
+ const FilePath targetFilePath = setting.defPos == DefPosImplementationFile
+ ? cppFile : filePath();
+ QTC_ASSERT(!targetFilePath.isEmpty(), continue);
+ if (setting.defPos == DefPosInsideClass) {
+ int line, column;
+ currentFile()->lineAndColumn(currentFile()->endOf(finder.decl()), &line, &column);
+ loc = InsertionLocation(filePath(), QString(), QString(), line, column);
+ }
+ ChangeSet &changeSet = changeSets[targetFilePath];
+ InsertDefOperation::insertDefinition(
+ this, loc, setting.defPos, finder.decl()->declarator_list->value,
+ setting.func->asDeclaration(),targetFilePath, &changeSet);
+ }
+ for (auto it = changeSets.cbegin(); it != changeSets.cend(); ++it)
+ refactoring.cppFile(it.key())->apply(it.value());
+ }
+
+ ClassSpecifierAST *m_classAST = nullptr;
+ InsertDefsFromDeclsMode m_mode = InsertDefsFromDeclsMode::User;
+ QList<Symbol *> m_declarations;
+};
+
+class InsertDefFromDecl: public CppQuickFixFactory
+{
+public:
+#ifdef WITH_TESTS
+ static QObject *createTest();
+#endif
+
+ void setOutside() { m_defPosOutsideClass = true; }
+
+private:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ const QList<AST *> &path = interface.path();
+
+ int idx = path.size() - 1;
+ for (; idx >= 0; --idx) {
+ AST *node = path.at(idx);
+ if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) {
+ if (idx > 0 && path.at(idx - 1)->asStatement())
+ return;
+ if (simpleDecl->symbols && !simpleDecl->symbols->next) {
+ if (Symbol *symbol = simpleDecl->symbols->value) {
+ if (Declaration *decl = symbol->asDeclaration()) {
+ if (Function *func = decl->type()->asFunctionType()) {
+ if (func->isSignal() || func->isPureVirtual() || func->isFriend())
+ return;
+
+ // Check if there is already a definition
+ SymbolFinder symbolFinder;
+ if (symbolFinder.findMatchingDefinition(decl, interface.snapshot(),
+ true)) {
+ return;
+ }
+
+ // Insert Position: Implementation File
+ DeclaratorAST *declAST = simpleDecl->declarator_list->value;
+ InsertDefOperation *op = nullptr;
+ ProjectFile::Kind kind = ProjectFile::classify(interface.filePath().toString());
+ const bool isHeaderFile = ProjectFile::isHeader(kind);
+ if (isHeaderFile) {
+ CppRefactoringChanges refactoring(interface.snapshot());
+ InsertionPointLocator locator(refactoring);
+ // find appropriate implementation file, but do not use this
+ // location, because insertLocationForMethodDefinition() should
+ // be used in perform() to get consistent insert positions.
+ for (const InsertionLocation &location :
+ locator.methodDefinition(decl, false, {})) {
+ if (!location.isValid())
+ continue;
+
+ const FilePath filePath = location.filePath();
+ if (ProjectFile::isHeader(ProjectFile::classify(filePath.path()))) {
+ const FilePath source = correspondingHeaderOrSource(filePath);
+ if (!source.isEmpty()) {
+ op = new InsertDefOperation(interface, decl, declAST,
+ InsertionLocation(),
+ DefPosImplementationFile,
+ source);
+ }
+ } else {
+ op = new InsertDefOperation(interface, decl, declAST,
+ InsertionLocation(),
+ DefPosImplementationFile,
+ filePath);
+ }
+
+ if (op)
+ result << op;
+ break;
+ }
+ }
+
+ // Determine if we are dealing with a free function
+ const bool isFreeFunction = func->enclosingClass() == nullptr;
+
+ // Insert Position: Outside Class
+ if (!isFreeFunction || m_defPosOutsideClass) {
+ result << new InsertDefOperation(interface, decl, declAST,
+ InsertionLocation(),
+ DefPosOutsideClass,
+ interface.filePath());
+ }
+
+ // Insert Position: Inside Class
+ // Determine insert location direct after the declaration.
+ int line, column;
+ const CppRefactoringFilePtr file = interface.currentFile();
+ file->lineAndColumn(file->endOf(simpleDecl), &line, &column);
+ const InsertionLocation loc
+ = InsertionLocation(interface.filePath(), QString(),
+ QString(), line, column);
+ result << new InsertDefOperation(interface, decl, declAST, loc,
+ DefPosInsideClass, FilePath(),
+ isFreeFunction);
+
+ return;
+ }
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ bool m_defPosOutsideClass = false;
+};
+
+//! Adds a definition for any number of member function declarations.
+class InsertDefsFromDecls : public CppQuickFixFactory
+{
+public:
+#ifdef WITH_TESTS
+ static QObject *createTest();
+#endif
+
+ void setMode(InsertDefsFromDeclsMode mode) { m_mode = mode; }
+
+private:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ const auto op = QSharedPointer<InsertDefsOperation>::create(interface);
+ op->setMode(m_mode);
+ if (op->isApplicable())
+ result << op;
+ }
+
+private:
+ InsertDefsFromDeclsMode m_mode = InsertDefsFromDeclsMode::User;
+};
+
+#ifdef WITH_TESTS
+using namespace Tests;
+
+static QList<TestDocumentPtr> singleHeader(const QByteArray &original, const QByteArray &expected)
+{
+ return {CppTestDocument::create("file.h", original, expected)};
+}
+
+class InsertDefFromDeclTest : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ /// Check if definition is inserted right after class for insert definition outside
+ void testAfterClass()
+ {
+ QList<TestDocumentPtr> testDocuments;
+ QByteArray original;
+ QByteArray expected;
+
+ // Header File
+ original =
+ "class Foo\n"
+ "{\n"
+ " Foo();\n"
+ " void a@();\n"
+ "};\n"
+ "\n"
+ "class Bar {};\n";
+ expected =
+ "class Foo\n"
+ "{\n"
+ " Foo();\n"
+ " void a();\n"
+ "};\n"
+ "\n"
+ "inline void Foo::a()\n"
+ "{\n\n}\n"
+ "\n"
+ "class Bar {};\n";
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File
+ original =
+ "#include \"file.h\"\n"
+ "\n"
+ "Foo::Foo()\n"
+ "{\n\n"
+ "}\n";
+ expected = original;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+ InsertDefFromDecl factory;
+ QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 1);
+ }
+
+ /// Check from header file: If there is a source file, insert the definition in the source file.
+ /// Case: Source file is empty.
+ void testHeaderSourceBasic1()
+ {
+ QList<TestDocumentPtr> testDocuments;
+
+ QByteArray original;
+ QByteArray expected;
+
+ // Header File
+ original =
+ "struct Foo\n"
+ "{\n"
+ " Foo()@;\n"
+ "};\n";
+ expected = original;
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File
+ original.resize(0);
+ expected =
+ "\n"
+ "Foo::Foo()\n"
+ "{\n\n"
+ "}\n"
+ ;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+ InsertDefFromDecl factory;
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+
+ /// Check from header file: If there is a source file, insert the definition in the source file.
+ /// Case: Source file is not empty.
+ void testHeaderSourceBasic2()
+ {
+ QList<TestDocumentPtr> testDocuments;
+
+ QByteArray original;
+ QByteArray expected;
+
+ // Header File
+ original = "void f(const std::vector<int> &v)@;\n";
+ expected = original;
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File
+ original =
+ "#include \"file.h\"\n"
+ "\n"
+ "int x;\n"
+ ;
+ expected =
+ "#include \"file.h\"\n"
+ "\n"
+ "int x;\n"
+ "\n"
+ "void f(const std::vector<int> &v)\n"
+ "{\n"
+ "\n"
+ "}\n"
+ ;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+ InsertDefFromDecl factory;
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+
+ /// Check from source file: Insert in source file, not header file.
+ void testHeaderSourceBasic3()
+ {
+ QList<TestDocumentPtr> testDocuments;
+
+ QByteArray original;
+ QByteArray expected;
+
+ // Empty Header File
+ testDocuments << CppTestDocument::create("file.h", "", "");
+
+ // Source File
+ original =
+ "struct Foo\n"
+ "{\n"
+ " Foo()@;\n"
+ "};\n";
+ expected = original +
+ "\n"
+ "Foo::Foo()\n"
+ "{\n\n"
+ "}\n"
+ ;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+ InsertDefFromDecl factory;
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+
+ /// Check from header file: If the class is in a namespace, the added function definition
+ /// name must be qualified accordingly.
+ void testHeaderSourceNamespace1()
+ {
+ QList<TestDocumentPtr> testDocuments;
+
+ QByteArray original;
+ QByteArray expected;
+
+ // Header File
+ original =
+ "namespace N {\n"
+ "struct Foo\n"
+ "{\n"
+ " Foo()@;\n"
+ "};\n"
+ "}\n";
+ expected = original;
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File
+ original.resize(0);
+ expected =
+ "\n"
+ "N::Foo::Foo()\n"
+ "{\n\n"
+ "}\n"
+ ;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+ InsertDefFromDecl factory;
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+
+ /// Check from header file: If the class is in namespace N and the source file has a
+ /// "using namespace N" line, the function definition name must be qualified accordingly.
+ void testHeaderSourceNamespace2()
+ {
+ QList<TestDocumentPtr> testDocuments;
+
+ QByteArray original;
+ QByteArray expected;
+
+ // Header File
+ original =
+ "namespace N {\n"
+ "struct Foo\n"
+ "{\n"
+ " Foo()@;\n"
+ "};\n"
+ "}\n";
+ expected = original;
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File
+ original =
+ "#include \"file.h\"\n"
+ "using namespace N;\n"
+ ;
+ expected = original +
+ "\n"
+ "Foo::Foo()\n"
+ "{\n\n"
+ "}\n"
+ ;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+ InsertDefFromDecl factory;
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+
+ /// Check definition insert inside class
+ void testInsideClass()
+ {
+ const QByteArray original =
+ "class Foo {\n"
+ " void b@ar();\n"
+ "};";
+ const QByteArray expected =
+ "class Foo {\n"
+ " void bar()\n"
+ " {\n\n"
+ " }\n"
+ "};";
+
+ InsertDefFromDecl factory;
+ QuickFixOperationTest(singleDocument(original, expected), &factory, ProjectExplorer::HeaderPaths(),
+ 1);
+ }
+
+ /// Check not triggering when definition exists
+ void testNotTriggeringWhenDefinitionExists()
+ {
+ const QByteArray original =
+ "class Foo {\n"
+ " void b@ar();\n"
+ "};\n"
+ "void Foo::bar() {}\n";
+
+ InsertDefFromDecl factory;
+ QuickFixOperationTest(singleDocument(original, ""), &factory, ProjectExplorer::HeaderPaths(), 1);
+ }
+
+ /// Find right implementation file.
+ void testFindRightImplementationFile()
+ {
+ QList<TestDocumentPtr> testDocuments;
+
+ QByteArray original;
+ QByteArray expected;
+
+ // Header File
+ original =
+ "struct Foo\n"
+ "{\n"
+ " Foo();\n"
+ " void a();\n"
+ " void b@();\n"
+ "};\n"
+ "}\n";
+ expected = original;
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File #1
+ original =
+ "#include \"file.h\"\n"
+ "\n"
+ "Foo::Foo()\n"
+ "{\n\n"
+ "}\n";
+ expected = original;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+
+ // Source File #2
+ original =
+ "#include \"file.h\"\n"
+ "\n"
+ "void Foo::a()\n"
+ "{\n\n"
+ "}\n";
+ expected = original +
+ "\n"
+ "void Foo::b()\n"
+ "{\n\n"
+ "}\n";
+ testDocuments << CppTestDocument::create("file2.cpp", original, expected);
+
+ InsertDefFromDecl factory;
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+
+ /// Ignore generated functions declarations when looking at the surrounding
+ /// functions declarations in order to find the right implementation file.
+ void testIgnoreSurroundingGeneratedDeclarations()
+ {
+ QList<TestDocumentPtr> testDocuments;
+
+ QByteArray original;
+ QByteArray expected;
+
+ // Header File
+ original =
+ "#define DECLARE_HIDDEN_FUNCTION void hidden();\n"
+ "struct Foo\n"
+ "{\n"
+ " void a();\n"
+ " DECLARE_HIDDEN_FUNCTION\n"
+ " void b@();\n"
+ "};\n"
+ "}\n";
+ expected = original;
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File #1
+ original =
+ "#include \"file.h\"\n"
+ "\n"
+ "void Foo::a()\n"
+ "{\n\n"
+ "}\n";
+ expected =
+ "#include \"file.h\"\n"
+ "\n"
+ "void Foo::a()\n"
+ "{\n\n"
+ "}\n"
+ "\n"
+ "void Foo::b()\n"
+ "{\n\n"
+ "}\n";
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+ // Source File #2
+ original =
+ "#include \"file.h\"\n"
+ "\n"
+ "void Foo::hidden()\n"
+ "{\n\n"
+ "}\n";
+ expected = original;
+ testDocuments << CppTestDocument::create("file2.cpp", original, expected);
+
+ InsertDefFromDecl factory;
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+
+ /// Check if whitespace is respected for operator functions
+ void testRespectWsInOperatorNames1()
+ {
+ QByteArray original =
+ "class Foo\n"
+ "{\n"
+ " Foo &opera@tor =();\n"
+ "};\n";
+ QByteArray expected =
+ "class Foo\n"
+ "{\n"
+ " Foo &operator =();\n"
+ "};\n"
+ "\n"
+ "Foo &Foo::operator =()\n"
+ "{\n"
+ "\n"
+ "}\n";
+
+ InsertDefFromDecl factory;
+ QuickFixOperationTest(singleDocument(original, expected), &factory);
+ }
+
+ /// Check if whitespace is respected for operator functions
+ void testRespectWsInOperatorNames2()
+ {
+ QByteArray original =
+ "class Foo\n"
+ "{\n"
+ " Foo &opera@tor=();\n"
+ "};\n";
+ QByteArray expected =
+ "class Foo\n"
+ "{\n"
+ " Foo &operator=();\n"
+ "};\n"
+ "\n"
+ "Foo &Foo::operator=()\n"
+ "{\n"
+ "\n"
+ "}\n";
+
+ InsertDefFromDecl factory;
+ QuickFixOperationTest(singleDocument(original, expected), &factory);
+ }
+
+ /// Check that the noexcept exception specifier is transferred
+ void testNoexceptSpecifier()
+ {
+ QByteArray original =
+ "class Foo\n"
+ "{\n"
+ " void @foo() noexcept(false);\n"
+ "};\n";
+ QByteArray expected =
+ "class Foo\n"
+ "{\n"
+ " void foo() noexcept(false);\n"
+ "};\n"
+ "\n"
+ "void Foo::foo() noexcept(false)\n"
+ "{\n"
+ "\n"
+ "}\n";
+
+ InsertDefFromDecl factory;
+ QuickFixOperationTest(singleDocument(original, expected), &factory);
+ }
+
+ /// Check if a function like macro use is not separated by the function to insert
+ /// Case: Macro preceded by preproceesor directives and declaration.
+ void testMacroUsesAtEndOfFile1()
+ {
+ QList<TestDocumentPtr> testDocuments;
+
+ QByteArray original;
+ QByteArray expected;
+
+ // Header File
+ original = "void f()@;\n";
+ expected = original;
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File
+ original =
+ "#include \"file.h\"\n"
+ "#define MACRO(X) X x;\n"
+ "int lala;\n"
+ "\n"
+ "MACRO(int)\n"
+ ;
+ expected =
+ "#include \"file.h\"\n"
+ "#define MACRO(X) X x;\n"
+ "int lala;\n"
+ "\n"
+ "\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ "\n"
+ "}\n"
+ "\n"
+ "MACRO(int)\n"
+ ;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+ InsertDefFromDecl factory;
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+
+ /// Check if a function like macro use is not separated by the function to insert
+ /// Case: Marco preceded only by preprocessor directives.
+ void testMacroUsesAtEndOfFile2()
+ {
+ QList<TestDocumentPtr> testDocuments;
+
+ QByteArray original;
+ QByteArray expected;
+
+ // Header File
+ original = "void f()@;\n";
+ expected = original;
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File
+ original =
+ "#include \"file.h\"\n"
+ "#define MACRO(X) X x;\n"
+ "\n"
+ "MACRO(int)\n"
+ ;
+ expected =
+ "#include \"file.h\"\n"
+ "#define MACRO(X) X x;\n"
+ "\n"
+ "\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ "\n"
+ "}\n"
+ "\n"
+ "MACRO(int)\n"
+ ;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+ InsertDefFromDecl factory;
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+
+ /// Check if insertion happens before syntactically erroneous statements at end of file.
+ void testErroneousStatementAtEndOfFile()
+ {
+ QList<TestDocumentPtr> testDocuments;
+
+ QByteArray original;
+ QByteArray expected;
+
+ // Header File
+ original = "void f()@;\n";
+ expected = original;
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File
+ original =
+ "#include \"file.h\"\n"
+ "\n"
+ "MissingSemicolon(int)\n"
+ ;
+ expected =
+ "#include \"file.h\"\n"
+ "\n"
+ "\n"
+ "\n"
+ "void f()\n"
+ "{\n"
+ "\n"
+ "}\n"
+ "\n"
+ "MissingSemicolon(int)\n"
+ ;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+ InsertDefFromDecl factory;
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+
+ /// Check: Respect rvalue references
+ void testRvalueReference()
+ {
+ QList<TestDocumentPtr> testDocuments;
+
+ QByteArray original;
+ QByteArray expected;
+
+ // Header File
+ original = "void f(Foo &&)@;\n";
+ expected = original;
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File
+ original = "";
+ expected =
+ "\n"
+ "void f(Foo &&)\n"
+ "{\n"
+ "\n"
+ "}\n"
+ ;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+ InsertDefFromDecl factory;
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+
+ void testFunctionTryBlock()
+ {
+ QList<TestDocumentPtr> testDocuments;
+
+ QByteArray original;
+ QByteArray expected;
+
+ // Header File
+ original = R"(
+struct Foo {
+ void tryCatchFunc();
+ void @otherFunc();
+};
+)";
+ expected = original;
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File
+ original = R"(
+#include "file.h"
+
+void Foo::tryCatchFunc() try {} catch (...) {}
+)";
+ expected = R"(
+#include "file.h"
+
+void Foo::tryCatchFunc() try {} catch (...) {}
+
+void Foo::otherFunc()
+{
+
+}
+)";
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+ InsertDefFromDecl factory;
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+
+ void testUsingDecl()
+ {
+ QList<TestDocumentPtr> testDocuments;
+
+ QByteArray original;
+ QByteArray expected;
+
+ // Header File
+ original = R"(
+namespace N { struct S; }
+using N::S;
+
+void @func(const S &s);
+)";
+ expected = original;
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File
+ original = R"(
+#include "file.h"
+)";
+ expected = R"(
+#include "file.h"
+
+void func(const S &s)
+{
+
+}
+)";
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+ InsertDefFromDecl factory;
+ QuickFixOperationTest(testDocuments, &factory);
+
+ testDocuments.clear();
+ original = R"(
+namespace N1 {
+namespace N2 { struct S; }
+using N2::S;
+}
+
+void @func(const N1::S &s);
+)";
+ expected = original;
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File
+ original = R"(
+#include "file.h"
+)";
+ expected = R"(
+#include "file.h"
+
+void func(const N1::S &s)
+{
+
+}
+)";
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+ QuickFixOperationTest(testDocuments, &factory);
+
+ // No using declarations here, but the code model has one. No idea why.
+ testDocuments.clear();
+ original = R"(
+class B {};
+class D : public B {
+ @D();
+};
+)";
+ expected = original;
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File
+ original = R"(
+#include "file.h"
+)";
+ expected = R"(
+#include "file.h"
+
+D::D()
+{
+
+}
+)";
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+ QuickFixOperationTest(testDocuments, &factory);
+
+ testDocuments.clear();
+ original = R"(
+namespace ns1 { template<typename T> class span {}; }
+
+namespace ns {
+using ns1::span;
+class foo
+{
+ void @bar(ns::span<int>);
+};
+}
+)";
+ expected = R"(
+namespace ns1 { template<typename T> class span {}; }
+
+namespace ns {
+using ns1::span;
+class foo
+{
+ void bar(ns::span<int>);
+};
+
+void foo::bar(ns::span<int>)
+{
+
+}
+
+}
+)";
+ // TODO: Unneeded namespace gets inserted in RewriteName::visit(const QualifiedNameId *)
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+
+ /// Find right implementation file. (QTCREATORBUG-10728)
+ void testFindImplementationFile()
+ {
+ QList<TestDocumentPtr> testDocuments;
+
+ QByteArray original;
+ QByteArray expected;
+
+ // Header File
+ original =
+ "class Foo {\n"
+ " void bar();\n"
+ " void ba@z();\n"
+ "};\n"
+ "\n"
+ "void Foo::bar()\n"
+ "{}\n";
+ expected = original;
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File
+ original =
+ "#include \"file.h\"\n"
+ ;
+ expected =
+ "#include \"file.h\"\n"
+ "\n"
+ "void Foo::baz()\n"
+ "{\n"
+ "\n"
+ "}\n"
+ ;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+ InsertDefFromDecl factory;
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+
+ void testUnicodeIdentifier()
+ {
+ QList<TestDocumentPtr> testDocuments;
+
+ QByteArray original;
+ QByteArray expected;
+
+//
+// The following "non-latin1" code points are used in the tests:
+//
+// U+00FC - 2 code units in UTF8, 1 in UTF16 - LATIN SMALL LETTER U WITH DIAERESIS
+// U+4E8C - 3 code units in UTF8, 1 in UTF16 - CJK UNIFIED IDEOGRAPH-4E8C
+// U+10302 - 4 code units in UTF8, 2 in UTF16 - OLD ITALIC LETTER KE
+//
+
+#define UNICODE_U00FC "\xc3\xbc"
+#define UNICODE_U4E8C "\xe4\xba\x8c"
+#define UNICODE_U10302 "\xf0\x90\x8c\x82"
+#define TEST_UNICODE_IDENTIFIER UNICODE_U00FC UNICODE_U4E8C UNICODE_U10302
+
+ original =
+ "class Foo {\n"
+ " void @" TEST_UNICODE_IDENTIFIER "();\n"
+ "};\n";
+ ;
+ expected = original;
+ expected +=
+ "\n"
+ "void Foo::" TEST_UNICODE_IDENTIFIER "()\n"
+ "{\n"
+ "\n"
+ "}\n";
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+#undef UNICODE_U00FC
+#undef UNICODE_U4E8C
+#undef UNICODE_U10302
+#undef TEST_UNICODE_IDENTIFIER
+
+ InsertDefFromDecl factory;
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+
+ void testTemplateClass()
+ {
+ QByteArray original =
+ "template<class T>\n"
+ "class Foo\n"
+ "{\n"
+ " void fun@c1();\n"
+ " void func2();\n"
+ "};\n\n"
+ "template<class T>\n"
+ "void Foo<T>::func2() {}\n";
+ QByteArray expected =
+ "template<class T>\n"
+ "class Foo\n"
+ "{\n"
+ " void func1();\n"
+ " void func2();\n"
+ "};\n\n"
+ "template<class T>\n"
+ "void Foo<T>::func1()\n"
+ "{\n"
+ "\n"
+ "}\n\n"
+ "template<class T>\n"
+ "void Foo<T>::func2() {}\n";
+
+ InsertDefFromDecl factory;
+ QuickFixOperationTest(singleDocument(original, expected), &factory);
+ }
+
+ void testTemplateClassWithValueParam()
+ {
+ QList<TestDocumentPtr> testDocuments;
+ QByteArray original =
+ "template<typename T, int size> struct MyArray {};\n"
+ "MyArray<int, 1> @foo();";
+ QByteArray expected = original;
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ original = "#include \"file.h\"\n";
+ expected =
+ "#include \"file.h\"\n\n"
+ "MyArray<int, 1> foo()\n"
+ "{\n\n"
+ "}\n";
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+ InsertDefFromDecl factory;
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+
+ void testTemplateFunction()
+ {
+ QByteArray original =
+ "class Foo\n"
+ "{\n"
+ " template<class T>\n"
+ " void fun@c();\n"
+ "};\n";
+ QByteArray expected =
+ "class Foo\n"
+ "{\n"
+ " template<class T>\n"
+ " void fun@c();\n"
+ "};\n"
+ "\n"
+ "template<class T>\n"
+ "void Foo::func()\n"
+ "{\n"
+ "\n"
+ "}\n";
+
+ InsertDefFromDecl factory;
+ QuickFixOperationTest(singleDocument(original, expected), &factory);
+ }
+
+ void testTemplateClassAndTemplateFunction()
+ {
+ QByteArray original =
+ "template<class T>"
+ "class Foo\n"
+ "{\n"
+ " template<class U>\n"
+ " T fun@c(U u);\n"
+ "};\n";
+ QByteArray expected =
+ "template<class T>"
+ "class Foo\n"
+ "{\n"
+ " template<class U>\n"
+ " T fun@c(U u);\n"
+ "};\n"
+ "\n"
+ "template<class T>\n"
+ "template<class U>\n"
+ "T Foo<T>::func(U u)\n"
+ "{\n"
+ "\n"
+ "}\n";
+
+ InsertDefFromDecl factory;
+ QuickFixOperationTest(singleDocument(original, expected), &factory);
+ }
+
+ void testTemplateClassAndFunctionInsideNamespace()
+ {
+ QByteArray original =
+ "namespace N {\n"
+ "template<class T>"
+ "class Foo\n"
+ "{\n"
+ " template<class U>\n"
+ " T fun@c(U u);\n"
+ "};\n"
+ "}\n";
+ QByteArray expected =
+ "namespace N {\n"
+ "template<class T>"
+ "class Foo\n"
+ "{\n"
+ " template<class U>\n"
+ " T fun@c(U u);\n"
+ "};\n"
+ "\n"
+ "template<class T>\n"
+ "template<class U>\n"
+ "T Foo<T>::func(U u)\n"
+ "{\n"
+ "\n"
+ "}\n"
+ "\n"
+ "}\n";
+
+ InsertDefFromDecl factory;
+ QuickFixOperationTest(singleDocument(original, expected), &factory);
+ }
+
+ void testFunctionWithSignedUnsignedArgument()
+ {
+ QByteArray original;
+ QByteArray expected;
+ InsertDefFromDecl factory;
+
+ original =R"--(
+class myclass
+{
+ myc@lass(QVector<signed> g);
+ myclass(QVector<unsigned> g);
+}
+)--";
+ expected =R"--(
+class myclass
+{
+ myclass(QVector<signed> g);
+ myclass(QVector<unsigned> g);
+}
+
+myclass::myclass(QVector<signed int> g)
+{
+
+}
+)--";
+
+ QuickFixOperationTest(singleDocument(original, expected), &factory);
+
+ original =R"--(
+class myclass
+{
+ myclass(QVector<signed> g);
+ myc@lass(QVector<unsigned> g);
+}
+)--";
+ expected =R"--(
+class myclass
+{
+ myclass(QVector<signed> g);
+ myclass(QVector<unsigned> g);
+}
+
+myclass::myclass(QVector<unsigned int> g)
+{
+
+}
+)--";
+
+ QuickFixOperationTest(singleDocument(original, expected), &factory);
+
+ original =R"--(
+class myclass
+{
+ unsigned f@oo(unsigned);
+}
+)--";
+ expected =R"--(
+class myclass
+{
+ unsigned foo(unsigned);
+}
+
+unsigned int myclass::foo(unsigned int)
+{
+
+}
+)--";
+ QuickFixOperationTest(singleDocument(original, expected), &factory);
+
+ original =R"--(
+class myclass
+{
+ signed f@oo(signed);
+}
+)--";
+ expected =R"--(
+class myclass
+{
+ signed foo(signed);
+}
+
+signed int myclass::foo(signed int)
+{
+
+}
+)--";
+ QuickFixOperationTest(singleDocument(original, expected), &factory);
+ }
+
+ void testNotTriggeredForFriendFunc()
+ {
+ const QByteArray contents =
+ "class Foo\n"
+ "{\n"
+ " friend void f@unc();\n"
+ "};\n"
+ "\n";
+
+ InsertDefFromDecl factory;
+ QuickFixOperationTest(singleDocument(contents, ""), &factory);
+ }
+
+ void testMinimalFunctionParameterType()
+ {
+ QList<TestDocumentPtr> testDocuments;
+
+ QByteArray original;
+ QByteArray expected;
+
+ // Header File
+ original = R"(
+class C {
+ typedef int A;
+ A @foo(A);
+};
+)";
+ expected = original;
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File
+ original = R"(
+#include "file.h"
+)";
+ expected = R"(
+#include "file.h"
+
+C::A C::foo(A)
+{
+
+}
+)";
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+ InsertDefFromDecl factory;
+ QuickFixOperationTest(testDocuments, &factory);
+
+ testDocuments.clear();
+ // Header File
+ original = R"(
+namespace N {
+ struct S;
+ S @foo(const S &s);
+};
+)";
+ expected = original;
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File
+ original = R"(
+#include "file.h"
+)";
+ expected = R"(
+#include "file.h"
+
+N::S N::foo(const S &s)
+{
+
+}
+)";
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+
+ void testAliasTemplateAsReturnType()
+ {
+ QList<TestDocumentPtr> testDocuments;
+
+ QByteArray original;
+ QByteArray expected;
+
+ // Header File
+ original = R"(
+struct foo {
+ struct foo2 {
+ template <typename T> using MyType = T;
+ MyType<int> @bar();
+ };
+};
+)";
+ expected = original;
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File
+ original = R"(
+#include "file.h"
+)";
+ expected = R"(
+#include "file.h"
+
+foo::foo2::MyType<int> foo::foo2::bar()
+{
+
+}
+)";
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+ InsertDefFromDecl factory;
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+
+ void test_data()
+ {
+ QTest::addColumn<QByteArray>("original");
+ QTest::addColumn<QByteArray>("expected");
+
+ // Check from source file: If there is no header file, insert the definition after the class.
+ QByteArray original =
+ "struct Foo\n"
+ "{\n"
+ " Foo();@\n"
+ "};\n";
+ QByteArray expected = original +
+ "\n"
+ "Foo::Foo()\n"
+ "{\n\n"
+ "}\n";
+ QTest::newRow("basic") << original << expected;
+
+ original = "void free()@;\n";
+ expected = "void free()\n{\n\n}\n";
+ QTest::newRow("freeFunction") << original << expected;
+
+ original = "class Foo {\n"
+ "public:\n"
+ " Foo() {}\n"
+ "};\n"
+ "void freeFunc() {\n"
+ " Foo @f();"
+ "}\n";
+
+ // Check not triggering when it is a statement
+ QTest::newRow("notTriggeringStatement") << original << QByteArray();
+ }
+
+ void test()
+ {
+ QFETCH(QByteArray, original);
+ QFETCH(QByteArray, expected);
+
+ InsertDefFromDecl factory;
+ QuickFixOperationTest(singleDocument(original, expected), &factory);
+ }
+
+ void testOutsideTemplateClassAndTemplateFunction()
+ {
+ QByteArray original =
+ "template<class T>"
+ "class Foo\n"
+ "{\n"
+ " template<class U>\n"
+ " void fun@c();\n"
+ "};\n";
+ QByteArray expected =
+ "template<class T>"
+ "class Foo\n"
+ "{\n"
+ " template<class U>\n"
+ " void fun@c();\n"
+ "};\n"
+ "\n"
+ "template<class T>\n"
+ "template<class U>\n"
+ "inline void Foo<T>::func()\n"
+ "{\n"
+ "\n"
+ "}\n";
+
+ InsertDefFromDecl factory;
+ factory.setOutside();
+ QuickFixOperationTest(singleHeader(original, expected), &factory);
+ }
+
+ void testOutsideTemplateClass()
+ {
+ QByteArray original =
+ "template<class T>"
+ "class Foo\n"
+ "{\n"
+ " void fun@c();\n"
+ "};\n";
+ QByteArray expected =
+ "template<class T>"
+ "class Foo\n"
+ "{\n"
+ " void fun@c();\n"
+ "};\n"
+ "\n"
+ "template<class T>\n"
+ "inline void Foo<T>::func()\n"
+ "{\n"
+ "\n"
+ "}\n";
+
+ InsertDefFromDecl factory;
+ factory.setOutside();
+ QuickFixOperationTest(singleHeader(original, expected), &factory);
+ }
+
+ void testOutsideTemplateFunction()
+ {
+ QByteArray original =
+ "class Foo\n"
+ "{\n"
+ " template<class U>\n"
+ " void fun@c();\n"
+ "};\n";
+ QByteArray expected =
+ "class Foo\n"
+ "{\n"
+ " template<class U>\n"
+ " void fun@c();\n"
+ "};\n"
+ "\n"
+ "template<class U>\n"
+ "inline void Foo::func()\n"
+ "{\n"
+ "\n"
+ "}\n";
+
+ InsertDefFromDecl factory;
+ factory.setOutside();
+ QuickFixOperationTest(singleHeader(original, expected), &factory);
+ }
+
+ void testOutsideFunction()
+ {
+ QByteArray original =
+ "class Foo\n"
+ "{\n"
+ " void fun@c();\n"
+ "};\n";
+ QByteArray expected =
+ "class Foo\n"
+ "{\n"
+ " void fun@c();\n"
+ "};\n"
+ "\n"
+ "inline void Foo::func()\n"
+ "{\n"
+ "\n"
+ "}\n";
+
+ InsertDefFromDecl factory;
+ factory.setOutside();
+ QuickFixOperationTest(singleHeader(original, expected), &factory);
+
+ }
+
+};
+
+class InsertDefsFromDeclsTest : public QObject
+{
+ Q_OBJECT
+
+private slots:
+
+ void test_data()
+ {
+ QTest::addColumn<QByteArrayList>("headers");
+ QTest::addColumn<QByteArrayList>("sources");
+ QTest::addColumn<int>("mode");
+
+ QByteArray origHeader = R"(
+namespace N {
+class @C
+{
+public:
+ friend void ignoredFriend();
+ void ignoredImplemented() {};
+ void ignoredImplemented2(); // Below
+ void ignoredImplemented3(); // In cpp file
+ void funcNotSelected();
+ void funcInline();
+ void funcBelow();
+ void funcCppFile();
+
+signals:
+ void ignoredSignal();
+};
+
+inline void C::ignoredImplemented2() {}
+
+} // namespace N)";
+ QByteArray origSource = R"(
+#include "file.h"
+
+namespace N {
+
+void C::ignoredImplemented3() {}
+
+} // namespace N)";
+
+ QByteArray expectedHeader = R"(
+namespace N {
+class C
+{
+public:
+ friend void ignoredFriend();
+ void ignoredImplemented() {};
+ void ignoredImplemented2(); // Below
+ void ignoredImplemented3(); // In cpp file
+ void funcNotSelected();
+ void funcInline()
+ {
+
+ }
+ void funcBelow();
+ void funcCppFile();
+
+signals:
+ void ignoredSignal();
+};
+
+inline void C::ignoredImplemented2() {}
+
+inline void C::funcBelow()
+{
+
+}
+
+} // namespace N)";
+ QByteArray expectedSource = R"(
+#include "file.h"
+
+namespace N {
+
+void C::ignoredImplemented3() {}
+
+void C::funcCppFile()
+{
+
+}
+
+} // namespace N)";
+ QTest::addRow("normal case")
+ << QByteArrayList{origHeader, expectedHeader}
+ << QByteArrayList{origSource, expectedSource}
+ << int(InsertDefsFromDeclsMode::Alternating);
+ QTest::addRow("aborted dialog")
+ << QByteArrayList{origHeader, origHeader}
+ << QByteArrayList{origSource, origSource}
+ << int(InsertDefsFromDeclsMode::Off);
+
+ origHeader = R"(
+ namespace N {
+ class @C
+ {
+ public:
+ friend void ignoredFriend();
+ void ignoredImplemented() {};
+ void ignoredImplemented2(); // Below
+ void ignoredImplemented3(); // In cpp file
+
+ signals:
+ void ignoredSignal();
+ };
+
+ inline void C::ignoredImplemented2() {}
+
+ } // namespace N)";
+ QTest::addRow("no candidates")
+ << QByteArrayList{origHeader, origHeader}
+ << QByteArrayList{origSource, origSource}
+ << int(InsertDefsFromDeclsMode::Alternating);
+
+ origHeader = R"(
+ namespace N {
+ class @C
+ {
+ public:
+ friend void ignoredFriend();
+ void ignoredImplemented() {};
+
+ signals:
+ void ignoredSignal();
+ };
+ } // namespace N)";
+ QTest::addRow("no member functions")
+ << QByteArrayList{origHeader, ""}
+ << QByteArrayList{origSource, ""}
+ << int(InsertDefsFromDeclsMode::Alternating);
+
+
+ }
+
+ void test()
+ {
+ QFETCH(QByteArrayList, headers);
+ QFETCH(QByteArrayList, sources);
+ QFETCH(int, mode);
+
+ QList<TestDocumentPtr> testDocuments(
+ {CppTestDocument::create("file.h", headers.at(0), headers.at(1)),
+ CppTestDocument::create("file.cpp", sources.at(0), sources.at(1))});
+ InsertDefsFromDecls factory;
+ factory.setMode(static_cast<InsertDefsFromDeclsMode>(mode));
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+
+ void testInsertAndFormat()
+ {
+ if (!isClangFormatPresent())
+ QSKIP("This test reqires ClangFormat");
+
+ const QByteArray origHeader = R"(
+class @C
+{
+public:
+ void func1 (int const &i);
+ void func2 (double const d);
+};
+)";
+ const QByteArray origSource = R"(
+#include "file.h"
+)";
+
+ const QByteArray expectedSource = R"(
+#include "file.h"
+
+void C::func1 (int const &i)
+{
+
+}
+
+void C::func2 (double const d)
+{
+
+}
+)";
+
+ const QByteArray clangFormatSettings = R"(
+BreakBeforeBraces: Allman
+QualifierAlignment: Right
+SpaceBeforeParens: Always
+)";
+
+ const QList<TestDocumentPtr> testDocuments({
+ CppTestDocument::create("file.h", origHeader, origHeader),
+ CppTestDocument::create("file.cpp", origSource, expectedSource)});
+ InsertDefsFromDecls factory;
+ factory.setMode(InsertDefsFromDeclsMode::Impl);
+ CppCodeStylePreferences * const prefs = CppToolsSettings::cppCodeStyle();
+ const CppCodeStyleSettings settings = prefs->codeStyleSettings();
+ CppCodeStyleSettings tempSettings = settings;
+ tempSettings.forceFormatting = true;
+ prefs->setCodeStyleSettings(tempSettings);
+ QuickFixOperationTest(testDocuments, &factory, {}, {}, {}, clangFormatSettings);
+ prefs->setCodeStyleSettings(settings);
+ }
+};
+
+QObject *InsertDefFromDecl::createTest()
+{
+ return new InsertDefFromDeclTest;
+}
+
+QObject *InsertDefsFromDecls::createTest()
+{
+ return new InsertDefsFromDeclsTest;
+}
+
+#endif // WITH_TESTS
+} // namespace
+
+void registerInsertFunctionDefinitionQuickfixes()
+{
+ CppQuickFixFactory::registerFactory<InsertDefFromDecl>();
+ CppQuickFixFactory::registerFactory<InsertDefsFromDecls>();
+}
+
+} // namespace CppEditor::Internal
+
+#ifdef WITH_TESTS
+#include <insertfunctiondefinition.moc>
+#endif
diff --git a/src/plugins/cppeditor/quickfixes/insertfunctiondefinition.h b/src/plugins/cppeditor/quickfixes/insertfunctiondefinition.h
new file mode 100644
index 0000000000..77c4c87b6d
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/insertfunctiondefinition.h
@@ -0,0 +1,8 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+namespace CppEditor::Internal {
+void registerInsertFunctionDefinitionQuickfixes();
+} // namespace CppEditor::Internal
diff --git a/src/plugins/cppeditor/quickfixes/logicaloperationquickfixes.cpp b/src/plugins/cppeditor/quickfixes/logicaloperationquickfixes.cpp
new file mode 100644
index 0000000000..05ad754074
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/logicaloperationquickfixes.cpp
@@ -0,0 +1,381 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "logicaloperationquickfixes.h"
+
+#include "../cppeditortr.h"
+#include "../cpprefactoringchanges.h"
+#include "cppquickfix.h"
+
+#ifdef WITH_TESTS
+#include "cppquickfix_test.h"
+#include <QtTest>
+#endif
+
+using namespace CPlusPlus;
+using namespace Utils;
+
+namespace CppEditor::Internal {
+namespace {
+
+class FlipLogicalOperandsOp : public CppQuickFixOperation
+{
+public:
+ FlipLogicalOperandsOp(const CppQuickFixInterface &interface, int priority,
+ BinaryExpressionAST *binary, QString replacement)
+ : CppQuickFixOperation(interface)
+ , binary(binary)
+ , replacement(replacement)
+ {
+ setPriority(priority);
+ }
+
+ QString description() const override
+ {
+ if (replacement.isEmpty())
+ return Tr::tr("Swap Operands");
+ else
+ return Tr::tr("Rewrite Using %1").arg(replacement);
+ }
+
+ void perform() override
+ {
+ ChangeSet changes;
+ changes.flip(currentFile()->range(binary->left_expression),
+ currentFile()->range(binary->right_expression));
+ if (!replacement.isEmpty())
+ changes.replace(currentFile()->range(binary->binary_op_token), replacement);
+
+ currentFile()->apply(changes);
+ }
+
+private:
+ BinaryExpressionAST *binary;
+ QString replacement;
+};
+
+class InverseLogicalComparisonOp : public CppQuickFixOperation
+{
+public:
+ InverseLogicalComparisonOp(const CppQuickFixInterface &interface,
+ int priority,
+ BinaryExpressionAST *binary,
+ Kind invertToken)
+ : CppQuickFixOperation(interface, priority)
+ , binary(binary)
+ {
+ Token tok;
+ tok.f.kind = invertToken;
+ replacement = QLatin1String(tok.spell());
+
+ // check for enclosing nested expression
+ if (priority - 1 >= 0)
+ nested = interface.path()[priority - 1]->asNestedExpression();
+
+ // check for ! before parentheses
+ if (nested && priority - 2 >= 0) {
+ negation = interface.path()[priority - 2]->asUnaryExpression();
+ if (negation && !interface.currentFile()->tokenAt(negation->unary_op_token).is(T_EXCLAIM))
+ negation = nullptr;
+ }
+ }
+
+ QString description() const override
+ {
+ return Tr::tr("Rewrite Using %1").arg(replacement);
+ }
+
+ void perform() override
+ {
+ ChangeSet changes;
+ if (negation) {
+ // can't remove parentheses since that might break precedence
+ changes.remove(currentFile()->range(negation->unary_op_token));
+ } else if (nested) {
+ changes.insert(currentFile()->startOf(nested), QLatin1String("!"));
+ } else {
+ changes.insert(currentFile()->startOf(binary), QLatin1String("!("));
+ changes.insert(currentFile()->endOf(binary), QLatin1String(")"));
+ }
+ changes.replace(currentFile()->range(binary->binary_op_token), replacement);
+ currentFile()->apply(changes);
+ }
+
+private:
+ BinaryExpressionAST *binary = nullptr;
+ NestedExpressionAST *nested = nullptr;
+ UnaryExpressionAST *negation = nullptr;
+
+ QString replacement;
+};
+
+class RewriteLogicalAndOp : public CppQuickFixOperation
+{
+public:
+ std::shared_ptr<ASTPatternBuilder> mk;
+ UnaryExpressionAST *left;
+ UnaryExpressionAST *right;
+ BinaryExpressionAST *pattern;
+
+ RewriteLogicalAndOp(const CppQuickFixInterface &interface)
+ : CppQuickFixOperation(interface)
+ , mk(new ASTPatternBuilder)
+ {
+ left = mk->UnaryExpression();
+ right = mk->UnaryExpression();
+ pattern = mk->BinaryExpression(left, right);
+ }
+
+ void perform() override
+ {
+ ChangeSet changes;
+ changes.replace(currentFile()->range(pattern->binary_op_token), QLatin1String("||"));
+ changes.remove(currentFile()->range(left->unary_op_token));
+ changes.remove(currentFile()->range(right->unary_op_token));
+ const int start = currentFile()->startOf(pattern);
+ const int end = currentFile()->endOf(pattern);
+ changes.insert(start, QLatin1String("!("));
+ changes.insert(end, QLatin1String(")"));
+
+ currentFile()->apply(changes);
+ }
+};
+
+/*!
+ Rewrite
+ a op b
+
+ As
+ b flipop a
+
+ Activates on: <= < > >= == != && ||
+*/
+class FlipLogicalOperands : public CppQuickFixFactory
+{
+#ifdef WITH_TESTS
+public:
+ static QObject *createTest();
+#endif
+
+private:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ const QList<AST *> &path = interface.path();
+ if (path.isEmpty())
+ return;
+ CppRefactoringFilePtr file = interface.currentFile();
+
+ int index = path.size() - 1;
+ BinaryExpressionAST *binary = path.at(index)->asBinaryExpression();
+ if (!binary)
+ return;
+ if (!interface.isCursorOn(binary->binary_op_token))
+ return;
+
+ Kind flipToken;
+ switch (file->tokenAt(binary->binary_op_token).kind()) {
+ case T_LESS_EQUAL:
+ flipToken = T_GREATER_EQUAL;
+ break;
+ case T_LESS:
+ flipToken = T_GREATER;
+ break;
+ case T_GREATER:
+ flipToken = T_LESS;
+ break;
+ case T_GREATER_EQUAL:
+ flipToken = T_LESS_EQUAL;
+ break;
+ case T_EQUAL_EQUAL:
+ case T_EXCLAIM_EQUAL:
+ case T_AMPER_AMPER:
+ case T_PIPE_PIPE:
+ flipToken = T_EOF_SYMBOL;
+ break;
+ default:
+ return;
+ }
+
+ QString replacement;
+ if (flipToken != T_EOF_SYMBOL) {
+ Token tok;
+ tok.f.kind = flipToken;
+ replacement = QLatin1String(tok.spell());
+ }
+
+ result << new FlipLogicalOperandsOp(interface, index, binary, replacement);
+ }
+};
+
+/*!
+ Rewrite
+ a op b -> !(a invop b)
+ (a op b) -> !(a invop b)
+ !(a op b) -> (a invob b)
+
+ Activates on: <= < > >= == !=
+*/
+class InverseLogicalComparison : public CppQuickFixFactory
+{
+#ifdef WITH_TESTS
+public:
+ static QObject *createTest() { return new QObject; }
+#endif
+
+private:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ CppRefactoringFilePtr file = interface.currentFile();
+
+ const QList<AST *> &path = interface.path();
+ if (path.isEmpty())
+ return;
+ int index = path.size() - 1;
+ BinaryExpressionAST *binary = path.at(index)->asBinaryExpression();
+ if (!binary)
+ return;
+ if (!interface.isCursorOn(binary->binary_op_token))
+ return;
+
+ Kind invertToken;
+ switch (file->tokenAt(binary->binary_op_token).kind()) {
+ case T_LESS_EQUAL:
+ invertToken = T_GREATER;
+ break;
+ case T_LESS:
+ invertToken = T_GREATER_EQUAL;
+ break;
+ case T_GREATER:
+ invertToken = T_LESS_EQUAL;
+ break;
+ case T_GREATER_EQUAL:
+ invertToken = T_LESS;
+ break;
+ case T_EQUAL_EQUAL:
+ invertToken = T_EXCLAIM_EQUAL;
+ break;
+ case T_EXCLAIM_EQUAL:
+ invertToken = T_EQUAL_EQUAL;
+ break;
+ default:
+ return;
+ }
+
+ result << new InverseLogicalComparisonOp(interface, index, binary, invertToken);
+ }
+};
+
+/*!
+ Rewrite
+ !a && !b
+
+ As
+ !(a || b)
+
+ Activates on: &&
+*/
+class RewriteLogicalAnd : public CppQuickFixFactory
+{
+#ifdef WITH_TESTS
+public:
+ static QObject *createTest() { return new QObject; }
+#endif
+
+private:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ BinaryExpressionAST *expression = nullptr;
+ const QList<AST *> &path = interface.path();
+ CppRefactoringFilePtr file = interface.currentFile();
+
+ int index = path.size() - 1;
+ for (; index != -1; --index) {
+ expression = path.at(index)->asBinaryExpression();
+ if (expression)
+ break;
+ }
+
+ if (!expression)
+ return;
+
+ if (!interface.isCursorOn(expression->binary_op_token))
+ return;
+
+ QSharedPointer<RewriteLogicalAndOp> op(new RewriteLogicalAndOp(interface));
+
+ ASTMatcher matcher;
+
+ if (expression->match(op->pattern, &matcher) &&
+ file->tokenAt(op->pattern->binary_op_token).is(T_AMPER_AMPER) &&
+ file->tokenAt(op->left->unary_op_token).is(T_EXCLAIM) &&
+ file->tokenAt(op->right->unary_op_token).is(T_EXCLAIM)) {
+ op->setDescription(Tr::tr("Rewrite Condition Using ||"));
+ op->setPriority(index);
+ result.append(op);
+ }
+ }
+};
+
+#ifdef WITH_TESTS
+using namespace Tests;
+class FlipLogicalOperandsTest : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void test_data()
+ {
+ QTest::addColumn<QByteArray>("original");
+ QTest::addColumn<QByteArray>("expected");
+
+ const auto makeDoc = [](const QString &expr) {
+ const QString pattern = "#define VALUE 7\n"
+ "int main() {\n"
+ " if (%1)\n"
+ " return 1;\n"
+ "}\n";
+ return pattern.arg(expr).toUtf8();
+ };
+
+ QTest::newRow("macro as left expr")
+ << makeDoc("VALUE @&& true")
+ << makeDoc("true && VALUE");
+ QTest::newRow("macro in left expr")
+ << makeDoc("(VALUE + 1) @&& true")
+ << makeDoc("true && (VALUE + 1)");
+ QTest::newRow("macro as right expr")
+ << makeDoc("false @|| VALUE")
+ << makeDoc("VALUE || false");
+ QTest::newRow("macro in right expr")
+ << makeDoc("false @|| (VALUE + 1)")
+ << makeDoc("(VALUE + 1) || false");
+ }
+
+ void test()
+ {
+ QFETCH(QByteArray, original);
+ QFETCH(QByteArray, expected);
+
+ FlipLogicalOperands factory;
+ QuickFixOperationTest(singleDocument(original, expected), &factory);
+ }
+};
+
+QObject *FlipLogicalOperands::createTest() { return new FlipLogicalOperandsTest; }
+
+#endif // WITH_TESTS
+
+} // namespace
+
+void registerLogicalOperationQuickfixes()
+{
+ CppQuickFixFactory::registerFactory<FlipLogicalOperands>();
+ CppQuickFixFactory::registerFactory<InverseLogicalComparison>();
+ CppQuickFixFactory::registerFactory<RewriteLogicalAnd>();
+}
+
+} // namespace CppEditor::Internal
+
+#ifdef WITH_TESTS
+#include <logicaloperationquickfixes.moc>
+#endif
diff --git a/src/plugins/cppeditor/quickfixes/logicaloperationquickfixes.h b/src/plugins/cppeditor/quickfixes/logicaloperationquickfixes.h
new file mode 100644
index 0000000000..cf77bed8e0
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/logicaloperationquickfixes.h
@@ -0,0 +1,8 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+namespace CppEditor::Internal {
+void registerLogicalOperationQuickfixes();
+} // namespace CppEditor::Internal
diff --git a/src/plugins/cppeditor/quickfixes/moveclasstoownfile.cpp b/src/plugins/cppeditor/quickfixes/moveclasstoownfile.cpp
new file mode 100644
index 0000000000..e8a6c9b33f
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/moveclasstoownfile.cpp
@@ -0,0 +1,722 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "moveclasstoownfile.h"
+
+#include "../cppeditortr.h"
+#include "../cppeditorwidget.h"
+#include "../cppfilesettingspage.h"
+#include "../cpprefactoringchanges.h"
+#include "cppquickfix.h"
+#include "cppquickfixhelpers.h"
+
+#include <coreplugin/messagemanager.h>
+#include <cplusplus/ASTPath.h>
+#include <cplusplus/Overview.h>
+#include <projectexplorer/projectmanager.h>
+#include <projectexplorer/projectnodes.h>
+#include <utils/codegeneration.h>
+#include <utils/layoutbuilder.h>
+#include <utils/treemodel.h>
+#include <utils/treeviewcombobox.h>
+
+#include <QCheckBox>
+#include <QDialog>
+#include <QDialogButtonBox>
+
+#ifdef WITH_TESTS
+#include "../cpptoolstestcase.h"
+#include <coreplugin/editormanager/editormanager.h>
+#include <projectexplorer/kitmanager.h>
+#include <texteditor/textdocument.h>
+#include <QtTest>
+#endif // WITH_TESTS
+
+using namespace CPlusPlus;
+using namespace Core;
+using namespace ProjectExplorer;
+using namespace Utils;
+
+namespace CppEditor::Internal {
+namespace {
+
+class MoveClassToOwnFileOp : public CppQuickFixOperation
+{
+public:
+ MoveClassToOwnFileOp(
+ const CppQuickFixInterface &interface,
+ AST *fullDecl,
+ ClassSpecifierAST *classAst,
+ const QList<Namespace *> &namespacePath,
+ bool interactive)
+ : CppQuickFixOperation(interface)
+ , m_state(std::make_shared<State>())
+ {
+ setDescription(Tr::tr("Move Class to a Dedicated Set of Source Files"));
+ m_state->originalFilePath = interface.currentFile()->filePath();
+ m_state->classAst = classAst;
+ m_state->namespacePath = namespacePath;
+ m_state->interactive = interactive;
+ PerFileState &perFileState = m_state->perFileState[interface.currentFile()->filePath()];
+ perFileState.refactoringFile = interface.currentFile();
+ perFileState.declarationsToMove << fullDecl;
+ }
+
+private:
+ struct PerFileState {
+ // We want to keep the relative order of moved code.
+ void insertSorted(AST *decl) {
+ declarationsToMove.insert(std::lower_bound(
+ declarationsToMove.begin(),
+ declarationsToMove.end(),
+ decl,
+ [](const AST *elem, const AST *value) {
+ return elem->firstToken() < value->firstToken();
+ }), decl);
+ }
+
+ CppRefactoringFilePtr refactoringFile;
+ QList<AST *> declarationsToMove;
+ };
+ struct State {
+ using Ptr = std::shared_ptr<State>;
+
+ FilePath originalFilePath;
+ AST *fullDecl = nullptr;
+ ClassSpecifierAST *classAst = nullptr;
+ QList<Namespace *> namespacePath;
+ Links lookupResults;
+ QMap<FilePath, PerFileState> perFileState; // A map for deterministic order of moved code.
+ CppRefactoringChanges factory{CppModelManager::snapshot()};
+ int remainingFollowSymbolOps = 0;
+ bool interactive = true;
+ };
+ class Dialog : public QDialog {
+ public:
+ Dialog(const FilePath &defaultHeaderFilePath, const FilePath &defaultSourceFilePath,
+ ProjectNode *defaultProjectNode)
+ : m_buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel)
+ {
+ ProjectNode * const rootNode = defaultProjectNode
+ ? defaultProjectNode->getProject()->rootProjectNode()
+ : nullptr;
+ if (rootNode) {
+ const auto projectRootItem = new NodeItem(rootNode);
+ buildTree(projectRootItem);
+ m_projectModel.rootItem()->appendChild(projectRootItem);
+ }
+ m_projectNodeComboBox.setModel(&m_projectModel);
+ if (defaultProjectNode) {
+ const auto matcher = [defaultProjectNode](TreeItem *item) {
+ return static_cast<NodeItem *>(item)->node == defaultProjectNode;
+ };
+ TreeItem * const defaultItem = m_projectModel.rootItem()->findAnyChild(matcher);
+ if (defaultItem ) {
+ QModelIndex index = m_projectModel.indexForItem(defaultItem);
+ m_projectNodeComboBox.setCurrentIndex(index);
+ while (index.isValid()) {
+ m_projectNodeComboBox.view()->expand(index);
+ index = index.parent();
+ }
+ }
+
+ }
+ connect(&m_projectNodeComboBox, &QComboBox::currentIndexChanged,
+ this, [this] {
+ if (m_filesEdited)
+ return;
+ const auto newProjectNode = projectNode();
+ QTC_ASSERT(newProjectNode, return);
+ const FilePath baseDir = newProjectNode->directory();
+ m_sourcePathChooser.setFilePath(
+ baseDir.pathAppended(sourceFilePath().fileName()));
+ m_headerPathChooser.setFilePath(
+ baseDir.pathAppended(headerFilePath().fileName()));
+ m_filesEdited = false;
+ });
+
+ m_headerOnlyCheckBox.setText(Tr::tr("Header file only"));
+ m_headerOnlyCheckBox.setChecked(false);
+ connect(&m_headerOnlyCheckBox, &QCheckBox::toggled,
+ this, [this](bool checked) { m_sourcePathChooser.setEnabled(!checked); });
+
+ m_headerPathChooser.setExpectedKind(PathChooser::SaveFile);
+ m_sourcePathChooser.setExpectedKind(PathChooser::SaveFile);
+ m_headerPathChooser.setFilePath(defaultHeaderFilePath);
+ m_sourcePathChooser.setFilePath(defaultSourceFilePath);
+ connect(&m_headerPathChooser, &PathChooser::textChanged,
+ this, [this] { m_filesEdited = true; });
+ connect(&m_sourcePathChooser, &PathChooser::textChanged,
+ this, [this] { m_filesEdited = true; });
+
+ connect(&m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
+ connect(&m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
+
+ using namespace Layouting;
+ Column {
+ Form {
+ Tr::tr("Project:"), &m_projectNodeComboBox, br,
+ &m_headerOnlyCheckBox, br,
+ Tr::tr("Header file:"), &m_headerPathChooser, br,
+ Tr::tr("Implementation file:"), &m_sourcePathChooser, br,
+ },
+ &m_buttonBox
+ }.attachTo(this);
+ }
+
+ ProjectNode *projectNode() const
+ {
+ const QVariant v = m_projectNodeComboBox.currentData(Qt::UserRole);
+ return v.isNull() ? nullptr : static_cast<ProjectNode *>(v.value<void *>());
+ }
+ bool createSourceFile() const { return !m_headerOnlyCheckBox.isChecked(); }
+ FilePath headerFilePath() const { return m_headerPathChooser.absoluteFilePath(); }
+ FilePath sourceFilePath() const { return m_sourcePathChooser.absoluteFilePath(); }
+
+ private:
+ struct NodeItem : public StaticTreeItem {
+ NodeItem(ProjectNode *node)
+ : StaticTreeItem({node->displayName()}, {node->directory().toUserOutput()})
+ , node(node)
+ {}
+ Qt::ItemFlags flags(int) const override
+ {
+ return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
+ }
+ QVariant data(int column, int role) const override
+ {
+ if (role == Qt::UserRole)
+ return QVariant::fromValue(static_cast<void *>(node));
+ return StaticTreeItem::data(column, role);
+ }
+
+ ProjectNode * const node;
+ };
+
+ void buildTree(NodeItem *parent)
+ {
+ for (Node * const node : parent->node->nodes()) {
+ if (const auto projNode = node->asProjectNode()) {
+ const auto child = new NodeItem(projNode);
+ buildTree(child);
+ parent->appendChild(child);
+ }
+ }
+ }
+
+ TreeViewComboBox m_projectNodeComboBox;
+ QCheckBox m_headerOnlyCheckBox;
+ PathChooser m_headerPathChooser;
+ PathChooser m_sourcePathChooser;
+ QDialogButtonBox m_buttonBox;
+ TreeModel<> m_projectModel;
+ bool m_filesEdited = false;
+ };
+
+ void perform() override
+ {
+ collectImplementations(m_state->classAst->symbol, m_state);
+ if (m_state->remainingFollowSymbolOps == 0)
+ finish(m_state);
+ }
+
+ static CppRefactoringFilePtr getRefactoringFile(const FilePath &filePath, const State::Ptr &state)
+ {
+ CppRefactoringFilePtr &refactoringFile = state->perFileState[filePath].refactoringFile;
+ if (!refactoringFile)
+ refactoringFile = state->factory.cppFile(filePath);
+ return refactoringFile;
+ }
+
+ static void lookupSymbol(Symbol *symbol, const State::Ptr &state)
+ {
+ const CppRefactoringFilePtr refactoringFile = getRefactoringFile(symbol->filePath(), state);
+ const auto editorWidget = qobject_cast<CppEditorWidget *>(refactoringFile->editor());
+ QTextCursor cursor(refactoringFile->document()->begin());
+ TranslationUnit * const tu = refactoringFile->cppDocument()->translationUnit();
+ const int symbolPos = tu->getTokenPositionInDocument(symbol->sourceLocation(),
+ refactoringFile->document());
+ cursor.setPosition(symbolPos);
+ const CursorInEditor cursorInEditor(
+ cursor,
+ symbol->filePath(),
+ editorWidget,
+ editorWidget ? editorWidget->textDocument() : nullptr,
+ refactoringFile->cppDocument());
+ const auto callback = [symbol, symbolPos, doc = cursor.document(), state](const Link &link) {
+ class FinishedChecker {
+ public:
+ FinishedChecker(const State::Ptr &state) : m_state(state) {}
+ ~FinishedChecker() {
+ if (--m_state->remainingFollowSymbolOps == 0)
+ finish(m_state);
+ };
+ private:
+ const State::Ptr &m_state;
+ } finishedChecker(state);
+ if (!link.hasValidTarget())
+ return;
+ if (symbol->filePath() == link.targetFilePath) {
+ const int linkPos = Text::positionInText(doc, link.targetLine,
+ link.targetColumn + 1);
+ if (linkPos == symbolPos)
+ return;
+ }
+ const CppRefactoringFilePtr refactoringFile
+ = getRefactoringFile(link.targetFilePath, state);
+ const QList<AST *> astPath = ASTPath(
+ refactoringFile->cppDocument())(link.targetLine, link.targetColumn);
+ const bool isTemplate = symbol->asTemplate();
+ const bool isFunction = symbol->type()->asFunctionType();
+ for (auto it = astPath.rbegin(); it != astPath.rend(); ++it) {
+ const bool match = isTemplate ? bool((*it)->asTemplateDeclaration())
+ : isFunction ? bool((*it)->asFunctionDefinition())
+ : bool((*it)->asSimpleDeclaration());
+ if (match) {
+ // For member functions of class templates.
+ if (isFunction) {
+ const auto next = std::next(it);
+ if (next != astPath.rend() && (*next)->asTemplateDeclaration())
+ it = next;
+ }
+ state->perFileState[link.targetFilePath].insertSorted(*it);
+ if (symbol->asForwardClassDeclaration()) {
+ if (const auto classSpec = (*(it - 1))->asClassSpecifier();
+ classSpec && classSpec->symbol) {
+ collectImplementations(classSpec->symbol, state);
+ }
+ }
+ break;
+ }
+ }
+ };
+ ++state->remainingFollowSymbolOps;
+
+ // Force queued execution, as the built-in editor can run the callback synchronously.
+ const auto followSymbol = [cursorInEditor, callback] {
+ NonInteractiveFollowSymbolMarker niMarker;
+ CppModelManager::followSymbol(
+ cursorInEditor, callback, true, false, FollowSymbolMode::Exact);
+ };
+ QMetaObject::invokeMethod(CppModelManager::instance(), followSymbol, Qt::QueuedConnection);
+ }
+
+ static void collectImplementations(Class *klass, const State::Ptr &state)
+ {
+ for (int i = 0; i < klass->memberCount(); ++i) {
+ Symbol * const member = klass->memberAt(i);
+ if (member->asForwardClassDeclaration() || member->asTemplate()) {
+ lookupSymbol(member, state);
+ continue;
+ }
+ const auto decl = member->asDeclaration();
+ if (!decl)
+ continue;
+ if (decl->type().type()->asFunctionType()) {
+ if (!decl->asFunction())
+ lookupSymbol(member, state);
+ } else if (decl->isStatic() && !decl->type().isInline()) {
+ lookupSymbol(member, state);
+ }
+ }
+ }
+
+ static void finish(const State::Ptr &state)
+ {
+ Overview ov;
+ Project * const project = ProjectManager::projectForFile(state->originalFilePath);
+ const CppFileSettings fileSettings = cppFileSettingsForProject(project);
+ const auto constructDefaultFilePaths = [&] {
+ const QString className = ov.prettyName(state->classAst->symbol->name());
+ const QString baseFileName = fileSettings.lowerCaseFiles ? className.toLower() : className;
+ const QString headerFileName = baseFileName + '.' + fileSettings.headerSuffix;
+ const FilePath baseDir = state->originalFilePath.parentDir();
+ const FilePath headerFilePath = baseDir.pathAppended(headerFileName);
+ const QString sourceFileName = baseFileName + '.' + fileSettings.sourceSuffix;
+ const FilePath sourceFilePath = baseDir.pathAppended(sourceFileName);
+ return std::make_pair(headerFilePath, sourceFilePath);
+ };
+ auto [headerFilePath, sourceFilePath] = constructDefaultFilePaths();
+ bool mustCreateSourceFile = false;
+ bool mustNotCreateSourceFile = false;
+ ProjectNode *projectNode = nullptr;
+ if (project && project->rootProjectNode()) {
+ const Node * const origNode = project->nodeForFilePath(state->originalFilePath);
+ if (origNode)
+ projectNode = const_cast<Node *>(origNode)->managingProject();
+ }
+ if (state->interactive) {
+ Dialog dlg(headerFilePath, sourceFilePath, projectNode);
+ if (dlg.exec() != QDialog::Accepted)
+ return;
+ projectNode = dlg.projectNode();
+ headerFilePath = dlg.headerFilePath();
+ sourceFilePath = dlg.sourceFilePath();
+ mustCreateSourceFile = dlg.createSourceFile();
+ mustNotCreateSourceFile = !dlg.createSourceFile();
+ }
+ const auto fileListForDisplay = [](const FilePaths &files) {
+ return Utils::transform<QStringList>(files, [](const FilePath &fp) {
+ return '"' + fp.toUserOutput() + '"';
+ }).join(", ");
+ };
+ FilePaths existingFiles;
+ if (headerFilePath.exists())
+ existingFiles << headerFilePath;
+ if (!mustNotCreateSourceFile && sourceFilePath.exists())
+ existingFiles << sourceFilePath;
+ if (!existingFiles.isEmpty()) {
+ MessageManager::writeDisrupting(
+ Tr::tr("Refusing to overwrite the following files: %1\n")
+ .arg(fileListForDisplay(existingFiles)));
+ return;
+ }
+ const QString headerFileName = headerFilePath.fileName();
+
+ QString headerContent;
+ QString sourceContent;
+ QList<QString *> commonContent{&headerContent};
+ if (!mustNotCreateSourceFile)
+ commonContent << &sourceContent;
+ for (QString *const content : std::as_const(commonContent)) {
+ content->append(fileSettings.licenseTemplate());
+ if (!content->isEmpty())
+ content->append('\n');
+ }
+ sourceContent.append('\n').append("#include \"").append(headerFileName).append("\"\n");
+ const QStringList namespaceNames
+ = Utils::transform<QStringList>(state->namespacePath, [&](const Namespace *ns) {
+ return ov.prettyName(ns->name());
+ });
+ const QString headerGuard = Utils::headerGuard(headerFileName, namespaceNames);
+ if (fileSettings.headerPragmaOnce) {
+ headerContent.append("#pragma once\n");
+ } else {
+ headerContent.append("#ifndef " + headerGuard + "\n");
+ headerContent.append("#define " + headerGuard + "\n");
+ }
+ if (!namespaceNames.isEmpty()) {
+ for (QString *const content : std::as_const(commonContent)) {
+ content->append('\n');
+ for (const QString &ns : namespaceNames)
+ content->append("namespace " + ns + " {\n");
+ }
+ }
+ bool hasSourceContent = false;
+ for (auto it = state->perFileState.begin(); it != state->perFileState.end(); ++it) {
+ if (it->declarationsToMove.isEmpty())
+ continue;
+ const CppRefactoringFilePtr refactoringFile = it->refactoringFile;
+ QTC_ASSERT(refactoringFile, continue);
+ const bool isDeclFile = refactoringFile->filePath() == state->originalFilePath;
+ ChangeSet changes;
+ if (isDeclFile) {
+ QString relInclude = headerFilePath.relativePathFrom(
+ refactoringFile->filePath().parentDir()).toString();
+ if (!relInclude.isEmpty())
+ relInclude.append('/');
+ relInclude.append('"').append(headerFileName).append('"');
+ insertNewIncludeDirective(relInclude, refactoringFile,
+ refactoringFile->cppDocument(), changes);
+ }
+ for (AST * const declToMove : std::as_const(it->declarationsToMove)) {
+ const ChangeSet::Range rangeToMove = refactoringFile->range(declToMove);
+ QString &content = isDeclFile || mustNotCreateSourceFile ? headerContent
+ : sourceContent;
+ if (&content == &sourceContent)
+ hasSourceContent = true;
+ content.append('\n')
+ .append(refactoringFile->textOf(rangeToMove))
+ .append('\n');
+ changes.remove(rangeToMove);
+ }
+ refactoringFile->apply(changes);
+ }
+
+ if (!namespaceNames.isEmpty()) {
+ for (QString *const content : std::as_const(commonContent)) {
+ content->append('\n');
+ for (auto it = namespaceNames.rbegin(); it != namespaceNames.rend(); ++it)
+ content->append("} // namespace " + *it + '\n');
+ }
+ }
+ if (!fileSettings.headerPragmaOnce)
+ headerContent.append("\n#endif // " + headerGuard + '\n');
+
+ headerFilePath.ensureExistingFile();
+ state->factory.cppFile(headerFilePath)->apply(ChangeSet::makeInsert(0, headerContent));
+ if (hasSourceContent || mustCreateSourceFile) {
+ sourceFilePath.ensureExistingFile();
+ state->factory.cppFile(sourceFilePath)->apply(ChangeSet::makeInsert(0, sourceContent));
+ }
+
+ if (!projectNode)
+ return;
+ FilePaths toAdd{headerFilePath};
+ if (hasSourceContent)
+ toAdd << sourceFilePath;
+ FilePaths notAdded;
+ projectNode->addFiles(toAdd, &notAdded);
+ if (!notAdded.isEmpty()) {
+ MessageManager::writeDisrupting(
+ Tr::tr("Failed to add to project file \"%1\": %2")
+ .arg(projectNode->filePath().toUserOutput(), fileListForDisplay(notAdded)));
+ }
+
+ if (state->interactive)
+ EditorManager::openEditor(headerFilePath);
+ }
+
+ const State::Ptr m_state;
+};
+
+//! Move a class into a dedicates set of files.
+class MoveClassToOwnFile : public CppQuickFixFactory
+{
+#ifdef WITH_TESTS
+public:
+ void setNonInteractive() { m_interactive = false; }
+ static QObject *createTest();
+#endif
+
+private:
+ // Applies if and only if:
+ // - Class is not a nested class.
+ // - Class name does not match file name via any of the usual transformations.
+ // - There are other declarations in the same file.
+ void doMatch(const CppQuickFixInterface &interface,
+ TextEditor::QuickFixOperations &result) override
+ {
+ ClassSpecifierAST * const classAst = astForClassOperations(interface);
+ if (!classAst || !classAst->symbol)
+ return;
+ AST *fullDecl = nullptr;
+ for (auto it = interface.path().rbegin(); it != interface.path().rend() && !fullDecl; ++it) {
+ if (*it == classAst && it != interface.path().rend() - 1) {
+ auto next = std::next(it);
+ fullDecl = (*next)->asSimpleDeclaration();
+ if (next != interface.path().rend() - 1) {
+ next = std::next(next);
+ if (const auto templ = (*next)->asTemplateDeclaration())
+ fullDecl = templ;
+ }
+ }
+ }
+ if (!fullDecl)
+ return;
+
+ // Check file name.
+ const QString className = Overview().prettyName(classAst->symbol->name());
+ if (className.isEmpty())
+ return;
+ const QString lowerFileBaseName = interface.filePath().baseName().toLower();
+ if (lowerFileBaseName.contains(className.toLower()))
+ return;
+ QString underscoredClassName = className;
+ QChar curChar = underscoredClassName.at(0);
+ for (int i = 1; i < underscoredClassName.size(); ++i) {
+ const QChar prevChar = curChar;
+ curChar = underscoredClassName.at(i);
+ if (curChar.isUpper() && prevChar.isLetterOrNumber() && !prevChar.isUpper()) {
+ underscoredClassName.insert(i, '_');
+ ++i;
+ }
+ }
+ if (lowerFileBaseName.contains(underscoredClassName.toLower()))
+ return;
+
+ // Is there more than one class definition in the file?
+ AST * const ast = interface.currentFile()->cppDocument()->translationUnit()->ast();
+ if (!ast)
+ return;
+ DeclarationListAST * const topLevelDecls = ast->asTranslationUnit()->declaration_list;
+ if (!topLevelDecls)
+ return;
+ QList<Namespace *> namespacePath;
+ QList<Namespace *> currentNamespacePath;
+ bool foundOtherDecls = false;
+ bool foundSelf = false;
+ std::function<void(Namespace *)> collectSymbolsFromNamespace;
+ const auto handleSymbol = [&](Symbol *symbol) {
+ if (!symbol)
+ return;
+ if (const auto nsMember = symbol->asNamespace()) {
+ collectSymbolsFromNamespace(nsMember);
+ return;
+ }
+ if (symbol != classAst->symbol) {
+ if (!symbol->asForwardClassDeclaration())
+ foundOtherDecls = true;
+ return;
+ }
+ QTC_ASSERT(symbol->asClass(), return);
+ foundSelf = true;
+ namespacePath = currentNamespacePath;
+ };
+ collectSymbolsFromNamespace = [&](Namespace *ns) {
+ currentNamespacePath << ns;
+ for (int i = 0; i < ns->memberCount() && (!foundSelf || !foundOtherDecls); ++i)
+ handleSymbol(ns->memberAt(i));
+ currentNamespacePath.removeLast();
+ };
+ for (DeclarationListAST *it = topLevelDecls; it && (!foundSelf || !foundOtherDecls);
+ it = it->next) {
+ DeclarationAST *decl = it->value;
+ if (!decl)
+ continue;
+ if (const auto templ = decl->asTemplateDeclaration())
+ decl = templ->declaration;
+ if (!decl)
+ continue;
+ if (const auto ns = decl->asNamespace(); ns && ns->symbol) {
+ collectSymbolsFromNamespace(ns->symbol);
+ continue;
+ }
+ if (const auto simpleDecl = decl->asSimpleDeclaration()) {
+ if (!simpleDecl->decl_specifier_list)
+ continue;
+ for (SpecifierListAST *spec = simpleDecl->decl_specifier_list; spec; spec = spec->next) {
+ if (!spec->value)
+ continue;
+ if (const auto klass = spec->value->asClassSpecifier())
+ handleSymbol(klass->symbol);
+ else if (!spec->value->asElaboratedTypeSpecifier()) // forward decl
+ foundOtherDecls = true;
+ }
+ } else if (decl->asDeclaration()) {
+ foundOtherDecls = true;
+ }
+ }
+
+ if (foundSelf && foundOtherDecls) {
+ result << new MoveClassToOwnFileOp(
+ interface, fullDecl, classAst, namespacePath, m_interactive);
+ }
+ }
+
+ bool m_interactive = true;
+};
+
+#ifdef WITH_TESTS
+class MoveClassToOwnFileTest : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void test_data()
+ {
+ QTest::addColumn<QString>("projectName");
+ QTest::addColumn<QString>("fileName");
+ QTest::addColumn<QString>("className");
+ QTest::addColumn<bool>("applicable");
+
+ QTest::newRow("nested") << "nested" << "main.cpp" << "Inner" << false;
+ QTest::newRow("file name match 1") << "match1" << "TheClass.h" << "TheClass" << false;
+ QTest::newRow("file name match 2") << "match2" << "theclass.h" << "TheClass" << false;
+ QTest::newRow("file name match 3") << "match3" << "the_class.h" << "TheClass" << false;
+ QTest::newRow("single") << "single" << "theheader.h" << "TheClass" << false;
+ QTest::newRow("complex") << "complex" << "theheader.h" << "TheClass" << true;
+ QTest::newRow("header only") << "header-only" << "theheader.h" << "TheClass" << true;
+ QTest::newRow("decl in source file") << "decl-in-source" << "thesource.cpp" << "TheClass" << true;
+ QTest::newRow("template") << "template" << "theheader.h" << "TheClass" << true;
+ }
+
+ void test()
+ {
+ QFETCH(QString, projectName);
+ QFETCH(QString, fileName);
+ QFETCH(QString, className);
+ QFETCH(bool, applicable);
+ using namespace CppEditor::Tests;
+ using namespace TextEditor;
+
+ // Set up project.
+ Kit * const kit = Utils::findOr(KitManager::kits(), nullptr, [](const Kit *k) {
+ return k->isValid() && !k->hasWarning() && k->value("QtSupport.QtInformation").isValid();
+ });
+ if (!kit)
+ QSKIP("The test requires at least one valid kit with a valid Qt");
+ const auto projectDir = std::make_unique<TemporaryCopiedDir>(
+ ":/cppeditor/testcases/move-class/" + projectName);
+ SourceFilesRefreshGuard refreshGuard;
+ ProjectOpenerAndCloser projectMgr;
+ QVERIFY(projectMgr.open(projectDir->absolutePath(projectName + ".pro"), true, kit));
+ QVERIFY(refreshGuard.wait());
+
+ // Open header file and locate class.
+ const auto headerFilePath = projectDir->absolutePath(fileName);
+ QVERIFY2(headerFilePath.exists(), qPrintable(headerFilePath.toUserOutput()));
+ const auto editor = qobject_cast<BaseTextEditor *>(EditorManager::openEditor(headerFilePath));
+ QVERIFY(editor);
+ const auto doc = qobject_cast<TextEditor::TextDocument *>(editor->document());
+ QVERIFY(doc);
+ QTextCursor classCursor = doc->document()->find("class " + className);
+ QVERIFY(!classCursor.isNull());
+ editor->setCursorPosition(classCursor.position());
+ const auto editorWidget = qobject_cast<CppEditorWidget *>(editor->editorWidget());
+ QVERIFY(editorWidget);
+ QVERIFY(TestCase::waitForRehighlightedSemanticDocument(editorWidget));
+
+ // Query factory.
+ MoveClassToOwnFile factory;
+ factory.setNonInteractive();
+ CppQuickFixInterface quickFixInterface(editorWidget, ExplicitlyInvoked);
+ QuickFixOperations operations;
+ factory.match(quickFixInterface, operations);
+ QCOMPARE(operations.isEmpty(), !applicable);
+ if (!applicable)
+ return;
+ operations.first()->perform();
+ QVERIFY(waitForSignalOrTimeout(doc, &IDocument::saved, 30000));
+ QTest::qWait(1000);
+
+ // Compare all files.
+ const FileFilter filter({"*_expected"}, QDir::Files);
+ const FilePaths expectedDocuments = projectDir->filePath().dirEntries(filter);
+ QVERIFY(!expectedDocuments.isEmpty());
+ for (const FilePath &expected : expectedDocuments) {
+ static const QString suffix = "_expected";
+ const FilePath actual = expected.parentDir()
+ .pathAppended(expected.fileName().chopped(suffix.length()));
+ QVERIFY(actual.exists());
+ const auto actualContents = actual.fileContents();
+ QVERIFY(actualContents);
+ const auto expectedContents = expected.fileContents();
+ const QByteArrayList actualLines = actualContents->split('\n');
+ const QByteArrayList expectedLines = expectedContents->split('\n');
+ if (actualLines.size() != expectedLines.size()) {
+ qDebug().noquote().nospace() << "---\n" << *expectedContents << "EOF";
+ qDebug().noquote().nospace() << "+++\n" << *actualContents << "EOF";
+ }
+ QCOMPARE(actualLines.size(), expectedLines.size());
+ for (int i = 0; i < actualLines.size(); ++i) {
+ const QByteArray actualLine = actualLines.at(i);
+ const QByteArray expectedLine = expectedLines.at(i);
+ if (actualLine != expectedLine)
+ qDebug() << "Unexpected content in line" << (i + 1) << "of file"
+ << actual.fileName();
+ QCOMPARE(actualLine, expectedLine);
+ }
+ }
+ }
+};
+
+QObject *MoveClassToOwnFile::createTest()
+{
+ return new MoveClassToOwnFileTest;
+}
+
+#endif // WITH_TESTS
+
+} // namespace
+
+void registerMoveClassToOwnFileQuickfix()
+{
+ CppQuickFixFactory::registerFactory<MoveClassToOwnFile>();
+}
+
+} // namespace CppEditor::Internal
+
+#ifdef WITH_TESTS
+#include <moveclasstoownfile.moc>
+#endif
diff --git a/src/plugins/cppeditor/quickfixes/moveclasstoownfile.h b/src/plugins/cppeditor/quickfixes/moveclasstoownfile.h
new file mode 100644
index 0000000000..c46b0e5c3a
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/moveclasstoownfile.h
@@ -0,0 +1,8 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+namespace CppEditor::Internal {
+void registerMoveClassToOwnFileQuickfix();
+} // namespace CppEditor::Internal
diff --git a/src/plugins/cppeditor/quickfixes/movefunctiondefinition.cpp b/src/plugins/cppeditor/quickfixes/movefunctiondefinition.cpp
new file mode 100644
index 0000000000..e6ec6a8b9f
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/movefunctiondefinition.cpp
@@ -0,0 +1,1889 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "movefunctiondefinition.h"
+
+#include "../cppcodestylesettings.h"
+#include "../cppeditortr.h"
+#include "../cpprefactoringchanges.h"
+#include "../insertionpointlocator.h"
+#include "../symbolfinder.h"
+#include "cppquickfix.h"
+#include "cppquickfixhelpers.h"
+
+#include <cplusplus/ASTPath.h>
+#include <cplusplus/CppRewriter.h>
+#include <cplusplus/Overview.h>
+
+using namespace CPlusPlus;
+using namespace TextEditor;
+using namespace Utils;
+
+#ifdef WITH_TESTS
+#include "cppquickfix_test.h"
+#include <QtTest>
+#endif
+
+namespace CppEditor::Internal {
+namespace {
+
+static QString definitionSignature(
+ const CppQuickFixInterface *assist,
+ FunctionDefinitionAST *functionDefinitionAST,
+ CppRefactoringFilePtr &baseFile,
+ CppRefactoringFilePtr &targetFile,
+ Scope *scope)
+{
+ QTC_ASSERT(assist, return QString());
+ QTC_ASSERT(functionDefinitionAST, return QString());
+ QTC_ASSERT(scope, return QString());
+ Function *func = functionDefinitionAST->symbol;
+ QTC_ASSERT(func, return QString());
+
+ LookupContext cppContext(targetFile->cppDocument(), assist->snapshot());
+ ClassOrNamespace *cppCoN = cppContext.lookupType(scope);
+ if (!cppCoN)
+ cppCoN = cppContext.globalNamespace();
+ SubstitutionEnvironment env;
+ env.setContext(assist->context());
+ env.switchScope(func->enclosingScope());
+ UseMinimalNames q(cppCoN);
+ env.enter(&q);
+ Control *control = assist->context().bindings()->control().get();
+ Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview();
+ oo.showFunctionSignatures = true;
+ oo.showReturnTypes = true;
+ oo.showArgumentNames = true;
+ oo.showEnclosingTemplate = true;
+ oo.showTemplateParameters = true;
+ oo.trailingReturnType = functionDefinitionAST->declarator
+ && functionDefinitionAST->declarator->postfix_declarator_list
+ && functionDefinitionAST->declarator->postfix_declarator_list->value
+ && functionDefinitionAST->declarator->postfix_declarator_list
+ ->value->asFunctionDeclarator()
+ && functionDefinitionAST->declarator->postfix_declarator_list
+ ->value->asFunctionDeclarator()->trailing_return_type;
+ const Name *name = func->name();
+ if (name && nameIncludesOperatorName(name)) {
+ CoreDeclaratorAST *coreDeclarator = functionDefinitionAST->declarator->core_declarator;
+ const QString operatorNameText = baseFile->textOf(coreDeclarator);
+ oo.includeWhiteSpaceInOperatorName = operatorNameText.contains(QLatin1Char(' '));
+ }
+ const QString nameText = oo.prettyName(LookupContext::minimalName(func, cppCoN, control));
+ oo.showTemplateParameters = false;
+ const FullySpecifiedType tn = rewriteType(func->type(), &env, control);
+
+ return oo.prettyType(tn, nameText);
+}
+
+class MoveFuncDefRefactoringHelper
+{
+public:
+ enum MoveType {
+ MoveOutside,
+ MoveToCppFile,
+ MoveOutsideMemberToCppFile
+ };
+
+ MoveFuncDefRefactoringHelper(CppQuickFixOperation *operation, MoveType type,
+ const FilePath &toFile)
+ : m_operation(operation), m_type(type), m_changes(m_operation->snapshot())
+ {
+ m_fromFile = operation->currentFile();
+ m_toFile = (m_type == MoveOutside) ? m_fromFile : m_changes.cppFile(toFile);
+ }
+
+ void performMove(FunctionDefinitionAST *funcAST)
+ {
+ // Determine file, insert position and scope
+ InsertionLocation l = insertLocationForMethodDefinition(
+ funcAST->symbol, false, NamespaceHandling::Ignore,
+ m_changes, m_toFile->filePath());
+ const QString prefix = l.prefix();
+ const QString suffix = l.suffix();
+ const int insertPos = m_toFile->position(l.line(), l.column());
+ Scope *scopeAtInsertPos = m_toFile->cppDocument()->scopeAt(l.line(), l.column());
+
+ // construct definition
+ const QString funcDec = inlinePrefix(m_toFile->filePath(), [this] { return m_type == MoveOutside; })
+ + definitionSignature(m_operation, funcAST, m_fromFile, m_toFile,
+ scopeAtInsertPos);
+ QString funcDef = prefix + funcDec;
+ const int startPosition = m_fromFile->endOf(funcAST->declarator);
+ const int endPosition = m_fromFile->endOf(funcAST);
+ funcDef += m_fromFile->textOf(startPosition, endPosition);
+ funcDef += suffix;
+
+ // insert definition at new position
+ m_toFileChangeSet.insert(insertPos, funcDef);
+ m_toFile->setOpenEditor(true, insertPos);
+
+ // remove definition from fromFile
+ if (m_type == MoveOutsideMemberToCppFile) {
+ m_fromFileChangeSet.remove(m_fromFile->range(funcAST));
+ } else {
+ QString textFuncDecl = m_fromFile->textOf(funcAST);
+ textFuncDecl.truncate(startPosition - m_fromFile->startOf(funcAST));
+ if (textFuncDecl.left(7) == QLatin1String("inline "))
+ textFuncDecl = textFuncDecl.mid(7);
+ else
+ textFuncDecl.replace(" inline ", QLatin1String(" "));
+ textFuncDecl = textFuncDecl.trimmed() + QLatin1Char(';');
+ m_fromFileChangeSet.replace(m_fromFile->range(funcAST), textFuncDecl);
+ }
+ }
+
+ void applyChanges()
+ {
+ m_toFile->apply(m_toFileChangeSet);
+ m_fromFile->apply(m_fromFileChangeSet);
+ }
+
+private:
+ CppQuickFixOperation *m_operation;
+ MoveType m_type;
+ CppRefactoringChanges m_changes;
+ CppRefactoringFilePtr m_fromFile;
+ CppRefactoringFilePtr m_toFile;
+ ChangeSet m_fromFileChangeSet;
+ ChangeSet m_toFileChangeSet;
+};
+
+class MoveFuncDefOutsideOp : public CppQuickFixOperation
+{
+public:
+ MoveFuncDefOutsideOp(const CppQuickFixInterface &interface,
+ MoveFuncDefRefactoringHelper::MoveType type,
+ FunctionDefinitionAST *funcDef, const FilePath &cppFilePath)
+ : CppQuickFixOperation(interface, 0)
+ , m_funcDef(funcDef)
+ , m_type(type)
+ , m_cppFilePath(cppFilePath)
+ {
+ if (m_type == MoveFuncDefRefactoringHelper::MoveOutside) {
+ setDescription(Tr::tr("Move Definition Outside Class"));
+ } else {
+ const FilePath resolved = m_cppFilePath.relativePathFrom(filePath().parentDir());
+ setDescription(Tr::tr("Move Definition to %1").arg(resolved.displayName()));
+ }
+ }
+
+ void perform() override
+ {
+ MoveFuncDefRefactoringHelper helper(this, m_type, m_cppFilePath);
+ helper.performMove(m_funcDef);
+ helper.applyChanges();
+ }
+
+private:
+ FunctionDefinitionAST *m_funcDef;
+ MoveFuncDefRefactoringHelper::MoveType m_type;
+ const FilePath m_cppFilePath;
+};
+
+class MoveAllFuncDefOutsideOp : public CppQuickFixOperation
+{
+public:
+ MoveAllFuncDefOutsideOp(const CppQuickFixInterface &interface,
+ MoveFuncDefRefactoringHelper::MoveType type,
+ ClassSpecifierAST *classDef, const FilePath &cppFileName)
+ : CppQuickFixOperation(interface, 0)
+ , m_type(type)
+ , m_classDef(classDef)
+ , m_cppFilePath(cppFileName)
+ {
+ if (m_type == MoveFuncDefRefactoringHelper::MoveOutside) {
+ setDescription(Tr::tr("Definitions Outside Class"));
+ } else {
+ const FilePath resolved = m_cppFilePath.relativePathFrom(filePath().parentDir());
+ setDescription(Tr::tr("Move All Function Definitions to %1")
+ .arg(resolved.displayName()));
+ }
+ }
+
+ void perform() override
+ {
+ MoveFuncDefRefactoringHelper helper(this, m_type, m_cppFilePath);
+ for (DeclarationListAST *it = m_classDef->member_specifier_list; it; it = it->next) {
+ if (FunctionDefinitionAST *funcAST = it->value->asFunctionDefinition()) {
+ if (funcAST->symbol && !funcAST->symbol->isGenerated())
+ helper.performMove(funcAST);
+ }
+ }
+ helper.applyChanges();
+ }
+
+private:
+ MoveFuncDefRefactoringHelper::MoveType m_type;
+ ClassSpecifierAST *m_classDef;
+ const FilePath m_cppFilePath;
+};
+
+class MoveFuncDefToDeclOp : public CppQuickFixOperation
+{
+public:
+ enum Type { Push, Pull };
+ MoveFuncDefToDeclOp(const CppQuickFixInterface &interface,
+ const FilePath &fromFilePath, const FilePath &toFilePath,
+ FunctionDefinitionAST *funcAst, Function *func, const QString &declText,
+ const ChangeSet::Range &fromRange,
+ const ChangeSet::Range &toRange,
+ Type type)
+ : CppQuickFixOperation(interface, 0)
+ , m_fromFilePath(fromFilePath)
+ , m_toFilePath(toFilePath)
+ , m_funcAST(funcAst)
+ , m_func(func)
+ , m_declarationText(declText)
+ , m_fromRange(fromRange)
+ , m_toRange(toRange)
+ {
+ if (type == Type::Pull) {
+ setDescription(Tr::tr("Move Definition Here"));
+ } else if (m_toFilePath == m_fromFilePath) {
+ setDescription(Tr::tr("Move Definition to Class"));
+ } else {
+ const FilePath resolved = m_toFilePath.relativePathFrom(m_fromFilePath.parentDir());
+ setDescription(Tr::tr("Move Definition to %1").arg(resolved.displayName()));
+ }
+ }
+
+private:
+ void perform() override
+ {
+ CppRefactoringChanges refactoring(snapshot());
+ CppRefactoringFilePtr fromFile = refactoring.cppFile(m_fromFilePath);
+ CppRefactoringFilePtr toFile = refactoring.cppFile(m_toFilePath);
+
+ ensureFuncDefAstAndRange(*fromFile);
+ if (!m_funcAST)
+ return;
+
+ const QString wholeFunctionText = m_declarationText
+ + fromFile->textOf(fromFile->endOf(m_funcAST->declarator),
+ fromFile->endOf(m_funcAST->function_body));
+
+ // Replace declaration with function and delete old definition
+ ChangeSet toTarget;
+ toTarget.replace(m_toRange, wholeFunctionText);
+ if (m_toFilePath == m_fromFilePath)
+ toTarget.remove(m_fromRange);
+ toFile->setOpenEditor(true, m_toRange.start);
+ toFile->apply(toTarget);
+ if (m_toFilePath != m_fromFilePath)
+ fromFile->apply(ChangeSet::makeRemove(m_fromRange));
+ }
+
+ void ensureFuncDefAstAndRange(CppRefactoringFile &defFile)
+ {
+ if (m_funcAST) {
+ QTC_CHECK(m_fromRange.end > m_fromRange.start);
+ return;
+ }
+ QTC_ASSERT(m_func, return);
+ const QList<AST *> astPath = ASTPath(defFile.cppDocument())(m_func->line(),
+ m_func->column());
+ if (astPath.isEmpty())
+ return;
+ for (auto it = std::rbegin(astPath); it != std::rend(astPath); ++it) {
+ m_funcAST = (*it)->asFunctionDefinition();
+ if (!m_funcAST)
+ continue;
+ AST *astForRange = m_funcAST;
+ const auto prev = std::next(it);
+ if (prev != std::rend(astPath)) {
+ if (const auto templAst = (*prev)->asTemplateDeclaration())
+ astForRange = templAst;
+ }
+ m_fromRange = defFile.range(astForRange);
+ return;
+ }
+ }
+
+ const FilePath m_fromFilePath;
+ const FilePath m_toFilePath;
+ FunctionDefinitionAST *m_funcAST;
+ Function *m_func;
+ const QString m_declarationText;
+ ChangeSet::Range m_fromRange;
+ const ChangeSet::Range m_toRange;
+};
+
+/*!
+ Moves the definition of a member function outside the class or moves the definition of a member
+ function or a normal function to the implementation file.
+ */
+class MoveFuncDefOutside : public CppQuickFixFactory
+{
+public:
+#ifdef WITH_TESTS
+ static QObject *createTest();
+#endif
+
+private:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ const QList<AST *> &path = interface.path();
+ SimpleDeclarationAST *classAST = nullptr;
+ FunctionDefinitionAST *funcAST = nullptr;
+ bool moveOutsideMemberDefinition = false;
+
+ const int pathSize = path.size();
+ for (int idx = 1; idx < pathSize; ++idx) {
+ if ((funcAST = path.at(idx)->asFunctionDefinition())) {
+ // check cursor position
+ if (idx != pathSize - 1 // Do not allow "void a() @ {..."
+ && funcAST->function_body
+ && !interface.isCursorOn(funcAST->function_body)) {
+ if (path.at(idx - 1)->asTranslationUnit()) { // normal function
+ if (idx + 3 < pathSize && path.at(idx + 3)->asQualifiedName()) // Outside member
+ moveOutsideMemberDefinition = true; // definition
+ break;
+ }
+
+ if (idx > 1) {
+ if ((classAST = path.at(idx - 2)->asSimpleDeclaration())) // member function
+ break;
+ if (path.at(idx - 2)->asNamespace()) // normal function in namespace
+ break;
+ }
+ if (idx > 2 && path.at(idx - 1)->asTemplateDeclaration()) {
+ if ((classAST = path.at(idx - 3)->asSimpleDeclaration())) // member template
+ break;
+ }
+ }
+ funcAST = nullptr;
+ }
+ }
+
+ if (!funcAST || !funcAST->symbol)
+ return;
+
+ bool isHeaderFile = false;
+ const FilePath cppFileName = correspondingHeaderOrSource(interface.filePath(), &isHeaderFile);
+
+ if (isHeaderFile && !cppFileName.isEmpty()) {
+ const MoveFuncDefRefactoringHelper::MoveType type = moveOutsideMemberDefinition
+ ? MoveFuncDefRefactoringHelper::MoveOutsideMemberToCppFile
+ : MoveFuncDefRefactoringHelper::MoveToCppFile;
+ result << new MoveFuncDefOutsideOp(interface, type, funcAST, cppFileName);
+ }
+
+ if (classAST)
+ result << new MoveFuncDefOutsideOp(interface, MoveFuncDefRefactoringHelper::MoveOutside,
+ funcAST, FilePath());
+
+ return;
+ }
+};
+
+//! Moves all member function definitions outside the class or to the implementation file.
+class MoveAllFuncDefOutside : public CppQuickFixFactory
+{
+public:
+#ifdef WITH_TESTS
+ static QObject *createTest();
+#endif
+
+private:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ ClassSpecifierAST * const classAST = astForClassOperations(interface);
+ if (!classAST)
+ return;
+
+ // Determine if the class has at least one function definition
+ bool classContainsFunctions = false;
+ for (DeclarationListAST *it = classAST->member_specifier_list; it; it = it->next) {
+ if (FunctionDefinitionAST *funcAST = it->value->asFunctionDefinition()) {
+ if (funcAST->symbol && !funcAST->symbol->isGenerated()) {
+ classContainsFunctions = true;
+ break;
+ }
+ }
+ }
+ if (!classContainsFunctions)
+ return;
+
+ bool isHeaderFile = false;
+ const FilePath cppFileName = correspondingHeaderOrSource(interface.filePath(), &isHeaderFile);
+ if (isHeaderFile && !cppFileName.isEmpty()) {
+ result << new MoveAllFuncDefOutsideOp(interface,
+ MoveFuncDefRefactoringHelper::MoveToCppFile,
+ classAST, cppFileName);
+ }
+ result << new MoveAllFuncDefOutsideOp(interface, MoveFuncDefRefactoringHelper::MoveOutside,
+ classAST, FilePath());
+ }
+};
+
+//! Moves the definition of a function to its declaration, with the cursor on the definition.
+class MoveFuncDefToDeclPush : public CppQuickFixFactory
+{
+public:
+#ifdef WITH_TESTS
+ static QObject *createTest();
+#endif
+
+private:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ const QList<AST *> &path = interface.path();
+ AST *completeDefAST = nullptr;
+ FunctionDefinitionAST *funcAST = nullptr;
+
+ const int pathSize = path.size();
+ for (int idx = 1; idx < pathSize; ++idx) {
+ if ((funcAST = path.at(idx)->asFunctionDefinition())) {
+ AST *enclosingAST = path.at(idx - 1);
+ if (enclosingAST->asClassSpecifier())
+ return;
+
+ // check cursor position
+ if (idx != pathSize - 1 // Do not allow "void a() @ {..."
+ && funcAST->function_body
+ && !interface.isCursorOn(funcAST->function_body)) {
+ completeDefAST = enclosingAST->asTemplateDeclaration() ? enclosingAST : funcAST;
+ break;
+ }
+ funcAST = nullptr;
+ }
+ }
+
+ if (!funcAST || !funcAST->symbol)
+ return;
+
+ const CppRefactoringChanges refactoring(interface.snapshot());
+ const CppRefactoringFilePtr defFile = interface.currentFile();
+ const ChangeSet::Range defRange = defFile->range(completeDefAST);
+
+ // Determine declaration (file, range, text);
+ ChangeSet::Range declRange;
+ QString declText;
+ FilePath declFilePath;
+
+ Function *func = funcAST->symbol;
+ if (Class *matchingClass = isMemberFunction(interface.context(), func)) {
+ // Dealing with member functions
+ const QualifiedNameId *qName = func->name()->asQualifiedNameId();
+ for (Symbol *symbol = matchingClass->find(qName->identifier());
+ symbol; symbol = symbol->next()) {
+ Symbol *s = symbol;
+ if (func->enclosingScope()->asTemplate()) {
+ if (const Template *templ = s->type()->asTemplateType()) {
+ if (Symbol *decl = templ->declaration()) {
+ if (decl->type()->asFunctionType())
+ s = decl;
+ }
+ }
+ }
+ if (!s->name()
+ || !qName->identifier()->match(s->identifier())
+ || !s->type()->asFunctionType()
+ || !s->type().match(func->type())
+ || s->asFunction()) {
+ continue;
+ }
+
+ declFilePath = matchingClass->filePath();
+ const CppRefactoringFilePtr declFile = refactoring.cppFile(declFilePath);
+ ASTPath astPath(declFile->cppDocument());
+ const QList<AST *> path = astPath(s->line(), s->column());
+ for (int idx = path.size() - 1; idx > 0; --idx) {
+ AST *node = path.at(idx);
+ if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) {
+ if (simpleDecl->symbols && !simpleDecl->symbols->next) {
+ declRange = declFile->range(simpleDecl);
+ declText = declFile->textOf(simpleDecl);
+ declText.remove(-1, 1); // remove ';' from declaration text
+ break;
+ }
+ }
+ }
+
+ if (!declText.isEmpty())
+ break;
+ }
+ } else if (Namespace *matchingNamespace = isNamespaceFunction(interface.context(), func)) {
+ // Dealing with free functions
+ bool isHeaderFile = false;
+ declFilePath = correspondingHeaderOrSource(interface.filePath(), &isHeaderFile);
+ if (isHeaderFile)
+ return;
+
+ const CppRefactoringFilePtr declFile = refactoring.cppFile(declFilePath);
+ const LookupContext lc(declFile->cppDocument(), interface.snapshot());
+ const QList<LookupItem> candidates = lc.lookup(func->name(), matchingNamespace);
+ for (const LookupItem &candidate : candidates) {
+ if (Symbol *s = candidate.declaration()) {
+ if (s->asDeclaration()) {
+ ASTPath astPath(declFile->cppDocument());
+ const QList<AST *> path = astPath(s->line(), s->column());
+ for (AST *node : path) {
+ if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) {
+ declRange = declFile->range(simpleDecl);
+ declText = declFile->textOf(simpleDecl);
+ declText.remove(-1, 1); // remove ';' from declaration text
+ break;
+ }
+ }
+ }
+ }
+
+ if (!declText.isEmpty()) {
+ declText.prepend(inlinePrefix(declFilePath));
+ break;
+ }
+ }
+ }
+
+ if (!declFilePath.isEmpty() && !declText.isEmpty())
+ result << new MoveFuncDefToDeclOp(interface,
+ interface.filePath(),
+ declFilePath,
+ funcAST, func, declText,
+ defRange, declRange, MoveFuncDefToDeclOp::Push);
+ }
+};
+
+//! Moves the definition of a function to its declaration, with the cursor on the declaration.
+class MoveFuncDefToDeclPull : public CppQuickFixFactory
+{
+public:
+#ifdef WITH_TESTS
+ static QObject *createTest();
+#endif
+
+private:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ const QList<AST *> &path = interface.path();
+ for (auto it = std::rbegin(path); it != std::rend(path); ++it) {
+ SimpleDeclarationAST * const simpleDecl = (*it)->asSimpleDeclaration();
+ if (!simpleDecl)
+ continue;
+ const auto prev = std::next(it);
+ if (prev != std::rend(path) && (*prev)->asStatement())
+ return;
+ if (!simpleDecl->symbols || !simpleDecl->symbols->value || simpleDecl->symbols->next)
+ return;
+ Declaration * const decl = simpleDecl->symbols->value->asDeclaration();
+ if (!decl)
+ return;
+ Function * const funcDecl = decl->type()->asFunctionType();
+ if (!funcDecl)
+ return;
+ if (funcDecl->isSignal() || funcDecl->isPureVirtual() || funcDecl->isFriend())
+ return;
+
+ // Is there a definition?
+ SymbolFinder symbolFinder;
+ Function * const funcDef = symbolFinder.findMatchingDefinition(decl, interface.snapshot(),
+ true);
+ if (!funcDef)
+ return;
+
+ QString declText = interface.currentFile()->textOf(simpleDecl);
+ declText.chop(1); // semicolon
+ declText.prepend(inlinePrefix(interface.filePath(), [funcDecl] {
+ return !funcDecl->enclosingScope()->asClass();
+ }));
+ result << new MoveFuncDefToDeclOp(interface, funcDef->filePath(), decl->filePath(), nullptr,
+ funcDef, declText, {},
+ interface.currentFile()->range(simpleDecl),
+ MoveFuncDefToDeclOp::Pull);
+ return;
+ }
+ }
+};
+
+#ifdef WITH_TESTS
+using namespace Tests;
+
+class MoveFuncDefOutsideTest : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ /// Check: Move definition from header to cpp.
+ void testMemberFuncToCpp()
+ {
+ QList<TestDocumentPtr> testDocuments;
+ QByteArray original;
+ QByteArray expected;
+
+ // Header File
+ original =
+ "class Foo {\n"
+ " inline int numbe@r() const\n"
+ " {\n"
+ " return 5;\n"
+ " }\n"
+ "\n"
+ " void bar();\n"
+ "};\n";
+ expected =
+ "class Foo {\n"
+ " int number() const;\n"
+ "\n"
+ " void bar();\n"
+ "};\n";
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File
+ original =
+ "#include \"file.h\"\n";
+ expected =
+ "#include \"file.h\"\n"
+ "\n"
+ "int Foo::number() const\n"
+ "{\n"
+ " return 5;\n"
+ "}\n"
+ ;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+ MoveFuncDefOutside factory;
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+
+ void testMemberFuncToCppInsideNS()
+ {
+ QList<TestDocumentPtr> testDocuments;
+ QByteArray original;
+ QByteArray expected;
+
+ // Header File
+ original =
+ "namespace SomeNamespace {\n"
+ "class Foo {\n"
+ " int ba@r()\n"
+ " {\n"
+ " return 5;\n"
+ " }\n"
+ "};\n"
+ "}\n";
+ expected =
+ "namespace SomeNamespace {\n"
+ "class Foo {\n"
+ " int ba@r();\n"
+ "};\n"
+ "}\n";
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File
+ original =
+ "#include \"file.h\"\n"
+ "namespace SomeNamespace {\n"
+ "\n"
+ "}\n";
+ expected =
+ "#include \"file.h\"\n"
+ "namespace SomeNamespace {\n"
+ "\n"
+ "int Foo::bar()\n"
+ "{\n"
+ " return 5;\n"
+ "}\n"
+ "\n"
+ "}\n";
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+ MoveFuncDefOutside factory;
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+
+ /// Check: Move definition outside class
+ void testMemberFuncOutside1()
+ {
+ QByteArray original =
+ "class Foo {\n"
+ " void f1();\n"
+ " inline int f2@() const\n"
+ " {\n"
+ " return 1;\n"
+ " }\n"
+ " void f3();\n"
+ " void f4();\n"
+ "};\n"
+ "\n"
+ "void Foo::f4() {}\n";
+ QByteArray expected =
+ "class Foo {\n"
+ " void f1();\n"
+ " int f2@() const;\n"
+ " void f3();\n"
+ " void f4();\n"
+ "};\n"
+ "\n"
+ "int Foo::f2() const\n"
+ "{\n"
+ " return 1;\n"
+ "}\n"
+ "\n"
+ "void Foo::f4() {}\n";
+
+ MoveFuncDefOutside factory;
+ QuickFixOperationTest(singleDocument(original, expected), &factory);
+ }
+
+ /// Check: Move definition outside class
+ void testMemberFuncOutside2()
+ {
+ QList<TestDocumentPtr> testDocuments;
+ QByteArray original;
+ QByteArray expected;
+
+ // Header File
+ original =
+ "class Foo {\n"
+ " void f1();\n"
+ " int f2@()\n"
+ " {\n"
+ " return 1;\n"
+ " }\n"
+ " void f3();\n"
+ "};\n";
+ expected =
+ "class Foo {\n"
+ " void f1();\n"
+ " int f2();\n"
+ " void f3();\n"
+ "};\n"
+ "\n"
+ "inline int Foo::f2()\n"
+ "{\n"
+ " return 1;\n"
+ "}\n";
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File
+ original =
+ "#include \"file.h\"\n"
+ "void Foo::f1() {}\n"
+ "void Foo::f3() {}\n";
+ expected = original;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+ MoveFuncDefOutside factory;
+ QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 1);
+ }
+
+ /// Check: Move definition from header to cpp (with namespace).
+ void testMemberFuncToCppNS()
+ {
+ QList<TestDocumentPtr> testDocuments;
+ QByteArray original;
+ QByteArray expected;
+
+ // Header File
+ original =
+ "namespace MyNs {\n"
+ "class Foo {\n"
+ " inline int numbe@r() const\n"
+ " {\n"
+ " return 5;\n"
+ " }\n"
+ "};\n"
+ "}\n";
+ expected =
+ "namespace MyNs {\n"
+ "class Foo {\n"
+ " int number() const;\n"
+ "};\n"
+ "}\n";
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File
+ original =
+ "#include \"file.h\"\n";
+ expected =
+ "#include \"file.h\"\n"
+ "\n"
+ "int MyNs::Foo::number() const\n"
+ "{\n"
+ " return 5;\n"
+ "}\n";
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+ MoveFuncDefOutside factory;
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+
+ /// Check: Move definition from header to cpp (with namespace + using).
+ void testMemberFuncToCppNSUsing()
+ {
+ QList<TestDocumentPtr> testDocuments;
+ QByteArray original;
+ QByteArray expected;
+
+ // Header File
+ original =
+ "namespace MyNs {\n"
+ "class Foo {\n"
+ " inline int numbe@r() const\n"
+ " {\n"
+ " return 5;\n"
+ " }\n"
+ "};\n"
+ "}\n";
+ expected =
+ "namespace MyNs {\n"
+ "class Foo {\n"
+ " int number() const;\n"
+ "};\n"
+ "}\n";
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File
+ original =
+ "#include \"file.h\"\n"
+ "using namespace MyNs;\n";
+ expected =
+ "#include \"file.h\"\n"
+ "using namespace MyNs;\n"
+ "\n"
+ "int Foo::number() const\n"
+ "{\n"
+ " return 5;\n"
+ "}\n";
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+ MoveFuncDefOutside factory;
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+
+ /// Check: Move definition outside class with Namespace
+ void testMemberFuncOutsideWithNs()
+ {
+ QByteArray original =
+ "namespace MyNs {\n"
+ "class Foo {\n"
+ " inline int numbe@r() const\n"
+ " {\n"
+ " return 5;\n"
+ " }\n"
+ "};}\n";
+ QByteArray expected =
+ "namespace MyNs {\n"
+ "class Foo {\n"
+ " int number() const;\n"
+ "};\n"
+ "\n"
+ "int Foo::number() const\n"
+ "{\n"
+ " return 5;\n"
+ "}\n"
+ "\n}\n";
+
+ MoveFuncDefOutside factory;
+ QuickFixOperationTest(singleDocument(original, expected), &factory);
+ }
+
+ /// Check: Move free function from header to cpp.
+ void testFreeFuncToCpp()
+ {
+ QList<TestDocumentPtr> testDocuments;
+ QByteArray original;
+ QByteArray expected;
+
+ // Header File
+ original =
+ "int numbe@r() const\n"
+ "{\n"
+ " return 5;\n"
+ "}\n";
+ expected =
+ "int number() const;\n"
+ ;
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File
+ original =
+ "#include \"file.h\"\n";
+ expected =
+ "#include \"file.h\"\n"
+ "\n"
+ "int number() const\n"
+ "{\n"
+ " return 5;\n"
+ "}\n";
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+ MoveFuncDefOutside factory;
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+
+ /// Check: Move free function from header to cpp (with namespace).
+ void testFreeFuncToCppNS()
+ {
+ QList<TestDocumentPtr> testDocuments;
+ QByteArray original;
+ QByteArray expected;
+
+ // Header File
+ original =
+ "namespace MyNamespace {\n"
+ "int numbe@r() const\n"
+ "{\n"
+ " return 5;\n"
+ "}\n"
+ "}\n";
+ expected =
+ "namespace MyNamespace {\n"
+ "int number() const;\n"
+ "}\n";
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File
+ original =
+ "#include \"file.h\"\n";
+ expected =
+ "#include \"file.h\"\n"
+ "\n"
+ "int MyNamespace::number() const\n"
+ "{\n"
+ " return 5;\n"
+ "}\n";
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+ MoveFuncDefOutside factory;
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+
+ /// Check: Move Ctor with member initialization list (QTCREATORBUG-9157).
+ void testCtorWithInitialization1()
+ {
+ QList<TestDocumentPtr> testDocuments;
+ QByteArray original;
+ QByteArray expected;
+
+ // Header File
+ original =
+ "class Foo {\n"
+ "public:\n"
+ " Fo@o() : a(42), b(3.141) {}\n"
+ "private:\n"
+ " int a;\n"
+ " float b;\n"
+ "};\n";
+ expected =
+ "class Foo {\n"
+ "public:\n"
+ " Foo();\n"
+ "private:\n"
+ " int a;\n"
+ " float b;\n"
+ "};\n";
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File
+ original ="#include \"file.h\"\n";
+ expected =
+ "#include \"file.h\"\n"
+ "\n"
+ "Foo::Foo() : a(42), b(3.141) {}\n"
+ ;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+ MoveFuncDefOutside factory;
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+
+ /// Check: Move Ctor with member initialization list (QTCREATORBUG-9462).
+ void testCtorWithInitialization2()
+ {
+ QList<TestDocumentPtr> testDocuments;
+ QByteArray original;
+ QByteArray expected;
+
+ // Header File
+ original =
+ "class Foo\n"
+ "{\n"
+ "public:\n"
+ " Fo@o() : member(2)\n"
+ " {\n"
+ " }\n"
+ "\n"
+ " int member;\n"
+ "};\n";
+
+ expected =
+ "class Foo\n"
+ "{\n"
+ "public:\n"
+ " Foo();\n"
+ "\n"
+ " int member;\n"
+ "};\n";
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File
+ original ="#include \"file.h\"\n";
+ expected =
+ "#include \"file.h\"\n"
+ "\n"
+ "Foo::Foo() : member(2)\n"
+ "{\n"
+ "}\n"
+ ;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+ MoveFuncDefOutside factory;
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+
+ /// Check if definition is inserted right after class for move definition outside
+ void testAfterClass()
+ {
+ QList<TestDocumentPtr> testDocuments;
+ QByteArray original;
+ QByteArray expected;
+
+ // Header File
+ original =
+ "class Foo\n"
+ "{\n"
+ " Foo();\n"
+ " void a@() {}\n"
+ "};\n"
+ "\n"
+ "class Bar {};\n";
+ expected =
+ "class Foo\n"
+ "{\n"
+ " Foo();\n"
+ " void a();\n"
+ "};\n"
+ "\n"
+ "inline void Foo::a() {}\n"
+ "\n"
+ "class Bar {};\n";
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File
+ original =
+ "#include \"file.h\"\n"
+ "\n"
+ "Foo::Foo()\n"
+ "{\n\n"
+ "}\n";
+ expected = original;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+ MoveFuncDefOutside factory;
+ QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 1);
+ }
+
+ /// Check if whitespace is respected for operator functions
+ void testRespectWsInOperatorNames1()
+ {
+ QByteArray original =
+ "class Foo\n"
+ "{\n"
+ " Foo &opera@tor =() {}\n"
+ "};\n";
+ QByteArray expected =
+ "class Foo\n"
+ "{\n"
+ " Foo &operator =();\n"
+ "};\n"
+ "\n"
+ "Foo &Foo::operator =() {}\n"
+ ;
+
+ MoveFuncDefOutside factory;
+ QuickFixOperationTest(singleDocument(original, expected), &factory);
+ }
+
+ /// Check if whitespace is respected for operator functions
+ void testRespectWsInOperatorNames2()
+ {
+ QByteArray original =
+ "class Foo\n"
+ "{\n"
+ " Foo &opera@tor=() {}\n"
+ "};\n";
+ QByteArray expected =
+ "class Foo\n"
+ "{\n"
+ " Foo &operator=();\n"
+ "};\n"
+ "\n"
+ "Foo &Foo::operator=() {}\n"
+ ;
+
+ MoveFuncDefOutside factory;
+ QuickFixOperationTest(singleDocument(original, expected), &factory);
+ }
+
+ void testMacroUses()
+ {
+ QByteArray original =
+ "#define CONST const\n"
+ "#define VOLATILE volatile\n"
+ "class Foo\n"
+ "{\n"
+ " int fu@nc(int a, int b) CONST VOLATILE\n"
+ " {\n"
+ " return 42;\n"
+ " }\n"
+ "};\n";
+ QByteArray expected =
+ "#define CONST const\n"
+ "#define VOLATILE volatile\n"
+ "class Foo\n"
+ "{\n"
+ " int func(int a, int b) CONST VOLATILE;\n"
+ "};\n"
+ "\n"
+ "\n"
+ // const volatile become lowercase: QTCREATORBUG-12620
+ "int Foo::func(int a, int b) const volatile\n"
+ "{\n"
+ " return 42;\n"
+ "}\n"
+ ;
+
+ MoveFuncDefOutside factory;
+ QuickFixOperationTest(singleDocument(original, expected), &factory,
+ ProjectExplorer::HeaderPaths(), 0, "QTCREATORBUG-12314");
+ }
+
+ void testTemplate()
+ {
+ QByteArray original =
+ "template<class T>\n"
+ "class Foo { void fu@nc() {} };\n";
+ QByteArray expected =
+ "template<class T>\n"
+ "class Foo { void fu@nc(); };\n"
+ "\n"
+ "template<class T>\n"
+ "void Foo<T>::func() {}\n";
+ ;
+
+ MoveFuncDefOutside factory;
+ QuickFixOperationTest(singleDocument(original, expected), &factory);
+ }
+
+ void testMemberFunctionTemplate()
+ {
+ const QByteArray original = R"(
+struct S {
+ template<typename In>
+ void @foo(In in) { (void)in; }
+};
+)";
+ const QByteArray expected = R"(
+struct S {
+ template<typename In>
+ void foo(In in);
+};
+
+template<typename In>
+void S::foo(In in) { (void)in; }
+)";
+
+ MoveFuncDefOutside factory;
+ QuickFixOperationTest(singleDocument(original, expected), &factory);
+ }
+
+ void testTemplateSpecializedClass()
+ {
+ QByteArray original = R"(
+template<typename T> class base {};
+template<>
+class base<int>
+{
+public:
+ void @bar() {}
+};
+)";
+ QByteArray expected = R"(
+template<typename T> class base {};
+template<>
+class base<int>
+{
+public:
+ void bar();
+};
+
+void base<int>::bar() {}
+)";
+
+ MoveFuncDefOutside factory;
+ QuickFixOperationTest(singleDocument(original, expected), &factory);
+ }
+
+ void testUnnamedTemplate()
+ {
+ QByteArray original =
+ "template<typename T, typename>\n"
+ "class Foo { void fu@nc() {} };\n";
+ QByteArray expected =
+ "template<typename T, typename>\n"
+ "class Foo { void fu@nc(); };\n"
+ "\n"
+ "template<typename T, typename T2>\n"
+ "void Foo<T, T2>::func() {}\n";
+ ;
+
+ MoveFuncDefOutside factory;
+ QuickFixOperationTest(singleDocument(original, expected), &factory);
+ }
+
+ void testMemberFuncToCppStatic()
+ {
+ QList<TestDocumentPtr> testDocuments;
+ QByteArray original;
+ QByteArray expected;
+
+ // Header File
+ original =
+ "class Foo {\n"
+ " static inline int numbe@r() const\n"
+ " {\n"
+ " return 5;\n"
+ " }\n"
+ "\n"
+ " void bar();\n"
+ "};\n";
+ expected =
+ "class Foo {\n"
+ " static int number() const;\n"
+ "\n"
+ " void bar();\n"
+ "};\n";
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File
+ original =
+ "#include \"file.h\"\n";
+ expected =
+ "#include \"file.h\"\n"
+ "\n"
+ "int Foo::number() const\n"
+ "{\n"
+ " return 5;\n"
+ "}\n";
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+ MoveFuncDefOutside factory;
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+
+ void testMemberFuncToCppWithInlinePartOfName()
+ {
+ QList<TestDocumentPtr> testDocuments;
+ QByteArray original;
+ QByteArray expected;
+
+ // Header File
+ original =
+ "class Foo {\n"
+ " static inline int numbe@r_inline () const\n"
+ " {\n"
+ " return 5;\n"
+ " }\n"
+ "\n"
+ " void bar();\n"
+ "};\n";
+ expected =
+ "class Foo {\n"
+ " static int number_inline () const;\n"
+ "\n"
+ " void bar();\n"
+ "};\n";
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File
+ original =
+ "#include \"file.h\"\n";
+ expected =
+ "#include \"file.h\"\n"
+ "\n"
+ "int Foo::number_inline() const\n"
+ "{\n"
+ " return 5;\n"
+ "}\n";
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+ MoveFuncDefOutside factory;
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+
+ void testMixedQualifiers()
+ {
+ QList<TestDocumentPtr> testDocuments;
+ QByteArray original;
+ QByteArray expected;
+
+ // Header File
+ original = R"(
+struct Base {
+ virtual auto func() const && noexcept -> void = 0;
+};
+struct Derived : public Base {
+ auto @func() const && noexcept -> void override {}
+};)";
+ expected = R"(
+struct Base {
+ virtual auto func() const && noexcept -> void = 0;
+};
+struct Derived : public Base {
+ auto func() const && noexcept -> void override;
+};)";
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File
+ original = "#include \"file.h\"\n";
+ expected = R"DELIM(#include "file.h"
+
+auto Derived::func() const && noexcept -> void {}
+)DELIM";
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+ MoveFuncDefOutside factory;
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+
+};
+
+class MoveAllFuncDefOutsideTest : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void testMemberFuncToCpp()
+ {
+ QList<TestDocumentPtr> testDocuments;
+ QByteArray original;
+ QByteArray expected;
+
+ // Header File
+ original =
+ "class Foo {@\n"
+ " int numberA() const\n"
+ " {\n"
+ " return 5;\n"
+ " }\n"
+ " int numberB() const\n"
+ " {\n"
+ " return 5;\n"
+ " }\n"
+ "};\n";
+ expected =
+ "class Foo {\n"
+ " int numberA() const;\n"
+ " int numberB() const;\n"
+ "};\n";
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File
+ original =
+ "#include \"file.h\"\n";
+ expected =
+ "#include \"file.h\"\n"
+ "\n"
+ "int Foo::numberA() const\n"
+ "{\n"
+ " return 5;\n"
+ "}\n"
+ "\n"
+ "int Foo::numberB() const\n"
+ "{\n"
+ " return 5;\n"
+ "}\n"
+ ;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+ MoveAllFuncDefOutside factory;
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+
+ void testMemberFuncOutside()
+ {
+ QByteArray original =
+ "class F@oo {\n"
+ " int f1()\n"
+ " {\n"
+ " return 1;\n"
+ " }\n"
+ " int f2() const\n"
+ " {\n"
+ " return 2;\n"
+ " }\n"
+ "};\n";
+ QByteArray expected =
+ "class Foo {\n"
+ " int f1();\n"
+ " int f2() const;\n"
+ "};\n"
+ "\n"
+ "int Foo::f1()\n"
+ "{\n"
+ " return 1;\n"
+ "}\n"
+ "\n"
+ "int Foo::f2() const\n"
+ "{\n"
+ " return 2;\n"
+ "}\n";
+
+ MoveAllFuncDefOutside factory;
+ QuickFixOperationTest(singleDocument(original, expected), &factory);
+ }
+
+ void testDoNotTriggerOnBaseClass()
+ {
+ QByteArray original =
+ "class Bar;\n"
+ "class Foo : public Ba@r {\n"
+ " int f1()\n"
+ " {\n"
+ " return 1;\n"
+ " }\n"
+ "};\n";
+
+ MoveAllFuncDefOutside factory;
+ QuickFixOperationTest(singleDocument(original, ""), &factory);
+ }
+
+ void testClassWithBaseClass()
+ {
+ QByteArray original =
+ "class Bar;\n"
+ "class Fo@o : public Bar {\n"
+ " int f1()\n"
+ " {\n"
+ " return 1;\n"
+ " }\n"
+ "};\n";
+ QByteArray expected =
+ "class Bar;\n"
+ "class Foo : public Bar {\n"
+ " int f1();\n"
+ "};\n"
+ "\n"
+ "int Foo::f1()\n"
+ "{\n"
+ " return 1;\n"
+ "}\n";
+
+ MoveAllFuncDefOutside factory;
+ QuickFixOperationTest(singleDocument(original, expected), &factory);
+ }
+
+ /// Check: Do not take macro expanded code into account (QTCREATORBUG-13900)
+ void testIgnoreMacroCode()
+ {
+ QByteArray original =
+ "#define FAKE_Q_OBJECT int bar() {return 5;}\n"
+ "class Fo@o {\n"
+ " FAKE_Q_OBJECT\n"
+ " int f1()\n"
+ " {\n"
+ " return 1;\n"
+ " }\n"
+ "};\n";
+ QByteArray expected =
+ "#define FAKE_Q_OBJECT int bar() {return 5;}\n"
+ "class Foo {\n"
+ " FAKE_Q_OBJECT\n"
+ " int f1();\n"
+ "};\n"
+ "\n"
+ "int Foo::f1()\n"
+ "{\n"
+ " return 1;\n"
+ "}\n";
+
+ MoveAllFuncDefOutside factory;
+ QuickFixOperationTest(singleDocument(original, expected), &factory);
+ }
+
+};
+
+class MoveFuncDefToDeclTest : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void test_data()
+ {
+ QTest::addColumn<QByteArrayList>("headers");
+ QTest::addColumn<QByteArrayList>("sources");
+
+ QByteArray originalHeader;
+ QByteArray expectedHeader;
+ QByteArray originalSource;
+ QByteArray expectedSource;
+
+ originalHeader =
+ "class Foo {\n"
+ " inline int @number() const;\n"
+ "};\n";
+ expectedHeader =
+ "class Foo {\n"
+ " inline int number() const {return 5;}\n"
+ "};\n";
+ originalSource =
+ "#include \"file.h\"\n"
+ "\n"
+ "int Foo::num@ber() const {return 5;}\n";
+ expectedSource =
+ "#include \"file.h\"\n"
+ "\n\n";
+ QTest::newRow("member function, two files") << QByteArrayList{originalHeader, expectedHeader}
+ << QByteArrayList{originalSource, expectedSource};
+
+ originalSource =
+ "class Foo {\n"
+ " inline int @number() const;\n"
+ "};\n"
+ "\n"
+ "int Foo::num@ber() const\n"
+ "{\n"
+ " return 5;\n"
+ "}\n";
+
+ expectedSource =
+ "class Foo {\n"
+ " inline int number() const\n"
+ " {\n"
+ " return 5;\n"
+ " }\n"
+ "};\n\n\n";
+ QTest::newRow("member function, one file") << QByteArrayList()
+ << QByteArrayList{originalSource, expectedSource};
+
+ originalHeader =
+ "namespace MyNs {\n"
+ "class Foo {\n"
+ " inline int @number() const;\n"
+ "};\n"
+ "}\n";
+ expectedHeader =
+ "namespace MyNs {\n"
+ "class Foo {\n"
+ " inline int number() const\n"
+ " {\n"
+ " return 5;\n"
+ " }\n"
+ "};\n"
+ "}\n";
+ originalSource =
+ "#include \"file.h\"\n"
+ "\n"
+ "int MyNs::Foo::num@ber() const\n"
+ "{\n"
+ " return 5;\n"
+ "}\n";
+ expectedSource = "#include \"file.h\"\n\n\n";
+ QTest::newRow("member function, two files, namespace")
+ << QByteArrayList{originalHeader, expectedHeader}
+ << QByteArrayList{originalSource, expectedSource};
+
+ originalHeader =
+ "namespace MyNs {\n"
+ "class Foo {\n"
+ " inline int numbe@r() const;\n"
+ "};\n"
+ "}\n";
+ expectedHeader =
+ "namespace MyNs {\n"
+ "class Foo {\n"
+ " inline int number() const\n"
+ " {\n"
+ " return 5;\n"
+ " }\n"
+ "};\n"
+ "}\n";
+ originalSource =
+ "#include \"file.h\"\n"
+ "using namespace MyNs;\n"
+ "\n"
+ "int Foo::num@ber() const\n"
+ "{\n"
+ " return 5;\n"
+ "}\n";
+ expectedSource =
+ "#include \"file.h\"\n"
+ "using namespace MyNs;\n"
+ "\n\n";
+ QTest::newRow("member function, two files, namespace with using-directive")
+ << QByteArrayList{originalHeader, expectedHeader}
+ << QByteArrayList{originalSource, expectedSource};
+
+ originalSource =
+ "namespace MyNs {\n"
+ "class Foo {\n"
+ " inline int @number() const;\n"
+ "};\n"
+ "\n"
+ "int Foo::numb@er() const\n"
+ "{\n"
+ " return 5;\n"
+ "}"
+ "\n}\n";
+ expectedSource =
+ "namespace MyNs {\n"
+ "class Foo {\n"
+ " inline int number() const\n"
+ " {\n"
+ " return 5;\n"
+ " }\n"
+ "};\n\n\n}\n";
+
+ QTest::newRow("member function, one file, namespace")
+ << QByteArrayList() << QByteArrayList{originalSource, expectedSource};
+
+ originalHeader = "int nu@mber() const;\n";
+ expectedHeader =
+ "inline int number() const\n"
+ "{\n"
+ " return 5;\n"
+ "}\n";
+ originalSource =
+ "#include \"file.h\"\n"
+ "\n"
+ "\n"
+ "int numb@er() const\n"
+ "{\n"
+ " return 5;\n"
+ "}\n";
+ expectedSource = "#include \"file.h\"\n\n\n\n";
+ QTest::newRow("free function") << QByteArrayList{originalHeader, expectedHeader}
+ << QByteArrayList{originalSource, expectedSource};
+
+ originalHeader =
+ "namespace MyNamespace {\n"
+ "int n@umber() const;\n"
+ "}\n";
+ expectedHeader =
+ "namespace MyNamespace {\n"
+ "inline int number() const\n"
+ "{\n"
+ " return 5;\n"
+ "}\n"
+ "}\n";
+ originalSource =
+ "#include \"file.h\"\n"
+ "\n"
+ "int MyNamespace::nu@mber() const\n"
+ "{\n"
+ " return 5;\n"
+ "}\n";
+ expectedSource =
+ "#include \"file.h\"\n"
+ "\n\n";
+ QTest::newRow("free function, namespace") << QByteArrayList{originalHeader, expectedHeader}
+ << QByteArrayList{originalSource, expectedSource};
+
+ originalHeader =
+ "class Foo {\n"
+ "public:\n"
+ " Fo@o();\n"
+ "private:\n"
+ " int a;\n"
+ " float b;\n"
+ "};\n";
+ expectedHeader =
+ "class Foo {\n"
+ "public:\n"
+ " Foo() : a(42), b(3.141) {}\n"
+ "private:\n"
+ " int a;\n"
+ " float b;\n"
+ "};\n";
+ originalSource =
+ "#include \"file.h\"\n"
+ "\n"
+ "Foo::F@oo() : a(42), b(3.141) {}"
+ ;
+ expectedSource ="#include \"file.h\"\n\n";
+ QTest::newRow("constructor") << QByteArrayList{originalHeader, expectedHeader}
+ << QByteArrayList{originalSource, expectedSource};
+
+ originalSource =
+ "struct Foo\n"
+ "{\n"
+ " void f@oo();\n"
+ "} bar;\n"
+ "void Foo::fo@o()\n"
+ "{\n"
+ " return;\n"
+ "}";
+ expectedSource =
+ "struct Foo\n"
+ "{\n"
+ " void foo()\n"
+ " {\n"
+ " return;\n"
+ " }\n"
+ "} bar;\n";
+ QTest::newRow("QTCREATORBUG-10303") << QByteArrayList()
+ << QByteArrayList{originalSource, expectedSource};
+
+ originalSource =
+ "struct Base {\n"
+ " virtual int foo() = 0;\n"
+ "};\n"
+ "struct Derived : Base {\n"
+ " int @foo() override;\n"
+ "};\n"
+ "\n"
+ "int Derived::fo@o()\n"
+ "{\n"
+ " return 5;\n"
+ "}\n";
+ expectedSource =
+ "struct Base {\n"
+ " virtual int foo() = 0;\n"
+ "};\n"
+ "struct Derived : Base {\n"
+ " int foo() override\n"
+ " {\n"
+ " return 5;\n"
+ " }\n"
+ "};\n\n\n";
+ QTest::newRow("overridden virtual") << QByteArrayList()
+ << QByteArrayList{originalSource, expectedSource};
+
+ originalSource =
+ "template<class T>\n"
+ "class Foo { void @func(); };\n"
+ "\n"
+ "template<class T>\n"
+ "void Foo<T>::fu@nc() {}\n";
+ expectedSource =
+ "template<class T>\n"
+ "class Foo { void fu@nc() {} };\n\n\n";
+ QTest::newRow("class template") << QByteArrayList()
+ << QByteArrayList{originalSource, expectedSource};
+
+ originalSource =
+ "class Foo\n"
+ "{\n"
+ " template<class T>\n"
+ " void @func();\n"
+ "};\n"
+ "\n"
+ "template<class T>\n"
+ "void Foo::fu@nc() {}\n";
+ expectedSource =
+ "class Foo\n"
+ "{\n"
+ " template<class T>\n"
+ " void func() {}\n"
+ "};\n\n\n";
+ QTest::newRow("function template") << QByteArrayList()
+ << QByteArrayList{originalSource, expectedSource};
+ }
+
+ void test()
+ {
+ QFETCH(QByteArrayList, headers);
+ QFETCH(QByteArrayList, sources);
+
+ QVERIFY(headers.isEmpty() || headers.size() == 2);
+ QVERIFY(sources.size() == 2);
+
+ QByteArray &declDoc = !headers.empty() ? headers.first() : sources.first();
+ const int declCursorPos = declDoc.indexOf('@');
+ QVERIFY(declCursorPos != -1);
+ const int defCursorPos = sources.first().lastIndexOf('@');
+ QVERIFY(defCursorPos != -1);
+ QVERIFY(declCursorPos != defCursorPos);
+
+ declDoc.remove(declCursorPos, 1);
+ QList<TestDocumentPtr> testDocuments;
+ if (!headers.isEmpty())
+ testDocuments << CppTestDocument::create("file.h", headers.first(), headers.last());
+ testDocuments << CppTestDocument::create("file.cpp", sources.first(), sources.last());
+
+ MoveFuncDefToDeclPush pushFactory;
+ QuickFixOperationTest(testDocuments, &pushFactory);
+
+ declDoc.insert(declCursorPos, '@');
+ sources.first().remove(defCursorPos, 1);
+ testDocuments.clear();
+ if (!headers.isEmpty())
+ testDocuments << CppTestDocument::create("file.h", headers.first(), headers.last());
+ testDocuments << CppTestDocument::create("file.cpp", sources.first(), sources.last());
+
+ MoveFuncDefToDeclPull pullFactory;
+ QuickFixOperationTest(testDocuments, &pullFactory);
+ }
+
+ void testMacroUses()
+ {
+ QByteArray original =
+ "#define CONST const\n"
+ "#define VOLATILE volatile\n"
+ "class Foo\n"
+ "{\n"
+ " int func(int a, int b) CONST VOLATILE;\n"
+ "};\n"
+ "\n"
+ "\n"
+ "int Foo::fu@nc(int a, int b) CONST VOLATILE"
+ "{\n"
+ " return 42;\n"
+ "}\n";
+ QByteArray expected =
+ "#define CONST const\n"
+ "#define VOLATILE volatile\n"
+ "class Foo\n"
+ "{\n"
+ " int func(int a, int b) CONST VOLATILE\n"
+ " {\n"
+ " return 42;\n"
+ " }\n"
+ "};\n\n\n\n";
+
+ MoveFuncDefToDeclPush factory;
+ QuickFixOperationTest(singleDocument(original, expected), &factory,
+ ProjectExplorer::HeaderPaths(), 0, "QTCREATORBUG-12314");
+ }
+};
+
+QObject *MoveFuncDefOutside::createTest()
+{
+ return new MoveFuncDefOutsideTest;
+}
+
+QObject *MoveAllFuncDefOutside::createTest()
+{
+ return new MoveAllFuncDefOutsideTest;
+}
+
+QObject *MoveFuncDefToDeclPush::createTest()
+{
+ return new MoveFuncDefToDeclTest;
+}
+
+QObject *MoveFuncDefToDeclPull::createTest()
+{
+ return new QObject; // The test for the push factory handled both cases.
+}
+
+#endif // WITH_TESTS
+
+} // namespace
+
+void registerMoveFunctionDefinitionQuickfixes()
+{
+ CppQuickFixFactory::registerFactory<MoveFuncDefOutside>();
+ CppQuickFixFactory::registerFactory<MoveAllFuncDefOutside>();
+ CppQuickFixFactory::registerFactory<MoveFuncDefToDeclPush>();
+ CppQuickFixFactory::registerFactory<MoveFuncDefToDeclPull>();
+}
+
+} // namespace CppEditor::Internal
+
+#ifdef WITH_TESTS
+#include <movefunctiondefinition.moc>
+#endif
diff --git a/src/plugins/cppeditor/quickfixes/movefunctiondefinition.h b/src/plugins/cppeditor/quickfixes/movefunctiondefinition.h
new file mode 100644
index 0000000000..4c6e481ad4
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/movefunctiondefinition.h
@@ -0,0 +1,8 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+namespace CppEditor::Internal {
+void registerMoveFunctionDefinitionQuickfixes();
+} // namespace CppEditor::Internal
diff --git a/src/plugins/cppeditor/quickfixes/rearrangeparamdeclarationlist.cpp b/src/plugins/cppeditor/quickfixes/rearrangeparamdeclarationlist.cpp
new file mode 100644
index 0000000000..6917771a67
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/rearrangeparamdeclarationlist.cpp
@@ -0,0 +1,121 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "rearrangeparamdeclarationlist.h"
+
+#include "../cppeditortr.h"
+#include "../cpprefactoringchanges.h"
+#include "cppquickfix.h"
+
+#ifdef WITH_TESTS
+#include <QObject>
+#endif
+
+using namespace CPlusPlus;
+
+namespace CppEditor::Internal {
+namespace {
+
+class RearrangeParamDeclarationListOp: public CppQuickFixOperation
+{
+public:
+ enum Target { TargetPrevious, TargetNext };
+
+ RearrangeParamDeclarationListOp(const CppQuickFixInterface &interface, AST *currentParam,
+ AST *targetParam, Target target)
+ : CppQuickFixOperation(interface)
+ , m_currentParam(currentParam)
+ , m_targetParam(targetParam)
+ {
+ QString targetString;
+ if (target == TargetPrevious)
+ targetString = Tr::tr("Switch with Previous Parameter");
+ else
+ targetString = Tr::tr("Switch with Next Parameter");
+ setDescription(targetString);
+ }
+
+ void perform() override
+ {
+ int targetEndPos = currentFile()->endOf(m_targetParam);
+ currentFile()->setOpenEditor(false, targetEndPos);
+ currentFile()->apply(Utils::ChangeSet::makeFlip(
+ currentFile()->startOf(m_currentParam),
+ currentFile()->endOf(m_currentParam),
+ currentFile()->startOf(m_targetParam),
+ targetEndPos));
+ }
+
+private:
+ AST *m_currentParam;
+ AST *m_targetParam;
+};
+
+
+/*!
+ Switches places of the parameter declaration under cursor
+ with the next or the previous one in the parameter declaration list
+
+ Activates on: parameter declarations
+*/
+class RearrangeParamDeclarationList : public CppQuickFixFactory
+{
+#ifdef WITH_TESTS
+public:
+ static QObject *createTest() { return new QObject; }
+#endif
+
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ const QList<AST *> path = interface.path();
+
+ ParameterDeclarationAST *paramDecl = nullptr;
+ int index = path.size() - 1;
+ for (; index != -1; --index) {
+ paramDecl = path.at(index)->asParameterDeclaration();
+ if (paramDecl)
+ break;
+ }
+
+ if (index < 1)
+ return;
+
+ ParameterDeclarationClauseAST *paramDeclClause
+ = path.at(index - 1)->asParameterDeclarationClause();
+ QTC_ASSERT(paramDeclClause && paramDeclClause->parameter_declaration_list, return);
+
+ ParameterDeclarationListAST *paramListNode = paramDeclClause->parameter_declaration_list;
+ ParameterDeclarationListAST *prevParamListNode = nullptr;
+ while (paramListNode) {
+ if (paramDecl == paramListNode->value)
+ break;
+ prevParamListNode = paramListNode;
+ paramListNode = paramListNode->next;
+ }
+
+ if (!paramListNode)
+ return;
+
+ if (prevParamListNode)
+ result << new RearrangeParamDeclarationListOp(
+ interface,
+ paramListNode->value,
+ prevParamListNode->value,
+ RearrangeParamDeclarationListOp::TargetPrevious);
+ if (paramListNode->next)
+ result << new RearrangeParamDeclarationListOp(
+ interface,
+ paramListNode->value,
+ paramListNode->next->value,
+ RearrangeParamDeclarationListOp::TargetNext);
+ }
+};
+
+} // namespace
+
+void registerRearrangeParamDeclarationListQuickfix()
+{
+ CppQuickFixFactory::registerFactory<RearrangeParamDeclarationList>();
+}
+
+} // namespace CppEditor::Internal
diff --git a/src/plugins/cppeditor/quickfixes/rearrangeparamdeclarationlist.h b/src/plugins/cppeditor/quickfixes/rearrangeparamdeclarationlist.h
new file mode 100644
index 0000000000..6fa78885d9
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/rearrangeparamdeclarationlist.h
@@ -0,0 +1,8 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+namespace CppEditor::Internal {
+void registerRearrangeParamDeclarationListQuickfix();
+} // namespace CppEditor::Internal
diff --git a/src/plugins/cppeditor/quickfixes/reformatpointerdeclaration.cpp b/src/plugins/cppeditor/quickfixes/reformatpointerdeclaration.cpp
new file mode 100644
index 0000000000..6a7ab4c240
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/reformatpointerdeclaration.cpp
@@ -0,0 +1,186 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "reformatpointerdeclaration.h"
+
+#include "../cppcodestylesettings.h"
+#include "../cppeditortr.h"
+#include "../cpppointerdeclarationformatter.h"
+#include "../cpprefactoringchanges.h"
+#include "cppquickfix.h"
+
+#include <cplusplus/Overview.h>
+
+#ifdef WITH_TESTS
+#include "cppquickfix_test.h"
+#include <QtTest>
+#endif
+
+using namespace CPlusPlus;
+using namespace Utils;
+
+namespace CppEditor::Internal {
+namespace {
+
+class ReformatPointerDeclarationOp: public CppQuickFixOperation
+{
+public:
+ ReformatPointerDeclarationOp(const CppQuickFixInterface &interface, const ChangeSet change)
+ : CppQuickFixOperation(interface)
+ , m_change(change)
+ {
+ QString description;
+ if (m_change.operationList().size() == 1) {
+ description = Tr::tr(
+ "Reformat to \"%1\"").arg(m_change.operationList().constFirst().text());
+ } else { // > 1
+ description = Tr::tr("Reformat Pointers or References");
+ }
+ setDescription(description);
+ }
+
+ void perform() override
+ {
+ currentFile()->apply(m_change);
+ }
+
+private:
+ ChangeSet m_change;
+};
+
+/// Filter the results of ASTPath.
+/// The resulting list contains the supported AST types only once.
+/// For this, the results of ASTPath are iterated in reverse order.
+class ReformatPointerDeclarationASTPathResultsFilter
+{
+public:
+ QList<AST*> filter(const QList<AST*> &astPathList)
+ {
+ QList<AST*> filtered;
+
+ for (int i = astPathList.size() - 1; i >= 0; --i) {
+ AST *ast = astPathList.at(i);
+
+ if (!m_hasSimpleDeclaration && ast->asSimpleDeclaration()) {
+ m_hasSimpleDeclaration = true;
+ filtered.append(ast);
+ } else if (!m_hasFunctionDefinition && ast->asFunctionDefinition()) {
+ m_hasFunctionDefinition = true;
+ filtered.append(ast);
+ } else if (!m_hasParameterDeclaration && ast->asParameterDeclaration()) {
+ m_hasParameterDeclaration = true;
+ filtered.append(ast);
+ } else if (!m_hasIfStatement && ast->asIfStatement()) {
+ m_hasIfStatement = true;
+ filtered.append(ast);
+ } else if (!m_hasWhileStatement && ast->asWhileStatement()) {
+ m_hasWhileStatement = true;
+ filtered.append(ast);
+ } else if (!m_hasForStatement && ast->asForStatement()) {
+ m_hasForStatement = true;
+ filtered.append(ast);
+ } else if (!m_hasForeachStatement && ast->asForeachStatement()) {
+ m_hasForeachStatement = true;
+ filtered.append(ast);
+ }
+ }
+
+ return filtered;
+ }
+
+private:
+ bool m_hasSimpleDeclaration = false;
+ bool m_hasFunctionDefinition = false;
+ bool m_hasParameterDeclaration = false;
+ bool m_hasIfStatement = false;
+ bool m_hasWhileStatement = false;
+ bool m_hasForStatement = false;
+ bool m_hasForeachStatement = false;
+};
+
+/*!
+ Reformats a pointer, reference or rvalue reference type/declaration.
+
+ Works also with selections (except when the cursor is not on any AST).
+
+ Activates on: simple declarations, parameters and return types of function
+ declarations and definitions, control flow statements (if,
+ while, for, foreach) with declarations.
+*/
+class ReformatPointerDeclaration : public CppQuickFixFactory
+{
+#ifdef WITH_TESTS
+public:
+ static QObject *createTest();
+#endif
+
+private:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ const QList<AST *> &path = interface.path();
+ CppRefactoringFilePtr file = interface.currentFile();
+
+ Overview overview = CppCodeStyleSettings::currentProjectCodeStyleOverview();
+ overview.showArgumentNames = true;
+ overview.showReturnTypes = true;
+
+ const QTextCursor cursor = file->cursor();
+ ChangeSet change;
+ PointerDeclarationFormatter formatter(file, overview,
+ PointerDeclarationFormatter::RespectCursor);
+
+ if (cursor.hasSelection()) {
+ // This will no work always as expected since this function is only called if
+ // interface-path() is not empty. If the user selects the whole document via
+ // ctrl-a and there is an empty line in the end, then the cursor is not on
+ // any AST and therefore no quick fix will be triggered.
+ change = formatter.format(file->cppDocument()->translationUnit()->ast());
+ if (!change.isEmpty())
+ result << new ReformatPointerDeclarationOp(interface, change);
+ } else {
+ const QList<AST *> suitableASTs
+ = ReformatPointerDeclarationASTPathResultsFilter().filter(path);
+ for (AST *ast : suitableASTs) {
+ change = formatter.format(ast);
+ if (!change.isEmpty()) {
+ result << new ReformatPointerDeclarationOp(interface, change);
+ return;
+ }
+ }
+ }
+ }
+};
+
+#ifdef WITH_TESTS
+using namespace Tests;
+
+class ReformatPointerDeclarationTest : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void test()
+ {
+ // Check: Just a basic test since the main functionality is tested in
+ // cpppointerdeclarationformatter_test.cpp
+ ReformatPointerDeclaration factory;
+ QuickFixOperationTest(
+ singleDocument(QByteArray("char@*s;"), QByteArray("char *s;")), &factory);
+ }
+};
+
+QObject *ReformatPointerDeclaration::createTest() { return new ReformatPointerDeclarationTest; }
+
+#endif // WITH_TESTS
+}
+
+void registerReformatPointerDeclarationQuickfix()
+{
+ CppQuickFixFactory::registerFactory<ReformatPointerDeclaration>();
+}
+
+} // namespace CppEditor::Internal
+
+#ifdef WITH_TESTS
+#include <reformatpointerdeclaration.moc>
+#endif
diff --git a/src/plugins/cppeditor/quickfixes/reformatpointerdeclaration.h b/src/plugins/cppeditor/quickfixes/reformatpointerdeclaration.h
new file mode 100644
index 0000000000..89bbe5cd77
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/reformatpointerdeclaration.h
@@ -0,0 +1,8 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+namespace CppEditor::Internal {
+void registerReformatPointerDeclarationQuickfix();
+} // namespace CppEditor::Internal
diff --git a/src/plugins/cppeditor/quickfixes/removeusingnamespace.cpp b/src/plugins/cppeditor/quickfixes/removeusingnamespace.cpp
new file mode 100644
index 0000000000..f889b14121
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/removeusingnamespace.cpp
@@ -0,0 +1,957 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "removeusingnamespace.h"
+
+#include "../cppeditortr.h"
+#include "../cppprojectfile.h"
+#include "../cpprefactoringchanges.h"
+#include "cppquickfix.h"
+
+#include <cplusplus/Overview.h>
+#include <projectexplorer/projectmanager.h>
+
+#ifdef WITH_TESTS
+#include "cppquickfix_test.h"
+#include <QtTest>
+#endif
+
+using namespace CPlusPlus;
+using namespace ProjectExplorer;
+using namespace TextEditor;
+using namespace Utils;
+
+namespace CppEditor::Internal {
+namespace {
+
+/**
+ * @brief The NameCounter class counts the parts of a name. E.g. 2 for std::vector or 1 for variant
+ */
+class NameCounter : private NameVisitor
+{
+public:
+ int count(const Name *name)
+ {
+ counter = 0;
+ accept(name);
+ return counter;
+ }
+
+private:
+ void visit(const Identifier *) override { ++counter; }
+ void visit(const DestructorNameId *) override { ++counter; }
+ void visit(const TemplateNameId *) override { ++counter; }
+ void visit(const QualifiedNameId *name) override
+ {
+ if (name->base())
+ accept(name->base());
+ accept(name->name());
+ }
+ int counter;
+};
+
+/**
+ * @brief getBaseName returns the base name of a qualified name or nullptr.
+ * E.g.: foo::bar => foo; bar => bar
+ * @param name The Name, maybe qualified
+ * @return The base name of the qualified name or nullptr
+ */
+const Identifier *getBaseName(const Name *name)
+{
+ class GetBaseName : public NameVisitor
+ {
+ void visit(const Identifier *name) override { baseName = name; }
+ void visit(const QualifiedNameId *name) override
+ {
+ if (name->base())
+ accept(name->base());
+ else
+ accept(name->name());
+ }
+
+ public:
+ const Identifier *baseName = nullptr;
+ };
+ GetBaseName getter;
+ getter.accept(name);
+ return getter.baseName;
+}
+
+/**
+ * @brief countNames counts the parts of the Name.
+ * E.g. if the name is std::vector, the function returns 2, if the name is variant, returns 1
+ * @param name The name that should be counted
+ * @return the number of parts of the name
+ */
+int countNames(const Name *name)
+{
+ return NameCounter{}.count(name);
+}
+
+/**
+ * @brief removeLine removes the whole line in which the ast node is located if there are otherwise only whitespaces
+ * @param file The file in which the AST node is located
+ * @param ast The ast node
+ * @param changeSet The ChangeSet of the file
+ */
+void removeLine(const CppRefactoringFile *file, AST *ast, ChangeSet &changeSet)
+{
+ RefactoringFile::Range range = file->range(ast);
+ --range.start;
+ while (range.start >= 0) {
+ QChar current = file->charAt(range.start);
+ if (!current.isSpace()) {
+ ++range.start;
+ break;
+ }
+ if (current == QChar::ParagraphSeparator)
+ break;
+ --range.start;
+ }
+ range.start = std::max(0, range.start);
+ while (range.end < file->document()->characterCount()) {
+ QChar current = file->charAt(range.end);
+ if (!current.isSpace())
+ break;
+ if (current == QChar::ParagraphSeparator)
+ break;
+ ++range.end;
+ }
+ range.end = std::min(file->document()->characterCount(), range.end);
+ const bool newLineStart = file->charAt(range.start) == QChar::ParagraphSeparator;
+ const bool newLineEnd = file->charAt(range.end) == QChar::ParagraphSeparator;
+ if (!newLineEnd && newLineStart)
+ ++range.start;
+ changeSet.remove(range);
+}
+
+/**
+ * @brief The RemoveNamespaceVisitor class removes a using namespace and rewrites all types that
+ * are in the namespace if needed
+ */
+class RemoveNamespaceVisitor : public ASTVisitor
+{
+public:
+ constexpr static int SearchGlobalUsingDirectivePos = std::numeric_limits<int>::max();
+ RemoveNamespaceVisitor(const CppRefactoringFile *file,
+ const Snapshot &snapshot,
+ const Name *namespace_,
+ int symbolPos,
+ bool removeAllAtGlobalScope)
+ : ASTVisitor(file->cppDocument()->translationUnit())
+ , m_file(file)
+ , m_snapshot(snapshot)
+ , m_namespace(namespace_)
+ , m_missingNamespace(toString(namespace_) + "::")
+ , m_context(m_file->cppDocument(), m_snapshot)
+ , m_symbolPos(symbolPos)
+ , m_removeAllAtGlobalScope(removeAllAtGlobalScope)
+
+ {}
+
+ const ChangeSet &getChanges() { return m_changeSet; }
+
+ /**
+ * @brief isGlobalUsingNamespace return true if the using namespace that should be removed
+ * is not scoped and other files that include this file will also use the using namespace
+ * @return true if using namespace statement is global and not scoped, false otherwise
+ */
+ bool isGlobalUsingNamespace() const { return m_parentNode == nullptr; }
+
+ /**
+ * @brief foundGlobalUsingNamespace return true if removeAllAtGlobalScope is false and
+ * another using namespace is found at the global scope, so that other files that include this
+ * file don't have to be processed
+ * @return true if there was a 'global' second using namespace in this file and
+ * removeAllAtGlobalScope is false
+ */
+ bool foundGlobalUsingNamespace() const { return m_foundNamespace; }
+
+private:
+ bool preVisit(AST *ast) override
+ {
+ if (!m_start) {
+ if (ast->asTranslationUnit())
+ return true;
+ if (UsingDirectiveAST *usingDirective = ast->asUsingDirective()) {
+ if (nameEqual(usingDirective->name->name, m_namespace)) {
+ if (m_symbolPos == SearchGlobalUsingDirectivePos) {
+ // we have found a global using directive, so lets start
+ m_start = true;
+ removeLine(m_file, ast, m_changeSet);
+ return false;
+ }
+ // ignore the using namespace that should be removed
+ if (m_file->endOf(ast) != m_symbolPos) {
+ if (m_removeAllAtGlobalScope)
+ removeLine(m_file, ast, m_changeSet);
+ else
+ m_done = true;
+ }
+ }
+ }
+ // if the end of the ast is before we should start, we are not interested in the node
+ if (m_file->endOf(ast) <= m_symbolPos)
+ return false;
+
+ if (m_file->startOf(ast) > m_symbolPos)
+ m_start = true;
+ }
+ return !m_foundNamespace && !m_done;
+ }
+
+ bool visit(NamespaceAST *ast) override
+ {
+ if (m_start && nameEqual(m_namespace, ast->symbol->name()))
+ return false;
+
+ return m_start;
+ }
+
+ // scopes for using namespace statements:
+ bool visit(LinkageBodyAST *ast) override { return visitNamespaceScope(ast); }
+ bool visit(CompoundStatementAST *ast) override { return visitNamespaceScope(ast); }
+ bool visitNamespaceScope(AST *ast)
+ {
+ ++m_namespaceScopeCounter;
+ if (!m_start)
+ m_parentNode = ast;
+ return true;
+ }
+
+ void endVisit(LinkageBodyAST *ast) override { endVisitNamespaceScope(ast); }
+ void endVisit(CompoundStatementAST *ast) override { endVisitNamespaceScope(ast); }
+ void endVisitNamespaceScope(AST *ast)
+ {
+ --m_namespaceScopeCounter;
+ m_foundNamespace = false;
+ // if we exit the scope of the using namespace we are done
+ if (ast == m_parentNode)
+ m_done = true;
+ }
+
+ bool visit(UsingDirectiveAST *ast) override
+ {
+ if (nameEqual(ast->name->name, m_namespace)) {
+ if (m_removeAllAtGlobalScope && m_namespaceScopeCounter == 0)
+ removeLine(m_file, ast, m_changeSet);
+ else
+ m_foundNamespace = true;
+ return false;
+ }
+ return handleAstWithLongestName(ast);
+ }
+
+ bool visit(DeclaratorIdAST *ast) override
+ {
+ // e.g. we have the following code and get the following Lookup items:
+ // namespace test {
+ // struct foo { // 1. item with test::foo
+ // foo(); // 2. item with test::foo::foo
+ // };
+ // }
+ // using namespace foo;
+ // foo::foo() { ... } // 3. item with foo::foo
+ // Our current name is foo::foo so we have to match with the 2. item / longest name
+ return handleAstWithLongestName(ast);
+ }
+
+ template<typename AST>
+ bool handleAstWithLongestName(AST *ast)
+ {
+ if (m_start) {
+ Scope *scope = m_file->scopeAt(ast->firstToken());
+ const QList<LookupItem> localLookup = m_context.lookup(ast->name->name, scope);
+ QList<const Name *> longestName;
+ for (const LookupItem &item : localLookup) {
+ QList<const Name *> names
+ = m_context.fullyQualifiedName(item.declaration(),
+ LookupContext::HideInlineNamespaces);
+ if (names.length() > longestName.length())
+ longestName = names;
+ }
+ const int currentNameCount = countNames(ast->name->name);
+ const bool needNew = needMissingNamespaces(std::move(longestName), currentNameCount);
+ if (needNew)
+ insertMissingNamespace(ast);
+ }
+ return false;
+ }
+
+ bool visit(NamedTypeSpecifierAST *ast) override { return handleAstWithName(ast); }
+
+ bool visit(IdExpressionAST *ast) override { return handleAstWithName(ast); }
+
+ template<typename AST>
+ bool handleAstWithName(AST *ast)
+ {
+ if (m_start) {
+ Scope *scope = m_file->scopeAt(ast->firstToken());
+ const Name *wantToLookup = ast->name->name;
+ // first check if the base name is a typedef. Consider the following example:
+ // using namespace std;
+ // using vec = std::vector<int>;
+ // vec::iterator it; // we have to lookup 'vec' and not iterator (would result in
+ // std::vector<int>::iterator => std::vec::iterator, which is wrong)
+ const Name *baseName = getBaseName(wantToLookup);
+ QList<LookupItem> typedefCandidates = m_context.lookup(baseName, scope);
+ if (!typedefCandidates.isEmpty()) {
+ if (typedefCandidates.front().declaration()->isTypedef())
+ wantToLookup = baseName;
+ }
+
+ const QList<LookupItem> lookups = m_context.lookup(wantToLookup, scope);
+ if (!lookups.empty()) {
+ QList<const Name *> fullName
+ = m_context.fullyQualifiedName(lookups.first().declaration(),
+ LookupContext::HideInlineNamespaces);
+ const int currentNameCount = countNames(wantToLookup);
+ const bool needNamespace = needMissingNamespaces(std::move(fullName),
+ currentNameCount);
+ if (needNamespace)
+ insertMissingNamespace(ast);
+ }
+ }
+ return true;
+ }
+
+ template<typename AST>
+ void insertMissingNamespace(AST *ast)
+ {
+ DestructorNameAST *destructorName = ast->name->asDestructorName();
+ if (destructorName)
+ m_changeSet.insert(m_file->startOf(destructorName->unqualified_name), m_missingNamespace);
+ else
+ m_changeSet.insert(m_file->startOf(ast->name), m_missingNamespace);
+ m_changeSet.operationList().last().setFormat1(false);
+ }
+
+ bool needMissingNamespaces(QList<const Name *> &&fullName, int currentNameCount)
+ {
+ if (currentNameCount > fullName.length())
+ return false;
+
+ // eg. fullName = std::vector, currentName = vector => result should be std
+ fullName.erase(fullName.end() - currentNameCount, fullName.end());
+ if (fullName.empty())
+ return false;
+ return nameEqual(m_namespace, fullName.last());
+ }
+
+ static bool nameEqual(const Name *name1, const Name *name2)
+ {
+ return Matcher::match(name1, name2);
+ }
+
+ QString toString(const Name *id)
+ {
+ const Identifier *identifier = id->asNameId();
+ QTC_ASSERT(identifier, return {});
+ return QString::fromUtf8(identifier->chars(), identifier->size());
+ }
+
+ const CppRefactoringFile *const m_file;
+ const Snapshot &m_snapshot;
+
+ const Name *m_namespace; // the name of the namespace that should be removed
+ const QString m_missingNamespace; // that should be added if a type was using the namespace
+ LookupContext m_context;
+ ChangeSet m_changeSet;
+ const int m_symbolPos; // the end position of the start symbol
+ bool m_done = false;
+ bool m_start = false;
+ // true if a using namespace was found at a scope and the scope should be left
+ bool m_foundNamespace = false;
+ bool m_removeAllAtGlobalScope;
+ // the scope where the using namespace that should be removed is valid
+ AST *m_parentNode = nullptr;
+ int m_namespaceScopeCounter = 0;
+};
+
+class RemoveUsingNamespaceOperation : public CppQuickFixOperation
+{
+ struct Node
+ {
+ Document::Ptr document;
+ bool hasGlobalUsingDirective = false;
+ int unprocessedParents;
+ std::vector<std::reference_wrapper<Node>> includes;
+ std::vector<std::reference_wrapper<Node>> includedBy;
+ Node() = default;
+ Node(const Node &) = delete;
+ Node(Node &&) = delete;
+ };
+
+public:
+ RemoveUsingNamespaceOperation(const CppQuickFixInterface &interface,
+ UsingDirectiveAST *usingDirective,
+ bool removeAllAtGlobalScope)
+ : CppQuickFixOperation(interface, 1)
+ , m_usingDirective(usingDirective)
+ , m_removeAllAtGlobalScope(removeAllAtGlobalScope)
+ {
+ const QString name = Overview{}.prettyName(usingDirective->name->name);
+ if (m_removeAllAtGlobalScope) {
+ setDescription(Tr::tr(
+ "Remove All Occurrences of \"using namespace %1\" in Global Scope "
+ "and Adjust Type Names Accordingly")
+ .arg(name));
+ } else {
+ setDescription(Tr::tr("Remove \"using namespace %1\" and "
+ "Adjust Type Names Accordingly")
+ .arg(name));
+ }
+ }
+
+private:
+ std::map<Utils::FilePath, Node> buildIncludeGraph(CppRefactoringChanges &refactoring)
+ {
+ using namespace ProjectExplorer;
+ using namespace Utils;
+
+ const Snapshot &s = refactoring.snapshot();
+ std::map<Utils::FilePath, Node> includeGraph;
+
+ auto handleFile = [&](const FilePath &filePath, Document::Ptr doc, auto shouldHandle) {
+ Node &node = includeGraph[filePath];
+ node.document = doc;
+ for (const Document::Include &include : doc->resolvedIncludes()) {
+ const FilePath filePath = include.resolvedFileName();
+ if (shouldHandle(filePath)) {
+ Node &includedNode = includeGraph[filePath];
+ includedNode.includedBy.push_back(node);
+ node.includes.push_back(includedNode);
+ }
+ }
+ };
+
+ if (const Project *project = ProjectManager::projectForFile(filePath())) {
+ const FilePaths files = project->files(ProjectExplorer::Project::SourceFiles);
+ QSet<FilePath> projectFiles(files.begin(), files.end());
+ for (const auto &file : files) {
+ const Document::Ptr doc = s.document(file);
+ if (!doc)
+ continue;
+ handleFile(file, doc, [&](const FilePath &file) {
+ return projectFiles.contains(file);
+ });
+ }
+ } else {
+ for (auto i = s.begin(); i != s.end(); ++i) {
+ if (ProjectFile::classify(i.key().toString()) != ProjectFile::Unsupported) {
+ handleFile(i.key(), i.value(), [](const FilePath &file) {
+ return ProjectFile::classify(file.toString()) != ProjectFile::Unsupported;
+ });
+ }
+ }
+ }
+ for (auto &[_, node] : includeGraph) {
+ Q_UNUSED(_)
+ node.unprocessedParents = static_cast<int>(node.includes.size());
+ }
+ return includeGraph;
+ }
+
+ void removeAllUsingsAtGlobalScope(CppRefactoringChanges &refactoring)
+ {
+ auto includeGraph = buildIncludeGraph(refactoring);
+ std::vector<std::reference_wrapper<Node>> nodesWithProcessedParents;
+ for (auto &[_, node] : includeGraph) {
+ Q_UNUSED(_)
+ if (!node.unprocessedParents)
+ nodesWithProcessedParents.push_back(node);
+ }
+ while (!nodesWithProcessedParents.empty()) {
+ Node &node = nodesWithProcessedParents.back();
+ nodesWithProcessedParents.pop_back();
+ CppRefactoringFilePtr file = refactoring.cppFile(node.document->filePath());
+ const bool parentHasUsing = Utils::anyOf(node.includes, &Node::hasGlobalUsingDirective);
+ const int startPos = parentHasUsing
+ ? 0
+ : RemoveNamespaceVisitor::SearchGlobalUsingDirectivePos;
+ const bool noGlobalUsing = refactorFile(file, refactoring.snapshot(), startPos);
+ node.hasGlobalUsingDirective = !noGlobalUsing || parentHasUsing;
+
+ for (Node &subNode : node.includedBy) {
+ --subNode.unprocessedParents;
+ if (subNode.unprocessedParents == 0)
+ nodesWithProcessedParents.push_back(subNode);
+ }
+ }
+ }
+
+ void perform() override
+ {
+ CppRefactoringChanges refactoring(snapshot());
+ if (m_removeAllAtGlobalScope) {
+ removeAllUsingsAtGlobalScope(refactoring);
+ } else if (refactorFile(currentFile(),
+ refactoring.snapshot(),
+ currentFile()->endOf(m_usingDirective),
+ true)) {
+ processIncludes(refactoring, filePath());
+ }
+
+ for (auto &file : std::as_const(m_changes))
+ file->apply();
+ }
+
+ /**
+ * @brief refactorFile remove using namespace xyz in the given file and rewrite types
+ * @param file The file that should be processed
+ * @param snapshot The snapshot to work on
+ * @param startSymbol start processing after this index
+ * @param removeUsing if the using directive is in this file, remove it
+ * @return true if the using statement is global and there is no other global using namespace
+ */
+ bool refactorFile(const CppRefactoringFilePtr &file,
+ const Snapshot &snapshot,
+ int startSymbol,
+ bool removeUsing = false)
+ {
+ RemoveNamespaceVisitor visitor(file.get(),
+ snapshot,
+ m_usingDirective->name->name,
+ startSymbol,
+ m_removeAllAtGlobalScope);
+ visitor.accept(file->cppDocument()->translationUnit()->ast());
+ Utils::ChangeSet changes = visitor.getChanges();
+ if (removeUsing)
+ removeLine(file.get(), m_usingDirective, changes);
+ if (!changes.isEmpty()) {
+ file->setChangeSet(changes);
+ // apply changes at the end, otherwise the symbol finder will fail to resolve symbols if
+ // the using namespace is missing
+ m_changes.insert(file);
+ }
+ return visitor.isGlobalUsingNamespace() && !visitor.foundGlobalUsingNamespace();
+ }
+
+ void processIncludes(CppRefactoringChanges &refactoring, const FilePath &filePath)
+ {
+ QList<Snapshot::IncludeLocation>
+ includeLocationsOfDocument = refactoring.snapshot().includeLocationsOfDocument(filePath);
+ for (Snapshot::IncludeLocation &loc : includeLocationsOfDocument) {
+ if (!Utils::insert(m_processed, loc.first))
+ continue;
+
+ CppRefactoringFilePtr file = refactoring.cppFile(loc.first->filePath());
+ const bool noGlobalUsing = refactorFile(file,
+ refactoring.snapshot(),
+ file->position(loc.second, 1));
+ if (noGlobalUsing)
+ processIncludes(refactoring, loc.first->filePath());
+ }
+ }
+
+ QSet<Document::Ptr> m_processed;
+ QSet<CppRefactoringFilePtr> m_changes;
+
+ UsingDirectiveAST *m_usingDirective;
+ bool m_removeAllAtGlobalScope;
+};
+
+//! Removes a using directive (using namespace xyz).
+class RemoveUsingNamespace : public CppQuickFixFactory
+{
+public:
+ RemoveUsingNamespace() { setClangdReplacement({10}); }
+#ifdef WITH_TESTS
+ static QObject *createTest();
+#endif
+
+private:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ const QList<AST *> &path = interface.path();
+ // We expect something like
+ // [0] TranslationUnitAST
+ // ...
+ // [] UsingDirectiveAST : if activated at 'using namespace'
+ // [] NameAST (optional): if activated at the name e.g. 'std'
+ int n = path.size() - 1;
+ if (n <= 0)
+ return;
+ if (path.last()->asName())
+ --n;
+ UsingDirectiveAST *usingDirective = path.at(n)->asUsingDirective();
+ if (usingDirective && usingDirective->name->name->asNameId()) {
+ result << new RemoveUsingNamespaceOperation(interface, usingDirective, false);
+ const bool isHeader = ProjectFile::isHeader(ProjectFile::classify(interface.filePath().toString()));
+ if (isHeader && path.at(n - 1)->asTranslationUnit()) // using namespace at global scope
+ result << new RemoveUsingNamespaceOperation(interface, usingDirective, true);
+ }
+ }
+};
+
+#ifdef WITH_TESTS
+using namespace Tests;
+class RemoveUsingNamespaceTest : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void test_data()
+ {
+ QTest::addColumn<QByteArray>("header1");
+ QTest::addColumn<QByteArray>("header2");
+ QTest::addColumn<QByteArray>("header3");
+ QTest::addColumn<QByteArray>("expected1");
+ QTest::addColumn<QByteArray>("expected2");
+ QTest::addColumn<QByteArray>("expected3");
+ QTest::addColumn<int>("operation");
+
+ const QByteArray header1 = "namespace std{\n"
+ " template<typename T>\n"
+ " class vector{};\n"
+ " namespace chrono{\n"
+ " using seconds = int;\n"
+ " }\n"
+ "}\n"
+ "using namespace std;\n"
+ "namespace test{\n"
+ " class vector{\n"
+ " std::vector<int> ints;\n"
+ " };\n"
+ "}\n";
+ const QByteArray header2 = "#include \"header1.h\"\n"
+ "using foo = test::vector;\n"
+ "using namespace std;\n"
+ "using namespace test;\n"
+ "vector<int> others;\n";
+
+ const QByteArray header3 = "#include \"header2.h\"\n"
+ "using namespace std;\n"
+ "using namespace chrono;\n"
+ "namespace test{\n"
+ " vector vec;\n"
+ " seconds t;\n"
+ "}\n"
+ "void scope(){\n"
+ " for (;;) {\n"
+ " using namespace std;\n"
+ " vector<int> fori;\n"
+ " }\n"
+ " vector<int> no;\n"
+ " using namespace std;\n"
+ " vector<int> _no_change;\n"
+ "}\n"
+ "foo foos;\n";
+
+ QByteArray h3 = "#include \"header2.h\"\n"
+ "using namespace s@td;\n"
+ "using namespace chrono;\n"
+ "namespace test{\n"
+ " vector vec;\n"
+ " seconds t;\n"
+ "}\n"
+ "void scope(){\n"
+ " for (;;) {\n"
+ " using namespace std;\n"
+ " vector<int> fori;\n"
+ " }\n"
+ " vector<int> no;\n"
+ " using namespace std;\n"
+ " vector<int> _no_change;\n"
+ "}\n"
+ "foo foos;\n";
+
+ // like header1 but without "using namespace std;\n"
+ QByteArray expected1 = "namespace std{\n"
+ " template<typename T>\n"
+ " class vector{};\n"
+ " namespace chrono{\n"
+ " using seconds = int;\n"
+ " }\n"
+ "}\n"
+ "namespace test{\n"
+ " class vector{\n"
+ " std::vector<int> ints;\n"
+ " };\n"
+ "}\n";
+
+ // like header2 but without "using namespace std;\n" and with std::vector
+ QByteArray expected2 = "#include \"header1.h\"\n"
+ "using foo = test::vector;\n"
+ "using namespace test;\n"
+ "std::vector<int> others;\n";
+
+ QByteArray expected3 = "#include \"header2.h\"\n"
+ "using namespace std::chrono;\n"
+ "namespace test{\n"
+ " vector vec;\n"
+ " seconds t;\n"
+ "}\n"
+ "void scope(){\n"
+ " for (;;) {\n"
+ " using namespace std;\n"
+ " vector<int> fori;\n"
+ " }\n"
+ " std::vector<int> no;\n"
+ " using namespace std;\n"
+ " vector<int> _no_change;\n"
+ "}\n"
+ "foo foos;\n";
+
+ QTest::newRow("remove only in one file local")
+ << header1 << header2 << h3 << header1 << header2 << expected3 << 0;
+ QTest::newRow("remove only in one file globally")
+ << header1 << header2 << h3 << expected1 << expected2 << expected3 << 1;
+
+ QByteArray h2 = "#include \"header1.h\"\n"
+ "using foo = test::vector;\n"
+ "using namespace s@td;\n"
+ "using namespace test;\n"
+ "vector<int> others;\n";
+
+ QTest::newRow("remove across two files only this")
+ << header1 << h2 << header3 << header1 << expected2 << header3 << 0;
+ QTest::newRow("remove across two files globally1")
+ << header1 << h2 << header3 << expected1 << expected2 << expected3 << 1;
+
+ QByteArray h1 = "namespace std{\n"
+ " template<typename T>\n"
+ " class vector{};\n"
+ " namespace chrono{\n"
+ " using seconds = int;\n"
+ " }\n"
+ "}\n"
+ "using namespace s@td;\n"
+ "namespace test{\n"
+ " class vector{\n"
+ " std::vector<int> ints;\n"
+ " };\n"
+ "}\n";
+
+ QTest::newRow("remove across tree files only this")
+ << h1 << header2 << header3 << expected1 << header2 << header3 << 0;
+ QTest::newRow("remove across tree files globally")
+ << h1 << header2 << header3 << expected1 << expected2 << expected3 << 1;
+
+ expected3 = "#include \"header2.h\"\n"
+ "using namespace std::chrono;\n"
+ "namespace test{\n"
+ " vector vec;\n"
+ " seconds t;\n"
+ "}\n"
+ "void scope(){\n"
+ " for (;;) {\n"
+ " using namespace s@td;\n"
+ " vector<int> fori;\n"
+ " }\n"
+ " std::vector<int> no;\n"
+ " using namespace std;\n"
+ " vector<int> _no_change;\n"
+ "}\n"
+ "foo foos;\n";
+
+ QByteArray expected3_new = "#include \"header2.h\"\n"
+ "using namespace std::chrono;\n"
+ "namespace test{\n"
+ " vector vec;\n"
+ " seconds t;\n"
+ "}\n"
+ "void scope(){\n"
+ " for (;;) {\n"
+ " std::vector<int> fori;\n"
+ " }\n"
+ " std::vector<int> no;\n"
+ " using namespace std;\n"
+ " vector<int> _no_change;\n"
+ "}\n"
+ "foo foos;\n";
+
+ QTest::newRow("scoped remove")
+ << expected1 << expected2 << expected3 << expected1 << expected2 << expected3_new << 0;
+
+ h2 = "#include \"header1.h\"\n"
+ "using foo = test::vector;\n"
+ "using namespace std;\n"
+ "using namespace t@est;\n"
+ "vector<int> others;\n";
+ expected2 = "#include \"header1.h\"\n"
+ "using foo = test::vector;\n"
+ "using namespace std;\n"
+ "vector<int> others;\n";
+
+ QTest::newRow("existing namespace")
+ << header1 << h2 << header3 << header1 << expected2 << header3 << 1;
+
+ // test: remove using directive at global scope in every file
+ h1 = "using namespace tes@t;";
+ h2 = "using namespace test;";
+ h3 = "using namespace test;";
+
+ expected1 = expected2 = expected3 = "";
+ QTest::newRow("global scope remove in every file")
+ << h1 << h2 << h3 << expected1 << expected2 << expected3 << 1;
+
+ // test: dont print inline namespaces
+ h1 = R"--(
+namespace test {
+ inline namespace test {
+ class Foo{
+ void foo1();
+ void foo2();
+ };
+ inline int TEST = 42;
+ }
+}
+)--";
+ h2 = R"--(
+#include "header1.h"
+using namespace tes@t;
+)--";
+ h3 = R"--(
+#include "header2.h"
+Foo f1;
+test::Foo f2;
+using T1 = Foo;
+using T2 = test::Foo;
+int i1 = TEST;
+int i2 = test::TEST;
+void Foo::foo1(){};
+void test::Foo::foo2(){};
+)--";
+
+ expected1 = h1;
+ expected2 = R"--(
+#include "header1.h"
+)--";
+ expected3 = R"--(
+#include "header2.h"
+test::Foo f1;
+test::Foo f2;
+using T1 = test::Foo;
+using T2 = test::Foo;
+int i1 = test::TEST;
+int i2 = test::TEST;
+void test::Foo::foo1(){};
+void test::Foo::foo2(){};
+)--";
+ QTest::newRow("don't insert inline namespaces")
+ << h1 << h2 << h3 << expected1 << expected2 << expected3 << 0;
+ }
+
+ void test()
+ {
+ QFETCH(QByteArray, header1);
+ QFETCH(QByteArray, header2);
+ QFETCH(QByteArray, header3);
+ QFETCH(QByteArray, expected1);
+ QFETCH(QByteArray, expected2);
+ QFETCH(QByteArray, expected3);
+ QFETCH(int, operation);
+
+ QList<TestDocumentPtr> testDocuments;
+ testDocuments << CppTestDocument::create("header1.h", header1, expected1);
+ testDocuments << CppTestDocument::create("header2.h", header2, expected2);
+ testDocuments << CppTestDocument::create("header3.h", header3, expected3);
+
+ RemoveUsingNamespace factory;
+ QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), operation);
+ }
+
+ void testSimple_data()
+ {
+ QTest::addColumn<QByteArray>("header");
+ QTest::addColumn<QByteArray>("expected");
+
+ const QByteArray common = R"--(
+namespace N{
+ template<typename T>
+ struct vector{
+ using iterator = T*;
+ };
+ using int_vector = vector<int>;
+}
+)--";
+ const QByteArray header = common + R"--(
+using namespace N@;
+int_vector ints;
+int_vector::iterator intIter;
+using vec = vector<int>;
+vec::iterator it;
+)--";
+ const QByteArray expected = common + R"--(
+N::int_vector ints;
+N::int_vector::iterator intIter;
+using vec = N::vector<int>;
+vec::iterator it;
+)--";
+
+ QTest::newRow("nested typedefs with Namespace") << header << expected;
+ }
+
+ void testSimple()
+ {
+ QFETCH(QByteArray, header);
+ QFETCH(QByteArray, expected);
+
+ QList<TestDocumentPtr> testDocuments;
+ testDocuments << CppTestDocument::create("header.h", header, expected);
+
+ RemoveUsingNamespace factory;
+ QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths());
+ }
+
+ void testDifferentSymbols()
+ {
+ QByteArray header = "namespace test{\n"
+ " struct foo{\n"
+ " foo();\n"
+ " void bar();\n"
+ " };\n"
+ " void func();\n"
+ " enum E {E1, E2};\n"
+ " int bar;\n"
+ "}\n"
+ "using namespace t@est;\n"
+ "foo::foo(){}\n"
+ "void foo::bar(){}\n"
+ "void test(){\n"
+ " int i = bar * 4;\n"
+ " E val = E1;\n"
+ " auto p = &foo::bar;\n"
+ " func()\n"
+ "}\n";
+ QByteArray expected = "namespace test{\n"
+ " struct foo{\n"
+ " foo();\n"
+ " void bar();\n"
+ " };\n"
+ " void func();\n"
+ " enum E {E1, E2};\n"
+ " int bar;\n"
+ "}\n"
+ "test::foo::foo(){}\n"
+ "void test::foo::bar(){}\n"
+ "void test(){\n"
+ " int i = test::bar * 4;\n"
+ " test::E val = test::E1;\n"
+ " auto p = &test::foo::bar;\n"
+ " test::func()\n"
+ "}\n";
+
+ QList<TestDocumentPtr> testDocuments;
+ testDocuments << CppTestDocument::create("file.h", header, expected);
+ RemoveUsingNamespace factory;
+ QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 0);
+ }
+};
+
+QObject *RemoveUsingNamespace::createTest() { return new RemoveUsingNamespaceTest; }
+#endif // WITH_TESTS
+
+} // namespace
+
+void registerRemoveUsingNamespaceQuickfix()
+{
+ CppQuickFixFactory::registerFactory<RemoveUsingNamespace>();
+}
+
+} // namespace CppEditor::Internal
+
+#ifdef WITH_TESTS
+#include <removeusingnamespace.moc>
+#endif
diff --git a/src/plugins/cppeditor/quickfixes/removeusingnamespace.h b/src/plugins/cppeditor/quickfixes/removeusingnamespace.h
new file mode 100644
index 0000000000..149cbe4d09
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/removeusingnamespace.h
@@ -0,0 +1,8 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+namespace CppEditor::Internal {
+void registerRemoveUsingNamespaceQuickfix();
+} // namespace CppEditor::Internal
diff --git a/src/plugins/cppeditor/quickfixes/rewritecomment.cpp b/src/plugins/cppeditor/quickfixes/rewritecomment.cpp
new file mode 100644
index 0000000000..3ac7fede2c
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/rewritecomment.cpp
@@ -0,0 +1,878 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "rewritecomment.h"
+
+#include "../cppeditortr.h"
+#include "../cppeditorwidget.h"
+#include "../cpprefactoringchanges.h"
+#include "cppquickfix.h"
+
+#include <cplusplus/ASTPath.h>
+#include <cplusplus/declarationcomments.h>
+#include <projectexplorer/editorconfiguration.h>
+#include <texteditor/tabsettings.h>
+#include <texteditor/textdocument.h>
+
+#ifdef WITH_TESTS
+#include "cppquickfix_test.h"
+#include <QtTest>
+#endif
+
+using namespace CPlusPlus;
+using namespace TextEditor;
+using namespace Utils;
+
+namespace CppEditor::Internal {
+namespace {
+
+class ConvertCommentStyleOp : public CppQuickFixOperation
+{
+public:
+ ConvertCommentStyleOp(const CppQuickFixInterface &interface, const QList<Token> &tokens,
+ Kind kind)
+ : CppQuickFixOperation(interface),
+ m_tokens(tokens),
+ m_kind(kind),
+ m_wasCxxStyle(m_kind == T_CPP_COMMENT || m_kind == T_CPP_DOXY_COMMENT),
+ m_isDoxygen(m_kind == T_DOXY_COMMENT || m_kind == T_CPP_DOXY_COMMENT)
+ {
+ setDescription(m_wasCxxStyle ? Tr::tr("Convert Comment to C-Style")
+ : Tr::tr("Convert Comment to C++-Style"));
+ }
+
+private:
+ // Turns every line of a C-style comment into a C++-style comment and vice versa.
+ // For C++ -> C, we use one /* */ comment block per line. However, doxygen
+ // requires a single comment, so there we just replace the prefix with whitespace and
+ // add the start and end comment in extra lines.
+ // For cosmetic reasons, we offer some convenience functionality:
+ // - Turn /***** ... into ////// ... and vice versa
+ // - With C -> C++, remove leading asterisks.
+ // - With C -> C++, remove the first and last line of a block if they have no content
+ // other than the comment start and end characters.
+ // - With C++ -> C, try to align the end comment characters.
+ // These are obviously heuristics; we do not guarantee perfect results for everybody.
+ // We also don't second-guess the users's selection: E.g. if there is an empty
+ // line between the tokens, then it's not the same doxygen comment, but we merge
+ // it anyway in C++ to C mode.
+ void perform() override
+ {
+ TranslationUnit * const tu = currentFile()->cppDocument()->translationUnit();
+ const QString newCommentStart = getNewCommentStart();
+ ChangeSet changeSet;
+ int endCommentColumn = -1;
+ const QChar oldFillChar = m_wasCxxStyle ? '/' : '*';
+ const QChar newFillChar = m_wasCxxStyle ? '*' : '/';
+
+ for (const Token &token : m_tokens) {
+ const int startPos = tu->getTokenPositionInDocument(token, textDocument());
+ const int endPos = tu->getTokenEndPositionInDocument(token, textDocument());
+
+ if (m_wasCxxStyle && m_isDoxygen) {
+ // Replace "///" characters with whitespace (to keep alignment).
+ // The insertion of "/*" and "*/" is done once after the loop.
+ changeSet.replace(startPos, startPos + 3, " ");
+ continue;
+ }
+
+ const QTextBlock firstBlock = textDocument()->findBlock(startPos);
+ const QTextBlock lastBlock = textDocument()->findBlock(endPos);
+ for (QTextBlock block = firstBlock; block.isValid() && block.position() <= endPos;
+ block = block.next()) {
+ const QString &blockText = block.text();
+ const int firstColumn = block == firstBlock ? startPos - block.position() : 0;
+ const int endColumn = block == lastBlock ? endPos - block.position()
+ : block.length();
+
+ // Returns true if the current line looks like "/********/" or "//////////",
+ // as is often the case at the start and end of comment blocks.
+ const auto fillChecker = [&] {
+ if (m_isDoxygen)
+ return false;
+ QString textToCheck = blockText;
+ if (block == firstBlock)
+ textToCheck.remove(0, 1);
+ if (block == lastBlock)
+ textToCheck.chop(block.length() - endColumn);
+ return Utils::allOf(textToCheck, [oldFillChar](const QChar &c)
+ { return c == oldFillChar || c == ' ';
+ }) && textToCheck.count(oldFillChar) > 2;
+ };
+
+ // Returns the index of the first character of actual comment content,
+ // as opposed to visual stuff like slashes, stars or whitespace.
+ const auto indexOfActualContent = [&] {
+ const int offset = block == firstBlock ? firstColumn + newCommentStart.length()
+ : firstColumn;
+
+ for (int i = offset, lastFillChar = -1; i < blockText.length(); ++i) {
+ if (blockText.at(i) == oldFillChar) {
+ lastFillChar = i;
+ continue;
+ }
+ if (!blockText.at(i).isSpace())
+ return lastFillChar + 1;
+ }
+ return -1;
+ };
+
+ if (fillChecker()) {
+ const QString replacement = QString(endColumn - 1 - firstColumn, newFillChar);
+ changeSet.replace(block.position() + firstColumn,
+ block.position() + endColumn - 1,
+ replacement);
+ if (m_wasCxxStyle) {
+ changeSet.replace(block.position() + firstColumn,
+ block.position() + firstColumn + 1, "/");
+ changeSet.insert(block.position() + endColumn - 1, "*");
+ endCommentColumn = endColumn - 1;
+ }
+ continue;
+ }
+
+ // Remove leading noise or even the entire block, if applicable.
+ const bool blockIsRemovable = (block == firstBlock || block == lastBlock)
+ && firstBlock != lastBlock;
+ const auto removeBlock = [&] {
+ changeSet.remove(block.position() + firstColumn, block.position() + endColumn);
+ };
+ const int contentIndex = indexOfActualContent();
+ int removed = 0;
+ if (contentIndex == -1) {
+ if (blockIsRemovable) {
+ removeBlock();
+ continue;
+ } else if (!m_wasCxxStyle) {
+ changeSet.replace(block.position() + firstColumn,
+ block.position() + endColumn - 1, newCommentStart);
+ continue;
+ }
+ } else if (block == lastBlock && contentIndex == endColumn - 1) {
+ if (blockIsRemovable) {
+ removeBlock();
+ break;
+ }
+ } else {
+ changeSet.remove(block.position() + firstColumn,
+ block.position() + firstColumn + contentIndex);
+ removed = contentIndex;
+ }
+
+ if (block == firstBlock) {
+ changeSet.replace(startPos, startPos + newCommentStart.length(),
+ newCommentStart);
+ } else {
+ // If the line starts with enough whitespace, replace it with the
+ // comment start characters, so we don't move the content to the right
+ // unnecessarily. Otherwise, insert the comment start characters.
+ if (blockText.startsWith(QString(newCommentStart.size() + removed + 1, ' '))) {
+ changeSet.replace(block.position(),
+ block.position() + newCommentStart.length(),
+ newCommentStart);
+ } else {
+ changeSet.insert(block.position(), newCommentStart);
+ }
+ }
+
+ if (block == lastBlock) {
+ if (m_wasCxxStyle) {
+ // This is for proper alignment of the end comment character.
+ if (endCommentColumn != -1) {
+ const int endCommentPos = block.position() + endCommentColumn;
+ if (endPos < endCommentPos)
+ changeSet.insert(endPos, QString(endCommentPos - endPos - 1, ' '));
+ }
+ changeSet.insert(endPos, " */");
+ } else {
+ changeSet.remove(endPos - 2, endPos);
+ }
+ }
+ }
+ }
+
+ if (m_wasCxxStyle && m_isDoxygen) {
+ const int startPos = tu->getTokenPositionInDocument(m_tokens.first(), textDocument());
+ const int endPos = tu->getTokenEndPositionInDocument(m_tokens.last(), textDocument());
+ changeSet.insert(startPos, "/*!\n");
+ changeSet.insert(endPos, "\n*/");
+ }
+
+ changeSet.apply(textDocument());
+ }
+
+ QString getNewCommentStart() const
+ {
+ if (m_wasCxxStyle) {
+ if (m_isDoxygen)
+ return "/*!";
+ return "/*";
+ }
+ if (m_isDoxygen)
+ return "//!";
+ return "//";
+ }
+
+ const QList<Token> m_tokens;
+ const Kind m_kind;
+ const bool m_wasCxxStyle;
+ const bool m_isDoxygen;
+};
+
+class MoveFunctionCommentsOp : public CppQuickFixOperation
+{
+public:
+ enum class Direction { ToDecl, ToDef };
+ MoveFunctionCommentsOp(const CppQuickFixInterface &interface, const Symbol *symbol,
+ const QList<Token> &commentTokens, Direction direction)
+ : CppQuickFixOperation(interface), m_symbol(symbol), m_commentTokens(commentTokens)
+ {
+ setDescription(direction == Direction::ToDecl
+ ? Tr::tr("Move Function Documentation to Declaration")
+ : Tr::tr("Move Function Documentation to Definition"));
+ }
+
+private:
+ void perform() override
+ {
+ const CppRefactoringFilePtr file = currentFile();
+ const auto textDoc = const_cast<QTextDocument *>(file->document());
+ const int pos = file->cppDocument()->translationUnit()->getTokenPositionInDocument(
+ m_symbol->sourceLocation(), textDoc);
+ QTextCursor cursor(textDoc);
+ cursor.setPosition(pos);
+ const CursorInEditor cursorInEditor(cursor, file->filePath(), editor(),
+ editor()->textDocument());
+ const auto callback = [symbolLoc = m_symbol->toLink(), comments = m_commentTokens, file]
+ (const Link &link) {
+ moveComments(file, link, symbolLoc, comments);
+ };
+ NonInteractiveFollowSymbolMarker niMarker;
+ CppCodeModelSettings::setInteractiveFollowSymbol(false);
+ CppModelManager::followSymbol(cursorInEditor, callback, true, false,
+ FollowSymbolMode::Exact);
+ }
+
+ static void moveComments(
+ const CppRefactoringFilePtr &sourceFile,
+ const Link &targetLoc,
+ const Link &symbolLoc,
+ const QList<Token> &comments)
+ {
+ if (!targetLoc.hasValidTarget() || targetLoc.hasSameLocation(symbolLoc))
+ return;
+
+ CppRefactoringChanges changes(CppModelManager::snapshot());
+ const CppRefactoringFilePtr targetFile
+ = targetLoc.targetFilePath == symbolLoc.targetFilePath
+ ? sourceFile
+ : changes.cppFile(targetLoc.targetFilePath);
+ const Document::Ptr &targetCppDoc = targetFile->cppDocument();
+ const QList<AST *> targetAstPath = ASTPath(targetCppDoc)(
+ targetLoc.targetLine, targetLoc.targetColumn + 1);
+ if (targetAstPath.isEmpty())
+ return;
+ const AST *targetDeclAst = nullptr;
+ for (auto it = std::next(std::rbegin(targetAstPath));
+ it != std::rend(targetAstPath); ++it) {
+ AST * const node = *it;
+ if (node->asDeclaration()) {
+ targetDeclAst = node;
+ continue;
+ }
+ if (targetDeclAst)
+ break;
+ }
+ if (!targetDeclAst)
+ return;
+ const int insertionPos = targetCppDoc->translationUnit()->getTokenPositionInDocument(
+ targetDeclAst->firstToken(), targetFile->document());
+ const TranslationUnit * const sourceTu = sourceFile->cppDocument()->translationUnit();
+ const int sourceCommentStartPos = sourceTu->getTokenPositionInDocument(
+ comments.first(), sourceFile->document());
+ const int sourceCommentEndPos = sourceTu->getTokenEndPositionInDocument(
+ comments.last(), sourceFile->document());
+
+ // Manually adjust indentation, as both our built-in indenter and ClangFormat
+ // are unreliable with regards to comment continuation lines.
+ auto tabSettings = [](CppRefactoringFilePtr file) {
+ if (auto editor = file->editor())
+ return editor->textDocument()->tabSettings();
+ return ProjectExplorer::actualTabSettings(file->filePath(), nullptr);
+ };
+ const TabSettings &sts = tabSettings(sourceFile);
+ const TabSettings &tts = tabSettings(targetFile);
+ const QTextBlock insertionBlock = targetFile->document()->findBlock(insertionPos);
+ const int insertionColumn = tts.columnAt(insertionBlock.text(),
+ insertionPos - insertionBlock.position());
+ const QTextBlock removalBlock = sourceFile->document()->findBlock(sourceCommentStartPos);
+ const QTextBlock removalBlockEnd = sourceFile->document()->findBlock(sourceCommentEndPos);
+ const int removalColumn = sts.columnAt(removalBlock.text(),
+ sourceCommentStartPos - removalBlock.position());
+ const int columnOffset = insertionColumn - removalColumn;
+ QString functionDoc;
+ if (columnOffset != 0) {
+ for (QTextBlock block = removalBlock;
+ block.isValid() && block != removalBlockEnd.next();
+ block = block.next()) {
+ QString text = block.text() + QChar::ParagraphSeparator;
+ if (block == removalBlockEnd)
+ text = text.left(sourceCommentEndPos - block.position());
+ if (block == removalBlock) {
+ text = text.mid(sourceCommentStartPos - block.position());
+ } else {
+ int lineIndentColumn = sts.indentationColumn(text) + columnOffset;
+ text.replace(0,
+ TabSettings::firstNonSpace(text),
+ tts.indentationString(0, lineIndentColumn, 0, insertionBlock));
+ }
+ functionDoc += text;
+ }
+ } else {
+ functionDoc = sourceFile->textOf(sourceCommentStartPos, sourceCommentEndPos);
+ }
+
+ // Remove comment plus leading and trailing whitespace, including trailing newline.
+ const auto removeAtSource = [&](ChangeSet &changeSet) {
+ int removalPos = sourceCommentStartPos;
+ const QChar newline(QChar::ParagraphSeparator);
+ while (true) {
+ const int prev = removalPos - 1;
+ if (prev < 0)
+ break;
+ const QChar prevChar = sourceFile->charAt(prev);
+ if (!prevChar.isSpace() || prevChar == newline)
+ break;
+ removalPos = prev;
+ }
+ int removalEndPos = sourceCommentEndPos;
+ while (true) {
+ if (removalEndPos == sourceFile->document()->characterCount())
+ break;
+ const QChar nextChar = sourceFile->charAt(removalEndPos);
+ if (!nextChar.isSpace())
+ break;
+ ++removalEndPos;
+ if (nextChar == newline)
+ break;
+ }
+ changeSet.remove(removalPos, removalEndPos);
+ };
+
+ ChangeSet targetChangeSet;
+ targetChangeSet.insert(insertionPos, functionDoc);
+ targetChangeSet.insert(insertionPos, "\n");
+ targetChangeSet.insert(insertionPos, QString(insertionColumn, ' '));
+ if (targetFile == sourceFile)
+ removeAtSource(targetChangeSet);
+ const bool targetFileSuccess = targetFile->apply(targetChangeSet);
+ if (targetFile == sourceFile || !targetFileSuccess)
+ return;
+ ChangeSet sourceChangeSet;
+ removeAtSource(sourceChangeSet);
+ sourceFile->apply(sourceChangeSet);
+ }
+
+ const Symbol * const m_symbol;
+ const QList<Token> m_commentTokens;
+};
+
+//! Converts C-style to C++-style comments and vice versa
+class ConvertCommentStyle : public CppQuickFixFactory
+{
+#ifdef WITH_TESTS
+public:
+ static QObject* createTest();
+#endif
+
+private:
+ void doMatch(const CppQuickFixInterface &interface,
+ TextEditor::QuickFixOperations &result) override
+ {
+ // If there's a selection, then it must entirely consist of comment tokens.
+ // If there's no selection, the cursor must be on a comment.
+ const QList<Token> &cursorTokens = interface.currentFile()->tokensForCursor();
+ if (cursorTokens.empty())
+ return;
+ if (!cursorTokens.front().isComment())
+ return;
+
+ // All tokens must be the same kind of comment, but we make an exception for doxygen comments
+ // that start with "///", as these are often not intended to be doxygen. For our purposes,
+ // we treat them as normal comments.
+ const auto effectiveKind = [&interface](const Token &token) {
+ if (token.kind() != T_CPP_DOXY_COMMENT)
+ return token.kind();
+ TranslationUnit * const tu = interface.currentFile()->cppDocument()->translationUnit();
+ const int startPos = tu->getTokenPositionInDocument(token, interface.textDocument());
+ const QString commentStart = interface.textAt(startPos, 3);
+ return commentStart == "///" ? T_CPP_COMMENT : T_CPP_DOXY_COMMENT;
+ };
+ const Kind kind = effectiveKind(cursorTokens.first());
+ for (int i = 1; i < cursorTokens.count(); ++i) {
+ if (effectiveKind(cursorTokens.at(i)) != kind)
+ return;
+ }
+
+ // Ok, all tokens are of same(ish) comment type, offer quickfix.
+ result << new ConvertCommentStyleOp(interface, cursorTokens, kind);
+ }
+};
+
+//! Moves function documentation between declaration and implementation.
+class MoveFunctionComments : public CppQuickFixFactory
+{
+#ifdef WITH_TESTS
+public:
+ static QObject* createTest();
+#endif
+
+private:
+ void doMatch(const CppQuickFixInterface &interface,
+ TextEditor::QuickFixOperations &result) override
+ {
+ const QList<AST *> &astPath = interface.path();
+ if (astPath.isEmpty())
+ return;
+ const Symbol *symbol = nullptr;
+ MoveFunctionCommentsOp::Direction direction = MoveFunctionCommentsOp::Direction::ToDecl;
+ for (auto it = std::next(std::rbegin(astPath)); it != std::rend(astPath); ++it) {
+ if (const auto func = (*it)->asFunctionDefinition()) {
+ symbol = func->symbol;
+ direction = MoveFunctionCommentsOp::Direction::ToDecl;
+ break;
+ }
+ const auto decl = (*it)->asSimpleDeclaration();
+ if (!decl || !decl->declarator_list)
+ continue;
+ for (auto it = decl->declarator_list->begin();
+ !symbol && it != decl->declarator_list->end(); ++it) {
+ PostfixDeclaratorListAST * const funcDecls = (*it)->postfix_declarator_list;
+ if (!funcDecls)
+ continue;
+ for (auto it = funcDecls->begin(); it != funcDecls->end(); ++it) {
+ if (const auto func = (*it)->asFunctionDeclarator()) {
+ symbol = func->symbol;
+ direction = MoveFunctionCommentsOp::Direction::ToDef;
+ break;
+ }
+ }
+ }
+
+ }
+ if (!symbol)
+ return;
+
+ if (const QList<Token> commentTokens = commentsForDeclaration(
+ symbol, *interface.textDocument(), interface.currentFile()->cppDocument());
+ !commentTokens.isEmpty()) {
+ result << new MoveFunctionCommentsOp(interface, symbol, commentTokens, direction);
+ }
+ }
+};
+
+#ifdef WITH_TESTS
+using namespace Tests;
+
+class ConvertCommentStyleTest : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void test_data()
+ {
+ QTest::addColumn<QString>("input");
+ QTest::addColumn<QString>("expectedOutput");
+
+ QTest::newRow("C -> C++ / no selection / single line") << R"(
+int var1;
+/* Other comment, unaffected */
+/* Our @comment */
+/* Another unaffected comment */
+int var2;)" << R"(
+int var1;
+/* Other comment, unaffected */
+// Our comment
+/* Another unaffected comment */
+int var2;)";
+
+ QTest::newRow("C -> C++ / no selection / multi-line / preserved header and footer") << R"(
+/****************************************************
+ * some info
+ * more @info
+ ***************************************************/)" << R"(
+/////////////////////////////////////////////////////
+// some info
+// more info
+/////////////////////////////////////////////////////)";
+
+ QTest::newRow("C -> C++ / no selection / multi-line / non-preserved header and footer") << R"(
+/*
+ * some info
+ * more @info
+ */)" << R"(
+// some info
+// more info
+)";
+
+ QTest::newRow("C -> C++ / no selection / qdoc") << R"(
+/*!
+ \qmlproperty string Type::element.name
+ \qmlproperty int Type::element.id
+
+ \brief Holds the @element name and id.
+*/)" << R"(
+//! \qmlproperty string Type::element.name
+//! \qmlproperty @int Type::element.id
+//!
+//! \brief Holds the element name and id.
+)";
+
+ QTest::newRow("C -> C++ / no selection / doxygen") << R"(
+/*! \class Test
+ \brief A test class.
+
+ A more detailed @class description.
+*/)" << R"(
+//! \class Test
+//! \brief A test class.
+//!
+//! A more detailed class description.
+)";
+
+ QTest::newRow("C -> C++ / selection / single line") << R"(
+int var1;
+/* Other comment, unaffected */
+@{start}/* Our comment */@{end}
+/* Another unaffected comment */
+int var2;)" << R"(
+int var1;
+/* Other comment, unaffected */
+// Our comment
+/* Another unaffected comment */
+int var2;)";
+
+ QTest::newRow("C -> C++ / selection / multi-line / preserved header and footer") << R"(
+/****************************************************
+ * @{start}some info
+ * more info@{end}
+ ***************************************************/)" << R"(
+/////////////////////////////////////////////////////
+// some info
+// more info
+/////////////////////////////////////////////////////)";
+
+ QTest::newRow("C -> C++ / selection / multi-line / non-preserved header and footer") << R"(
+/*@{start}
+ * some in@{end}fo
+ * more info
+ */)" << R"(
+// some info
+// more info
+)";
+
+ QTest::newRow("C -> C++ / selection / qdoc") << R"(
+/*!@{start}
+ \qmlproperty string Type::element.name
+ \qmlproperty int Type::element.id
+
+ \brief Holds the element name and id.
+*/@{end})" << R"(
+//! \qmlproperty string Type::element.name
+//! \qmlproperty int Type::element.id
+//!
+//! \brief Holds the element name and id.
+)";
+
+ QTest::newRow("C -> C++ / selection / doxygen") << R"(
+/** Expand envi@{start}ronment variables in a string.
+ *
+ * Environment variables are accepted in the @{end}following forms:
+ * $SOMEVAR, ${SOMEVAR} on Unix and %SOMEVAR% on Windows.
+ * No escapes and quoting are supported.
+ * If a variable is not found, it is not substituted.
+ */)" << R"(
+//! Expand environment variables in a string.
+//!
+//! Environment variables are accepted in the following forms:
+//! $SOMEVAR, ${SOMEVAR} on Unix and %SOMEVAR% on Windows.
+//! No escapes and quoting are supported.
+//! If a variable is not found, it is not substituted.
+)";
+
+ QTest::newRow("C -> C++ / selection / multiple comments") << R"(
+@{start}/* Affected comment */
+/* Another affected comment */
+/* A third affected comment */@{end}
+/* An unaffected comment */)" << R"(
+// Affected comment
+// Another affected comment
+// A third affected comment
+/* An unaffected comment */)";
+
+ // FIXME: Remove adjacent newline along with last block
+ // FIXME: Use CppRefactoringFile to auto-indent continuation lines?
+ QTest::newRow("C -> C++, indented") << R"(
+struct S {
+ /*
+ * @This is an
+ * indented comment.
+ */
+ void func();
+)" << R"(
+struct S {
+ // This is an
+// indented comment.
+
+ void func();
+)";
+
+ QTest::newRow("C++ -> C / no selection / single line") << R"(
+// Other comment, unaffected
+// Our @comment
+// Another unaffected comment)" << R"(
+// Other comment, unaffected
+/* Our comment */
+// Another unaffected comment)";
+
+ QTest::newRow("C++ -> C / selection / single line") << R"(
+// Other comment, unaffected
+@{start}// Our comment@{end}
+// Another unaffected comment)" << R"(
+// Other comment, unaffected
+/* Our comment */
+// Another unaffected comment)";
+
+ QTest::newRow("C++ -> C / selection / multi-line / preserved header and footer") << R"(
+@{start}/////////////////////////////////////////////////////
+// some info
+// more info
+/////////////////////////////////////////////////////@{end})" << R"(
+/****************************************************/
+/* some info */
+/* more info */
+/****************************************************/)";
+
+ QTest::newRow("C++ -> C / selection / qdoc") << R"(
+@{start}//! \qmlproperty string Type::element.name
+//! \qmlproperty int Type::element.id
+//!
+//! \brief Holds the element name and id.@{end}
+)" << R"(
+/*!
+ \qmlproperty string Type::element.name
+ \qmlproperty int Type::element.id
+
+ \brief Holds the element name and id.
+*/
+)";
+
+ QTest::newRow("C++ -> C / selection / doxygen") << R"(
+@{start}//! \class Test
+//! \brief A test class.
+//!
+//! A more detailed class description.@{end}
+)" << R"(
+/*!
+ \class Test
+ \brief A test class.
+
+ A more detailed class description.
+*/
+)";
+ }
+
+ void test()
+ {
+ QFETCH(QString, input);
+ QFETCH(QString, expectedOutput);
+
+ ConvertCommentStyle factory;
+ QuickFixOperationTest(
+ {CppTestDocument::create("file.h", input.toUtf8(), expectedOutput.toUtf8())},
+ &factory);
+ }
+};
+
+class MoveFunctionCommentsTest : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void test_data()
+ {
+ QTest::addColumn<QByteArrayList>("headers");
+ QTest::addColumn<QByteArrayList>("sources");
+
+ const QByteArrayList headersFuncDecl2Def{R"(
+// Function comment
+void @aFunction();
+)", R"(
+void aFunction();
+)"};
+ const QByteArrayList sourcesFuncDecl2Def{R"(
+#include "file.h"
+
+void aFunction() {}
+)", R"(
+#include "file.h"
+
+// Function comment
+void aFunction() {}
+)"};
+ QTest::newRow("function: from decl to def") << headersFuncDecl2Def << sourcesFuncDecl2Def;
+
+ const QByteArrayList headersFuncDef2Decl{R"(
+void aFunction();
+)", R"(
+/* function */
+/* comment */
+void aFunction();
+)"};
+ const QByteArrayList sourcesFuncDef2Decl{R"(
+#include "file.h"
+
+/* function */
+/* comment */
+void a@Function() {}
+)", R"(
+#include "file.h"
+
+void aFunction() {}
+)"};
+ QTest::newRow("function: from def to decl") << headersFuncDef2Decl << sourcesFuncDef2Decl;
+
+ const QByteArrayList headersFuncNoDef{R"(
+// Function comment
+void @aFunction();
+)", R"(
+// Function comment
+void aFunction();
+)"};
+ QTest::newRow("function: no def") << headersFuncNoDef << QByteArrayList();
+
+ const QByteArrayList headersFuncNoDecl{R"(
+// Function comment
+inline void @aFunction() {}
+)", R"(
+// Function comment
+inline void aFunction() {}
+)"};
+ QTest::newRow("function: no decl") << headersFuncNoDecl << QByteArrayList();
+
+ const QByteArrayList headersFuncTemplateDecl2Def{R"(
+// Function comment
+template<typename T> T @aFunction();
+
+template<typename T> inline T aFunction() { return T(); }
+)", R"(
+template<typename T> T aFunction();
+
+// Function comment
+template<typename T> inline T aFunction() { return T(); }
+)"};
+ QTest::newRow("function template: from decl to def") << headersFuncTemplateDecl2Def
+ << QByteArrayList();
+
+ const QByteArrayList headersFuncTemplateDef2Decl{R"(
+template<typename T> T aFunction();
+
+// Function comment
+template<typename T> inline T @aFunction() { return T(); }
+)", R"(
+// Function comment
+template<typename T> T aFunction();
+
+template<typename T> inline T aFunction() { return T(); }
+)"};
+ QTest::newRow("function template: from def to decl") << headersFuncTemplateDef2Decl
+ << QByteArrayList();
+
+ const QByteArrayList headersMemberDecl2Def{R"(
+class C {
+ /**
+ * \brief Foo::aMember
+ */
+ void @aMember();
+)", R"(
+class C {
+ void aMember();
+)"};
+ const QByteArrayList sourcesMemberDecl2Def{R"(
+#include "file.h"
+
+void C::aMember() {}
+)", R"(
+#include "file.h"
+
+/**
+ * \brief Foo::aMember
+ */
+void C::aMember() {}
+)"};
+ QTest::newRow("member function: from decl to def") << headersMemberDecl2Def
+ << sourcesMemberDecl2Def;
+
+ const QByteArrayList headersMemberDef2Decl{R"(
+class C {
+ void aMember();
+)", R"(
+class C {
+ /**
+ * \brief Foo::aMember
+ */
+ void aMember();
+)"};
+ const QByteArrayList sourcesMemberDef2Decl{R"(
+#include "file.h"
+
+/**
+ * \brief Foo::aMember
+ */
+void C::aMember() {@}
+)", R"(
+#include "file.h"
+
+void C::aMember() {}
+)"};
+ QTest::newRow("member function: from def to decl") << headersMemberDef2Decl
+ << sourcesMemberDef2Decl;
+ }
+
+ void test()
+ {
+ QFETCH(QByteArrayList, headers);
+ QFETCH(QByteArrayList, sources);
+
+ QList<TestDocumentPtr> documents;
+ QCOMPARE(headers.size(), 2);
+ documents << CppTestDocument::create("file.h", headers.at(0), headers.at(1));
+ if (!sources.isEmpty()) {
+ QCOMPARE(sources.size(), 2);
+ documents << CppTestDocument::create("file.cpp", sources.at(0), sources.at(1));
+ }
+ MoveFunctionComments factory;
+ QByteArray failMessage;
+ if (QByteArray(QTest::currentDataTag()) == "function template: from def to decl")
+ failMessage = "decl/def switch doesn't work for templates";
+ QuickFixOperationTest(documents, &factory, {}, {}, failMessage);
+ }
+};
+
+QObject * ConvertCommentStyle::createTest() { return new ConvertCommentStyleTest; }
+QObject * MoveFunctionComments::createTest() { return new MoveFunctionCommentsTest; }
+
+#endif
+} // namespace
+
+void registerRewriteCommentQuickfixes()
+{
+ CppQuickFixFactory::registerFactory<ConvertCommentStyle>();
+ CppQuickFixFactory::registerFactory<MoveFunctionComments>();
+}
+
+} // namespace CppEditor::Internal
+
+#ifdef WITH_TESTS
+#include <rewritecomment.moc>
+#endif
diff --git a/src/plugins/cppeditor/quickfixes/rewritecomment.h b/src/plugins/cppeditor/quickfixes/rewritecomment.h
new file mode 100644
index 0000000000..50ad0af415
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/rewritecomment.h
@@ -0,0 +1,8 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+namespace CppEditor::Internal {
+void registerRewriteCommentQuickfixes();
+} // namespace CppEditor::Internal
diff --git a/src/plugins/cppeditor/quickfixes/rewritecontrolstatements.cpp b/src/plugins/cppeditor/quickfixes/rewritecontrolstatements.cpp
new file mode 100644
index 0000000000..2b9c2392df
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/rewritecontrolstatements.cpp
@@ -0,0 +1,1323 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "rewritecontrolstatements.h"
+
+#include "../cppcodestylesettings.h"
+#include "../cppeditortr.h"
+#include "../cppeditorwidget.h"
+#include "../cpprefactoringchanges.h"
+#include "cppquickfix.h"
+
+#include <cplusplus/Overview.h>
+#include <cplusplus/TypeOfExpression.h>
+
+#ifdef WITH_TESTS
+#include "cppquickfix_test.h"
+#include <QTest>
+#endif
+
+using namespace CPlusPlus;
+using namespace TextEditor;
+using namespace Utils;
+
+namespace CppEditor::Internal {
+namespace {
+
+template<typename Statement> Statement *asControlStatement(AST *node)
+{
+ if constexpr (std::is_same_v<Statement, IfStatementAST>)
+ return node->asIfStatement();
+ if constexpr (std::is_same_v<Statement, WhileStatementAST>)
+ return node->asWhileStatement();
+ if constexpr (std::is_same_v<Statement, ForStatementAST>)
+ return node->asForStatement();
+ if constexpr (std::is_same_v<Statement, RangeBasedForStatementAST>)
+ return node->asRangeBasedForStatement();
+ if constexpr (std::is_same_v<Statement, DoStatementAST>)
+ return node->asDoStatement();
+ return nullptr;
+}
+
+template<typename Statement>
+int triggerToken(const Statement *statement)
+{
+ if constexpr (std::is_same_v<Statement, IfStatementAST>)
+ return statement->if_token;
+ if constexpr (std::is_same_v<Statement, WhileStatementAST>)
+ return statement->while_token;
+ if constexpr (std::is_same_v<Statement, DoStatementAST>)
+ return statement->do_token;
+ if constexpr (std::is_same_v<Statement, ForStatementAST>
+ || std::is_same_v<Statement, RangeBasedForStatementAST>) {
+ return statement->for_token;
+ }
+}
+
+template<typename Statement>
+int tokenToInsertOpeningBraceAfter(const Statement *statement)
+{
+ if constexpr (std::is_same_v<Statement, DoStatementAST>)
+ return statement->do_token;
+ return statement->rparen_token;
+}
+
+template<typename Statement> class AddBracesToControlStatementOp : public CppQuickFixOperation
+{
+public:
+ AddBracesToControlStatementOp(const CppQuickFixInterface &interface,
+ const QList<Statement *> &statements,
+ StatementAST *elseStatement,
+ int elseToken)
+ : CppQuickFixOperation(interface, 0)
+ , m_statements(statements), m_elseStatement(elseStatement), m_elseToken(elseToken)
+ {
+ setDescription(Tr::tr("Add Curly Braces"));
+ }
+
+ void perform() override
+ {
+ ChangeSet changes;
+ for (Statement * const statement : m_statements) {
+ const int start = currentFile()->endOf(tokenToInsertOpeningBraceAfter(statement));
+ changes.insert(start, QLatin1String(" {"));
+ if constexpr (std::is_same_v<Statement, DoStatementAST>) {
+ const int end = currentFile()->startOf(statement->while_token);
+ changes.insert(end, QLatin1String("} "));
+ } else if constexpr (std::is_same_v<Statement, IfStatementAST>) {
+ if (statement->else_statement) {
+ changes.insert(currentFile()->startOf(statement->else_token), "} ");
+ } else {
+ changes.insert(currentFile()->endOf(statement->statement->lastToken() - 1),
+ "\n}");
+ }
+
+ } else {
+ const int end = currentFile()->endOf(statement->statement->lastToken() - 1);
+ changes.insert(end, QLatin1String("\n}"));
+ }
+ }
+ if (m_elseStatement) {
+ changes.insert(currentFile()->endOf(m_elseToken), " {");
+ changes.insert(currentFile()->endOf(m_elseStatement->lastToken() - 1), "\n}");
+ }
+
+ currentFile()->setChangeSet(changes);
+ currentFile()->apply();
+ }
+
+private:
+ const QList<Statement *> m_statements;
+ StatementAST * const m_elseStatement;
+ const int m_elseToken;
+};
+
+template<typename Statement>
+bool checkControlStatementsHelper(const CppQuickFixInterface &interface, QuickFixOperations &result)
+{
+ Statement * const statement = asControlStatement<Statement>(interface.path().last());
+ if (!statement)
+ return false;
+
+ QList<Statement *> statements;
+ if (interface.isCursorOn(triggerToken(statement)) && statement->statement
+ && !statement->statement->asCompoundStatement()) {
+ statements << statement;
+ }
+
+ StatementAST *elseStmt = nullptr;
+ int elseToken = 0;
+ if constexpr (std::is_same_v<Statement, IfStatementAST>) {
+ IfStatementAST *currentIfStmt = statement;
+ for (elseStmt = currentIfStmt->else_statement, elseToken = currentIfStmt->else_token;
+ elseStmt && (currentIfStmt = elseStmt->asIfStatement());
+ elseStmt = currentIfStmt->else_statement, elseToken = currentIfStmt->else_token) {
+ if (currentIfStmt->statement && !currentIfStmt->statement->asCompoundStatement())
+ statements << currentIfStmt;
+ }
+ if (elseStmt && (elseStmt->asIfStatement() || elseStmt->asCompoundStatement())) {
+ elseStmt = nullptr;
+ elseToken = 0;
+ }
+ }
+
+ if (!statements.isEmpty() || elseStmt)
+ result << new AddBracesToControlStatementOp(interface, statements, elseStmt, elseToken);
+ return true;
+}
+
+template<typename ...Statements>
+void checkControlStatements(const CppQuickFixInterface &interface, QuickFixOperations &result)
+{
+ (... || checkControlStatementsHelper<Statements>(interface, result));
+}
+
+class MoveDeclarationOutOfIfOp: public CppQuickFixOperation
+{
+public:
+ MoveDeclarationOutOfIfOp(const CppQuickFixInterface &interface)
+ : CppQuickFixOperation(interface)
+ {
+ setDescription(Tr::tr("Move Declaration out of Condition"));
+
+ reset();
+ }
+
+ void reset()
+ {
+ condition = mk.Condition();
+ pattern = mk.IfStatement(condition);
+ }
+
+ void perform() override
+ {
+ ChangeSet changes;
+
+ changes.copy(currentFile()->range(core), currentFile()->startOf(condition));
+
+ int insertPos = currentFile()->startOf(pattern);
+ changes.move(currentFile()->range(condition), insertPos);
+ changes.insert(insertPos, QLatin1String(";\n"));
+
+ currentFile()->apply(changes);
+ }
+
+ ASTMatcher matcher;
+ ASTPatternBuilder mk;
+ ConditionAST *condition = nullptr;
+ IfStatementAST *pattern = nullptr;
+ CoreDeclaratorAST *core = nullptr;
+};
+
+class MoveDeclarationOutOfWhileOp: public CppQuickFixOperation
+{
+public:
+ MoveDeclarationOutOfWhileOp(const CppQuickFixInterface &interface)
+ : CppQuickFixOperation(interface)
+ {
+ setDescription(Tr::tr("Move Declaration out of Condition"));
+ reset();
+ }
+
+ void reset()
+ {
+ condition = mk.Condition();
+ pattern = mk.WhileStatement(condition);
+ }
+
+ void perform() override
+ {
+ ChangeSet changes;
+
+ changes.insert(currentFile()->startOf(condition), QLatin1String("("));
+ changes.insert(currentFile()->endOf(condition), QLatin1String(") != 0"));
+
+ int insertPos = currentFile()->startOf(pattern);
+ const int conditionStart = currentFile()->startOf(condition);
+ changes.move(conditionStart, currentFile()->startOf(core), insertPos);
+ changes.copy(currentFile()->range(core), insertPos);
+ changes.insert(insertPos, QLatin1String(";\n"));
+
+ currentFile()->apply(changes);
+ }
+
+ ASTMatcher matcher;
+ ASTPatternBuilder mk;
+ ConditionAST *condition = nullptr;
+ WhileStatementAST *pattern = nullptr;
+ CoreDeclaratorAST *core = nullptr;
+};
+
+class SplitIfStatementOp: public CppQuickFixOperation
+{
+public:
+ SplitIfStatementOp(const CppQuickFixInterface &interface, int priority,
+ IfStatementAST *pattern, BinaryExpressionAST *condition)
+ : CppQuickFixOperation(interface, priority)
+ , pattern(pattern)
+ , condition(condition)
+ {
+ setDescription(Tr::tr("Split if Statement"));
+ }
+
+ void perform() override
+ {
+ const Token binaryToken = currentFile()->tokenAt(condition->binary_op_token);
+
+ if (binaryToken.is(T_AMPER_AMPER))
+ splitAndCondition();
+ else
+ splitOrCondition();
+ }
+
+ void splitAndCondition() const
+ {
+ ChangeSet changes;
+
+ int startPos = currentFile()->startOf(pattern);
+ changes.insert(startPos, QLatin1String("if ("));
+ changes.move(currentFile()->range(condition->left_expression), startPos);
+ changes.insert(startPos, QLatin1String(") {\n"));
+
+ const int lExprEnd = currentFile()->endOf(condition->left_expression);
+ changes.remove(lExprEnd, currentFile()->startOf(condition->right_expression));
+ changes.insert(currentFile()->endOf(pattern), QLatin1String("\n}"));
+
+ currentFile()->apply(changes);
+ }
+
+ void splitOrCondition() const
+ {
+ ChangeSet changes;
+
+ StatementAST *ifTrueStatement = pattern->statement;
+ CompoundStatementAST *compoundStatement = ifTrueStatement->asCompoundStatement();
+
+ int insertPos = currentFile()->endOf(ifTrueStatement);
+ if (compoundStatement)
+ changes.insert(insertPos, QLatin1String(" "));
+ else
+ changes.insert(insertPos, QLatin1String("\n"));
+ changes.insert(insertPos, QLatin1String("else if ("));
+
+ const int rExprStart = currentFile()->startOf(condition->right_expression);
+ changes.move(rExprStart, currentFile()->startOf(pattern->rparen_token), insertPos);
+ changes.insert(insertPos, QLatin1String(")"));
+
+ const int rParenEnd = currentFile()->endOf(pattern->rparen_token);
+ changes.copy(rParenEnd, currentFile()->endOf(pattern->statement), insertPos);
+
+ const int lExprEnd = currentFile()->endOf(condition->left_expression);
+ changes.remove(lExprEnd, currentFile()->startOf(condition->right_expression));
+
+ currentFile()->apply(changes);
+ }
+
+private:
+ IfStatementAST *pattern;
+ BinaryExpressionAST *condition;
+};
+
+class OptimizeForLoopOperation: public CppQuickFixOperation
+{
+public:
+ OptimizeForLoopOperation(const CppQuickFixInterface &interface, const ForStatementAST *forAst,
+ const bool optimizePostcrement, const ExpressionAST *expression,
+ const FullySpecifiedType &type)
+ : CppQuickFixOperation(interface)
+ , m_forAst(forAst)
+ , m_optimizePostcrement(optimizePostcrement)
+ , m_expression(expression)
+ , m_type(type)
+ {
+ setDescription(Tr::tr("Optimize for-Loop"));
+ }
+
+ void perform() override
+ {
+ QTC_ASSERT(m_forAst, return);
+
+ const CppRefactoringFilePtr file = currentFile();
+ ChangeSet change;
+
+ // Optimize post (in|de)crement operator to pre (in|de)crement operator
+ if (m_optimizePostcrement && m_forAst->expression) {
+ PostIncrDecrAST *incrdecr = m_forAst->expression->asPostIncrDecr();
+ if (incrdecr && incrdecr->base_expression && incrdecr->incr_decr_token) {
+ change.flip(file->range(incrdecr->base_expression),
+ file->range(incrdecr->incr_decr_token));
+ }
+ }
+
+ // Optimize Condition
+ int renamePos = -1;
+ if (m_expression) {
+ QString varName = QLatin1String("total");
+
+ if (file->textOf(m_forAst->initializer).length() == 1) {
+ Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview();
+ const QString typeAndName = oo.prettyType(m_type, varName);
+ renamePos = file->endOf(m_forAst->initializer) - 1 + typeAndName.length();
+ change.insert(file->endOf(m_forAst->initializer) - 1, // "-1" because of ";"
+ typeAndName + QLatin1String(" = ") + file->textOf(m_expression));
+ } else {
+ // Check if varName is already used
+ if (DeclarationStatementAST *ds = m_forAst->initializer->asDeclarationStatement()) {
+ if (DeclarationAST *decl = ds->declaration) {
+ if (SimpleDeclarationAST *sdecl = decl->asSimpleDeclaration()) {
+ for (;;) {
+ bool match = false;
+ for (DeclaratorListAST *it = sdecl->declarator_list; it;
+ it = it->next) {
+ if (file->textOf(it->value->core_declarator) == varName) {
+ varName += QLatin1Char('X');
+ match = true;
+ break;
+ }
+ }
+ if (!match)
+ break;
+ }
+ }
+ }
+ }
+
+ renamePos = file->endOf(m_forAst->initializer) + 1;
+ change.insert(file->endOf(m_forAst->initializer) - 1, // "-1" because of ";"
+ QLatin1String(", ") + varName + QLatin1String(" = ")
+ + file->textOf(m_expression));
+ }
+
+ ChangeSet::Range exprRange(file->startOf(m_expression), file->endOf(m_expression));
+ change.replace(exprRange, varName);
+ }
+
+ file->apply(change);
+
+ // Select variable name and trigger symbol rename
+ if (renamePos != -1) {
+ QTextCursor c = file->cursor();
+ c.setPosition(renamePos);
+ editor()->setTextCursor(c);
+ editor()->renameSymbolUnderCursor();
+ c.select(QTextCursor::WordUnderCursor);
+ editor()->setTextCursor(c);
+ }
+ }
+
+private:
+ const ForStatementAST *m_forAst;
+ const bool m_optimizePostcrement;
+ const ExpressionAST *m_expression;
+ const FullySpecifiedType m_type;
+};
+
+/*!
+ Replace
+ if (Type name = foo()) {...}
+
+ With
+ Type name = foo();
+ if (name) {...}
+
+ Activates on: the name of the introduced variable
+*/
+class MoveDeclarationOutOfIf: public CppQuickFixFactory
+{
+#ifdef WITH_TESTS
+public:
+ static QObject *createTest();
+#endif
+
+private:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ const QList<AST *> &path = interface.path();
+ using Ptr = QSharedPointer<MoveDeclarationOutOfIfOp>;
+ Ptr op(new MoveDeclarationOutOfIfOp(interface));
+
+ int index = path.size() - 1;
+ for (; index != -1; --index) {
+ if (IfStatementAST *statement = path.at(index)->asIfStatement()) {
+ if (statement->match(op->pattern, &op->matcher) && op->condition->declarator) {
+ DeclaratorAST *declarator = op->condition->declarator;
+ op->core = declarator->core_declarator;
+ if (!op->core)
+ return;
+
+ if (interface.isCursorOn(op->core)) {
+ op->setPriority(index);
+ result.append(op);
+ return;
+ }
+
+ op->reset();
+ }
+ }
+ }
+ }
+};
+
+/*!
+ Replace
+ while (Type name = foo()) {...}
+
+ With
+ Type name;
+ while ((name = foo()) != 0) {...}
+
+ Activates on: the name of the introduced variable
+*/
+class MoveDeclarationOutOfWhile: public CppQuickFixFactory
+{
+#ifdef WITH_TESTS
+public:
+ static QObject *createTest();
+#endif
+
+private:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ const QList<AST *> &path = interface.path();
+ QSharedPointer<MoveDeclarationOutOfWhileOp> op(new MoveDeclarationOutOfWhileOp(interface));
+
+ int index = path.size() - 1;
+ for (; index != -1; --index) {
+ if (WhileStatementAST *statement = path.at(index)->asWhileStatement()) {
+ if (statement->match(op->pattern, &op->matcher) && op->condition->declarator) {
+ DeclaratorAST *declarator = op->condition->declarator;
+ op->core = declarator->core_declarator;
+
+ if (!op->core)
+ return;
+
+ if (!declarator->equal_token)
+ return;
+
+ if (!declarator->initializer)
+ return;
+
+ if (interface.isCursorOn(op->core)) {
+ op->setPriority(index);
+ result.append(op);
+ return;
+ }
+
+ op->reset();
+ }
+ }
+ }
+ }
+};
+
+/*!
+ Replace
+ if (something && something_else) {
+ }
+
+ with
+ if (something)
+ if (something_else) {
+ }
+ }
+
+ and
+ if (something || something_else)
+ x;
+
+ with
+ if (something)
+ x;
+ else if (something_else)
+ x;
+
+ Activates on: && or ||
+*/
+class SplitIfStatement: public CppQuickFixFactory
+{
+#ifdef WITH_TESTS
+public:
+ static QObject *createTest() { return new QObject; }
+#endif
+
+private:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ IfStatementAST *pattern = nullptr;
+ const QList<AST *> &path = interface.path();
+
+ int index = path.size() - 1;
+ for (; index != -1; --index) {
+ AST *node = path.at(index);
+ if (IfStatementAST *stmt = node->asIfStatement()) {
+ pattern = stmt;
+ break;
+ }
+ }
+
+ if (!pattern || !pattern->statement)
+ return;
+
+ unsigned splitKind = 0;
+ for (++index; index < path.size(); ++index) {
+ AST *node = path.at(index);
+ BinaryExpressionAST *condition = node->asBinaryExpression();
+ if (!condition)
+ return;
+
+ Token binaryToken = interface.currentFile()->tokenAt(condition->binary_op_token);
+
+ // only accept a chain of ||s or &&s - no mixing
+ if (!splitKind) {
+ splitKind = binaryToken.kind();
+ if (splitKind != T_AMPER_AMPER && splitKind != T_PIPE_PIPE)
+ return;
+ // we can't reliably split &&s in ifs with an else branch
+ if (splitKind == T_AMPER_AMPER && pattern->else_statement)
+ return;
+ } else if (splitKind != binaryToken.kind()) {
+ return;
+ }
+
+ if (interface.isCursorOn(condition->binary_op_token)) {
+ result << new SplitIfStatementOp(interface, index, pattern, condition);
+ return;
+ }
+ }
+ }
+};
+
+/*!
+ Add curly braces to a control statement that doesn't already contain a
+ compound statement. I.e.
+
+ if (a)
+ b;
+ becomes
+ if (a) {
+ b;
+ }
+
+ Activates on: the keyword
+*/
+class AddBracesToControlStatement : public CppQuickFixFactory
+{
+#ifdef WITH_TESTS
+public:
+ static QObject *createTest();
+#endif
+
+private:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ if (interface.path().isEmpty())
+ return;
+ checkControlStatements<IfStatementAST,
+ WhileStatementAST,
+ ForStatementAST,
+ RangeBasedForStatementAST,
+ DoStatementAST>(interface, result);
+ }
+};
+
+/*!
+ Optimizes a for loop to avoid permanent condition check and forces to use preincrement
+ or predecrement operators in the expression of the for loop.
+ */
+class OptimizeForLoop : public CppQuickFixFactory
+{
+#ifdef WITH_TESTS
+public:
+ static QObject *createTest();
+#endif
+
+private:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ const QList<AST *> path = interface.path();
+ ForStatementAST *forAst = nullptr;
+ if (!path.isEmpty())
+ forAst = path.last()->asForStatement();
+ if (!forAst || !interface.isCursorOn(forAst))
+ return;
+
+ // Check for optimizing a postcrement
+ const CppRefactoringFilePtr file = interface.currentFile();
+ bool optimizePostcrement = false;
+ if (forAst->expression) {
+ if (PostIncrDecrAST *incrdecr = forAst->expression->asPostIncrDecr()) {
+ const Token t = file->tokenAt(incrdecr->incr_decr_token);
+ if (t.is(T_PLUS_PLUS) || t.is(T_MINUS_MINUS))
+ optimizePostcrement = true;
+ }
+ }
+
+ // Check for optimizing condition
+ bool optimizeCondition = false;
+ FullySpecifiedType conditionType;
+ ExpressionAST *conditionExpression = nullptr;
+ if (forAst->initializer && forAst->condition) {
+ if (BinaryExpressionAST *binary = forAst->condition->asBinaryExpression()) {
+ // Get the expression against which we should evaluate
+ IdExpressionAST *conditionId = binary->left_expression->asIdExpression();
+ if (conditionId) {
+ conditionExpression = binary->right_expression;
+ } else {
+ conditionId = binary->right_expression->asIdExpression();
+ conditionExpression = binary->left_expression;
+ }
+
+ if (conditionId && conditionExpression
+ && !(conditionExpression->asNumericLiteral()
+ || conditionExpression->asStringLiteral()
+ || conditionExpression->asIdExpression()
+ || conditionExpression->asUnaryExpression())) {
+ // Determine type of for initializer
+ FullySpecifiedType initializerType;
+ if (DeclarationStatementAST *stmt = forAst->initializer->asDeclarationStatement()) {
+ if (stmt->declaration) {
+ if (SimpleDeclarationAST *decl = stmt->declaration->asSimpleDeclaration()) {
+ if (decl->symbols) {
+ if (Symbol *symbol = decl->symbols->value)
+ initializerType = symbol->type();
+ }
+ }
+ }
+ }
+
+ // Determine type of for condition
+ TypeOfExpression typeOfExpression;
+ typeOfExpression.init(interface.semanticInfo().doc, interface.snapshot(),
+ interface.context().bindings());
+ typeOfExpression.setExpandTemplates(true);
+ Scope *scope = file->scopeAt(conditionId->firstToken());
+ const QList<LookupItem> conditionItems = typeOfExpression(
+ conditionId, interface.semanticInfo().doc, scope);
+ if (!conditionItems.isEmpty())
+ conditionType = conditionItems.first().type();
+
+ if (conditionType.isValid()
+ && (file->textOf(forAst->initializer) == QLatin1String(";")
+ || initializerType == conditionType)) {
+ optimizeCondition = true;
+ }
+ }
+ }
+ }
+
+ if (optimizePostcrement || optimizeCondition) {
+ result << new OptimizeForLoopOperation(interface, forAst, optimizePostcrement,
+ optimizeCondition ? conditionExpression : nullptr,
+ conditionType);
+ }
+ }
+};
+
+#ifdef WITH_TESTS
+using namespace Tests;
+
+class MoveDeclarationOutOfIfTest : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void test_data()
+ {
+ QTest::addColumn<QByteArray>("original");
+ QTest::addColumn<QByteArray>("expected");
+
+ QTest::newRow("ifOnly")
+ << QByteArray(
+ "void f()\n"
+ "{\n"
+ " if (Foo *@foo = g())\n"
+ " h();\n"
+ "}\n")
+ << QByteArray(
+ "void f()\n"
+ "{\n"
+ " Foo *foo = g();\n"
+ " if (foo)\n"
+ " h();\n"
+ "}\n");
+ QTest::newRow("ifElse")
+ << QByteArray(
+ "void f()\n"
+ "{\n"
+ " if (Foo *@foo = g())\n"
+ " h();\n"
+ " else\n"
+ " i();\n"
+ "}\n")
+ << QByteArray(
+ "void f()\n"
+ "{\n"
+ " Foo *foo = g();\n"
+ " if (foo)\n"
+ " h();\n"
+ " else\n"
+ " i();\n"
+ "}\n");
+
+ QTest::newRow("MoveDeclarationOutOfIf_ifElseIf")
+ << QByteArray(
+ "void f()\n"
+ "{\n"
+ " if (Foo *foo = g()) {\n"
+ " if (Bar *@bar = x()) {\n"
+ " h();\n"
+ " j();\n"
+ " }\n"
+ " } else {\n"
+ " i();\n"
+ " }\n"
+ "}\n")
+ << QByteArray(
+ "void f()\n"
+ "{\n"
+ " if (Foo *foo = g()) {\n"
+ " Bar *bar = x();\n"
+ " if (bar) {\n"
+ " h();\n"
+ " j();\n"
+ " }\n"
+ " } else {\n"
+ " i();\n"
+ " }\n"
+ "}\n");
+ }
+
+ void test()
+ {
+ QFETCH(QByteArray, original);
+ QFETCH(QByteArray, expected);
+ MoveDeclarationOutOfIf factory;
+ QuickFixOperationTest(singleDocument(original, expected), &factory);
+ }
+};
+
+class MoveDeclarationOutOfWhileTest : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void test_data()
+ {
+ QTest::addColumn<QByteArray>("original");
+ QTest::addColumn<QByteArray>("expected");
+
+ QTest::newRow("singleWhile")
+ << QByteArray(
+ "void f()\n"
+ "{\n"
+ " while (Foo *@foo = g())\n"
+ " j();\n"
+ "}\n")
+ << QByteArray(
+ "void f()\n"
+ "{\n"
+ " Foo *foo;\n"
+ " while ((foo = g()) != 0)\n"
+ " j();\n"
+ "}\n");
+ QTest::newRow("whileInWhile")
+ << QByteArray(
+ "void f()\n"
+ "{\n"
+ " while (Foo *foo = g()) {\n"
+ " while (Bar *@bar = h()) {\n"
+ " i();\n"
+ " j();\n"
+ " }\n"
+ " }\n"
+ "}\n")
+ << QByteArray(
+ "void f()\n"
+ "{\n"
+ " while (Foo *foo = g()) {\n"
+ " Bar *bar;\n"
+ " while ((bar = h()) != 0) {\n"
+ " i();\n"
+ " j();\n"
+ " }\n"
+ " }\n"
+ "}\n"
+ );
+
+ }
+
+ void test()
+ {
+ QFETCH(QByteArray, original);
+ QFETCH(QByteArray, expected);
+ MoveDeclarationOutOfWhile factory;
+ QuickFixOperationTest(singleDocument(original, expected), &factory);
+ }
+};
+
+class OptimizeForLoopTest : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void test_data()
+ {
+ QTest::addColumn<QByteArray>("original");
+ QTest::addColumn<QByteArray>("expected");
+
+ // Check: optimize postcrement
+ QTest::newRow("OptimizeForLoop_postcrement")
+ << QByteArray("void foo() {f@or (int i = 0; i < 3; i++) {}}\n")
+ << QByteArray("void foo() {for (int i = 0; i < 3; ++i) {}}\n");
+
+ // Check: optimize condition
+ QTest::newRow("OptimizeForLoop_condition")
+ << QByteArray("void foo() {f@or (int i = 0; i < 3 + 5; ++i) {}}\n")
+ << QByteArray("void foo() {for (int i = 0, total = 3 + 5; i < total; ++i) {}}\n");
+
+ // Check: optimize fliped condition
+ QTest::newRow("OptimizeForLoop_flipedCondition")
+ << QByteArray("void foo() {f@or (int i = 0; 3 + 5 > i; ++i) {}}\n")
+ << QByteArray("void foo() {for (int i = 0, total = 3 + 5; total > i; ++i) {}}\n");
+
+ // Check: if "total" used, create other name.
+ QTest::newRow("OptimizeForLoop_alterVariableName")
+ << QByteArray("void foo() {f@or (int i = 0, total = 0; i < 3 + 5; ++i) {}}\n")
+ << QByteArray("void foo() {for (int i = 0, total = 0, totalX = 3 + 5; i < totalX; ++i) {}}\n");
+
+ // Check: optimize postcrement and condition
+ QTest::newRow("OptimizeForLoop_optimizeBoth")
+ << QByteArray("void foo() {f@or (int i = 0; i < 3 + 5; i++) {}}\n")
+ << QByteArray("void foo() {for (int i = 0, total = 3 + 5; i < total; ++i) {}}\n");
+
+ // Check: empty initializier
+ QTest::newRow("OptimizeForLoop_emptyInitializer")
+ << QByteArray("int i; void foo() {f@or (; i < 3 + 5; ++i) {}}\n")
+ << QByteArray("int i; void foo() {for (int total = 3 + 5; i < total; ++i) {}}\n");
+
+ // Check: wrong initializier type -> no trigger
+ QTest::newRow("OptimizeForLoop_wrongInitializer")
+ << QByteArray("int i; void foo() {f@or (double a = 0; i < 3 + 5; ++i) {}}\n")
+ << QByteArray();
+
+ // Check: No trigger when numeric
+ QTest::newRow("OptimizeForLoop_noTriggerNumeric1")
+ << QByteArray("void foo() {fo@r (int i = 0; i < 3; ++i) {}}\n")
+ << QByteArray();
+
+ // Check: No trigger when numeric
+ QTest::newRow("OptimizeForLoop_noTriggerNumeric2")
+ << QByteArray("void foo() {fo@r (int i = 0; i < -3; ++i) {}}\n")
+ << QByteArray();
+ }
+
+ void test()
+ {
+ QFETCH(QByteArray, original);
+ QFETCH(QByteArray, expected);
+ OptimizeForLoop factory;
+ QuickFixOperationTest(singleDocument(original, expected), &factory);
+ }
+};
+
+class AddBracesToControlStatementTest : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void test_data()
+ {
+ QTest::addColumn<QByteArray>("original");
+ QTest::addColumn<QByteArray>("expected");
+
+ QByteArray original = R"delim(
+void MyObject::f()
+{
+ @if (true)
+ emit mySig();
+})delim";
+ QByteArray expected = R"delim(
+void MyObject::f()
+{
+ if (true) {
+ emit mySig();
+ }
+})delim";
+ QTest::newRow("if") << original << expected;
+
+ original = R"delim(
+void MyObject::f()
+{
+ @if (true)
+ emit mySig();
+ else
+ emit otherSig();
+})delim";
+ expected = R"delim(
+void MyObject::f()
+{
+ @if (true) {
+ emit mySig();
+ } else {
+ emit otherSig();
+ }
+})delim";
+ QTest::newRow("if with one else, unbraced") << original << expected;
+
+ original = R"delim(
+void MyObject::f()
+{
+ @if (true) {
+ emit mySig();
+ } else
+ emit otherSig();
+})delim";
+ expected = R"delim(
+void MyObject::f()
+{
+ @if (true) {
+ emit mySig();
+ } else {
+ emit otherSig();
+ }
+})delim";
+ QTest::newRow("if with one else, if braced") << original << expected;
+
+ original = R"delim(
+void MyObject::f()
+{
+ @if (true)
+ emit mySig();
+ else {
+ emit otherSig();
+ }
+})delim";
+ expected = R"delim(
+void MyObject::f()
+{
+ @if (true) {
+ emit mySig();
+ } else {
+ emit otherSig();
+ }
+})delim";
+ QTest::newRow("if with one else, else braced") << original << expected;
+
+ original = R"delim(
+void MyObject::f()
+{
+ @if (true) {
+ emit mySig();
+ } else {
+ emit otherSig();
+ }
+})delim";
+ expected.clear();
+ QTest::newRow("if with one else, both braced") << original << expected;
+
+ original = R"delim(
+void MyObject::f()
+{
+ @if (x == 1)
+ emit sig1();
+ else if (x == 2)
+ emit sig2();
+})delim";
+ expected = R"delim(
+void MyObject::f()
+{
+ if (x == 1) {
+ emit sig1();
+ } else if (x == 2) {
+ emit sig2();
+ }
+})delim";
+ QTest::newRow("if-else chain without final else, unbraced") << original << expected;
+
+ original = R"delim(
+void MyObject::f()
+{
+ @if (x == 1) {
+ emit sig1();
+ } else if (x == 2)
+ emit sig2();
+})delim";
+ expected = R"delim(
+void MyObject::f()
+{
+ if (x == 1) {
+ emit sig1();
+ } else if (x == 2) {
+ emit sig2();
+ }
+})delim";
+ QTest::newRow("if-else chain without final else, partially braced 1") << original << expected;
+
+ original = R"delim(
+void MyObject::f()
+{
+ @if (x == 1)
+ emit sig1();
+ else if (x == 2) {
+ emit sig2();
+ }
+})delim";
+ expected = R"delim(
+void MyObject::f()
+{
+ if (x == 1) {
+ emit sig1();
+ } else if (x == 2) {
+ emit sig2();
+ }
+})delim";
+ QTest::newRow("if-else chain without final else, partially braced 2") << original << expected;
+
+ original = R"delim(
+void MyObject::f()
+{
+ @if (x == 1) {
+ emit sig1();
+ } else if (x == 2) {
+ emit sig2();
+ }
+})delim";
+ expected.clear();
+ QTest::newRow("if-else chain without final else, fully braced") << original << expected;
+
+ original = R"delim(
+void MyObject::f()
+{
+ @if (x == 1)
+ emit sig1();
+ else if (x == 2)
+ emit sig2();
+ else if (x == 3)
+ emit sig3();
+ else
+ emit otherSig();
+})delim";
+ expected = R"delim(
+void MyObject::f()
+{
+ if (x == 1) {
+ emit sig1();
+ } else if (x == 2) {
+ emit sig2();
+ } else if (x == 3) {
+ emit sig3();
+ } else {
+ emit otherSig();
+ }
+})delim";
+ QTest::newRow("if-else chain, unbraced") << original << expected;
+
+ original = R"delim(
+void MyObject::f()
+{
+ @if (x == 1) {
+ emit sig1();
+ } else if (x == 2)
+ emit sig2();
+ else if (x == 3)
+ emit sig3();
+ else
+ emit otherSig();
+})delim";
+ expected = R"delim(
+void MyObject::f()
+{
+ if (x == 1) {
+ emit sig1();
+ } else if (x == 2) {
+ emit sig2();
+ } else if (x == 3) {
+ emit sig3();
+ } else {
+ emit otherSig();
+ }
+})delim";
+ QTest::newRow("if-else chain, partially braced 1") << original << expected;
+
+ original = R"delim(
+void MyObject::f()
+{
+ @if (x == 1)
+ emit sig1();
+ else if (x == 2) {
+ emit sig2();
+ } else if (x == 3)
+ emit sig3();
+ else
+ emit otherSig();
+})delim";
+ expected = R"delim(
+void MyObject::f()
+{
+ if (x == 1) {
+ emit sig1();
+ } else if (x == 2) {
+ emit sig2();
+ } else if (x == 3) {
+ emit sig3();
+ } else {
+ emit otherSig();
+ }
+})delim";
+ QTest::newRow("if-else chain, partially braced 2") << original << expected;
+
+ original = R"delim(
+void MyObject::f()
+{
+ @if (x == 1)
+ emit sig1();
+ else if (x == 2)
+ emit sig2();
+ else if (x == 3) {
+ emit sig3();
+ } else
+ emit otherSig();
+})delim";
+ expected = R"delim(
+void MyObject::f()
+{
+ if (x == 1) {
+ emit sig1();
+ } else if (x == 2) {
+ emit sig2();
+ } else if (x == 3) {
+ emit sig3();
+ } else {
+ emit otherSig();
+ }
+})delim";
+ QTest::newRow("if-else chain, partially braced 3") << original << expected;
+
+ original = R"delim(
+void MyObject::f()
+{
+ @if (x == 1)
+ emit sig1();
+ else if (x == 2)
+ emit sig2();
+ else if (x == 3)
+ emit sig3();
+ else {
+ emit otherSig();
+ }
+})delim";
+ expected = R"delim(
+void MyObject::f()
+{
+ if (x == 1) {
+ emit sig1();
+ } else if (x == 2) {
+ emit sig2();
+ } else if (x == 3) {
+ emit sig3();
+ } else {
+ emit otherSig();
+ }
+})delim";
+ QTest::newRow("if-else chain, partially braced 4") << original << expected;
+
+ original = R"delim(
+void MyObject::f()
+{
+ @if (x == 1) {
+ emit sig1();
+ } else if (x == 2) {
+ emit sig2();
+ } else if (x == 3) {
+ emit sig3();
+ } else {
+ emit otherSig();
+ }
+})delim";
+ expected.clear();
+ QTest::newRow("if-else chain, fully braced") << original << expected;
+
+ original = R"delim(
+void MyObject::f()
+{
+ @while (true)
+ emit mySig();
+})delim";
+ expected = R"delim(
+void MyObject::f()
+{
+ while (true) {
+ emit mySig();
+ }
+})delim";
+ QTest::newRow("while") << original << expected;
+
+ original = R"delim(
+void MyObject::f()
+{
+ @for (int i = 0; i < 10; ++i)
+ emit mySig();
+})delim";
+ expected = R"delim(
+void MyObject::f()
+{
+ for (int i = 0; i < 10; ++i) {
+ emit mySig();
+ }
+})delim";
+ QTest::newRow("for") << original << expected;
+
+ original = R"delim(
+void MyObject::f()
+{
+ @for (int i : list)
+ emit mySig();
+})delim";
+ expected = R"delim(
+void MyObject::f()
+{
+ for (int i : list) {
+ emit mySig();
+ }
+})delim";
+ QTest::newRow("range-based for") << original << expected;
+
+ original = R"delim(
+void MyObject::f()
+{
+ @do
+ emit mySig();
+ while (true);
+})delim";
+ expected = R"delim(
+void MyObject::f()
+{
+ do {
+ emit mySig();
+ } while (true);
+})delim";
+ QTest::newRow("do") << original << expected;
+
+ original = R"delim(
+void MyObject::f()
+{
+ @do {
+ emit mySig();
+ } while (true);
+})delim";
+ expected.clear();
+ QTest::newRow("already has braces") << original << expected;
+ }
+
+ void test()
+ {
+ QFETCH(QByteArray, original);
+ QFETCH(QByteArray, expected);
+
+ AddBracesToControlStatement factory;
+ QuickFixOperationTest({CppTestDocument::create("file.cpp", original, expected)}, &factory);
+ }
+};
+
+QObject *MoveDeclarationOutOfIf::createTest() { return new MoveDeclarationOutOfIfTest; }
+QObject *MoveDeclarationOutOfWhile::createTest() { return new MoveDeclarationOutOfWhileTest; }
+QObject *OptimizeForLoop::createTest() { return new OptimizeForLoopTest; }
+QObject *AddBracesToControlStatement::createTest() { return new AddBracesToControlStatementTest; }
+
+#endif // WITH_TESTS
+} // namespace
+
+void registerRewriteControlStatementQuickfixes()
+{
+ CppQuickFixFactory::registerFactory<AddBracesToControlStatement>();
+ CppQuickFixFactory::registerFactory<MoveDeclarationOutOfIf>();
+ CppQuickFixFactory::registerFactory<MoveDeclarationOutOfWhile>();
+ CppQuickFixFactory::registerFactory<OptimizeForLoop>();
+ CppQuickFixFactory::registerFactory<SplitIfStatement>();
+}
+
+} // namespace CppEditor::Internal
+
+#ifdef WITH_TESTS
+#include <rewritecontrolstatements.moc>
+#endif
diff --git a/src/plugins/cppeditor/quickfixes/rewritecontrolstatements.h b/src/plugins/cppeditor/quickfixes/rewritecontrolstatements.h
new file mode 100644
index 0000000000..d450708a4f
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/rewritecontrolstatements.h
@@ -0,0 +1,8 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+namespace CppEditor::Internal {
+void registerRewriteControlStatementQuickfixes();
+} // namespace CppEditor::Internal
diff --git a/src/plugins/cppeditor/quickfixes/splitsimpledeclaration.cpp b/src/plugins/cppeditor/quickfixes/splitsimpledeclaration.cpp
new file mode 100644
index 0000000000..9d160d71ed
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/splitsimpledeclaration.cpp
@@ -0,0 +1,140 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "splitsimpledeclaration.h"
+
+#include "../cppeditortr.h"
+#include "../cpprefactoringchanges.h"
+#include "cppquickfix.h"
+
+using namespace CPlusPlus;
+using namespace Utils;
+
+namespace CppEditor::Internal {
+namespace {
+
+static bool checkDeclarationForSplit(SimpleDeclarationAST *declaration)
+{
+ if (!declaration->semicolon_token)
+ return false;
+
+ if (!declaration->decl_specifier_list)
+ return false;
+
+ for (SpecifierListAST *it = declaration->decl_specifier_list; it; it = it->next) {
+ SpecifierAST *specifier = it->value;
+ if (specifier->asEnumSpecifier() || specifier->asClassSpecifier())
+ return false;
+ }
+
+ return declaration->declarator_list && declaration->declarator_list->next;
+}
+
+class SplitSimpleDeclarationOp : public CppQuickFixOperation
+{
+public:
+ SplitSimpleDeclarationOp(const CppQuickFixInterface &interface, int priority,
+ SimpleDeclarationAST *decl)
+ : CppQuickFixOperation(interface, priority)
+ , declaration(decl)
+ {
+ setDescription(Tr::tr("Split Declaration"));
+ }
+
+ void perform() override
+ {
+ ChangeSet changes;
+
+ SpecifierListAST *specifiers = declaration->decl_specifier_list;
+ int declSpecifiersStart = currentFile()->startOf(specifiers->firstToken());
+ int declSpecifiersEnd = currentFile()->endOf(specifiers->lastToken() - 1);
+ int insertPos = currentFile()->endOf(declaration->semicolon_token);
+
+ DeclaratorAST *prevDeclarator = declaration->declarator_list->value;
+
+ for (DeclaratorListAST *it = declaration->declarator_list->next; it; it = it->next) {
+ DeclaratorAST *declarator = it->value;
+
+ changes.insert(insertPos, QLatin1String("\n"));
+ changes.copy(declSpecifiersStart, declSpecifiersEnd, insertPos);
+ changes.insert(insertPos, QLatin1String(" "));
+ changes.move(currentFile()->range(declarator), insertPos);
+ changes.insert(insertPos, QLatin1String(";"));
+
+ const int prevDeclEnd = currentFile()->endOf(prevDeclarator);
+ changes.remove(prevDeclEnd, currentFile()->startOf(declarator));
+
+ prevDeclarator = declarator;
+ }
+
+ currentFile()->apply(changes);
+ }
+
+private:
+ SimpleDeclarationAST *declaration;
+};
+
+/*!
+ Rewrite
+ int *a, b;
+
+ As
+ int *a;
+ int b;
+
+ Activates on: the type or the variable names.
+*/
+class SplitSimpleDeclaration : public CppQuickFixFactory
+{
+#ifdef WITH_TESTS
+public:
+ static QObject *createTest() { return new QObject; }
+#endif
+
+private:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ CoreDeclaratorAST *core_declarator = nullptr;
+ const QList<AST *> &path = interface.path();
+ CppRefactoringFilePtr file = interface.currentFile();
+ const int cursorPosition = file->cursor().selectionStart();
+
+ for (int index = path.size() - 1; index != -1; --index) {
+ AST *node = path.at(index);
+
+ if (CoreDeclaratorAST *coreDecl = node->asCoreDeclarator()) {
+ core_declarator = coreDecl;
+ } else if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) {
+ if (checkDeclarationForSplit(simpleDecl)) {
+ SimpleDeclarationAST *declaration = simpleDecl;
+
+ const int startOfDeclSpecifier = file->startOf(declaration->decl_specifier_list->firstToken());
+ const int endOfDeclSpecifier = file->endOf(declaration->decl_specifier_list->lastToken() - 1);
+
+ if (cursorPosition >= startOfDeclSpecifier && cursorPosition <= endOfDeclSpecifier) {
+ // the AST node under cursor is a specifier.
+ result << new SplitSimpleDeclarationOp(interface, index, declaration);
+ return;
+ }
+
+ if (core_declarator && interface.isCursorOn(core_declarator)) {
+ // got a core-declarator under the text cursor.
+ result << new SplitSimpleDeclarationOp(interface, index, declaration);
+ return;
+ }
+ }
+
+ return;
+ }
+ }
+ }
+};
+
+} // namespace
+
+void registerSplitSimpleDeclarationQuickfix()
+{
+ CppQuickFixFactory::registerFactory<SplitSimpleDeclaration>();
+}
+
+} // namespace CppEditor::Internal
diff --git a/src/plugins/cppeditor/quickfixes/splitsimpledeclaration.h b/src/plugins/cppeditor/quickfixes/splitsimpledeclaration.h
new file mode 100644
index 0000000000..8c3100a9f4
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/splitsimpledeclaration.h
@@ -0,0 +1,8 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+namespace CppEditor::Internal {
+void registerSplitSimpleDeclarationQuickfix();
+} // namespace CppEditor::Internal
diff --git a/src/plugins/cppeditor/quickfixes/synchronizememberfunctionorder.cpp b/src/plugins/cppeditor/quickfixes/synchronizememberfunctionorder.cpp
new file mode 100644
index 0000000000..8429e6762f
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/synchronizememberfunctionorder.cpp
@@ -0,0 +1,340 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "synchronizememberfunctionorder.h"
+
+#include "../cppeditortr.h"
+#include "../cppeditorwidget.h"
+#include "../cpprefactoringchanges.h"
+#include "cppquickfix.h"
+#include "cppquickfixhelpers.h"
+
+#include <coreplugin/editormanager/editormanager.h>
+#include <cplusplus/ASTPath.h>
+
+#include <QList>
+#include <QHash>
+
+#ifdef WITH_TESTS
+#include "cppquickfix_test.h"
+#include <projectexplorer/kitmanager.h>
+#include <texteditor/textdocument.h>
+#include <QtTest>
+#endif
+
+#include <memory>
+
+using namespace Core;
+using namespace CPlusPlus;
+using namespace Utils;
+
+namespace CppEditor::Internal {
+namespace {
+
+class SynchronizeMemberFunctionOrderOp : public CppQuickFixOperation
+{
+public:
+ SynchronizeMemberFunctionOrderOp(
+ const CppQuickFixInterface &interface, const QList<Symbol *> &decls)
+ : CppQuickFixOperation(interface), m_state(std::make_shared<State>())
+ {
+ setDescription(
+ Tr::tr("Re-order Member Function Definitions According to Declaration Order"));
+ m_state->decls = decls;
+ m_state->currentFile = currentFile();
+ }
+
+private:
+ struct DefLocation {
+ Symbol *decl = nullptr;
+ Link defLoc;
+ bool operator==(const DefLocation &other) const
+ {
+ return decl == other.decl && defLoc == other.defLoc;
+ }
+ };
+ using DefLocations = QList<DefLocation>;
+ struct State {
+ using Ptr = std::shared_ptr<State>;
+
+ void insertSorted(Symbol *decl, const Link &link) {
+ DefLocations &dl = defLocations[link.targetFilePath];
+ DefLocation newElem{decl, link};
+ const auto cmp = [](const DefLocation &elem, const DefLocation &value) {
+ if (elem.defLoc.targetLine < value.defLoc.targetLine)
+ return true;
+ if (elem.defLoc.targetLine > value.defLoc.targetLine)
+ return false;
+ return elem.defLoc.targetColumn < value.defLoc.targetColumn;
+ };
+ dl.insert(std::lower_bound(dl.begin(), dl.end(), newElem, cmp), newElem);
+ }
+
+ QList<Symbol *> decls;
+ QHash<FilePath, DefLocations> defLocations;
+ CppRefactoringFilePtr currentFile;
+ int remainingFollowSymbolOps = 0;
+ };
+
+ void perform() override
+ {
+ for (Symbol * const decl : std::as_const(m_state->decls)) {
+ QTextCursor cursor(currentFile()->document()->begin());
+ TranslationUnit * const tu = currentFile()->cppDocument()->translationUnit();
+ const int declPos = tu->getTokenPositionInDocument(decl->sourceLocation(),
+ currentFile()->document());
+ cursor.setPosition(declPos);
+ const CursorInEditor cursorInEditor(
+ cursor,
+ decl->filePath(),
+ qobject_cast<CppEditorWidget *>(currentFile()->editor()),
+ currentFile()->editor()->textDocument(),
+ currentFile()->cppDocument());
+
+ const auto callback = [decl, declPos, doc = cursor.document(), state = m_state](
+ const Link &link) {
+ class FinishedChecker
+ {
+ public:
+ FinishedChecker(const State::Ptr &state) : m_state(state)
+ {}
+ ~FinishedChecker()
+ {
+ if (--m_state->remainingFollowSymbolOps == 0)
+ finish(m_state);
+ };
+ private:
+ const State::Ptr &m_state;
+ } finishedChecker(state);
+
+ if (!link.hasValidTarget())
+ return;
+ if (decl->filePath() == link.targetFilePath) {
+ const int linkPos = Text::positionInText(doc, link.targetLine,
+ link.targetColumn + 1);
+ if (linkPos == declPos)
+ return;
+ }
+ state->insertSorted(decl, link);
+ };
+
+ ++m_state->remainingFollowSymbolOps;
+
+ // Force queued execution, as the built-in editor can run the callback synchronously.
+ const auto followSymbol = [cursorInEditor, callback] {
+ NonInteractiveFollowSymbolMarker niMarker;
+ CppModelManager::followSymbol(
+ cursorInEditor, callback, true, false, FollowSymbolMode::Exact);
+ };
+ QMetaObject::invokeMethod(CppModelManager::instance(), followSymbol, Qt::QueuedConnection);
+ }
+ }
+
+ static void finish(const State::Ptr &state)
+ {
+ CppRefactoringChanges factory{CppModelManager::snapshot()};
+
+ const auto findAstRange = [](const CppRefactoringFile &file, const Link &pos) {
+ const QList<AST *> astPath = ASTPath(
+ file.cppDocument())(pos.targetLine, pos.targetColumn + 1);
+ for (auto it = astPath.rbegin(); it != astPath.rend(); ++it) {
+ if (const auto funcDef = (*it)->asFunctionDefinition()) {
+ AST *ast = funcDef;
+ for (auto next = std::next(it);
+ next != astPath.rend() && (*next)->asTemplateDeclaration();
+ ++next) {
+ ast = *next;
+ }
+ return file.range(ast);
+ }
+ }
+ return ChangeSet::Range();
+ };
+
+ for (auto it = state->defLocations.cbegin(); it != state->defLocations.cend(); ++it) {
+ const DefLocations &defLocsActualOrder = it.value();
+ const DefLocations defLocsExpectedOrder = Utils::sorted(
+ defLocsActualOrder, [](const DefLocation &loc1, const DefLocation &loc2) {
+ return loc1.decl->sourceLocation() < loc2.decl->sourceLocation();
+ });
+ if (defLocsExpectedOrder == defLocsActualOrder)
+ continue;
+
+ CppRefactoringFilePtr file = it.key() == state->currentFile->filePath()
+ ? state->currentFile
+ : factory.cppFile(it.key());
+ ChangeSet changes;
+ for (int i = 0; i < defLocsActualOrder.size(); ++i) {
+ const DefLocation &actualLoc = defLocsActualOrder[i];
+ int expectedPos = -1;
+ for (int j = 0; j < defLocsExpectedOrder.size(); ++j) {
+ if (defLocsExpectedOrder[j].decl == actualLoc.decl) {
+ expectedPos = j;
+ break;
+ }
+ }
+ if (expectedPos == i)
+ continue;
+ const ChangeSet::Range actualRange = findAstRange(*file, actualLoc.defLoc);
+ const ChangeSet::Range expectedRange
+ = findAstRange(*file, defLocsActualOrder[expectedPos].defLoc);
+ if (actualRange.end > actualRange.start && expectedRange.end > expectedRange.start)
+ changes.move(actualRange, expectedRange.start);
+ }
+ QTC_ASSERT(!changes.hadErrors(), continue);
+ file->setChangeSet(changes);
+ file->apply();
+ }
+ }
+
+ const State::Ptr m_state;
+};
+
+//! Ensures relative order of member function implementations is the same as declaration order.
+class SynchronizeMemberFunctionOrder : public CppQuickFixFactory
+{
+#ifdef WITH_TESTS
+public:
+ static QObject *createTest();
+#endif
+
+private:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ ClassSpecifierAST * const classAst = astForClassOperations(interface);
+ if (!classAst || !classAst->symbol)
+ return;
+
+ QList<Symbol *> memberFunctions;
+ const TranslationUnit * const tu
+ = interface.currentFile()->cppDocument()->translationUnit();
+ for (int i = 0; i < classAst->symbol->memberCount(); ++i) {
+ Symbol *member = classAst->symbol->memberAt(i);
+
+ // Skip macros
+ if (tu->tokenAt(member->sourceLocation()).expanded())
+ continue;
+
+ if (const auto templ = member->asTemplate())
+ member = templ->declaration();
+ if (member->type()->asFunctionType() && !member->asFunction())
+ memberFunctions << member;
+ }
+ if (!memberFunctions.isEmpty())
+ result << new SynchronizeMemberFunctionOrderOp(interface, memberFunctions);
+ }
+};
+
+#ifdef WITH_TESTS
+using namespace Tests;
+
+class SynchronizeMemberFunctionOrderTest : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void test_data()
+ {
+ QTest::addColumn<QString>("projectName");
+ QTest::addColumn<bool>("expectChanges");
+
+ QTest::newRow("no out-of-line definitions") << "no-out-of-line" << false;
+ QTest::newRow("already sorted") << "already-sorted" << false;
+ QTest::newRow("different impl locations") << "different-locations" << true;
+ QTest::newRow("templates") << "templates" << true;
+ }
+
+ void test()
+ {
+ QFETCH(QString, projectName);
+ QFETCH(bool, expectChanges);
+ using namespace CppEditor::Tests;
+ using namespace ProjectExplorer;
+ using namespace TextEditor;
+
+ // Set up project.
+ Kit * const kit = Utils::findOr(KitManager::kits(), nullptr, [](const Kit *k) {
+ return k->isValid() && !k->hasWarning() && k->value("QtSupport.QtInformation").isValid();
+ });
+ if (!kit)
+ QSKIP("The test requires at least one valid kit with a valid Qt");
+ const auto projectDir = std::make_unique<TemporaryCopiedDir>(
+ ":/cppeditor/testcases/reorder-member-impls/" + projectName);
+ SourceFilesRefreshGuard refreshGuard;
+ ProjectOpenerAndCloser projectMgr;
+ QVERIFY(projectMgr.open(projectDir->absolutePath(projectName + ".pro"), true, kit));
+ QVERIFY(refreshGuard.wait());
+
+ // Open header file and locate class.
+ const auto headerFilePath = projectDir->absolutePath("header.h");
+ QVERIFY2(headerFilePath.exists(), qPrintable(headerFilePath.toUserOutput()));
+ const auto editor = qobject_cast<BaseTextEditor *>(EditorManager::openEditor(headerFilePath));
+ QVERIFY(editor);
+ const auto doc = qobject_cast<TextEditor::TextDocument *>(editor->document());
+ QVERIFY(doc);
+ QTextCursor classCursor = doc->document()->find("struct S");
+ QVERIFY(!classCursor.isNull());
+ editor->setCursorPosition(classCursor.position());
+ const auto editorWidget = qobject_cast<CppEditorWidget *>(editor->editorWidget());
+ QVERIFY(editorWidget);
+ QVERIFY(TestCase::waitForRehighlightedSemanticDocument(editorWidget));
+
+ // Query factory.
+ SynchronizeMemberFunctionOrder factory;
+ CppQuickFixInterface quickFixInterface(editorWidget, ExplicitlyInvoked);
+ QuickFixOperations operations;
+ factory.match(quickFixInterface, operations);
+ operations.first()->perform();
+ if (expectChanges)
+ QVERIFY(waitForSignalOrTimeout(doc, &IDocument::saved, 30000));
+ QTest::qWait(1000);
+
+ // Compare all files.
+ const FileFilter filter({"*_expected"}, QDir::Files);
+ const FilePaths expectedDocuments = projectDir->filePath().dirEntries(filter);
+ QVERIFY(!expectedDocuments.isEmpty());
+ for (const FilePath &expected : expectedDocuments) {
+ static const QString suffix = "_expected";
+ const FilePath actual = expected.parentDir()
+ .pathAppended(expected.fileName().chopped(suffix.length()));
+ QVERIFY(actual.exists());
+ const auto actualContents = actual.fileContents();
+ QVERIFY(actualContents);
+ const auto expectedContents = expected.fileContents();
+ const QByteArrayList actualLines = actualContents->split('\n');
+ const QByteArrayList expectedLines = expectedContents->split('\n');
+ if (actualLines.size() != expectedLines.size()) {
+ qDebug().noquote().nospace() << "---\n" << *expectedContents << "EOF";
+ qDebug().noquote().nospace() << "+++\n" << *actualContents << "EOF";
+ }
+ QCOMPARE(actualLines.size(), expectedLines.size());
+ for (int i = 0; i < actualLines.size(); ++i) {
+ const QByteArray actualLine = actualLines.at(i);
+ const QByteArray expectedLine = expectedLines.at(i);
+ if (actualLine != expectedLine)
+ qDebug() << "Unexpected content in line" << (i + 1) << "of file"
+ << actual.fileName();
+ QCOMPARE(actualLine, expectedLine);
+ }
+ }
+ }
+};
+
+QObject *SynchronizeMemberFunctionOrder::createTest()
+{
+ return new SynchronizeMemberFunctionOrderTest;
+}
+
+#endif
+} // namespace
+
+void registerSynchronizeMemberFunctionOrderQuickfix()
+{
+ CppQuickFixFactory::registerFactory<SynchronizeMemberFunctionOrder>();
+}
+
+} // namespace CppEditor::Internal
+
+#ifdef WITH_TESTS
+#include <synchronizememberfunctionorder.moc>
+#endif
diff --git a/src/plugins/cppeditor/quickfixes/synchronizememberfunctionorder.h b/src/plugins/cppeditor/quickfixes/synchronizememberfunctionorder.h
new file mode 100644
index 0000000000..da6ad971db
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/synchronizememberfunctionorder.h
@@ -0,0 +1,8 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+namespace CppEditor::Internal {
+void registerSynchronizeMemberFunctionOrderQuickfix();
+} // namespace CppEditor::Internal
diff --git a/src/plugins/cppeditor/symbolsfindfilter.cpp b/src/plugins/cppeditor/symbolsfindfilter.cpp
index f53fd34c4d..2888cf3604 100644
--- a/src/plugins/cppeditor/symbolsfindfilter.cpp
+++ b/src/plugins/cppeditor/symbolsfindfilter.cpp
@@ -166,26 +166,30 @@ QWidget *SymbolsFindFilter::createConfigWidget()
return new SymbolsFindFilterConfigWidget(this);
}
-void SymbolsFindFilter::writeSettings(QtcSettings *settings)
+Store SymbolsFindFilter::save() const
{
- settings->beginGroup(SETTINGS_GROUP);
- settings->setValue(SETTINGS_SYMBOLTYPES, int(m_symbolsToSearch));
- settings->setValue(SETTINGS_SEARCHSCOPE, int(m_scope));
- settings->endGroup();
+ Store s;
+ if (m_symbolsToSearch != SearchSymbols::AllTypes)
+ s.insert(SETTINGS_SYMBOLTYPES, int(m_symbolsToSearch));
+ if (m_scope != SymbolSearcher::SearchProjectsOnly)
+ s.insert(SETTINGS_SEARCHSCOPE, int(m_scope));
+ return s;
}
-void SymbolsFindFilter::readSettings(QtcSettings *settings)
+void SymbolsFindFilter::restore(const Utils::Store &s)
{
- settings->beginGroup(SETTINGS_GROUP);
m_symbolsToSearch = static_cast<SearchSymbols::SymbolTypes>(
- settings->value(SETTINGS_SYMBOLTYPES, int(SearchSymbols::AllTypes)).toInt());
+ s.value(SETTINGS_SYMBOLTYPES, int(SearchSymbols::AllTypes)).toInt());
m_scope = static_cast<SearchScope>(
- settings->value(SETTINGS_SEARCHSCOPE,
- int(SymbolSearcher::SearchProjectsOnly)).toInt());
- settings->endGroup();
+ s.value(SETTINGS_SEARCHSCOPE, int(SymbolSearcher::SearchProjectsOnly)).toInt());
emit symbolsToSearchChanged();
}
+QByteArray SymbolsFindFilter::settingsKey() const
+{
+ return SETTINGS_GROUP;
+}
+
void SymbolsFindFilter::onTaskStarted(Id type)
{
if (type == Constants::TASK_INDEX) {
diff --git a/src/plugins/cppeditor/symbolsfindfilter.h b/src/plugins/cppeditor/symbolsfindfilter.h
index e9c997d219..2513111cdc 100644
--- a/src/plugins/cppeditor/symbolsfindfilter.h
+++ b/src/plugins/cppeditor/symbolsfindfilter.h
@@ -37,8 +37,8 @@ public:
void findAll(const QString &txt, Utils::FindFlags findFlags) override;
QWidget *createConfigWidget() override;
- void writeSettings(Utils::QtcSettings *settings) override;
- void readSettings(Utils::QtcSettings *settings) override;
+ Utils::Store save() const override;
+ void restore(const Utils::Store &s) override;
void setSymbolsToSearch(const SearchSymbols::SymbolTypes &types) { m_symbolsToSearch = types; }
SearchSymbols::SymbolTypes symbolsToSearch() const { return m_symbolsToSearch; }
@@ -46,6 +46,9 @@ public:
void setSearchScope(SearchScope scope) { m_scope = scope; }
SearchScope searchScope() const { return m_scope; }
+ // deprecated
+ QByteArray settingsKey() const override;
+
signals:
void symbolsToSearchChanged();
diff --git a/src/plugins/cppeditor/testcases/move-class/complex/complex.pro b/src/plugins/cppeditor/testcases/move-class/complex/complex.pro
new file mode 100644
index 0000000000..efdac1a69a
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/complex/complex.pro
@@ -0,0 +1,2 @@
+HEADERS = theheader.h
+SOURCES = thesource.cpp main.cpp
diff --git a/src/plugins/cppeditor/testcases/move-class/complex/complex.pro_expected b/src/plugins/cppeditor/testcases/move-class/complex/complex.pro_expected
new file mode 100644
index 0000000000..9a7a013363
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/complex/complex.pro_expected
@@ -0,0 +1,4 @@
+HEADERS = theheader.h \
+ theclass.h
+SOURCES = thesource.cpp main.cpp \
+ theclass.cpp
diff --git a/src/plugins/cppeditor/testcases/move-class/complex/main.cpp b/src/plugins/cppeditor/testcases/move-class/complex/main.cpp
new file mode 100644
index 0000000000..2934727d74
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/complex/main.cpp
@@ -0,0 +1,11 @@
+#include "theheader.h"
+
+namespace Project {
+namespace Internal {
+bool TheClass::needsDefinition = true;
+}
+}
+
+int main()
+{
+}
diff --git a/src/plugins/cppeditor/testcases/move-class/complex/main.cpp_expected b/src/plugins/cppeditor/testcases/move-class/complex/main.cpp_expected
new file mode 100644
index 0000000000..10bdcd280a
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/complex/main.cpp_expected
@@ -0,0 +1,11 @@
+#include "theheader.h"
+
+namespace Project {
+namespace Internal {
+
+}
+}
+
+int main()
+{
+}
diff --git a/src/plugins/cppeditor/testcases/move-class/complex/theclass.cpp_expected b/src/plugins/cppeditor/testcases/move-class/complex/theclass.cpp_expected
new file mode 100644
index 0000000000..5cb8bbb375
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/complex/theclass.cpp_expected
@@ -0,0 +1,20 @@
+
+#include "theclass.h"
+
+namespace Project {
+namespace Internal {
+
+bool TheClass::needsDefinition = true;
+
+class TheClass::Private
+{
+ void func();
+ int m_member = 0;
+};
+
+void TheClass::defined() {}
+
+void TheClass::Private::func() {}
+
+} // namespace Internal
+} // namespace Project
diff --git a/src/plugins/cppeditor/testcases/move-class/complex/theclass.h_expected b/src/plugins/cppeditor/testcases/move-class/complex/theclass.h_expected
new file mode 100644
index 0000000000..c7db776d27
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/complex/theclass.h_expected
@@ -0,0 +1,29 @@
+#ifndef PROJECT_INTERNAL_THECLASS_H
+#define PROJECT_INTERNAL_THECLASS_H
+
+namespace Project {
+namespace Internal {
+
+class TheClass
+{
+public:
+ TheClass() = default;
+
+ void defined();
+ void undefined();
+
+ template<typename T> T defaultValue() const;
+private:
+ class Private;
+ class Undefined;
+ static inline bool doesNotNeedDefinition = true;
+ static bool needsDefinition;
+ int m_value = 0;
+};
+
+template<typename T> T TheClass::defaultValue() const { return T(); }
+
+} // namespace Internal
+} // namespace Project
+
+#endif // PROJECT_INTERNAL_THECLASS_H
diff --git a/src/plugins/cppeditor/testcases/move-class/complex/theheader.h b/src/plugins/cppeditor/testcases/move-class/complex/theheader.h
new file mode 100644
index 0000000000..93f0d47a81
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/complex/theheader.h
@@ -0,0 +1,30 @@
+#ifndef THE_HEADER_H
+#define THE_HEADER_H
+
+namespace Project {
+namespace Internal {
+
+class SomeClass {};
+class TheClass
+{
+public:
+ TheClass() = default;
+
+ void defined();
+ void undefined();
+
+ template<typename T> T defaultValue() const;
+private:
+ class Private;
+ class Undefined;
+ static inline bool doesNotNeedDefinition = true;
+ static bool needsDefinition;
+ int m_value = 0;
+};
+
+template<typename T> T TheClass::defaultValue() const { return T(); }
+
+}
+}
+
+#endif
diff --git a/src/plugins/cppeditor/testcases/move-class/complex/theheader.h_expected b/src/plugins/cppeditor/testcases/move-class/complex/theheader.h_expected
new file mode 100644
index 0000000000..65bf2ec7f4
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/complex/theheader.h_expected
@@ -0,0 +1,18 @@
+#ifndef THE_HEADER_H
+#define THE_HEADER_H
+
+#include "theclass.h"
+
+
+namespace Project {
+namespace Internal {
+
+class SomeClass {};
+
+
+
+
+}
+}
+
+#endif
diff --git a/src/plugins/cppeditor/testcases/move-class/complex/thesource.cpp b/src/plugins/cppeditor/testcases/move-class/complex/thesource.cpp
new file mode 100644
index 0000000000..645b6a4b13
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/complex/thesource.cpp
@@ -0,0 +1,19 @@
+#include "theheader.h"
+
+namespace Project {
+namespace Internal {
+
+static void someFunction() {}
+
+class TheClass::Private
+{
+ void func();
+ int m_member = 0;
+};
+
+void TheClass::defined() {}
+
+void TheClass::Private::func() {}
+
+}
+}
diff --git a/src/plugins/cppeditor/testcases/move-class/complex/thesource.cpp_expected b/src/plugins/cppeditor/testcases/move-class/complex/thesource.cpp_expected
new file mode 100644
index 0000000000..d84f180808
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/complex/thesource.cpp_expected
@@ -0,0 +1,15 @@
+#include "theheader.h"
+
+namespace Project {
+namespace Internal {
+
+static void someFunction() {}
+
+
+
+
+
+
+
+}
+}
diff --git a/src/plugins/cppeditor/testcases/move-class/decl-in-source/decl-in-source.pro b/src/plugins/cppeditor/testcases/move-class/decl-in-source/decl-in-source.pro
new file mode 100644
index 0000000000..d527c86ef3
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/decl-in-source/decl-in-source.pro
@@ -0,0 +1,2 @@
+HEADERS = theheader.h
+SOURCES = thesource.cpp
diff --git a/src/plugins/cppeditor/testcases/move-class/decl-in-source/decl-in-source.pro_expected b/src/plugins/cppeditor/testcases/move-class/decl-in-source/decl-in-source.pro_expected
new file mode 100644
index 0000000000..969d1fdb80
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/decl-in-source/decl-in-source.pro_expected
@@ -0,0 +1,3 @@
+HEADERS = theheader.h \
+ theclass.h
+SOURCES = thesource.cpp
diff --git a/src/plugins/cppeditor/testcases/move-class/decl-in-source/theclass.h_expected b/src/plugins/cppeditor/testcases/move-class/decl-in-source/theclass.h_expected
new file mode 100644
index 0000000000..4af91c47e3
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/decl-in-source/theclass.h_expected
@@ -0,0 +1,11 @@
+#ifndef THECLASS_H
+#define THECLASS_H
+
+class TheClass
+{
+ void func();
+};
+
+void TheClass::func() {}
+
+#endif // THECLASS_H
diff --git a/src/plugins/cppeditor/testcases/move-class/decl-in-source/theheader.h b/src/plugins/cppeditor/testcases/move-class/decl-in-source/theheader.h
new file mode 100644
index 0000000000..9c0f94351f
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/decl-in-source/theheader.h
@@ -0,0 +1,4 @@
+class SomeClass
+{
+ void func();
+};
diff --git a/src/plugins/cppeditor/testcases/move-class/decl-in-source/thesource.cpp b/src/plugins/cppeditor/testcases/move-class/decl-in-source/thesource.cpp
new file mode 100644
index 0000000000..79a7651309
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/decl-in-source/thesource.cpp
@@ -0,0 +1,8 @@
+#include "theheader.h"
+
+class TheClass
+{
+ void func();
+};
+void SomeClass::func() {}
+void TheClass::func() {}
diff --git a/src/plugins/cppeditor/testcases/move-class/decl-in-source/thesource.cpp_expected b/src/plugins/cppeditor/testcases/move-class/decl-in-source/thesource.cpp_expected
new file mode 100644
index 0000000000..e26225d719
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/decl-in-source/thesource.cpp_expected
@@ -0,0 +1,6 @@
+#include "theclass.h"
+#include "theheader.h"
+
+
+void SomeClass::func() {}
+
diff --git a/src/plugins/cppeditor/testcases/move-class/header-only/header-only.pro b/src/plugins/cppeditor/testcases/move-class/header-only/header-only.pro
new file mode 100644
index 0000000000..d527c86ef3
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/header-only/header-only.pro
@@ -0,0 +1,2 @@
+HEADERS = theheader.h
+SOURCES = thesource.cpp
diff --git a/src/plugins/cppeditor/testcases/move-class/header-only/header-only.pro_expected b/src/plugins/cppeditor/testcases/move-class/header-only/header-only.pro_expected
new file mode 100644
index 0000000000..969d1fdb80
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/header-only/header-only.pro_expected
@@ -0,0 +1,3 @@
+HEADERS = theheader.h \
+ theclass.h
+SOURCES = thesource.cpp
diff --git a/src/plugins/cppeditor/testcases/move-class/header-only/theclass.h_expected b/src/plugins/cppeditor/testcases/move-class/header-only/theclass.h_expected
new file mode 100644
index 0000000000..9b1f655f3f
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/header-only/theclass.h_expected
@@ -0,0 +1,10 @@
+#ifndef THECLASS_H
+#define THECLASS_H
+
+class TheClass {
+ void func();
+};
+
+void TheClass::func() {}
+
+#endif // THECLASS_H
diff --git a/src/plugins/cppeditor/testcases/move-class/header-only/theheader.h b/src/plugins/cppeditor/testcases/move-class/header-only/theheader.h
new file mode 100644
index 0000000000..131a6d42a6
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/header-only/theheader.h
@@ -0,0 +1,7 @@
+class SomeClass {
+ void func();
+};
+class TheClass {
+ void func();
+};
+void TheClass::func() {}
diff --git a/src/plugins/cppeditor/testcases/move-class/header-only/theheader.h_expected b/src/plugins/cppeditor/testcases/move-class/header-only/theheader.h_expected
new file mode 100644
index 0000000000..1cf9e1d06c
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/header-only/theheader.h_expected
@@ -0,0 +1,7 @@
+#include "theclass.h"
+
+class SomeClass {
+ void func();
+};
+
+
diff --git a/src/plugins/cppeditor/testcases/move-class/header-only/thesource.cpp b/src/plugins/cppeditor/testcases/move-class/header-only/thesource.cpp
new file mode 100644
index 0000000000..d01e0845ab
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/header-only/thesource.cpp
@@ -0,0 +1,3 @@
+#include "theheader.h"
+
+void SomeClass::func() {}
diff --git a/src/plugins/cppeditor/testcases/move-class/header-only/thesource.cpp_expected b/src/plugins/cppeditor/testcases/move-class/header-only/thesource.cpp_expected
new file mode 100644
index 0000000000..d01e0845ab
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/header-only/thesource.cpp_expected
@@ -0,0 +1,3 @@
+#include "theheader.h"
+
+void SomeClass::func() {}
diff --git a/src/plugins/cppeditor/testcases/move-class/match1/TheClass.h b/src/plugins/cppeditor/testcases/move-class/match1/TheClass.h
new file mode 100644
index 0000000000..11b0cf15d1
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/match1/TheClass.h
@@ -0,0 +1,2 @@
+class SomeClass {};
+class TheClass {};
diff --git a/src/plugins/cppeditor/testcases/move-class/match1/match1.pro b/src/plugins/cppeditor/testcases/move-class/match1/match1.pro
new file mode 100644
index 0000000000..72ccc46616
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/match1/match1.pro
@@ -0,0 +1 @@
+HEADERS = TheClass.h
diff --git a/src/plugins/cppeditor/testcases/move-class/match2/match2.pro b/src/plugins/cppeditor/testcases/move-class/match2/match2.pro
new file mode 100644
index 0000000000..6f982e4f16
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/match2/match2.pro
@@ -0,0 +1 @@
+HEADERS = theclass.h
diff --git a/src/plugins/cppeditor/testcases/move-class/match2/theclass.h b/src/plugins/cppeditor/testcases/move-class/match2/theclass.h
new file mode 100644
index 0000000000..11b0cf15d1
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/match2/theclass.h
@@ -0,0 +1,2 @@
+class SomeClass {};
+class TheClass {};
diff --git a/src/plugins/cppeditor/testcases/move-class/match3/match3.pro b/src/plugins/cppeditor/testcases/move-class/match3/match3.pro
new file mode 100644
index 0000000000..850a113845
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/match3/match3.pro
@@ -0,0 +1 @@
+HEADERS = the_class.h
diff --git a/src/plugins/cppeditor/testcases/move-class/match3/the_class.h b/src/plugins/cppeditor/testcases/move-class/match3/the_class.h
new file mode 100644
index 0000000000..11b0cf15d1
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/match3/the_class.h
@@ -0,0 +1,2 @@
+class SomeClass {};
+class TheClass {};
diff --git a/src/plugins/cppeditor/testcases/move-class/nested/main.cpp b/src/plugins/cppeditor/testcases/move-class/nested/main.cpp
new file mode 100644
index 0000000000..73fd1620fa
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/nested/main.cpp
@@ -0,0 +1,6 @@
+class SomeClass {};
+class Outer {
+ class Inner {};
+};
+
+int main() {}
diff --git a/src/plugins/cppeditor/testcases/move-class/nested/nested.pro b/src/plugins/cppeditor/testcases/move-class/nested/nested.pro
new file mode 100644
index 0000000000..bba41b9c12
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/nested/nested.pro
@@ -0,0 +1 @@
+SOURCES = main.cpp
diff --git a/src/plugins/cppeditor/testcases/move-class/single/single.pro b/src/plugins/cppeditor/testcases/move-class/single/single.pro
new file mode 100644
index 0000000000..77db8b92e4
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/single/single.pro
@@ -0,0 +1 @@
+HEADERS = theheader.h
diff --git a/src/plugins/cppeditor/testcases/move-class/single/theheader.h b/src/plugins/cppeditor/testcases/move-class/single/theheader.h
new file mode 100644
index 0000000000..5f7cd96730
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/single/theheader.h
@@ -0,0 +1,2 @@
+class Private;
+class TheClass {};
diff --git a/src/plugins/cppeditor/testcases/move-class/template/template.pro b/src/plugins/cppeditor/testcases/move-class/template/template.pro
new file mode 100644
index 0000000000..77db8b92e4
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/template/template.pro
@@ -0,0 +1 @@
+HEADERS = theheader.h
diff --git a/src/plugins/cppeditor/testcases/move-class/template/template.pro_expected b/src/plugins/cppeditor/testcases/move-class/template/template.pro_expected
new file mode 100644
index 0000000000..0ed81f0008
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/template/template.pro_expected
@@ -0,0 +1,2 @@
+HEADERS = theheader.h \
+ theclass.h
diff --git a/src/plugins/cppeditor/testcases/move-class/template/theclass.h_expected b/src/plugins/cppeditor/testcases/move-class/template/theclass.h_expected
new file mode 100644
index 0000000000..f9681cc796
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/template/theclass.h_expected
@@ -0,0 +1,11 @@
+#ifndef THECLASS_H
+#define THECLASS_H
+
+template<typename T> class TheClass
+{
+ T defaultValue() const;
+};
+
+template<typename T> inline T TheClass<T>::defaultValue() const { return T(); }
+
+#endif // THECLASS_H
diff --git a/src/plugins/cppeditor/testcases/move-class/template/theheader.h b/src/plugins/cppeditor/testcases/move-class/template/theheader.h
new file mode 100644
index 0000000000..58f7a07372
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/template/theheader.h
@@ -0,0 +1,7 @@
+enum SomeEnum { E };
+template<typename T> class TheClass
+{
+ T defaultValue() const;
+};
+
+template<typename T> inline T TheClass<T>::defaultValue() const { return T(); }
diff --git a/src/plugins/cppeditor/testcases/move-class/template/theheader.h_expected b/src/plugins/cppeditor/testcases/move-class/template/theheader.h_expected
new file mode 100644
index 0000000000..20087c3199
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/move-class/template/theheader.h_expected
@@ -0,0 +1,6 @@
+#include "theclass.h"
+
+enum SomeEnum { E };
+
+
+
diff --git a/src/plugins/cppeditor/testcases/reorder-member-impls/already-sorted/already-sorted.pro b/src/plugins/cppeditor/testcases/reorder-member-impls/already-sorted/already-sorted.pro
new file mode 100644
index 0000000000..7051801269
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/reorder-member-impls/already-sorted/already-sorted.pro
@@ -0,0 +1 @@
+HEADERS = header.h
diff --git a/src/plugins/cppeditor/testcases/reorder-member-impls/already-sorted/header.h b/src/plugins/cppeditor/testcases/reorder-member-impls/already-sorted/header.h
new file mode 100644
index 0000000000..c0d7e0caa7
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/reorder-member-impls/already-sorted/header.h
@@ -0,0 +1,9 @@
+struct S {
+ void firstFunction();
+ void secondFunction();
+ void thirdFunction();
+};
+
+void S::firstFunction() {}
+void S::secondFunction() {}
+void S::thirdFunction() {}
diff --git a/src/plugins/cppeditor/testcases/reorder-member-impls/already-sorted/header.h_expected b/src/plugins/cppeditor/testcases/reorder-member-impls/already-sorted/header.h_expected
new file mode 100644
index 0000000000..c0d7e0caa7
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/reorder-member-impls/already-sorted/header.h_expected
@@ -0,0 +1,9 @@
+struct S {
+ void firstFunction();
+ void secondFunction();
+ void thirdFunction();
+};
+
+void S::firstFunction() {}
+void S::secondFunction() {}
+void S::thirdFunction() {}
diff --git a/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/different-locations.pro b/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/different-locations.pro
new file mode 100644
index 0000000000..a8dd832020
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/different-locations.pro
@@ -0,0 +1,2 @@
+SOURCES = impl1.cpp impl2.cpp
+HEADERS = header.h
diff --git a/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/header.h b/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/header.h
new file mode 100644
index 0000000000..15a428eb7c
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/header.h
@@ -0,0 +1,15 @@
+struct S {
+ void firstFunction();
+ void secondFunction();
+ void thirdFunction() {}
+ void fourthFunction();
+ void fifthFunction();
+ void sixthFunction();
+ void seventhFunction();
+ void eighthFunction();
+ void ninthFunction();
+ void tenthFunction();
+};
+
+void S::tenthFunction() {}
+void S::fourthFunction() {}
diff --git a/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/header.h_expected b/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/header.h_expected
new file mode 100644
index 0000000000..eeec93f300
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/header.h_expected
@@ -0,0 +1,15 @@
+struct S {
+ void firstFunction();
+ void secondFunction();
+ void thirdFunction() {}
+ void fourthFunction();
+ void fifthFunction();
+ void sixthFunction();
+ void seventhFunction();
+ void eighthFunction();
+ void ninthFunction();
+ void tenthFunction();
+};
+
+void S::fourthFunction() {}
+void S::tenthFunction() {}
diff --git a/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/impl1.cpp b/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/impl1.cpp
new file mode 100644
index 0000000000..816fef1779
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/impl1.cpp
@@ -0,0 +1,5 @@
+#include "header.h"
+
+void S::ninthFunction() {}
+void S::firstFunction() {}
+void S::secondFunction() {}
diff --git a/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/impl1.cpp_expected b/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/impl1.cpp_expected
new file mode 100644
index 0000000000..24d0f3a212
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/impl1.cpp_expected
@@ -0,0 +1,5 @@
+#include "header.h"
+
+void S::firstFunction() {}
+void S::secondFunction() {}
+void S::ninthFunction() {}
diff --git a/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/impl2.cpp b/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/impl2.cpp
new file mode 100644
index 0000000000..f1040e089e
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/impl2.cpp
@@ -0,0 +1,6 @@
+#include "header.h"
+
+void S::eighthFunction() {}
+void S::seventhFunction() {}
+void S::sixthFunction() {}
+void S::fifthFunction() {}
diff --git a/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/impl2.cpp_expected b/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/impl2.cpp_expected
new file mode 100644
index 0000000000..2b65df96a5
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/reorder-member-impls/different-locations/impl2.cpp_expected
@@ -0,0 +1,6 @@
+#include "header.h"
+
+void S::fifthFunction() {}
+void S::sixthFunction() {}
+void S::seventhFunction() {}
+void S::eighthFunction() {}
diff --git a/src/plugins/cppeditor/testcases/reorder-member-impls/no-out-of-line/header.h b/src/plugins/cppeditor/testcases/reorder-member-impls/no-out-of-line/header.h
new file mode 100644
index 0000000000..cff8945127
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/reorder-member-impls/no-out-of-line/header.h
@@ -0,0 +1,5 @@
+struct S {
+ void firstFunction() {}
+ void secondFunction();
+ void thirdFunction() {}
+};
diff --git a/src/plugins/cppeditor/testcases/reorder-member-impls/no-out-of-line/header.h_expected b/src/plugins/cppeditor/testcases/reorder-member-impls/no-out-of-line/header.h_expected
new file mode 100644
index 0000000000..cff8945127
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/reorder-member-impls/no-out-of-line/header.h_expected
@@ -0,0 +1,5 @@
+struct S {
+ void firstFunction() {}
+ void secondFunction();
+ void thirdFunction() {}
+};
diff --git a/src/plugins/cppeditor/testcases/reorder-member-impls/no-out-of-line/no-out-of-line.pro b/src/plugins/cppeditor/testcases/reorder-member-impls/no-out-of-line/no-out-of-line.pro
new file mode 100644
index 0000000000..7051801269
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/reorder-member-impls/no-out-of-line/no-out-of-line.pro
@@ -0,0 +1 @@
+HEADERS = header.h
diff --git a/src/plugins/cppeditor/testcases/reorder-member-impls/templates/header.h b/src/plugins/cppeditor/testcases/reorder-member-impls/templates/header.h
new file mode 100644
index 0000000000..08b33a51d9
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/reorder-member-impls/templates/header.h
@@ -0,0 +1,10 @@
+template <typename T> struct S {
+ void firstFunction();
+ template<typename U> void secondFunction(U);
+ template<typename U> void irrelevantFunction(U) {}
+ void thirdFunction();
+};
+
+template<typename T> template<typename U> void S<T>::secondFunction(U) {}
+template<typename T> void S<T>::thirdFunction() {}
+template<typename T> void S<T>::firstFunction() {}
diff --git a/src/plugins/cppeditor/testcases/reorder-member-impls/templates/header.h_expected b/src/plugins/cppeditor/testcases/reorder-member-impls/templates/header.h_expected
new file mode 100644
index 0000000000..f182bbe62d
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/reorder-member-impls/templates/header.h_expected
@@ -0,0 +1,10 @@
+template <typename T> struct S {
+ void firstFunction();
+ template<typename U> void secondFunction(U);
+ template<typename U> void irrelevantFunction(U) {}
+ void thirdFunction();
+};
+
+template<typename T> void S<T>::firstFunction() {}
+template<typename T> template<typename U> void S<T>::secondFunction(U) {}
+template<typename T> void S<T>::thirdFunction() {}
diff --git a/src/plugins/cppeditor/testcases/reorder-member-impls/templates/templates.pro b/src/plugins/cppeditor/testcases/reorder-member-impls/templates/templates.pro
new file mode 100644
index 0000000000..7051801269
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/reorder-member-impls/templates/templates.pro
@@ -0,0 +1 @@
+HEADERS = header.h
diff --git a/src/plugins/ctfvisualizer/CtfVisualizer.json.in b/src/plugins/ctfvisualizer/CtfVisualizer.json.in
index 1b4b91a6d5..a887857bc0 100644
--- a/src/plugins/ctfvisualizer/CtfVisualizer.json.in
+++ b/src/plugins/ctfvisualizer/CtfVisualizer.json.in
@@ -13,7 +13,11 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Code Analyzer",
- "Description" : "Chrome Trace Format Visualizer Plugin.",
+ "Description" : "View Chrome Trace Format (CTF) files",
+ "LongDescription" : [
+ "You also need:",
+ "- CTF files (in Trace Event Format)"
+ ],
"Url" : "https://www.kdab.com",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/cvs/CVS.json.in b/src/plugins/cvs/CVS.json.in
index 6d2f92ea78..9ea0a986ca 100644
--- a/src/plugins/cvs/CVS.json.in
+++ b/src/plugins/cvs/CVS.json.in
@@ -13,9 +13,10 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Version Control",
- "Description" : "CVS integration.",
+ "Description" : "Access the CVS open source version control system",
+ "LongDescription" : [],
"DisabledByDefault" : true,
- "Url" : "http://www.qt.io",
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES},
"Mimetypes" : [
diff --git a/src/plugins/cvs/cvsplugin.cpp b/src/plugins/cvs/cvsplugin.cpp
index 01c47b1646..6a25b29fa0 100644
--- a/src/plugins/cvs/cvsplugin.cpp
+++ b/src/plugins/cvs/cvsplugin.cpp
@@ -339,7 +339,7 @@ bool CvsPluginPrivate::isVcsFileOrDirectory(const Utils::FilePath &filePath) con
bool CvsPluginPrivate::isConfigured() const
{
- const FilePath binary = settings().binaryPath();
+ const FilePath binary = settings().binaryPath.effectiveBinary();
if (binary.isEmpty())
return false;
QFileInfo fi = binary.toFileInfo();
diff --git a/src/plugins/debugger/Debugger.json.in b/src/plugins/debugger/Debugger.json.in
index 06df0489d6..ce4282d0ed 100644
--- a/src/plugins/debugger/Debugger.json.in
+++ b/src/plugins/debugger/Debugger.json.in
@@ -13,8 +13,14 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Qt Creator",
- "Description" : "Debugger integration.",
- "Url" : "http://www.qt.io",
+ "Description" : "Debug applications with native debuggers",
+ "LongDescription" : [
+ "Debug an application to see what happens inside it while it runs or when it crashes.",
+ "The debugger extension acts as an interface between the Qt Creator core and native debuggers.",
+ "You also need:",
+ "- A debugger"
+ ],
+ "Url" : "https://www.qt.io",
"Arguments" : [
{
"Name" : "-debug",
diff --git a/src/plugins/debugger/cdb/cdbengine.cpp b/src/plugins/debugger/cdb/cdbengine.cpp
index 83c5a9459b..840f78dfc9 100644
--- a/src/plugins/debugger/cdb/cdbengine.cpp
+++ b/src/plugins/debugger/cdb/cdbengine.cpp
@@ -282,7 +282,7 @@ void CdbEngine::setupEngine()
DebuggerRunParameters sp = runParameters();
if (terminal()) {
m_effectiveStartMode = AttachToLocalProcess;
- sp.inferior.command = CommandLine();
+ sp.inferior.command = {};
sp.attachPID = ProcessHandle(terminal()->applicationPid());
sp.startMode = AttachToLocalProcess;
sp.useTerminal = false; // Force no terminal.
@@ -1042,6 +1042,8 @@ void CdbEngine::doUpdateLocals(const UpdateParameters &updateParameters)
cmd.arg("partialvar", updateParameters.partialVariable);
cmd.arg("qobjectnames", s.showQObjectNames());
cmd.arg("timestamps", s.logTimeStamps());
+ cmd.arg("qtversion", runParameters().qtVersion);
+ cmd.arg("qtnamespace", runParameters().qtNamespace);
StackFrame frame = stackHandler()->currentFrame();
cmd.arg("context", frame.context);
@@ -2798,7 +2800,7 @@ void CdbEngine::setupScripting(const DebuggerResponse &response)
}
const FilePath path = settings().extraDumperFile();
- if (!path.isEmpty() && path.isReadableFile()) {
+ if (path.isReadableFile()) {
DebuggerCommand cmd("theDumper.addDumperModule", ScriptCommand);
cmd.arg("path", path.path());
runCommand(cmd);
@@ -2809,10 +2811,6 @@ void CdbEngine::setupScripting(const DebuggerResponse &response)
runCommand({command, 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/commonoptionspage.cpp b/src/plugins/debugger/commonoptionspage.cpp
index bb24d674a0..5739845fe6 100644
--- a/src/plugins/debugger/commonoptionspage.cpp
+++ b/src/plugins/debugger/commonoptionspage.cpp
@@ -157,7 +157,7 @@ CommonSettings::CommonSettings()
};
return Column {
- Group { title("Behavior"), Row { col1, col2, st } },
+ Group { title(Tr::tr("Behavior")), Row { col1, col2, st } },
sourcePathMap,
st
};
diff --git a/src/plugins/debugger/commonoptionspage.h b/src/plugins/debugger/commonoptionspage.h
index 5bf41af7a4..4d391280e8 100644
--- a/src/plugins/debugger/commonoptionspage.h
+++ b/src/plugins/debugger/commonoptionspage.h
@@ -25,7 +25,7 @@ public:
void fromMap(const Utils::Store &map) override;
void toMap(Utils::Store &map) const override;
- void addToLayout(Layouting::LayoutItem &parent) override;
+ void addToLayout(Layouting::Layout &parent) override;
void readSettings() override;
void writeSettings() const override;
diff --git a/src/plugins/debugger/console/consoleitemdelegate.cpp b/src/plugins/debugger/console/consoleitemdelegate.cpp
index 8f76704b70..cd7ead6de8 100644
--- a/src/plugins/debugger/console/consoleitemdelegate.cpp
+++ b/src/plugins/debugger/console/consoleitemdelegate.cpp
@@ -53,7 +53,6 @@ QColor ConsoleItemDelegate::drawBackground(QPainter *painter, const QRect &rect,
void ConsoleItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
- const Utils::Theme *theme = Utils::creatorTheme();
QStyleOptionViewItem opt = option;
initStyleOption(&opt, index);
painter->save();
@@ -65,23 +64,23 @@ void ConsoleItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &o
ConsoleItem::TypeRole).toInt();
switch (type) {
case ConsoleItem::DebugType:
- textColor = theme->color(Utils::Theme::OutputPanes_NormalMessageTextColor);
+ textColor = creatorColor(Utils::Theme::OutputPanes_NormalMessageTextColor);
taskIcon = m_logIcon;
break;
case ConsoleItem::WarningType:
- textColor = theme->color(Utils::Theme::OutputPanes_WarningMessageTextColor);
+ textColor = creatorColor(Utils::Theme::OutputPanes_WarningMessageTextColor);
taskIcon = m_warningIcon;
break;
case ConsoleItem::ErrorType:
- textColor = theme->color(Utils::Theme::OutputPanes_ErrorMessageTextColor);
+ textColor = creatorColor(Utils::Theme::OutputPanes_ErrorMessageTextColor);
taskIcon = m_errorIcon;
break;
case ConsoleItem::InputType:
- textColor = theme->color(Utils::Theme::TextColorNormal);
+ textColor = creatorColor(Utils::Theme::TextColorNormal);
taskIcon = m_prompt;
break;
default:
- textColor = theme->color(Utils::Theme::TextColorNormal);
+ textColor = creatorColor(Utils::Theme::TextColorNormal);
break;
}
diff --git a/src/plugins/debugger/console/consoleview.cpp b/src/plugins/debugger/console/consoleview.cpp
index d42f4ae918..de68944e8f 100644
--- a/src/plugins/debugger/console/consoleview.cpp
+++ b/src/plugins/debugger/console/consoleview.cpp
@@ -162,9 +162,8 @@ void ConsoleView::onRowActivated(const QModelIndex &index)
const Utils::FilePath fp
= m_finder.findFile(model()->data(index, ConsoleItem::FileRole).toString()).constFirst();
- if (fp.exists() && fp.isFile() && fp.isReadableFile()) {
+ if (fp.isReadableFile())
Core::EditorManager::openEditorAt({fp, model()->data(index, ConsoleItem::LineRole).toInt()});
- }
}
void ConsoleView::copyToClipboard(const QModelIndex &index)
diff --git a/src/plugins/debugger/debuggerengine.h b/src/plugins/debugger/debuggerengine.h
index e82a386311..b5e8a3cba8 100644
--- a/src/plugins/debugger/debuggerengine.h
+++ b/src/plugins/debugger/debuggerengine.h
@@ -188,7 +188,8 @@ public:
QStringList validationErrors;
- int fallbackQtVersion = 0x50200;
+ int qtVersion = 0;
+ QString qtNamespace;
// Common debugger constants.
Utils::FilePath peripheralDescriptionFile;
diff --git a/src/plugins/debugger/debuggeritemmanager.cpp b/src/plugins/debugger/debuggeritemmanager.cpp
index 0aa3b2e769..b93e0800de 100644
--- a/src/plugins/debugger/debuggeritemmanager.cpp
+++ b/src/plugins/debugger/debuggeritemmanager.cpp
@@ -9,8 +9,6 @@
#include <coreplugin/dialogs/ioptionspage.h>
#include <coreplugin/icore.h>
-#include <extensionsystem/pluginmanager.h>
-
#include <projectexplorer/devicesupport/devicemanager.h>
#include <projectexplorer/kitoptionspage.h>
#include <projectexplorer/projectexplorerconstants.h>
@@ -381,7 +379,7 @@ DebuggerItemConfigWidget::DebuggerItemConfigWidget()
// clang-format off
using namespace Layouting;
Form {
- fieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow),
+ fieldGrowthPolicy(int(QFormLayout::AllNonFixedFieldsGrow)),
Tr::tr("Name:"), m_displayNameLineEdit, br,
Tr::tr("Path:"), m_binaryChooser, br,
m_cdbLabel, br,
@@ -488,7 +486,7 @@ void DebuggerItemConfigWidget::binaryPathHasChanged()
tmp.reinitializeFromFile();
return tmp;
}));
- ExtensionSystem::PluginManager::futureSynchronizer()->addFuture(m_updateWatcher.future());
+ Utils::futureSynchronizer()->addFuture(m_updateWatcher.future());
} else {
const DebuggerItem tmp;
setAbis(tmp.abiNames());
diff --git a/src/plugins/debugger/debuggerkitaspect.cpp b/src/plugins/debugger/debuggerkitaspect.cpp
index 02f21c582a..10559a7084 100644
--- a/src/plugins/debugger/debuggerkitaspect.cpp
+++ b/src/plugins/debugger/debuggerkitaspect.cpp
@@ -66,7 +66,7 @@ public:
}
private:
- void addToLayoutImpl(Layouting::LayoutItem &parent) override
+ void addToLayoutImpl(Layouting::Layout &parent) override
{
addMutableAction(m_comboBox);
parent.addItem(m_comboBox);
@@ -329,7 +329,7 @@ public:
}
if (!item.detectionSource().isEmpty() && item.detectionSource() == k->autoDetectionSource())
level = DebuggerItem::MatchLevel(level + 2);
- } else if (rawId.type() == QVariant::String) {
+ } else if (rawId.typeId() == QMetaType::QString) {
// New structure.
if (item.id() == rawId) {
// Detected by ID.
diff --git a/src/plugins/debugger/debuggerrunconfigurationaspect.cpp b/src/plugins/debugger/debuggerrunconfigurationaspect.cpp
index d4f4361f78..4bd1dec967 100644
--- a/src/plugins/debugger/debuggerrunconfigurationaspect.cpp
+++ b/src/plugins/debugger/debuggerrunconfigurationaspect.cpp
@@ -72,7 +72,7 @@ DebuggerRunConfigurationAspect::DebuggerRunConfigurationAspect(Target *target)
details->setState(DetailsWidget::Expanded);
auto innerPane = new QWidget;
details->setWidget(innerPane);
- builder.addItem(Layouting::noMargin);
+ builder.setNoMargins();
builder.attachTo(innerPane);
const auto setSummaryText = [this, details] {
diff --git a/src/plugins/debugger/debuggerruncontrol.cpp b/src/plugins/debugger/debuggerruncontrol.cpp
index 503bad4bc2..522191e25b 100644
--- a/src/plugins/debugger/debuggerruncontrol.cpp
+++ b/src/plugins/debugger/debuggerruncontrol.cpp
@@ -898,7 +898,7 @@ DebuggerRunTool::DebuggerRunTool(RunControl *runControl, AllowTerminal allowTerm
if (QtSupport::QtVersion *baseQtVersion = QtSupport::QtKitAspect::qtVersion(kit)) {
const QVersionNumber qtVersion = baseQtVersion->qtVersion();
- m_runParameters.fallbackQtVersion = 0x10000 * qtVersion.majorVersion()
+ m_runParameters.qtVersion = 0x10000 * qtVersion.majorVersion()
+ 0x100 * qtVersion.minorVersion()
+ qtVersion.microVersion();
}
diff --git a/src/plugins/debugger/debuggersourcepathmappingwidget.cpp b/src/plugins/debugger/debuggersourcepathmappingwidget.cpp
index be983cf584..98b84e2020 100644
--- a/src/plugins/debugger/debuggersourcepathmappingwidget.cpp
+++ b/src/plugins/debugger/debuggersourcepathmappingwidget.cpp
@@ -467,7 +467,7 @@ bool SourcePathMapAspect::isDirty()
return m_internal != m_buffer;
}
-void SourcePathMapAspect::addToLayout(Layouting::LayoutItem &parent)
+void SourcePathMapAspect::addToLayout(Layouting::Layout &parent)
{
QTC_CHECK(!d->m_widget);
d->m_widget = createSubWidget<DebuggerSourcePathMappingWidget>();
diff --git a/src/plugins/debugger/gdb/gdbengine.cpp b/src/plugins/debugger/gdb/gdbengine.cpp
index e4873de775..1079fb24ea 100644
--- a/src/plugins/debugger/gdb/gdbengine.cpp
+++ b/src/plugins/debugger/gdb/gdbengine.cpp
@@ -4044,7 +4044,7 @@ void GdbEngine::handleGdbStarted()
}
const FilePath path = settings().extraDumperFile();
- if (!path.isEmpty() && path.isReadableFile()) {
+ if (path.isReadableFile()) {
DebuggerCommand cmd("addDumperModule");
cmd.arg("path", path.path());
runCommand(cmd);
@@ -4054,10 +4054,6 @@ void GdbEngine::handleGdbStarted()
if (!commands.isEmpty())
runCommand({commands});
- DebuggerCommand cmd1("setFallbackQtVersion");
- cmd1.arg("version", rp.fallbackQtVersion);
- runCommand(cmd1);
-
runCommand({"loadDumpers", CB(handlePythonSetup)});
// Reload peripheral register description.
@@ -5133,6 +5129,8 @@ void GdbEngine::doUpdateLocals(const UpdateParameters &params)
cmd.arg("dyntype", s.useDynamicType());
cmd.arg("qobjectnames", s.showQObjectNames());
cmd.arg("timestamps", s.logTimeStamps());
+ cmd.arg("qtversion", runParameters().qtVersion);
+ cmd.arg("qtnamespace", runParameters().qtNamespace);
StackFrame frame = stackHandler()->currentFrame();
cmd.arg("context", frame.context);
diff --git a/src/plugins/debugger/lldb/lldbengine.cpp b/src/plugins/debugger/lldb/lldbengine.cpp
index 3d21f82ca2..e234aefc21 100644
--- a/src/plugins/debugger/lldb/lldbengine.cpp
+++ b/src/plugins/debugger/lldb/lldbengine.cpp
@@ -238,7 +238,7 @@ void LldbEngine::handleLldbStarted()
executeCommand(commands);
const FilePath path = settings().extraDumperFile();
- if (!path.isEmpty() && path.isReadableFile()) {
+ if (path.isReadableFile()) {
DebuggerCommand cmd("addDumperModule");
cmd.arg("path", path.path());
runCommand(cmd);
@@ -346,10 +346,6 @@ void LldbEngine::handleLldbStarted()
cmd2.flags = Silent;
runCommand(cmd2);
-
- DebuggerCommand cmd0("setFallbackQtVersion");
- cmd0.arg("version", rp.fallbackQtVersion);
- runCommand(cmd0);
}
void LldbEngine::runEngine()
@@ -407,9 +403,10 @@ void LldbEngine::handleResponse(const QString &response)
const QString name = item.name();
if (name == "result") {
QString msg = item["status"].data();
- if (!msg.isEmpty())
+ if (!msg.isEmpty()) {
msg[0] = msg.at(0).toUpper();
- showStatusMessage(msg);
+ showStatusMessage(msg);
+ }
int token = item["token"].toInt();
showMessage(QString("%1^").arg(token), LogOutput);
@@ -767,6 +764,8 @@ void LldbEngine::doUpdateLocals(const UpdateParameters &params)
cmd.arg("partialvar", params.partialVariable);
cmd.arg("qobjectnames", s.showQObjectNames());
cmd.arg("timestamps", s.logTimeStamps());
+ cmd.arg("qtversion", runParameters().qtVersion);
+ cmd.arg("qtnamespace", runParameters().qtNamespace);
StackFrame frame = stackHandler()->currentFrame();
cmd.arg("context", frame.context);
diff --git a/src/plugins/debugger/logwindow.cpp b/src/plugins/debugger/logwindow.cpp
index 482f2605e7..a1ca5e30e8 100644
--- a/src/plugins/debugger/logwindow.cpp
+++ b/src/plugins/debugger/logwindow.cpp
@@ -101,26 +101,25 @@ private:
{
using Utils::Theme;
QTextCharFormat format;
- Theme *theme = Utils::creatorTheme();
switch (channelForChar(text.isEmpty() ? QChar() : text.at(0))) {
case LogInput:
- format.setForeground(theme->color(Theme::Debugger_LogWindow_LogInput));
+ format.setForeground(creatorColor(Theme::Debugger_LogWindow_LogInput));
setFormat(1, text.size(), format);
break;
case LogStatus:
- format.setForeground(theme->color(Theme::Debugger_LogWindow_LogStatus));
+ format.setForeground(creatorColor(Theme::Debugger_LogWindow_LogStatus));
setFormat(1, text.size(), format);
break;
case LogWarning:
- format.setForeground(theme->color(Theme::OutputPanes_WarningMessageTextColor));
+ format.setForeground(creatorColor(Theme::OutputPanes_WarningMessageTextColor));
setFormat(1, text.size(), format);
break;
case LogError:
- format.setForeground(theme->color(Theme::OutputPanes_ErrorMessageTextColor));
+ format.setForeground(creatorColor(Theme::OutputPanes_ErrorMessageTextColor));
setFormat(1, text.size(), format);
break;
case LogTime:
- format.setForeground(theme->color(Theme::Debugger_LogWindow_LogTime));
+ format.setForeground(creatorColor(Theme::Debugger_LogWindow_LogTime));
setFormat(1, text.size(), format);
break;
default:
@@ -152,11 +151,9 @@ public:
private:
void highlightBlock(const QString &text) override
{
- using Utils::Theme;
- Theme *theme = Utils::creatorTheme();
if (text.size() > 3 && text.at(2) == ':') {
QTextCharFormat format;
- format.setForeground(theme->color(Theme::Debugger_LogWindow_LogTime));
+ format.setForeground(creatorColor(Theme::Debugger_LogWindow_LogTime));
setFormat(1, text.size(), format);
}
}
diff --git a/src/plugins/debugger/qml/qmlengine.cpp b/src/plugins/debugger/qml/qmlengine.cpp
index dda927f0f1..c214926ec4 100644
--- a/src/plugins/debugger/qml/qmlengine.cpp
+++ b/src/plugins/debugger/qml/qmlengine.cpp
@@ -783,7 +783,7 @@ void QmlEngine::assignValueInDebugger(WatchItem *item,
const QString &expression, const QVariant &editValue)
{
if (!expression.isEmpty()) {
- QTC_CHECK(editValue.typeId() == QVariant::String);
+ QTC_CHECK(editValue.typeId() == QMetaType::QString);
QVariant value;
QString val = editValue.toString();
if (item->type == "boolean")
@@ -861,7 +861,7 @@ static ConsoleItem *constructLogItemTree(const QVariant &result, const QString &
QString text;
ConsoleItem *item = nullptr;
- if (result.typeId() == QVariant::Map) {
+ if (result.typeId() == QMetaType::QVariantMap) {
if (key.isEmpty())
text = "Object";
else
@@ -885,7 +885,7 @@ static ConsoleItem *constructLogItemTree(const QVariant &result, const QString &
item->appendChild(child);
}
- } else if (result.typeId() == QVariant::List) {
+ } else if (result.typeId() == QMetaType::QVariantList) {
if (key.isEmpty())
text = "List";
else
@@ -904,7 +904,7 @@ static ConsoleItem *constructLogItemTree(const QVariant &result, const QString &
if (child)
item->appendChild(child);
}
- } else if (result.canConvert(QVariant::String)) {
+ } else if (result.canConvert(QMetaType::QString)) {
item = new ConsoleItem(ConsoleItem::DefaultType, result.toString());
} else {
item = new ConsoleItem(ConsoleItem::DefaultType, "Unknown Value");
@@ -1356,9 +1356,9 @@ void QmlEnginePrivate::scripts(int types, const QList<int> ids, bool includeSour
if (includeSource)
cmd.arg(INCLUDESOURCE, includeSource);
- if (filter.typeId() == QVariant::String)
+ if (filter.typeId() == QMetaType::QString)
cmd.arg(FILTER, filter.toString());
- else if (filter.typeId() == QVariant::Int)
+ else if (filter.typeId() == QMetaType::Int)
cmd.arg(FILTER, filter.toInt());
else
QTC_CHECK(!filter.isValid());
@@ -2041,7 +2041,7 @@ StackFrame QmlEnginePrivate::extractStackFrame(const QVariant &bodyVal)
}
auto extractString = [this](const QVariant &item) {
- return (item.typeId() == QVariant::String ? item : extractData(item).value).toString();
+ return (item.typeId() == QMetaType::QString ? item : extractData(item).value).toString();
};
stackFrame.function = extractString(body.value("func"));
diff --git a/src/plugins/debugger/qml/qmlinspectoragent.cpp b/src/plugins/debugger/qml/qmlinspectoragent.cpp
index 9e30d2ad83..2769d49cd6 100644
--- a/src/plugins/debugger/qml/qmlinspectoragent.cpp
+++ b/src/plugins/debugger/qml/qmlinspectoragent.cpp
@@ -220,7 +220,7 @@ void QmlInspectorAgent::onResult(quint32 queryId, const QVariant &value,
if (m_objectTreeQueryIds.contains(queryId)) {
m_objectTreeQueryIds.removeOne(queryId);
- if (value.typeId() == QVariant::List) {
+ if (value.typeId() == QMetaType::QVariantList) {
const QVariantList objList = value.toList();
for (const QVariant &var : objList) {
// TODO: check which among the list is the actual
@@ -289,7 +289,7 @@ static void sortChildrenIfNecessary(WatchItem *propertiesWatch)
static bool insertChildren(WatchItem *parent, const QVariant &value)
{
switch (value.typeId()) {
- case QVariant::Map: {
+ case QMetaType::QVariantMap: {
const QVariantMap map = value.toMap();
for (auto it = map.begin(), end = map.end(); it != end; ++it) {
auto child = new WatchItem;
@@ -303,7 +303,7 @@ static bool insertChildren(WatchItem *parent, const QVariant &value)
sortChildrenIfNecessary(parent);
return true;
}
- case QVariant::List: {
+ case QMetaType::QVariantList: {
const QVariantList list = value.toList();
for (int i = 0, end = list.size(); i != end; ++i) {
auto child = new WatchItem;
diff --git a/src/plugins/debugger/watchdata.h b/src/plugins/debugger/watchdata.h
index 672ded5e7c..914e92decf 100644
--- a/src/plugins/debugger/watchdata.h
+++ b/src/plugins/debugger/watchdata.h
@@ -33,7 +33,7 @@ public:
QString toToolTip() const;
QVariant editValue() const;
- int editType() const;
+ QMetaType::Type editType() const;
static const qint64 InvalidId = -1;
constexpr static char loadMoreName[] = "<load more>";
diff --git a/src/plugins/debugger/watchdelegatewidgets.cpp b/src/plugins/debugger/watchdelegatewidgets.cpp
index b9337f2eea..ecaa0fda91 100644
--- a/src/plugins/debugger/watchdelegatewidgets.cpp
+++ b/src/plugins/debugger/watchdelegatewidgets.cpp
@@ -189,25 +189,25 @@ void IntegerWatchLineEdit::setModelData(const QVariant &v)
qDebug(">IntegerLineEdit::setModelData(%s, '%s'): base=%d, signed=%d, bigint=%d",
v.typeName(), qPrintable(v.toString()),
base(), isSigned(), isBigInt());
- switch (v.type()) {
- case QVariant::Int:
- case QVariant::LongLong: {
+ switch (v.typeId()) {
+ case QMetaType::Int:
+ case QMetaType::LongLong: {
const qint64 iv = v.toLongLong();
setSigned(true);
setText(QString::number(iv, base()));
}
break;
- case QVariant::UInt:
- case QVariant::ULongLong: {
+ case QMetaType::UInt:
+ case QMetaType::ULongLong: {
const quint64 iv = v.toULongLong();
setSigned(false);
setText(QString::number(iv, base()));
}
break;
- case QVariant::ByteArray:
+ case QMetaType::QByteArray:
setNumberText(QString::fromLatin1(v.toByteArray()));
break;
- case QVariant::String:
+ case QMetaType::QString:
setNumberText(v.toString());
break;
default:
@@ -243,12 +243,12 @@ void FloatWatchLineEdit::setModelData(const QVariant &v)
if (debug)
qDebug("FloatWatchLineEdit::setModelData(%s, '%s')",
v.typeName(), qPrintable(v.toString()));
- switch (v.type()) {
- case QVariant::Double:
- case QVariant::String:
+ switch (v.typeId()) {
+ case QMetaType::Double:
+ case QMetaType::QString:
setText(v.toString());
break;
- case QVariant::ByteArray:
+ case QMetaType::QByteArray:
setText(QString::fromLatin1(v.toByteArray()));
break;
default:
@@ -259,17 +259,17 @@ void FloatWatchLineEdit::setModelData(const QVariant &v)
}
}
-WatchLineEdit *WatchLineEdit::create(QVariant::Type t, QWidget *parent)
+WatchLineEdit *WatchLineEdit::create(QMetaType::Type typeId, QWidget *parent)
{
- switch (t) {
- case QVariant::Bool:
- case QVariant::Int:
- case QVariant::UInt:
- case QVariant::LongLong:
- case QVariant::ULongLong:
+ switch (typeId) {
+ case QMetaType::Bool:
+ case QMetaType::Int:
+ case QMetaType::UInt:
+ case QMetaType::LongLong:
+ case QMetaType::ULongLong:
return new IntegerWatchLineEdit(parent);
break;
- case QVariant::Double:
+ case QMetaType::Double:
return new FloatWatchLineEdit(parent);
default:
break;
@@ -297,14 +297,14 @@ void BooleanComboBox::setModelData(const QVariant &v)
qDebug("BooleanComboBox::setModelData(%s, '%s')", v.typeName(), qPrintable(v.toString()));
bool value = false;
- switch (v.type()) {
- case QVariant::Bool:
+ switch (v.typeId()) {
+ case QMetaType::Bool:
value = v.toBool();
break;
- case QVariant::Int:
- case QVariant::UInt:
- case QVariant::LongLong:
- case QVariant::ULongLong:
+ case QMetaType::Int:
+ case QMetaType::UInt:
+ case QMetaType::LongLong:
+ case QMetaType::ULongLong:
value = v.toInt() != 0;
break;
default:
diff --git a/src/plugins/debugger/watchdelegatewidgets.h b/src/plugins/debugger/watchdelegatewidgets.h
index 68b1dd2403..54b85c6257 100644
--- a/src/plugins/debugger/watchdelegatewidgets.h
+++ b/src/plugins/debugger/watchdelegatewidgets.h
@@ -27,7 +27,7 @@ public:
virtual QVariant modelData() const;
virtual void setModelData(const QVariant &);
- static WatchLineEdit *create(QVariant::Type t, QWidget *parent = nullptr);
+ static WatchLineEdit *create(QMetaType::Type typeId, QWidget *parent = nullptr);
};
/* Watch delegate line edit for integer numbers based on quint64/qint64.
diff --git a/src/plugins/debugger/watchhandler.cpp b/src/plugins/debugger/watchhandler.cpp
index 3f47df4b88..c5600cd9ee 100644
--- a/src/plugins/debugger/watchhandler.cpp
+++ b/src/plugins/debugger/watchhandler.cpp
@@ -844,33 +844,33 @@ static inline quint64 pointerValue(QString data)
}
// Return the type used for editing
-int WatchItem::editType() const
+QMetaType::Type WatchItem::editType() const
{
if (type == "bool")
- return QVariant::Bool;
+ return QMetaType::Bool;
if (isIntType(type))
- return type.contains('u') ? QVariant::ULongLong : QVariant::LongLong;
+ return type.contains('u') ? QMetaType::ULongLong : QMetaType::LongLong;
if (isFloatType(type))
- return QVariant::Double;
+ return QMetaType::Double;
// Check for pointers using hex values (0xAD00 "Hallo")
if (isPointerType(type) && value.startsWith("0x"))
- return QVariant::ULongLong;
- return QVariant::String;
+ return QMetaType::ULongLong;
+ return QMetaType::QString;
}
// Convert to editable (see above)
QVariant WatchItem::editValue() const
{
switch (editType()) {
- case QVariant::Bool:
+ case QMetaType::Bool:
return value != "0" && value != "false";
- case QVariant::ULongLong:
+ case QMetaType::ULongLong:
if (isPointerType(type)) // Fix pointer values (0xAD00 "Hallo" -> 0xAD00)
return QVariant(pointerValue(value));
return QVariant(value.toULongLong());
- case QVariant::LongLong:
+ case QMetaType::LongLong:
return QVariant(value.toLongLong());
- case QVariant::Double:
+ case QMetaType::Double:
return QVariant(value.toDouble());
default:
break;
@@ -978,7 +978,7 @@ static QColor valueColor(const WatchItem *item, int column)
color = Theme::Debugger_WatchItem_ValueChanged;
}
}
- return creatorTheme()->color(color);
+ return creatorColor(color);
}
static DisplayFormats typeFormatList(const WatchItem *item)
@@ -2888,8 +2888,8 @@ public:
// Value column: Custom editor. Apply integer-specific settings.
if (index.column() == 1) {
- auto editType = QVariant::Type(item->editType());
- if (editType == QVariant::Bool)
+ const QMetaType::Type editType = item->editType();
+ if (editType == QMetaType::Bool)
return new BooleanComboBox(parent);
WatchLineEdit *edit = WatchLineEdit::create(editType, parent);
diff --git a/src/plugins/designer/CMakeLists.txt b/src/plugins/designer/CMakeLists.txt
index c7b3cdac0d..d9da4cfaf4 100644
--- a/src/plugins/designer/CMakeLists.txt
+++ b/src/plugins/designer/CMakeLists.txt
@@ -5,12 +5,12 @@ if(Qt6_VERSION VERSION_GREATER_EQUAL 6.7.0)
{
\"Name\" : \"-designer-qt-pluginpath\",
\"Parameter\" : \"path\",
- \"Description\" : \"Override the default search path for Qt Designer plugins\"
+ \"Description\" : \"Override the default search path for Qt Widgets Designer plugins\"
},
{
\"Name\" : \"-designer-pluginpath\",
\"Parameter\" : \"path\",
- \"Description\" : \"Add a custom search path for Qt Designer plugins\"
+ \"Description\" : \"Add a custom search path for Qt Widgets Designer plugins\"
}
],")
else()
@@ -19,7 +19,7 @@ else()
{
\"Name\" : \"-designer-qt-pluginpath\",
\"Parameter\" : \"path\",
- \"Description\" : \"Override the default search path for Qt Designer plugins\"
+ \"Description\" : \"Override the default search path for Qt Widgets Designer plugins\"
}
],")
endif()
diff --git a/src/plugins/designer/Designer.json.in b/src/plugins/designer/Designer.json.in
index 2d03527c30..966becb0d4 100644
--- a/src/plugins/designer/Designer.json.in
+++ b/src/plugins/designer/Designer.json.in
@@ -13,8 +13,11 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Qt Creator",
- "Description" : "Qt Designer integration.",
- "Url" : "http://www.qt.io",
+ "Description" : "Design Qt-widgets based UIs",
+ "LongDescription" : [
+ "Create widgets and forms that are integrated with Qt C++ code."
+ ],
+ "Url" : "https://www.qt.io",
${DESIGNER_PLUGIN_ARGUMENTS}
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/designer/cpp/formclasswizarddialog.cpp b/src/plugins/designer/cpp/formclasswizarddialog.cpp
index cc912683f6..765bf470a0 100644
--- a/src/plugins/designer/cpp/formclasswizarddialog.cpp
+++ b/src/plugins/designer/cpp/formclasswizarddialog.cpp
@@ -27,7 +27,7 @@ FormClassWizardDialog::FormClassWizardDialog(const Core::BaseFileWizardFactory *
m_formPage(new FormTemplateWizardPage),
m_classPage(new FormClassWizardPage)
{
- setWindowTitle(Tr::tr("Qt Designer Form Class"));
+ setWindowTitle(Tr::tr("Qt Widgets Designer Form Class"));
setPage(FormPageId, m_formPage);
setPage(ClassPageId, m_classPage);
diff --git a/src/plugins/designer/designer.qbs b/src/plugins/designer/designer.qbs
index 7fb484226b..38cfa5aa0d 100644
--- a/src/plugins/designer/designer.qbs
+++ b/src/plugins/designer/designer.qbs
@@ -32,12 +32,12 @@ QtcPlugin {
{\n\
\"Name\" : \"-designer-qt-pluginpath\",\n\
\"Parameter\" : \"path\",\n\
- \"Description\" : \"Override the default search path for Qt Designer plugins\"\n\
+ \"Description\" : \"Override the default search path for Qt Widgets Designer plugins\"\n\
},\n\
{\n\
\"Name\" : \"-designer-pluginpath\",\n\
\"Parameter\" : \"path\",\n\
- \"Description\" : \"Add a custom search path for Qt Designer plugins\"\n\
+ \"Description\" : \"Add a custom search path for Qt Widgets Designer plugins\"\n\
}\n\
],"})
diff --git a/src/plugins/designer/designerplugin.cpp b/src/plugins/designer/designerplugin.cpp
index e355379727..cc7b43a0a6 100644
--- a/src/plugins/designer/designerplugin.cpp
+++ b/src/plugins/designer/designerplugin.cpp
@@ -139,11 +139,13 @@ class DesignerPlugin final : public ExtensionSystem::IPlugin
IWizardFactory *wizard = new FormClassWizard;
wizard->setCategory(Core::Constants::WIZARD_CATEGORY_QT);
wizard->setDisplayCategory(::Core::Tr::tr(Core::Constants::WIZARD_TR_CATEGORY_QT));
- wizard->setDisplayName(Tr::tr("Qt Designer Form Class"));
+ wizard->setDisplayName(Tr::tr("Qt Widgets Designer Form Class"));
wizard->setIcon({}, "ui/h");
wizard->setId("C.FormClass");
- wizard->setDescription(Tr::tr("Creates a Qt Designer form along with a matching class (C++ header and source file) "
- "for implementation purposes. You can add the form and class to an existing Qt Widget Project."));
+ wizard->setDescription(Tr::tr("Creates a Qt Widgets Designer form along with a matching "
+ "class (C++ header and source file) "
+ "for implementation purposes. You can add the form and "
+ "class to an existing Qt Widget Project."));
return wizard;
});
diff --git a/src/plugins/designer/formeditor.cpp b/src/plugins/designer/formeditor.cpp
index 46afe017d0..aaf4a921dd 100644
--- a/src/plugins/designer/formeditor.cpp
+++ b/src/plugins/designer/formeditor.cpp
@@ -370,7 +370,7 @@ void FormEditorData::fullInit()
initDesignerSubWindows();
m_integration = new QtCreatorIntegration(m_formeditor, this);
m_formeditor->setIntegration(m_integration);
- // Connect Qt Designer help request to HelpManager.
+ // Connect Qt Widgets Designer help request to HelpManager.
QObject::connect(m_integration, &QtCreatorIntegration::creatorHelpRequested,
HelpManager::Signals::instance(),
[](const QUrl &url) { HelpManager::showHelpUrl(url, HelpManager::HelpModeAlways); });
@@ -657,7 +657,7 @@ void FormEditorData::setupActions()
QString(), Core::Constants::G_DEFAULT_THREE);
mformtools->addSeparator(m_contexts, Core::Constants::G_DEFAULT_THREE);
- m_actionAboutPlugins = new QAction(Tr::tr("About Qt Designer Plugins..."), d);
+ m_actionAboutPlugins = new QAction(Tr::tr("About Qt Widgets Designer Plugins..."), d);
addToolAction(m_actionAboutPlugins, m_contexts, "FormEditor.AboutPlugins", mformtools,
QString(), Core::Constants::G_DEFAULT_THREE);
QObject::connect(m_actionAboutPlugins, &QAction::triggered,
diff --git a/src/plugins/designer/formeditor.h b/src/plugins/designer/formeditor.h
index e3bf9c8ebf..0419789f5d 100644
--- a/src/plugins/designer/formeditor.h
+++ b/src/plugins/designer/formeditor.h
@@ -35,7 +35,7 @@ namespace Internal {
* This is based on the assumption that the Designer settings work with
* no plugins loaded.
*
- * The form editor shows a read-only XML editor in edit mode and Qt Designer
+ * The form editor shows a read-only XML editor in edit mode and Qt Widgets Designer
* in Design mode. */
enum InitializationStage {
diff --git a/src/plugins/designer/formeditorstack.cpp b/src/plugins/designer/formeditorstack.cpp
index 1bccd34178..6631de9a4c 100644
--- a/src/plugins/designer/formeditorstack.cpp
+++ b/src/plugins/designer/formeditorstack.cpp
@@ -24,6 +24,19 @@
#include <QDebug>
#include <QRect>
+/*!
+ \class Designer::Internal::FormEditorStack
+ \inmodule QtCreator
+ \internal
+
+ \brief The FormEditorStack class maintains a stack of \QD form windows embedded
+ into a scroll area and their associated XML editors.
+
+ Takes care of updating the XML editor once design mode is left.
+ Also updates the mainc ontainer resize handles when the active form
+ window changes.
+*/
+
namespace Designer {
namespace Internal {
diff --git a/src/plugins/designer/formeditorstack.h b/src/plugins/designer/formeditorstack.h
index 0f6425e96b..125ae2940c 100644
--- a/src/plugins/designer/formeditorstack.h
+++ b/src/plugins/designer/formeditorstack.h
@@ -20,11 +20,6 @@ namespace Core { class IEditor; }
namespace Designer {
namespace Internal {
-/* FormEditorStack: Maintains a stack of Qt Designer form windows embedded
- * into a scrollarea and their associated XML editors.
- * Takes care of updating the XML editor once design mode is left.
- * Also updates the maincontainer resize handles when the active form
- * window changes. */
class FormEditorStack : public QStackedWidget
{
Q_OBJECT
diff --git a/src/plugins/designer/formtemplatewizardpage.cpp b/src/plugins/designer/formtemplatewizardpage.cpp
index 68e3fb1711..cfe02e5263 100644
--- a/src/plugins/designer/formtemplatewizardpage.cpp
+++ b/src/plugins/designer/formtemplatewizardpage.cpp
@@ -45,7 +45,7 @@ Utils::WizardPage *FormPageFactory::create(ProjectExplorer::JsonWizard *wizard,
bool FormPageFactory::validateData(Utils::Id typeId, const QVariant &data, QString *errorMessage)
{
QTC_ASSERT(canCreate(typeId), return false);
- if (!data.isNull() && (data.type() != QVariant::Map || !data.toMap().isEmpty())) {
+ if (!data.isNull() && (data.typeId() != QMetaType::QVariantMap || !data.toMap().isEmpty())) {
*errorMessage = ::ProjectExplorer::Tr::tr(
"\"data\" for a \"Form\" page needs to be unset or an empty object.");
return false;
diff --git a/src/plugins/designer/formtemplatewizardpage.h b/src/plugins/designer/formtemplatewizardpage.h
index 96bcb2c74e..40f0434221 100644
--- a/src/plugins/designer/formtemplatewizardpage.h
+++ b/src/plugins/designer/formtemplatewizardpage.h
@@ -24,7 +24,7 @@ public:
bool validateData(Utils::Id typeId, const QVariant &data, QString *errorMessage) override;
};
-// A wizard page embedding Qt Designer's QDesignerNewFormWidgetInterface
+// A wizard page embedding Qt Widgets Designer's QDesignerNewFormWidgetInterface
// widget.
// Sets FormContents property.
diff --git a/src/plugins/designer/qtcreatorintegration.cpp b/src/plugins/designer/qtcreatorintegration.cpp
index 2da21f0e54..5e6d890095 100644
--- a/src/plugins/designer/qtcreatorintegration.cpp
+++ b/src/plugins/designer/qtcreatorintegration.cpp
@@ -627,10 +627,7 @@ bool QtCreatorIntegration::navigateToSlot(const QString &objectName,
const RefactoringFilePtr file = refactoring.file(location.filePath());
const int insertionPos = Utils::Text::positionInText(file->document(),
location.line(), location.column());
- ChangeSet changeSet;
- changeSet.insert(insertionPos, definition);
- file->setChangeSet(changeSet);
- file->apply();
+ file->apply(ChangeSet::makeInsert(insertionPos, definition));
const int indentationPos = file->document()->toPlainText().indexOf('}', insertionPos) - 1;
QTextCursor cursor(editor->textDocument()->document());
cursor.setPosition(indentationPos);
diff --git a/src/plugins/designer/qtdesignerformclasscodegenerator.cpp b/src/plugins/designer/qtdesignerformclasscodegenerator.cpp
index d935079cf4..677494658b 100644
--- a/src/plugins/designer/qtdesignerformclasscodegenerator.cpp
+++ b/src/plugins/designer/qtdesignerformclasscodegenerator.cpp
@@ -103,7 +103,7 @@ bool QtDesignerFormClassCodeGenerator::generateCpp(const FormClassWizardParamete
Utils::writeIncludeFileDirective("QtGui/" + formBaseClass, true, headerStr);
headerStr << "#endif\n";
} else {
- Utils::writeIncludeFileDirective("QtGui/" + formBaseClass, true, headerStr);
+ Utils::writeIncludeFileDirective("QtWidgets/" + formBaseClass, true, headerStr);
}
} else {
Utils::writeIncludeFileDirective(formBaseClass, true, headerStr);
diff --git a/src/plugins/diffeditor/DiffEditor.json.in b/src/plugins/diffeditor/DiffEditor.json.in
index 8fc93667da..754475b537 100644
--- a/src/plugins/diffeditor/DiffEditor.json.in
+++ b/src/plugins/diffeditor/DiffEditor.json.in
@@ -12,7 +12,8 @@
"",
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
- "Description" : "Diff editor component.",
- "Url" : "http://www.qt.io",
+ "Description" : "Compare two files or two versions of a file",
+ "LongDescription" : [],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/diffeditor/diffeditorplugin.cpp b/src/plugins/diffeditor/diffeditorplugin.cpp
index da2e4daaf2..22492b5123 100644
--- a/src/plugins/diffeditor/diffeditorplugin.cpp
+++ b/src/plugins/diffeditor/diffeditorplugin.cpp
@@ -13,8 +13,6 @@
#include <coreplugin/editormanager/documentmodel.h>
#include <coreplugin/editormanager/editormanager.h>
-#include <extensionsystem/pluginmanager.h>
-
#include <texteditor/textdocument.h>
#include <utils/async.h>
@@ -121,7 +119,6 @@ DiffFilesController::DiffFilesController(IDocument *document)
const auto onDiffSetup = [this, reloadInput = inputList.at(i)](Async<FileData> &async) {
async.setConcurrentCallData(
DiffFile(ignoreWhitespace(), contextLineCount()), reloadInput);
- async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
};
const auto onDiffDone = [outputList, i](const Async<FileData> &async) {
diff --git a/src/plugins/diffeditor/selectabletexteditorwidget.cpp b/src/plugins/diffeditor/selectabletexteditorwidget.cpp
index bcb3ee0381..af7ed7f3d1 100644
--- a/src/plugins/diffeditor/selectabletexteditorwidget.cpp
+++ b/src/plugins/diffeditor/selectabletexteditorwidget.cpp
@@ -91,12 +91,12 @@ DiffSelections SelectableTextEditorWidget::polishedSelections(const DiffSelectio
continue;
int j = 0;
- while (j < workingList.count()) {
+ while (j < workingList.size()) {
const DiffSelection existingSelection = workingList.takeAt(j);
const QList<DiffSelection> newSelection = subtractSelection(existingSelection, diffSelection);
- for (int k = 0; k < newSelection.count(); k++)
+ for (int k = 0; k < newSelection.size(); k++)
workingList.insert(j + k, newSelection.at(k));
- j += newSelection.count();
+ j += newSelection.size();
}
workingList.append(diffSelection);
}
@@ -126,8 +126,8 @@ void SelectableTextEditorWidget::paintBlock(QPainter *painter,
QTextLayout::FormatRange formatRange;
formatRange.start = qMax(0, diffSelection.start);
const int end = diffSelection.end < 0
- ? block.text().count() + 1
- : qMin(block.text().count(), diffSelection.end);
+ ? block.text().size() + 1
+ : qMin(block.text().size(), diffSelection.end);
formatRange.length = end - formatRange.start;
formatRange.format = *diffSelection.format;
diff --git a/src/plugins/diffeditor/sidebysidediffeditorwidget.cpp b/src/plugins/diffeditor/sidebysidediffeditorwidget.cpp
index ecdb87f80b..65fde112c3 100644
--- a/src/plugins/diffeditor/sidebysidediffeditorwidget.cpp
+++ b/src/plugins/diffeditor/sidebysidediffeditorwidget.cpp
@@ -12,8 +12,6 @@
#include <coreplugin/minisplitter.h>
#include <coreplugin/progressmanager/progressmanager.h>
-#include <extensionsystem/pluginmanager.h>
-
#include <texteditor/displaysettings.h>
#include <texteditor/fontsettings.h>
#include <texteditor/textdocument.h>
@@ -868,7 +866,6 @@ void SideBySideDiffEditorWidget::restoreState()
void SideBySideDiffEditorWidget::showDiff()
{
m_asyncTask.reset(new Async<SideBySideShowResults>());
- m_asyncTask->setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
m_controller.setBusyShowing(true);
connect(m_asyncTask.get(), &AsyncBase::done, this, [this] {
diff --git a/src/plugins/diffeditor/unifieddiffeditorwidget.cpp b/src/plugins/diffeditor/unifieddiffeditorwidget.cpp
index 31d8e1b190..0b34db53d9 100644
--- a/src/plugins/diffeditor/unifieddiffeditorwidget.cpp
+++ b/src/plugins/diffeditor/unifieddiffeditorwidget.cpp
@@ -10,8 +10,6 @@
#include <coreplugin/icore.h>
#include <coreplugin/progressmanager/progressmanager.h>
-#include <extensionsystem/pluginmanager.h>
-
#include <texteditor/fontsettings.h>
#include <texteditor/textdocument.h>
#include <texteditor/texteditorsettings.h>
@@ -451,7 +449,6 @@ void UnifiedDiffEditorWidget::showDiff()
}
m_asyncTask.reset(new Async<UnifiedShowResult>());
- m_asyncTask->setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
m_controller.setBusyShowing(true);
connect(m_asyncTask.get(), &AsyncBase::done, this, [this] {
if (m_asyncTask->isCanceled() || !m_asyncTask->isResultAvailable()) {
diff --git a/src/plugins/docker/Docker.json.in b/src/plugins/docker/Docker.json.in
index 6fc896f803..d587e7d0a9 100644
--- a/src/plugins/docker/Docker.json.in
+++ b/src/plugins/docker/Docker.json.in
@@ -13,7 +13,11 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Experimental" : true,
- "Description" : "Basic support for Docker",
- "Url" : "http://www.qt.io",
+ "Description" : "Create Docker devices from Docker images",
+ "LongDescription" : [
+ "You also need:",
+ "- Docker CLI"
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/docker/dockerapi.cpp b/src/plugins/docker/dockerapi.cpp
index 537b6c66f3..ea62fd8811 100644
--- a/src/plugins/docker/dockerapi.cpp
+++ b/src/plugins/docker/dockerapi.cpp
@@ -41,7 +41,7 @@ bool DockerApi::canConnect()
bool result = false;
- process.setCommand(CommandLine(dockerExe, QStringList{"info"}));
+ process.setCommand({dockerExe, {"info"}});
connect(&process, &Process::done, [&process, &result] {
qCInfo(dockerApiLog) << "'docker info' result:\n" << qPrintable(process.allOutput());
if (process.result() == ProcessResult::FinishedWithSuccess)
@@ -115,8 +115,7 @@ QFuture<Utils::expected_str<QList<Network>>> DockerApi::networks()
if (dockerExe.isEmpty() || !dockerExe.isExecutableFile())
return make_unexpected(Tr::tr("Docker executable not found"));
- process.setCommand(
- CommandLine(dockerExe, QStringList{"network", "ls", "--format", "{{json .}}"}));
+ process.setCommand({dockerExe, {"network", "ls", "--format", "{{json .}}"}});
process.runBlocking();
if (process.result() != ProcessResult::FinishedWithSuccess) {
diff --git a/src/plugins/docker/dockerdevice.cpp b/src/plugins/docker/dockerdevice.cpp
index d225bbf6e6..464a76be98 100644
--- a/src/plugins/docker/dockerdevice.cpp
+++ b/src/plugins/docker/dockerdevice.cpp
@@ -605,7 +605,7 @@ DockerDevice::DockerDevice(std::unique_ptr<DockerDeviceSettings> deviceSettings)
proc.setTerminalMode(TerminalMode::Detached);
proc.setEnvironment(env);
proc.setWorkingDirectory(workingDir);
- proc.setCommand({*shell, {}});
+ proc.setCommand(CommandLine{*shell});
proc.start();
return {};
@@ -1057,13 +1057,13 @@ expected_str<void> DockerDevicePrivate::fetchSystemEnviroment()
QString stdErr;
if (m_shell && m_shell->state() == DeviceShell::State::Succeeded) {
- const RunResult result = runInShell({"env", {}});
+ const RunResult result = runInShell(CommandLine{"env"});
const QString out = QString::fromUtf8(result.stdOut);
m_cachedEnviroment = Environment(out.split('\n', Qt::SkipEmptyParts), q->osType());
stdErr = QString::fromUtf8(result.stdErr);
} else {
Process proc;
- proc.setCommand(withDockerExecCmd({"env", {}}));
+ proc.setCommand(withDockerExecCmd(CommandLine{"env"}));
proc.start();
proc.waitForFinished();
const QString remoteOutput = proc.cleanedStdOut();
diff --git a/src/plugins/effectcomposer/EffectComposer.json.in b/src/plugins/effectcomposer/EffectComposer.json.in
index 48c6955796..619fda7f01 100644
--- a/src/plugins/effectcomposer/EffectComposer.json.in
+++ b/src/plugins/effectcomposer/EffectComposer.json.in
@@ -3,13 +3,13 @@
"Version" : "${IDE_VERSION}",
"CompatVersion" : "${IDE_VERSION_COMPAT}",
"Vendor" : "The Qt Company Ltd",
- "Copyright" : "(C) $$QTCREATOR_COPYRIGHT_YEAR The Qt Company Ltd",
+ "Copyright" : "(C) ${IDE_COPYRIGHT_YEAR} The Qt Company Ltd",
"License" : [ "Commercial Usage",
"",
"Licensees holding valid Qt Enterprise licenses may use this plugin in accordance with the Qt Enterprise License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and The Qt Company."
],
"Description" : "Plugin for Effect Composer.",
- "Url" : "http://www.qt.io",
+ "Url" : "https://www.qt.io",
"DisabledByDefault" : true,
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/effectcomposer/compositionnode.cpp b/src/plugins/effectcomposer/compositionnode.cpp
index d939e2283a..a69cd00e07 100644
--- a/src/plugins/effectcomposer/compositionnode.cpp
+++ b/src/plugins/effectcomposer/compositionnode.cpp
@@ -128,7 +128,7 @@ void CompositionNode::parse(const QString &effectName, const QString &qenPath, c
// parse properties
QJsonArray jsonProps = json.value("properties").toArray();
- for (const auto /*QJsonValueRef*/ &prop : jsonProps) {
+ for (const QJsonValueConstRef &prop : jsonProps) {
const auto uniform = new Uniform(effectName, prop.toObject(), qenPath);
m_unifomrsModel.addUniform(uniform);
m_uniforms.append(uniform);
diff --git a/src/plugins/effectcomposer/effectcomposermodel.cpp b/src/plugins/effectcomposer/effectcomposermodel.cpp
index a983072334..43efefe51d 100644
--- a/src/plugins/effectcomposer/effectcomposermodel.cpp
+++ b/src/plugins/effectcomposer/effectcomposermodel.cpp
@@ -21,6 +21,7 @@
#include <QByteArrayView>
#include <QLibraryInfo>
+#include <QTemporaryDir>
#include <QVector2D>
namespace EffectComposer {
@@ -48,6 +49,7 @@ static bool writeToFile(const QByteArray &buf, const QString &filename, FileType
EffectComposerModel::EffectComposerModel(QObject *parent)
: QAbstractListModel{parent}
+ , m_shaderDir(QDir::tempPath() + "/qds_ec_XXXXXX")
{
m_rebakeTimer.setSingleShot(true);
connect(&m_rebakeTimer, &QTimer::timeout, this, &EffectComposerModel::bakeShaders);
@@ -516,11 +518,9 @@ QJsonObject nodeToJson(const CompositionNode &node)
uniformObject.insert("type", type);
- if (uniform->type() == Uniform::Type::Define || uniform->type() == Uniform::Type::Channel) {
- QString controlType = Uniform::stringFromType(uniform->controlType());
- if (controlType != type)
- uniformObject.insert("controlType", controlType);
- }
+ QString controlType = Uniform::stringFromType(uniform->controlType());
+ if (controlType != type)
+ uniformObject.insert("controlType", controlType);
if (!uniform->displayName().isEmpty())
uniformObject.insert("displayName", QString(uniform->displayName()));
@@ -542,7 +542,8 @@ QJsonObject nodeToJson(const CompositionNode &node)
if (!uniform->description().isEmpty())
uniformObject.insert("description", uniform->description());
if (uniform->type() == Uniform::Type::Float
- || uniform->type() == Uniform::Type::Int
+ || (uniform->type() == Uniform::Type::Int
+ && uniform->controlType() != Uniform::Type::Channel)
|| uniform->type() == Uniform::Type::Vec2
|| uniform->type() == Uniform::Type::Vec3
|| uniform->type() == Uniform::Type::Vec4
@@ -1713,37 +1714,33 @@ void EffectComposerModel::updateCustomUniforms()
m_exportedEffectPropertiesString = exportedEffectPropertiesString;
}
-void EffectComposerModel::createFiles()
+void EffectComposerModel::initShaderDir()
{
- if (QFileInfo::exists(m_vertexShaderFilename))
- QFile(m_vertexShaderFilename).remove();
- if (QFileInfo::exists(m_fragmentShaderFilename))
- QFile(m_fragmentShaderFilename).remove();
- if (QFileInfo::exists(m_vertexShaderPreviewFilename))
- QFile(m_vertexShaderPreviewFilename).remove();
- if (QFileInfo::exists(m_fragmentShaderPreviewFilename))
- QFile(m_fragmentShaderPreviewFilename).remove();
+ static int count = 0;
+ static const QString fileNameTemplate = "%1_%2.%3";
+ const QString countStr = QString::number(count);
+
+ auto resetFile = [&countStr, this](QString &fileName, const QString prefix, const QString suffix) {
+ // qsb generation is done in separate process, so it is not guaranteed all of the old files
+ // get deleted here, as they may not exist yet. Any dangling files will be deleted at
+ // application shutdown, when the temporary directory is destroyed.
+ if (!fileName.isEmpty()) {
+ QFile file(fileName);
+ if (file.exists())
+ file.remove();
+ }
- auto vertexShaderFile = QTemporaryFile(QDir::tempPath() + "/dsem_XXXXXX.vert.qsb");
- auto fragmentShaderFile = QTemporaryFile(QDir::tempPath() + "/dsem_XXXXXX.frag.qsb");
- auto vertexShaderPreviewFile = QTemporaryFile(QDir::tempPath() + "/dsem_prev_XXXXXX.vert.qsb");
- auto fragmentShaderPreviewFile = QTemporaryFile(QDir::tempPath() + "/dsem_prev_XXXXXX.frag.qsb");
+ fileName = m_shaderDir.filePath(fileNameTemplate.arg(prefix, countStr, suffix));
+ };
- m_vertexSourceFile.setFileTemplate(QDir::tempPath() + "/dsem_XXXXXX.vert");
- m_fragmentSourceFile.setFileTemplate(QDir::tempPath() + "/dsem_XXXXXX.frag");
+ resetFile(m_vertexSourceFilename, "source", "vert");
+ resetFile(m_fragmentSourceFilename, "source", "frag");
+ resetFile(m_vertexShaderFilename, "compiled", "vert.qsb");
+ resetFile(m_fragmentShaderFilename, "compiled", "frag.qsb");
+ resetFile(m_vertexShaderPreviewFilename, "compiled_prev", "vert.qsb");
+ resetFile(m_fragmentShaderPreviewFilename, "compiled_prev", "frag.qsb");
- if (!m_vertexSourceFile.open() || !m_fragmentSourceFile.open()
- || !vertexShaderFile.open() || !fragmentShaderFile.open()
- || !vertexShaderPreviewFile.open() || !fragmentShaderPreviewFile.open()) {
- qWarning() << "Unable to open temporary files";
- } else {
- m_vertexSourceFilename = m_vertexSourceFile.fileName();
- m_fragmentSourceFilename = m_fragmentSourceFile.fileName();
- m_vertexShaderFilename = vertexShaderFile.fileName();
- m_fragmentShaderFilename = fragmentShaderFile.fileName();
- m_vertexShaderPreviewFilename = vertexShaderPreviewFile.fileName();
- m_fragmentShaderPreviewFilename = fragmentShaderPreviewFile.fileName();
- }
+ ++count;
}
void EffectComposerModel::bakeShaders()
@@ -1756,7 +1753,7 @@ void EffectComposerModel::bakeShaders()
return;
}
- createFiles();
+ initShaderDir();
resetEffectError(ErrorPreprocessor);
if (m_vertexShader == generateVertexShader() && m_fragmentShader == generateFragmentShader()) {
@@ -1775,11 +1772,11 @@ void EffectComposerModel::bakeShaders()
setVertexShader(generateVertexShader());
QString vs = m_vertexShader;
- writeToFile(vs.toUtf8(), m_vertexSourceFile.fileName(), FileType::Text);
+ writeToFile(vs.toUtf8(), m_vertexSourceFilename, FileType::Text);
setFragmentShader(generateFragmentShader());
QString fs = m_fragmentShader;
- writeToFile(fs.toUtf8(), m_fragmentSourceFile.fileName(), FileType::Text);
+ writeToFile(fs.toUtf8(), m_fragmentSourceFilename, FileType::Text);
QtSupport::QtVersion *qtVer = QtSupport::QtKitAspect::qtVersion(target->kit());
if (!qtVer) {
diff --git a/src/plugins/effectcomposer/effectcomposermodel.h b/src/plugins/effectcomposer/effectcomposermodel.h
index 14ef09e8a9..b377ae0618 100644
--- a/src/plugins/effectcomposer/effectcomposermodel.h
+++ b/src/plugins/effectcomposer/effectcomposermodel.h
@@ -12,7 +12,7 @@
#include <QMap>
#include <QRegularExpression>
#include <QSet>
-#include <QTemporaryFile>
+#include <QTemporaryDir>
#include <QTimer>
namespace ProjectExplorer {
@@ -176,7 +176,7 @@ private:
QString getQmlEffectString();
void updateCustomUniforms();
- void createFiles();
+ void initShaderDir();
void bakeShaders();
void saveResources(const QString &name);
@@ -205,8 +205,7 @@ private:
QStringList m_defaultRootVertexShader;
QStringList m_defaultRootFragmentShader;
// Temp files to store shaders sources and binary data
- QTemporaryFile m_fragmentSourceFile;
- QTemporaryFile m_vertexSourceFile;
+ QTemporaryDir m_shaderDir;
QString m_fragmentSourceFilename;
QString m_vertexSourceFilename;
QString m_fragmentShaderFilename;
diff --git a/src/plugins/emacskeys/EmacsKeys.json.in b/src/plugins/emacskeys/EmacsKeys.json.in
index ab3e1e5c26..82c486ee7c 100644
--- a/src/plugins/emacskeys/EmacsKeys.json.in
+++ b/src/plugins/emacskeys/EmacsKeys.json.in
@@ -28,6 +28,6 @@
"",
"Also it's worth mentioning that EmacsKeys plugin forces disabling of menu mnemonics by calling Qt's qt_set_sequence_auto_mnemonic function with false argument. Many of the english menu mnemonics get into the way of typical emacs keys, this includes: Alt+F (File), Alt+B (Build), Alt+W (Window). It's a temporary solution, it remains until there is a better one."
],
- "Url" : "http://nosmileface.ru",
+ "Url" : "https://nosmileface.ru",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/extensionmanager/CMakeLists.txt b/src/plugins/extensionmanager/CMakeLists.txt
index 1afb628ff7..094c1eb7d9 100644
--- a/src/plugins/extensionmanager/CMakeLists.txt
+++ b/src/plugins/extensionmanager/CMakeLists.txt
@@ -9,4 +9,14 @@ add_qtc_plugin(ExtensionManager
extensionmanagerwidget.h
extensionsbrowser.cpp
extensionsbrowser.h
+ extensionsmodel.cpp
+ extensionsmodel.h
+)
+
+extend_qtc_plugin(ExtensionManager
+ CONDITION WITH_TESTS
+ SOURCES
+ extensionmanager_test.cpp
+ extensionmanager_test.h
+ extensionmanager_test.qrc
)
diff --git a/src/plugins/extensionmanager/ExtensionManager.json.in b/src/plugins/extensionmanager/ExtensionManager.json.in
index 1dcc10805b..7cf4fec436 100644
--- a/src/plugins/extensionmanager/ExtensionManager.json.in
+++ b/src/plugins/extensionmanager/ExtensionManager.json.in
@@ -15,6 +15,6 @@
"Category" : "Core",
"Description" : "Extension Manager",
"Experimental": true,
- "Url" : "http://www.qt.io",
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/extensionmanager/extensionmanager.qbs b/src/plugins/extensionmanager/extensionmanager.qbs
index 062bdfc652..1a456824a7 100644
--- a/src/plugins/extensionmanager/extensionmanager.qbs
+++ b/src/plugins/extensionmanager/extensionmanager.qbs
@@ -4,6 +4,8 @@ QtcPlugin {
name: "ExtensionManager"
Depends { name: "Core" }
+ Depends { name: "Tasking" }
+ Depends { name: "Qt.network" }
files: [
"extensionmanager.qrc",
@@ -14,5 +16,15 @@ QtcPlugin {
"extensionmanagerwidget.h",
"extensionsbrowser.cpp",
"extensionsbrowser.h",
+ "extensionsmodel.cpp",
+ "extensionsmodel.h",
]
+
+ QtcTestFiles {
+ files: [
+ "extensionmanager_test.h",
+ "extensionmanager_test.cpp",
+ "extensionmanager_test.qrc",
+ ]
+ }
}
diff --git a/src/plugins/extensionmanager/extensionmanager.qrc b/src/plugins/extensionmanager/extensionmanager.qrc
index cff16c156f..b6a3554cb1 100644
--- a/src/plugins/extensionmanager/extensionmanager.qrc
+++ b/src/plugins/extensionmanager/extensionmanager.qrc
@@ -1,5 +1,7 @@
<RCC>
<qresource prefix="/extensionmanager">
+ <file>images/download.png</file>
+ <file>images/download@2x.png</file>
<file>images/extensionsmall.png</file>
<file>images/extensionsmall@2x.png</file>
<file>images/mode_extensionmanager_mask.png</file>
diff --git a/src/plugins/extensionmanager/extensionmanager_test.cpp b/src/plugins/extensionmanager/extensionmanager_test.cpp
new file mode 100644
index 0000000000..7c66644ad7
--- /dev/null
+++ b/src/plugins/extensionmanager/extensionmanager_test.cpp
@@ -0,0 +1,40 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "extensionmanager_test.h"
+
+#include "extensionsmodel.h"
+
+#include <utils/fileutils.h>
+
+#include <QTest>
+
+namespace ExtensionManager::Internal {
+
+class ExtensionsModelTest final : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void testRepositoryJsonParser();
+};
+
+void ExtensionsModelTest::testRepositoryJsonParser()
+{
+ ExtensionsModel model;
+ model.setExtensionsJson(testData("defaultpacks"));
+}
+
+QObject *createExtensionsModelTest()
+{
+ return new ExtensionsModelTest;
+}
+
+QByteArray testData(const QString &id)
+{
+ return Utils::FileReader::fetchQrc(":/extensionmanager/testdata/" + id + ".json");
+}
+
+} // ExtensionManager::Internal
+
+#include "extensionmanager_test.moc"
diff --git a/src/plugins/extensionmanager/extensionmanager_test.h b/src/plugins/extensionmanager/extensionmanager_test.h
new file mode 100644
index 0000000000..e913688d0d
--- /dev/null
+++ b/src/plugins/extensionmanager/extensionmanager_test.h
@@ -0,0 +1,14 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <QObject>
+
+namespace ExtensionManager::Internal {
+
+QObject *createExtensionsModelTest();
+
+QByteArray testData(const QString &id);
+
+} // namespace ExtensionManager::Internal
diff --git a/src/plugins/extensionmanager/extensionmanager_test.qrc b/src/plugins/extensionmanager/extensionmanager_test.qrc
new file mode 100644
index 0000000000..4c4d59f002
--- /dev/null
+++ b/src/plugins/extensionmanager/extensionmanager_test.qrc
@@ -0,0 +1,6 @@
+<RCC>
+ <qresource prefix="/extensionmanager">
+ <file>testdata/defaultpacks.json</file>
+ <file>testdata/thirdpartyplugins.json</file>
+ </qresource>
+</RCC>
diff --git a/src/plugins/extensionmanager/extensionmanagerplugin.cpp b/src/plugins/extensionmanager/extensionmanagerplugin.cpp
index 22dde816a9..979a75ad29 100644
--- a/src/plugins/extensionmanager/extensionmanagerplugin.cpp
+++ b/src/plugins/extensionmanager/extensionmanagerplugin.cpp
@@ -5,6 +5,9 @@
#include "extensionmanagerconstants.h"
#include "extensionmanagerwidget.h"
+#ifdef WITH_TESTS
+#include "extensionmanager_test.h"
+#endif // WITH_TESTS
#include <coreplugin/actionmanager/actioncontainer.h>
#include <coreplugin/actionmanager/actionmanager.h>
@@ -15,7 +18,6 @@
#include <coreplugin/imode.h>
#include <extensionsystem/iplugin.h>
-#include <extensionsystem/pluginspec.h>
#include <utils/icon.h>
#include <utils/layoutbuilder.h>
@@ -24,7 +26,6 @@
#include <QAction>
#include <QMainWindow>
-using namespace ExtensionSystem;
using namespace Core;
using namespace Utils;
@@ -43,7 +44,7 @@ public:
Theme::IconsBaseColor}});
const Icon FLAT_ACTIVE({{":/extensionmanager/images/mode_extensionmanager_mask.png",
Theme::IconsModeWelcomeActiveColor}});
- setIcon(Utils::Icon::modeIcon(FLAT, FLAT, FLAT_ACTIVE));
+ setIcon(Icon::modeIcon(FLAT, FLAT, FLAT_ACTIVE));
setPriority(72);
using namespace Layouting;
@@ -73,6 +74,10 @@ public:
void initialize() final
{
m_mode = new ExtensionManagerMode;
+
+#ifdef WITH_TESTS
+ addTestCreator(createExtensionsModelTest);
+#endif // WITH_TESTS
}
private:
diff --git a/src/plugins/extensionmanager/extensionmanagerwidget.cpp b/src/plugins/extensionmanager/extensionmanagerwidget.cpp
index be7e0f92ee..363b8cb90e 100644
--- a/src/plugins/extensionmanager/extensionmanagerwidget.cpp
+++ b/src/plugins/extensionmanager/extensionmanagerwidget.cpp
@@ -3,26 +3,40 @@
#include "extensionmanagerwidget.h"
-#include "extensionmanagerconstants.h"
#include "extensionmanagertr.h"
#include "extensionsbrowser.h"
+#include "extensionsmodel.h"
#include <coreplugin/coreconstants.h>
#include <coreplugin/icontext.h>
#include <coreplugin/icore.h>
#include <coreplugin/iwelcomepage.h>
+#include <coreplugin/plugininstallwizard.h>
#include <coreplugin/welcomepagehelper.h>
+#include <extensionsystem/pluginmanager.h>
#include <extensionsystem/pluginspec.h>
+#include <solutions/tasking/networkquery.h>
+#include <solutions/tasking/tasktree.h>
+#include <solutions/tasking/tasktreerunner.h>
+
#include <utils/algorithm.h>
+#include <utils/fileutils.h>
+#include <utils/hostosinfo.h>
#include <utils/icon.h>
+#include <utils/infolabel.h>
#include <utils/layoutbuilder.h>
+#include <utils/networkaccessmanager.h>
#include <utils/stylehelper.h>
+#include <utils/temporarydirectory.h>
#include <utils/utilsicons.h>
#include <QAction>
+#include <QCheckBox>
+#include <QMessageBox>
#include <QTextBrowser>
+#include <QProgressDialog>
using namespace Core;
using namespace Utils;
@@ -54,80 +68,172 @@ private:
int m_width = 100;
};
-ExtensionManagerWidget::ExtensionManagerWidget()
+class PluginStatusWidget : public QWidget
{
- m_leftColumn = new ExtensionsBrowser;
+public:
+ explicit PluginStatusWidget(QWidget *parent = nullptr)
+ : QWidget(parent)
+ {
+ m_label = new InfoLabel;
+ m_checkBox = new QCheckBox(Tr::tr("Load on Start"));
+
+ using namespace Layouting;
+ Column {
+ m_label,
+ m_checkBox,
+ }.attachTo(this);
+
+ connect(m_checkBox, &QCheckBox::clicked, this, [this](bool checked) {
+ ExtensionSystem::PluginSpec *spec = ExtensionsModel::pluginSpecForName(m_pluginName);
+ if (spec == nullptr)
+ return;
+ spec->setEnabledBySettings(checked);
+ ExtensionSystem::PluginManager::writeSettings();
+ });
+
+ update();
+ }
+
+ void setPluginName(const QString &name)
+ {
+ m_pluginName = name;
+ update();
+ }
+
+private:
+ void update()
+ {
+ const ExtensionSystem::PluginSpec *spec = ExtensionsModel::pluginSpecForName(m_pluginName);
+ setVisible(spec != nullptr);
+ if (spec == nullptr)
+ return;
+
+ if (spec->hasError()) {
+ m_label->setType(InfoLabel::Error);
+ m_label->setText(Tr::tr("Error"));
+ } else if (spec->state() == ExtensionSystem::PluginSpec::Running) {
+ m_label->setType(InfoLabel::Ok);
+ m_label->setText(Tr::tr("Loaded"));
+ } else {
+ m_label->setType(InfoLabel::NotOk);
+ m_label->setText(Tr::tr("Not loaded"));
+ }
+
+ m_checkBox->setChecked(spec->isRequired() || spec->isEnabledBySettings());
+ m_checkBox->setEnabled(!spec->isRequired());
+ }
+
+ InfoLabel *m_label;
+ QCheckBox *m_checkBox;
+ QString m_pluginName;
+};
+
+class ExtensionManagerWidgetPrivate
+{
+public:
+ QString currentItemName;
+ ExtensionsBrowser *leftColumn;
+ CollapsingWidget *secondaryDescriptionWidget;
+ QTextBrowser *primaryDescription;
+ QTextBrowser *secondaryDescription;
+ PluginStatusWidget *pluginStatus;
+ QAbstractButton *installButton;
+ PluginsData currentItemPlugins;
+ Tasking::TaskTreeRunner taskTreeRunner;
+};
+
+ExtensionManagerWidget::ExtensionManagerWidget(QWidget *parent)
+ : ResizeSignallingWidget(parent)
+ , d(new ExtensionManagerWidgetPrivate)
+{
+ d->leftColumn = new ExtensionsBrowser;
auto descriptionColumns = new QWidget;
- m_secondarDescriptionWidget = new CollapsingWidget;
+ d->secondaryDescriptionWidget = new CollapsingWidget;
+
+ d->primaryDescription = new QTextBrowser;
+ d->primaryDescription->setOpenExternalLinks(true);
+ d->primaryDescription->setFrameStyle(QFrame::NoFrame);
+
+ d->secondaryDescription = new QTextBrowser;
+ d->secondaryDescription->setFrameStyle(QFrame::NoFrame);
- m_primaryDescription = new QTextBrowser;
- m_primaryDescription->setOpenExternalLinks(true);
- m_primaryDescription->setFrameStyle(QFrame::NoFrame);
+ d->pluginStatus = new PluginStatusWidget;
- m_secondaryDescription = new QTextBrowser;
- m_secondaryDescription->setFrameStyle(QFrame::NoFrame);
+ d->installButton = new Button(Tr::tr("Install..."), Button::MediumPrimary);
+ d->installButton->hide();
using namespace Layouting;
Row {
WelcomePageHelpers::createRule(Qt::Vertical),
- m_secondaryDescription,
- noMargin(), spacing(0),
- }.attachTo(m_secondarDescriptionWidget);
+ Column {
+ d->secondaryDescription,
+ d->pluginStatus,
+ d->installButton,
+ },
+ noMargin, spacing(0),
+ }.attachTo(d->secondaryDescriptionWidget);
Row {
WelcomePageHelpers::createRule(Qt::Vertical),
Row {
- m_primaryDescription,
- noMargin(),
+ d->primaryDescription,
+ noMargin,
},
- m_secondarDescriptionWidget,
- noMargin(), spacing(0),
+ d->secondaryDescriptionWidget,
+ noMargin, spacing(0),
}.attachTo(descriptionColumns);
Row {
Space(StyleHelper::SpacingTokens::ExVPaddingGapXl),
- m_leftColumn,
+ d->leftColumn,
descriptionColumns,
- noMargin(), spacing(0),
+ noMargin, spacing(0),
}.attachTo(this);
WelcomePageHelpers::setBackgroundColor(this, Theme::Token_Background_Default);
- connect(m_leftColumn, &ExtensionsBrowser::itemSelected,
+ connect(d->leftColumn, &ExtensionsBrowser::itemSelected,
this, &ExtensionManagerWidget::updateView);
connect(this, &ResizeSignallingWidget::resized, this, [this](const QSize &size) {
const int intendedLeftColumnWidth = size.width() - 580;
- m_leftColumn->adjustToWidth(intendedLeftColumnWidth);
+ d->leftColumn->adjustToWidth(intendedLeftColumnWidth);
const bool secondaryDescriptionVisible = size.width() > 970;
const int secondaryDescriptionWidth = secondaryDescriptionVisible ? 264 : 0;
- m_secondarDescriptionWidget->setWidth(secondaryDescriptionWidth);
+ d->secondaryDescriptionWidget->setWidth(secondaryDescriptionWidth);
+ });
+ connect(d->installButton, &QAbstractButton::pressed, this, [this]() {
+ fetchAndInstallPlugin(QUrl::fromUserInput(d->currentItemPlugins.constFirst().second));
});
- updateView({}, {});
+ updateView({});
+}
+
+ExtensionManagerWidget::~ExtensionManagerWidget()
+{
+ delete d;
}
-void ExtensionManagerWidget::updateView(const QModelIndex &current,
- [[maybe_unused]] const QModelIndex &previous)
+void ExtensionManagerWidget::updateView(const QModelIndex &current)
{
const QString h5Css =
StyleHelper::fontToCssProperties(StyleHelper::uiFont(StyleHelper::UiElementH5))
- + "; margin-top: 28px;";
+ + "; margin-top: 0px;";
const QString h6Css =
StyleHelper::fontToCssProperties(StyleHelper::uiFont(StyleHelper::UiElementH6))
+ "; margin-top: 28px;";
const QString h6CapitalCss =
StyleHelper::fontToCssProperties(StyleHelper::uiFont(StyleHelper::UiElementH6Capital))
- + QString::fromLatin1("; color: %1;")
- .arg(creatorTheme()->color(Theme::Token_Text_Muted).name());
- const QString bodyStyle = QString::fromLatin1("color: %1; background-color: %2;"
+ + QString::fromLatin1("; margin-top: 0px; color: %1;")
+ .arg(creatorColor(Theme::Token_Text_Muted).name());
+ const QString bodyStyle = QString::fromLatin1("color: %1; background-color: %2; "
"margin-left: %3px; margin-right: %3px;")
- .arg(creatorTheme()->color(Theme::Token_Text_Default).name())
- .arg(creatorTheme()->color(Theme::Token_Background_Muted).name())
+ .arg(creatorColor(Theme::Token_Text_Default).name())
+ .arg(creatorColor(Theme::Token_Background_Muted).name())
.arg(StyleHelper::SpacingTokens::ExVPaddingGapXl);
const QString htmlStart = QString(R"(
<html>
- <body style="%1">
+ <body style="%1"><br/>
)").arg(bodyStyle);
const QString htmlEnd = QString(R"(
</body></html>
@@ -135,161 +241,235 @@ void ExtensionManagerWidget::updateView(const QModelIndex &current,
if (!current.isValid()) {
const QString emptyHtml = htmlStart + htmlEnd;
- m_primaryDescription->setText(emptyHtml);
- m_secondaryDescription->setText(emptyHtml);
+ d->primaryDescription->setText(emptyHtml);
+ d->secondaryDescription->setText(emptyHtml);
return;
}
- const ItemData data = itemData(current);
- const bool isPack = data.type == ItemTypePack;
- const ExtensionSystem::PluginSpec *extension = data.plugins.first();
+ d->currentItemName = current.data().toString();
+ const bool isPack = current.data(RoleItemType) == ItemTypePack;
+ d->pluginStatus->setPluginName(isPack ? QString() : d->currentItemName);
+ const bool isRemotePlugin = !(isPack || ExtensionsModel::pluginSpecForName(d->currentItemName));
+ d->currentItemPlugins = current.data(RolePlugins).value<PluginsData>();
+ d->installButton->setVisible(isRemotePlugin && !d->currentItemPlugins.empty());
+ if (!d->currentItemPlugins.empty())
+ d->installButton->setToolTip(d->currentItemPlugins.constFirst().second);
{
- const QString shortDescription =
- isPack ? QLatin1String("Short description for pack ") + data.name
- : extension->description();
- QString longDescription =
- isPack ? QLatin1String("Some longer text that describes the purpose and functionality "
- "of the extensions that are part of pack ") + data.name
- : extension->longDescription();
- longDescription.replace("\n", "<br/>");
- const QString location = isPack ? extension->location() : extension->filePath();
-
QString description = htmlStart;
- description.append(QString(R"(
- <div style="%1"><br/>%2</div>
- <p>%3</p>
- )").arg(h5Css)
- .arg(shortDescription)
- .arg(longDescription));
-
- description.append(QString(R"(
- <div style="%1">%2</div>
- <p>%3</p>
- )").arg(h6Css)
- .arg(Tr::tr("Get started"))
- .arg(Tr::tr("Install the extension from above. Installation starts automatically. "
- "You can always uninstall the extension afterwards.")));
+ QString descriptionHtml;
+ {
+ const TextData textData = current.data(RoleDescriptionText).value<TextData>();
+ for (const TextData::Type &text : textData) {
+ if (text.second.isEmpty())
+ continue;
+ const QString paragraph =
+ QString::fromLatin1("<div style=\"%1\">%2</div><p>%3</p>")
+ .arg(descriptionHtml.isEmpty() ? h5Css : h6Css)
+ .arg(text.first)
+ .arg(text.second.join("<br/>"));
+ descriptionHtml.append(paragraph);
+ }
+ }
+ description.append(descriptionHtml);
+
+ description.append(QString::fromLatin1("<div style=\"%1\">%2</div>")
+ .arg(h6Css)
+ .arg(Tr::tr("More information")));
+ const LinksData linksData = current.data(RoleDescriptionLinks).value<LinksData>();
+ if (!linksData.isEmpty()) {
+ QString linksHtml;
+ const QStringList links = transform(linksData, [](const LinksData::Type &link) {
+ const QString anchor = link.first.isEmpty() ? link.second : link.first;
+ return QString::fromLatin1("<a href=\"%1\">%2 &gt;</a>")
+ .arg(link.second).arg(anchor);
+ });
+ linksHtml = links.join("<br/>");
+ description.append(QString::fromLatin1("<p>%1</p>").arg(linksHtml));
+ }
- description.append(QString(R"(
- <div style="%1">%2</div>
- <p>
- <a href="%3">%4 &gt;</a>
- <br/>
- <a href="%5">%6 &gt;</a>
- </p>
- )").arg(h6Css)
- .arg(Tr::tr("More information"))
- .arg(Tr::tr("Online Documentation"))
- .arg("https://doc.qt.io/qtcreator/")
- .arg(Tr::tr("Tutorials"))
- .arg("https://doc.qt.io/qtcreator/creator-tutorials.html"));
-
- const QString examplesBoxCss =
- QString::fromLatin1("height: 168px; background-color: %1; ")
- .arg(creatorTheme()->color(Theme::Token_Background_Default).name());
- description.append(QString(R"(
- <div style="%1">%2</div>
- <p style="%3">
- <br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
- </p>
- )").arg(h6CapitalCss)
- .arg(Tr::tr("Examples"))
- .arg(examplesBoxCss));
+ const ImagesData imagesData = current.data(RoleDescriptionImages).value<ImagesData>();
+ if (!imagesData.isEmpty()) {
+ const QString examplesBoxCss =
+ QString::fromLatin1("height: 168px; background-color: %1; ")
+ .arg(creatorColor(Theme::Token_Background_Default).name());
+ description.append(QString(R"(
+ <br/>
+ <div style="%1">%2</div>
+ <p style="%3">
+ <br/><br/><br/><br/><br/>
+ TODO: Load imagea asynchronously, and show them in a QLabel.
+ Also Use QMovie for animated images.
+ <br/><br/><br/><br/><br/>
+ </p>
+ )").arg(h6CapitalCss)
+ .arg(Tr::tr("Examples"))
+ .arg(examplesBoxCss));
+ }
- const QString captionStrongCss = StyleHelper::fontToCssProperties(
- StyleHelper::uiFont(StyleHelper::UiElementCaptionStrong));
- description.append(QString(R"(
- <div style="%1">%2</div>
- <p>
- <table>
- <tr><td style="%3">%4</td><td>%5</td></tr>
- <tr><td style="%3">%6</td><td>%7</td></tr>
- <tr><td style="%3">%8</td><td>%9</td></tr>
- </table>
- </p>
- )").arg(h6Css)
- .arg(Tr::tr("Extension library details"))
- .arg(captionStrongCss)
- .arg(Tr::tr("Size"))
- .arg("547 MB")
- .arg(Tr::tr("Version"))
- .arg(extension->version())
- .arg(Tr::tr("Location"))
- .arg(location));
+ // Library details vanished from the Figma designs. The data is available, though.
+ const bool showDetails = false;
+ if (showDetails) {
+ const QString captionStrongCss = StyleHelper::fontToCssProperties(
+ StyleHelper::uiFont(StyleHelper::UiElementCaptionStrong));
+ const QLocale locale;
+ const uint size = current.data(RoleSize).toUInt();
+ const QString sizeFmt = locale.formattedDataSize(size);
+ const FilePath location = FilePath::fromVariant(current.data(RoleLocation));
+ const QString version = current.data(RoleVersion).toString();
+ description.append(QString(R"(
+ <div style="%1">%2</div>
+ <p>
+ <table>
+ <tr><td style="%3">%4</td><td>%5</td></tr>
+ <tr><td style="%3">%6</td><td>%7</td></tr>
+ )").arg(h6Css)
+ .arg(Tr::tr("Extension library details"))
+ .arg(captionStrongCss)
+ .arg(Tr::tr("Size"))
+ .arg(sizeFmt)
+ .arg(Tr::tr("Version"))
+ .arg(version));
+ if (!location.isEmpty()) {
+ const QString locationFmt =
+ HostOsInfo::isWindowsHost() ? location.toUserOutput()
+ : location.withTildeHomePath();
+ description.append(QString(R"(
+ <tr><td style="%3">%1</td><td>%2</td></tr>
+ )").arg(Tr::tr("Location"))
+ .arg(locationFmt));
+ }
+ description.append(QString(R"(
+ </table>
+ </p>
+ )"));
+ }
description.append(htmlEnd);
- m_primaryDescription->setText(description);
+ d->primaryDescription->setText(description);
}
{
QString description = htmlStart;
description.append(QString(R"(
- <p style="%1"><br/>%2</p>
+ <p style="%1">%2</p>
)").arg(h6CapitalCss)
.arg(Tr::tr("Extension details")));
- description.append(QString(R"(
- <div style="%1">%2</div>
- <p>%3</p>
- )").arg(h6Css)
- .arg(Tr::tr("Released"))
- .arg("23.5.2023"));
-
- const QString tagTemplate = QString(R"(
- <td style="border: 1px solid %1; padding: 3px; ">%2</td>
- )").arg(creatorTheme()->color(Theme::Token_Stroke_Subtle).name());
- const QStringList tags = Utils::transform(data.tags,
- [&tagTemplate] (const QString &tag) {
- return tagTemplate.arg(tag);
- });
- description.append(QString(R"(
- <div style="%1">%2</div>
- <p>%3</p>
- )").arg(h6Css)
- .arg(Tr::tr("Related tags"))
- .arg(tags.join("&nbsp;")));
+ const QStringList tags = current.data(RoleTags).toStringList();
+ if (!tags.isEmpty()) {
+ const QString tagTemplate = QString(R"(
+ <td style="border: 1px solid %1; padding: 3px; ">%2</td>
+ )").arg(creatorColor(Theme::Token_Stroke_Subtle).name());
+ const QStringList tagsFmt = transform(tags, [&tagTemplate](const QString &tag) {
+ return tagTemplate.arg(tag);
+ });
+ description.append(QString(R"(
+ <div style="%1">%2</div>
+ <p>%3</p>
+ )").arg(h6Css)
+ .arg(Tr::tr("Related tags"))
+ .arg(tagsFmt.join("&nbsp;")));
+ }
- description.append(QString(R"(
- <div style="%1">%2</div>
- <p>
- macOS<br/>
- Windows<br/>
- Linux
- </p>
- )").arg(h6Css)
- .arg(Tr::tr("Platforms")));
-
- QStringList dependencies;
- for (const ExtensionSystem::PluginSpec *spec : data.plugins) {
- dependencies.append(Utils::transform(spec->dependencies(),
- &ExtensionSystem::PluginDependency::toString));
+ const QStringList platforms = current.data(RolePlatforms).toStringList();
+ if (!platforms.isEmpty()) {
+ description.append(QString(R"(
+ <div style="%1">%2</div>
+ <p>%3</p>
+ )").arg(h6Css)
+ .arg(Tr::tr("Platforms"))
+ .arg(platforms.join("<br/>")));
+ }
+
+ const QStringList dependencies = current.data(RoleDependencies).toStringList();
+ if (!dependencies.isEmpty()) {
+ const QString dependenciesFmt = dependencies.join("<br/>");
+ description.append(QString(R"(
+ <div style="%1">%2</div>
+ <p>%3</p>
+ )").arg(h6Css)
+ .arg(Tr::tr("Dependencies"))
+ .arg(dependenciesFmt));
}
- dependencies.removeDuplicates();
- dependencies.sort();
- description.append(QString(R"(
- <div style="%1">%2</div>
- <p>%3</p>
- )").arg(h6Css)
- .arg(Tr::tr("Dependencies"))
- .arg(dependencies.isEmpty() ? "-" : dependencies.join("<br/>")));
if (isPack) {
- const QStringList extensions = Utils::transform(data.plugins,
- &ExtensionSystem::PluginSpec::name);
+ const PluginsData plugins = current.data(RolePlugins).value<PluginsData>();
+ const QStringList extensions = transform(plugins, &QPair<QString, QString>::first);
+ const QString extensionsFmt = extensions.join("<br/>");
description.append(QString(R"(
<div style="%1">%2</div>
<p>%3</p>
)").arg(h6Css)
- .arg(Tr::tr("Extensions in pack"))
- .arg(extensions.join("<br/>")));
+ .arg(Tr::tr("Extensions in pack"))
+ .arg(extensionsFmt));
}
description.append(htmlEnd);
- m_secondaryDescription->setText(description);
+ d->secondaryDescription->setText(description);
}
}
+void ExtensionManagerWidget::fetchAndInstallPlugin(const QUrl &url)
+{
+ using namespace Tasking;
+
+ struct StorageStruct
+ {
+ StorageStruct() {
+ progressDialog.reset(new QProgressDialog(Tr::tr("Downloading Plugin..."),
+ Tr::tr("Cancel"), 0, 0,
+ ICore::dialogParent()));
+ progressDialog->setWindowModality(Qt::ApplicationModal);
+ progressDialog->setFixedSize(progressDialog->sizeHint());
+ progressDialog->setAutoClose(false);
+ progressDialog->show(); // TODO: Should not be needed. Investigate possible QT_BUG
+ }
+ std::unique_ptr<QProgressDialog> progressDialog;
+ QByteArray packageData;
+ QUrl url;
+ };
+ Storage<StorageStruct> storage;
+
+ const auto onQuerySetup = [url, storage](NetworkQuery &query) {
+ storage->url = url;
+ query.setRequest(QNetworkRequest(url));
+ query.setNetworkAccessManager(NetworkAccessManager::instance());
+ };
+ const auto onQueryDone = [storage](const NetworkQuery &query, DoneWith result) {
+ storage->progressDialog->close();
+ if (result == DoneWith::Success) {
+ storage->packageData = query.reply()->readAll();
+ } else {
+ QMessageBox::warning(
+ ICore::dialogParent(),
+ Tr::tr("Download Error"),
+ Tr::tr("Could not download Plugin") + "\n\n" + storage->url.toString() + "\n\n"
+ + Tr::tr("Code: %1.").arg(query.reply()->error()));
+ }
+ };
+
+ const auto onPluginInstallation = [storage]() {
+ if (storage->packageData.isEmpty())
+ return;
+ const FilePath source = FilePath::fromUrl(storage->url);
+ TempFileSaver saver(TemporaryDirectory::masterDirectoryPath()
+ + "/XXXXXX" + source.fileName());
+
+ saver.write(storage->packageData);
+ if (saver.finalize(ICore::dialogParent()))
+ executePluginInstallWizard(saver.filePath());;
+ };
+
+ Group group{
+ storage,
+ NetworkQueryTask{onQuerySetup, onQueryDone},
+ onGroupDone(onPluginInstallation),
+ };
+
+ d->taskTreeRunner.start(group);
+}
+
} // ExtensionManager::Internal
diff --git a/src/plugins/extensionmanager/extensionmanagerwidget.h b/src/plugins/extensionmanager/extensionmanagerwidget.h
index efa2925e25..aeaad3db07 100644
--- a/src/plugins/extensionmanager/extensionmanagerwidget.h
+++ b/src/plugins/extensionmanager/extensionmanagerwidget.h
@@ -3,27 +3,19 @@
#include <coreplugin/welcomepagehelper.h>
-QT_BEGIN_NAMESPACE
-class QTextBrowser;
-QT_END_NAMESPACE
-
namespace ExtensionManager::Internal {
-class CollapsingWidget;
-class ExtensionsBrowser;
-
class ExtensionManagerWidget final : public Core::ResizeSignallingWidget
{
public:
- explicit ExtensionManagerWidget();
+ explicit ExtensionManagerWidget(QWidget *parent = nullptr);
+ ~ExtensionManagerWidget();
private:
- void updateView(const QModelIndex &current, [[maybe_unused]] const QModelIndex &previous);
+ void updateView(const QModelIndex &current);
+ void fetchAndInstallPlugin(const QUrl &url);
- ExtensionsBrowser *m_leftColumn;
- CollapsingWidget *m_secondarDescriptionWidget;
- QTextBrowser *m_primaryDescription;
- QTextBrowser *m_secondaryDescription;
+ class ExtensionManagerWidgetPrivate *d = nullptr;
};
} // ExtensionManager::Internal
diff --git a/src/plugins/extensionmanager/extensionsbrowser.cpp b/src/plugins/extensionmanager/extensionsbrowser.cpp
index d83c1ef3ee..ccf814cd7c 100644
--- a/src/plugins/extensionmanager/extensionsbrowser.cpp
+++ b/src/plugins/extensionmanager/extensionsbrowser.cpp
@@ -4,10 +4,17 @@
#include "extensionsbrowser.h"
#include "extensionmanagertr.h"
+#include "extensionsmodel.h"
+#include "utils/hostosinfo.h"
+
+#ifdef WITH_TESTS
+#include "extensionmanager_test.h"
+#endif // WITH_TESTS
#include <coreplugin/coreconstants.h>
#include <coreplugin/icontext.h>
#include <coreplugin/icore.h>
+#include <coreplugin/plugininstallwizard.h>
#include <coreplugin/welcomepagehelper.h>
#include <extensionsystem/iplugin.h>
@@ -15,9 +22,14 @@
#include <extensionsystem/pluginview.h>
#include <extensionsystem/pluginmanager.h>
+#include <solutions/tasking/networkquery.h>
+#include <solutions/tasking/tasktree.h>
+#include <solutions/tasking/tasktreerunner.h>
+
#include <utils/fancylineedit.h>
#include <utils/icon.h>
#include <utils/layoutbuilder.h>
+#include <utils/networkaccessmanager.h>
#include <utils/stylehelper.h>
#include <QItemDelegate>
@@ -26,292 +38,39 @@
#include <QMessageBox>
#include <QPainter>
#include <QPainterPath>
-#include <QStandardItemModel>
#include <QStyle>
-using namespace ExtensionSystem;
using namespace Core;
+using namespace ExtensionSystem;
using namespace Utils;
+using namespace StyleHelper;
+using namespace SpacingTokens;
+using namespace WelcomePageHelpers;
namespace ExtensionManager::Internal {
Q_LOGGING_CATEGORY(browserLog, "qtc.extensionmanager.browser", QtWarningMsg)
-using PluginSpecList = QList<const PluginSpec *>;
-using Tags = QStringList;
-
-constexpr QSize itemSize = {330, 86};
-constexpr int gapSize = StyleHelper::SpacingTokens::ExVPaddingGapXl;
-constexpr QSize cellSize = {itemSize.width() + gapSize, itemSize.height() + gapSize};
-
-enum Role {
- RoleName = Qt::UserRole,
- RoleItemType,
- RoleTags,
- RolePluginSpecs,
- RoleSearchText,
-};
-
-ItemData itemData(const QModelIndex &index)
-{
- return {
- index.data(RoleName).toString(),
- index.data(RoleItemType).value<ItemType>(),
- index.data(RoleTags).toStringList(),
- index.data(RolePluginSpecs).value<PluginSpecList>(),
- };
-}
-
-static QColor colorForExtensionName(const QString &name)
-{
- const size_t hash = qHash(name);
- return QColor::fromHsv(hash % 360, 180, 110);
-}
-
-static QStandardItemModel *extensionsModel()
-{
- // The new extensions concept renames plugins to extensions and adds "packs" which are
- // groups of extensions.
- //
- // TODO: The "meta data" here which is injected into the model is only a place holder that
- // helps exploring the upcoming extensions concept.
- //
- // Before this loses the WIP prefix, we should at least have a concrete idea of how the data
- // is structured and where it lives. Ideally, it continues to reside exclusively in the
- // extension meta data.
- //
- // The grouping of extensions into packs could be done via extension tag. Extensions and will
- // receive tags and if possible screen shots.
- // Packs will also have a complete set of meta data. That could be an accumulation of the data
- // of the contained extensions. Or simply the data from the "first" extension in a pack.
-
- static const char tagBuildTools[] = "Build Tools";
- static const char tagCodeAnalyzing[] = "Code Analyzing";
- static const char tagConnectivity[] = "Connectivity";
- static const char tagCore[] = "Core";
- static const char tagCpp[] = "C++";
- static const char tagEditorConvenience[] = "Editor Convenience";
- static const char tagEditor[] = "Editor";
- static const char tagEssentials[] = "Essentials";
- static const char tagGlsl[] = "GLSL";
- static const char tagPackageManager[] = "Package Manager";
- static const char tagPlatformSupport[] = "Platform Support";
- static const char tagProgrammingLanguage[] = "Programming Language";
- static const char tagPython[] = "Python";
- static const char tagQml[] = "QML";
- static const char tagQuick[] = "Quick";
- static const char tagService[] = "Service";
- static const char tagTestAutomation[] = "Test Automation";
- static const char tagUiEditor[] = "Visual UI Editor" ;
- static const char tagVersionControl[] = "Version Control";
- static const char tagVisualEditor[] = "Visual editor";
- static const char tagWidgets[] = "Widgets";
-
- static const char tagTagUndefined[] = "Tag undefined";
-
- static const struct {
- const QString name;
- const QStringList extensions;
- const Tags tags;
- } packs[] = {
- {"Core",
- {"Core", "Help", "ProjectExplorer", "TextEditor", "Welcome", "GenericProjectManager",
- "QtSupport"},
- {tagCore}
- },
- {"Core (from Installer)",
- {"LicenseChecker", "Marketplace", "UpdateInfo"},
- {tagCore}
- },
- {"Essentials",
- {"Bookmarks", "BinEditor", "Debugger", "DiffEditor", "ImageViewer", "Macros",
- "LanguageClient", "ResourceEditor"},
- {tagEssentials}
- },
- {"C++ Language support",
- {"ClangCodeModel", "ClangFormat", "ClassView", "CppEditor"},
- {tagProgrammingLanguage, tagCpp}
- },
- {"QML Language Support (Qt Quick libraries)",
- {"QmlJSEditor", "QmlJSTools", "QmlPreview", "QmlProfiler", "QmlProjectManager"},
- {tagProgrammingLanguage, tagQml}
- },
- {"Visual QML UI Editor",
- {"QmlDesigner", "QmlDesignerBase"},
- {tagUiEditor, tagQml, tagQuick}
- },
- {"Visual C++ Widgets UI Editor",
- {"Designer"},
- {tagUiEditor, tagCpp, tagWidgets}
- },
- };
-
- static const struct {
- const QString name;
- const Tags tags;
- } extensions[] = {
- {"GLSLEditor", {tagProgrammingLanguage, tagGlsl}},
- {"Nim", {tagProgrammingLanguage}},
- {"Python", {tagProgrammingLanguage, tagPython}},
- {"Haskell", {tagProgrammingLanguage}},
-
- {"ModelEditor", {tagVisualEditor}},
- {"ScxmlEditor", {tagVisualEditor}},
-
- {"Bazaar", {tagVersionControl}},
- {"CVS", {tagVersionControl}},
- {"ClearCase", {tagVersionControl}},
- {"Fossil", {tagVersionControl}},
- {"Git", {tagVersionControl}},
- {"Mercurial", {tagVersionControl}},
- {"Perforce", {tagVersionControl}},
- {"Subversion", {tagVersionControl}},
- {"VcsBase", {tagVersionControl}},
- {"GitLab", {tagVersionControl, tagService}},
-
- {"AutoTest", {tagTestAutomation}},
- {"Squish", {tagTestAutomation}},
- {"Coco", {tagTestAutomation}},
-
- {"Vcpkg", {tagPackageManager}},
- {"Conan", {tagPackageManager}},
-
- {"Copilot", {tagEditorConvenience}},
- {"EmacsKeys", {tagEditorConvenience}},
- {"FakeVim", {tagEditorConvenience}},
- {"Terminal", {tagEditorConvenience}},
- {"Todo", {tagEditorConvenience}},
- {"CodePaster", {tagEditorConvenience}},
- {"Beautifier", {tagEditorConvenience}},
-
- {"SerialTerminal", {tagConnectivity}},
-
- {"SilverSearcher", {tagEditor}},
-
- {"AutotoolsProjectManager", {tagBuildTools}},
- {"CMakeProjectManager", {tagBuildTools}},
- {"CompilationDatabaseProjectManager", {tagBuildTools}},
- {"IncrediBuild", {tagBuildTools}},
- {"MesonProjectManager", {tagBuildTools}},
- {"QbsProjectManager", {tagBuildTools}},
- {"QmakeProjectManager", {tagBuildTools}},
-
- {"Axivion", {tagCodeAnalyzing}},
- {"ClangTools", {tagCodeAnalyzing}},
- {"Cppcheck", {tagCodeAnalyzing}},
- {"CtfVisualizer", {tagCodeAnalyzing}},
- {"PerfProfiler", {tagCodeAnalyzing}},
- {"Valgrind", {tagCodeAnalyzing}},
-
- {"Android", {tagPlatformSupport}},
- {"BareMetal", {tagPlatformSupport}},
- {"Boot2Qt", {tagPlatformSupport}},
- {"Ios", {tagPlatformSupport}},
- {"McuSupport", {tagPlatformSupport}},
- {"Qnx", {tagPlatformSupport}},
- {"RemoteLinux", {tagPlatformSupport}},
- {"SafeRenderer", {tagPlatformSupport}},
- {"VxWorks", {tagPlatformSupport}},
- {"WebAssembly", {tagPlatformSupport}},
- {"Docker", {tagPlatformSupport}},
-
- // Missing in Kimmo's excel sheet:
- {"CompilerExplorer", {tagTagUndefined}},
- {"ExtensionManager", {tagTagUndefined}},
- {"ScreenRecorder", {tagTagUndefined}},
- };
-
- QList<QStandardItem*> items;
- QStringList expectedExtensions;
- QStringList unexpectedExtensions;
- QHash<const QString, const PluginSpec*> installedPlugins;
- for (const PluginSpec *ps : PluginManager::plugins()) {
- installedPlugins.insert(ps->name(), ps);
- unexpectedExtensions.append(ps->name());
- }
-
- const auto handleExtension = [&] (const ItemData &extension, bool addToBrowser) {
- if (!installedPlugins.contains(extension.name)) {
- expectedExtensions.append(extension.name);
- return false;
- }
- unexpectedExtensions.removeOne(extension.name);
-
- if (addToBrowser) {
- QStandardItem *item = new QStandardItem;
- const PluginSpecList pluginSpecs = {installedPlugins.value(extension.name)};
- item->setData(ItemTypeExtension, RoleItemType);
- item->setData(QVariant::fromValue(extension.tags), RoleTags);
- item->setData(QVariant::fromValue<PluginSpecList>(pluginSpecs), RolePluginSpecs);
- item->setData(extension.name, RoleName);
- items.append(item);
- }
-
- return true;
- };
-
- const bool addPackedExtensionsToBrowser = true; // TODO: Determine how we want this. As setting?
- for (const auto &pack : packs) {
- PluginSpecList pluginSpecs;
- for (const QString &extension : pack.extensions) {
- const ItemData extensionData = {extension, {}, pack.tags, {}};
- if (!handleExtension(extensionData, addPackedExtensionsToBrowser))
- continue;
- pluginSpecs.append(installedPlugins.value(extension));
- }
- if (pluginSpecs.isEmpty())
- continue;
-
- QStandardItem *item = new QStandardItem;
- item->setData(ItemTypePack, RoleItemType);
- item->setData(QVariant::fromValue(pack.tags), RoleTags);
- item->setData(QVariant::fromValue<PluginSpecList>(pluginSpecs), RolePluginSpecs);
- item->setData(pack.name, RoleName);
- items.append(item);
- }
-
- for (const auto &extension : extensions) {
- const ItemData extensionData = {extension.name, {}, extension.tags, {}};
- handleExtension(extensionData, true);
- }
-
- QStandardItemModel *result = new QStandardItemModel;
- for (auto item : items) {
- QStringList searchTexts;
- searchTexts.append(item->data(RoleName).toString());
- searchTexts.append(item->data(RoleTags).toStringList());
- const PluginSpecList pluginSpecs = item->data(RolePluginSpecs).value<PluginSpecList>();
- for (auto pluginSpec : pluginSpecs) {
- searchTexts.append(pluginSpec->name());
- searchTexts.append(pluginSpec->description());
- searchTexts.append(pluginSpec->longDescription());
- searchTexts.append(pluginSpec->category());
- searchTexts.append(pluginSpec->copyright());
- }
- searchTexts.removeDuplicates();
- item->setData(searchTexts.join(" "), RoleSearchText);
-
- item->setDragEnabled(false);
- item->setEditable(false);
-
- result->appendRow(item);
- }
-
- if (browserLog().isDebugEnabled()) {
- if (!expectedExtensions.isEmpty())
- qCDebug(browserLog) << "Expected extensions/plugins are not installed:"
- << expectedExtensions.join(", ");
- if (!unexpectedExtensions.isEmpty())
- qCDebug(browserLog) << "Unexpected extensions/plugins are installed:"
- << unexpectedExtensions.join(", ");
- }
-
- return result;
-}
+constexpr int gapSize = ExVPaddingGapXl;
+constexpr int itemWidth = 330;
+constexpr int cellWidth = itemWidth + HPaddingL;
class ExtensionItemDelegate : public QItemDelegate
{
public:
+ constexpr static QSize dividerS{1, 16};
+ constexpr static QSize iconBgS{50, 50};
+ constexpr static TextFormat itemNameTF
+ {Theme::Token_Text_Default, UiElement::UiElementH6};
+ constexpr static TextFormat countTF
+ {Theme::Token_Text_Default, UiElement::UiElementLabelSmall,
+ Qt::AlignCenter | Qt::TextDontClip};
+ constexpr static TextFormat vendorTF
+ {Theme::Token_Text_Muted, UiElement::UiElementLabelSmall,
+ Qt::AlignVCenter | Qt::TextDontClip};
+ constexpr static TextFormat tagsTF
+ {Theme::Token_Text_Default, UiElement::UiElementCaption};
+
explicit ExtensionItemDelegate(QObject *parent = nullptr)
: QItemDelegate(parent)
{
@@ -320,190 +79,320 @@ public:
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index)
const override
{
+ // +---------------+-------+---------------+----------------------------------------------------------------------+---------------+-----------+
+ // | | | | (ExPaddingGapL) | | |
+ // | | | +-------------------------------------------------------------+--------+ | |
+ // | | | | <itemName> |<status>| | |
+ // | | | +-------------------------------------------------------------+--------+ | |
+ // | | | | (VGapXxs) | | |
+ // | | | +--------+--------+--------------+--------+--------+---------+---------+ | |
+ // |(ExPaddingGapL)|<icon> |(ExPaddingGapL)|<vendor>|(HGapXs)|<divider>(h16)|(HGapXs)|<dlIcon>|(HGapXxs)|<dlCount>|(ExPaddingGapL)|(HPaddingL)|
+ // | |(50x50)| +--------+--------+--------------+--------+--------+---------+---------+ | |
+ // | | | | (VGapXxs) | | |
+ // | | | +----------------------------------------------------------------------+ | |
+ // | | | | <tags> | | |
+ // | | | +----------------------------------------------------------------------+ | |
+ // | | | | (ExPaddingGapL) | | |
+ // +---------------+-------+---------------+----------------------------------------------------------------------+---------------+-----------+
+ // | (ExVPaddingGapXl) |
+ // +------------------------------------------------------------------------------------------------------------------------------------------+
+
+ const QRect bgRGlobal = option.rect.adjusted(0, 0, -HPaddingL, -gapSize);
+ const QRect bgR = bgRGlobal.translated(-option.rect.topLeft());
+
+ const int middleColumnW = bgR.width() - ExPaddingGapL - iconBgS.width() - ExPaddingGapL
+ - ExPaddingGapL;
+
+ int x = bgR.x();
+ int y = bgR.y();
+ x += ExPaddingGapL;
+ const QRect iconBgR(x, y + (bgR.height() - iconBgS.height()) / 2,
+ iconBgS.width(), iconBgS.height());
+ x += iconBgS.width() + ExPaddingGapL;
+ y += ExPaddingGapL;
+ const QRect itemNameR(x, y, middleColumnW, itemNameTF.lineHeight());
+ const QString itemName = index.data().toString();
+
+ y += itemNameR.height() + VGapXxs;
+ const QRect vendorRowR(x, y, middleColumnW, vendorRowHeight());
+ QRect vendorR = vendorRowR;
+
+ y += vendorRowR.height() + VGapXxs;
+ const QRect tagsR(x, y, middleColumnW, tagsTF.lineHeight());
+
+ QTC_CHECK(option.rect.height() - 1 == tagsR.bottom() + ExPaddingGapL + gapSize);
+
painter->save();
painter->setRenderHint(QPainter::Antialiasing);
+ painter->translate(bgRGlobal.topLeft());
- const ItemData data = itemData(index);
- const bool isPack = data.type == ItemTypePack;
- const QRectF itemRect(option.rect.topLeft(), itemSize);
+ const bool isPack = index.data(RoleItemType) == ItemTypePack;
{
const bool selected = option.state & QStyle::State_Selected;
const bool hovered = option.state & QStyle::State_MouseOver;
const QColor fillColor =
- creatorTheme()->color(hovered ? WelcomePageHelpers::cardHoverBackground
+ creatorColor(hovered ? WelcomePageHelpers::cardHoverBackground
: WelcomePageHelpers::cardDefaultBackground);
const QColor strokeColor =
- creatorTheme()->color(selected ? Theme::Token_Stroke_Strong
+ creatorColor(selected ? Theme::Token_Stroke_Strong
: hovered ? WelcomePageHelpers::cardHoverStroke
: WelcomePageHelpers::cardDefaultStroke);
- WelcomePageHelpers::drawCardBackground(painter, itemRect, fillColor, strokeColor);
+ WelcomePageHelpers::drawCardBackground(painter, bgR, fillColor, strokeColor);
}
{
- constexpr QRectF bigCircle(16, 16, 48, 48);
- constexpr double gradientMargin = 0.14645;
- const QRectF bigCircleLocal = bigCircle.translated(itemRect.topLeft());
- QPainterPath bigCirclePath;
- bigCirclePath.addEllipse(bigCircleLocal);
- QLinearGradient gradient(bigCircleLocal.topLeft(), bigCircleLocal.bottomRight());
- const QColor startColor = isPack ? qRgb(0x1e, 0x99, 0x6e)
- : colorForExtensionName(data.name);
- const QColor endColor = isPack ? qRgb(0x07, 0x6b, 0x6d) : startColor.lighter(150);
- gradient.setColorAt(gradientMargin, startColor);
- gradient.setColorAt(1 - gradientMargin, endColor);
- painter->fillPath(bigCirclePath, gradient);
-
- static const QIcon packIcon =
- Icon({{":/extensionmanager/images/packsmall.png",
- Theme::Token_Text_Default}}, Icon::Tint).icon();
- static const QIcon extensionIcon =
- Icon({{":/extensionmanager/images/extensionsmall.png",
- Theme::Token_Text_Default}}, Icon::Tint).icon();
- QRectF iconRect(0, 0, 32, 32);
- iconRect.moveCenter(bigCircleLocal.center());
- (isPack ? packIcon : extensionIcon).paint(painter, iconRect.toRect());
+ QLinearGradient gradient(iconBgR.topRight(), iconBgR.bottomLeft());
+ const QColor startColor = creatorColor(Utils::Theme::Token_Gradient01_Start);
+ const QColor endColor = creatorColor(Utils::Theme::Token_Gradient01_End);
+ gradient.setColorAt(0, startColor);
+ gradient.setColorAt(1, endColor);
+ constexpr int iconRectRounding = 4;
+ drawCardBackground(painter, iconBgR, gradient, Qt::NoPen, iconRectRounding);
+
+ // Icon
+ constexpr Theme::Color color = Theme::Token_Basic_White;
+ static const QIcon pack = Icon({{":/extensionmanager/images/packsmall.png", color}},
+ Icon::Tint).icon();
+ static const QIcon extension = Icon({{":/extensionmanager/images/extensionsmall.png",
+ color}}, Icon::Tint).icon();
+ (isPack ? pack : extension).paint(painter, iconBgR);
}
if (isPack) {
- constexpr QRectF smallCircle(47, 50, 18, 18);
- constexpr qreal strokeWidth = 1;
- constexpr qreal shrink = strokeWidth / 2;
- constexpr QRectF smallCircleAdjusted = smallCircle.adjusted(shrink, shrink,
- -shrink, -shrink);
- const QRectF smallCircleLocal = smallCircleAdjusted.translated(itemRect.topLeft());
- const QColor fillColor = creatorTheme()->color(Theme::Token_Foreground_Muted);
- const QColor strokeColor = creatorTheme()->color(Theme::Token_Stroke_Subtle);
- painter->setBrush(fillColor);
- painter->setPen(strokeColor);
- painter->drawEllipse(smallCircleLocal);
-
- painter->setFont(StyleHelper::uiFont(StyleHelper::UiElementCaptionStrong));
- const QColor textColor = creatorTheme()->color(Theme::Token_Text_Default);
- painter->setPen(textColor);
- painter->drawText(smallCircleLocal, QString::number(data.plugins.count()),
- QTextOption(Qt::AlignCenter));
+ constexpr int circleSize = 18;
+ constexpr int circleOverlap = 3; // Protrusion from lower right corner of iconRect
+ const QRect smallCircle(iconBgR.right() + 1 + circleOverlap - circleSize,
+ iconBgR.bottom() + 1 + circleOverlap - circleSize,
+ circleSize, circleSize);
+ const QColor fillColor = creatorColor(Theme::Token_Foreground_Muted);
+ const QColor strokeColor = creatorColor(Theme::Token_Stroke_Subtle);
+ drawCardBackground(painter, smallCircle, fillColor, strokeColor, circleSize / 2);
+
+ painter->setFont(countTF.font());
+ painter->setPen(countTF.color());
+ const PluginsData plugins = index.data(RolePlugins).value<PluginsData>();
+ painter->drawText(smallCircle, countTF.drawTextFlags, QString::number(plugins.count()));
+ }
+ {
+ painter->setPen(itemNameTF.color());
+ painter->setFont(itemNameTF.font());
+ const QString titleElided
+ = painter->fontMetrics().elidedText(itemName, Qt::ElideRight, itemNameR.width());
+ painter->drawText(itemNameR, itemNameTF.drawTextFlags, titleElided);
}
{
- constexpr int textX = 80;
- constexpr int rightMargin = StyleHelper::SpacingTokens::ExVPaddingGapXl;
- constexpr int maxTextWidth = itemSize.width() - textX - rightMargin;
- constexpr Qt::TextElideMode elideMode = Qt::ElideRight;
-
- constexpr int titleY = 30;
- const QPointF titleOrigin(itemRect.topLeft() + QPointF(textX, titleY));
- painter->setPen(creatorTheme()->color(Theme::Token_Text_Default));
- painter->setFont(StyleHelper::uiFont(StyleHelper::UiElementH6));
- const QString titleElided = painter->fontMetrics().elidedText(
- data.name, elideMode, maxTextWidth);
- painter->drawText(titleOrigin, titleElided);
-
- constexpr int copyrightY = 52;
- const QPointF copyrightOrigin(itemRect.topLeft() + QPointF(textX, copyrightY));
- painter->setPen(creatorTheme()->color(Theme::Token_Text_Muted));
- painter->setFont(StyleHelper::uiFont(StyleHelper::UiElementCaptionStrong));
- const QString copyrightElided = painter->fontMetrics().elidedText(
- data.plugins.first()->copyright(), elideMode, maxTextWidth);
- painter->drawText(copyrightOrigin, copyrightElided);
-
- constexpr int tagsY = 70;
- const QPointF tagsOrigin(itemRect.topLeft() + QPointF(textX, tagsY));
- const QString tags = data.tags.join(", ");
- painter->setPen(creatorTheme()->color(Theme::Token_Text_Default));
- painter->setFont(StyleHelper::uiFont(StyleHelper::UiElementCaption));
- const QString tagsElided = painter->fontMetrics().elidedText(
- tags, elideMode, maxTextWidth);
- painter->drawText(tagsOrigin, tagsElided);
+ const QString vendor = index.data(RoleVendor).toString();
+ const QFontMetrics fm(vendorTF.font());
+ painter->setPen(vendorTF.color());
+ painter->setFont(vendorTF.font());
+
+ if (const int dlCount = index.data(RoleDownloadCount).toInt(); dlCount > 0) {
+ constexpr QSize dlIconS(16, 16);
+ const QString dlCountString = QString::number(dlCount);
+ const int dlCountW = fm.horizontalAdvance(dlCountString);
+ const int dlItemsW = HGapXs + dividerS.width() + HGapXs + dlIconS.width()
+ + HGapXxs + dlCountW;
+ const int vendorW = fm.horizontalAdvance(vendor);
+ vendorR.setWidth(qMin(middleColumnW - dlItemsW, vendorW));
+
+ QRect dividerR = vendorRowR;
+ dividerR.setLeft(vendorR.right() + HGapXs);
+ dividerR.setWidth(dividerS.width());
+ painter->fillRect(dividerR, vendorTF.color());
+
+ QRect dlIconR = vendorRowR;
+ dlIconR.setLeft(dividerR.right() + HGapXs);
+ dlIconR.setWidth(dlIconS.width());
+ static const QIcon dlIcon = Icon({{":/extensionmanager/images/download.png",
+ vendorTF.themeColor}}, Icon::Tint).icon();
+ dlIcon.paint(painter, dlIconR);
+
+ QRect dlCountR = vendorRowR;
+ dlCountR.setLeft(dlIconR.right() + HGapXxs);
+ painter->drawText(dlCountR, vendorTF.drawTextFlags, dlCountString);
+ }
+
+ const QString vendorElided = fm.elidedText(vendor, Qt::ElideRight, vendorR.width());
+ painter->drawText(vendorR, vendorTF.drawTextFlags, vendorElided);
+ }
+ {
+ const QStringList tagList = index.data(RoleTags).toStringList();
+ const QString tags = tagList.join(", ");
+ painter->setPen(tagsTF.color());
+ painter->setFont(tagsTF.font());
+ const QString tagsElided
+ = painter->fontMetrics().elidedText(tags, Qt::ElideRight, tagsR.width());
+ painter->drawText(tagsR, tagsTF.drawTextFlags, tagsElided);
}
painter->restore();
}
+ static int vendorRowHeight()
+ {
+ return qMax(vendorTF.lineHeight(), dividerS.height());
+ }
+
QSize sizeHint([[maybe_unused]] const QStyleOptionViewItem &option,
[[maybe_unused]] const QModelIndex &index) const override
{
- return cellSize;
+ const int middleColumnH =
+ itemNameTF.lineHeight()
+ + VGapXxs
+ + vendorRowHeight()
+ + VGapXxs
+ + tagsTF.lineHeight();
+ const int height =
+ ExPaddingGapL
+ + qMax(iconBgS.height(), middleColumnH)
+ + ExPaddingGapL;
+ return {cellWidth, height + gapSize};
}
};
-ExtensionsBrowser::ExtensionsBrowser()
+class ExtensionsBrowserPrivate
+{
+public:
+ ExtensionsModel *model;
+ QLineEdit *searchBox;
+ QAbstractButton *updateButton;
+ QListView *extensionsView;
+ QItemSelectionModel *selectionModel = nullptr;
+ QSortFilterProxyModel *filterProxyModel;
+ int columnsCount = 2;
+ Tasking::TaskTreeRunner taskTreeRunner;
+};
+
+ExtensionsBrowser::ExtensionsBrowser(QWidget *parent)
+ : QWidget(parent)
+ , d(new ExtensionsBrowserPrivate)
{
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
auto manageLabel = new QLabel(Tr::tr("Manage Extensions"));
- manageLabel->setFont(StyleHelper::uiFont(StyleHelper::UiElementH1));
-
- m_searchBox = new Core::SearchBox;
- m_searchBox->setFixedWidth(itemSize.width());
-
- m_updateButton = new Button(Tr::tr("Install..."), Button::MediumPrimary);
-
- m_filterProxyModel = new QSortFilterProxyModel(this);
- m_filterProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
- m_filterProxyModel->setFilterRole(RoleSearchText);
- m_filterProxyModel->setSortRole(RoleItemType);
-
- m_extensionsView = new QListView;
- m_extensionsView->setFrameStyle(QFrame::NoFrame);
- m_extensionsView->setItemDelegate(new ExtensionItemDelegate(this));
- m_extensionsView->setResizeMode(QListView::Adjust);
- m_extensionsView->setSelectionMode(QListView::SingleSelection);
- m_extensionsView->setUniformItemSizes(true);
- m_extensionsView->setViewMode(QListView::IconMode);
- m_extensionsView->setModel(m_filterProxyModel);
- m_extensionsView->setMouseTracking(true);
+ manageLabel->setFont(uiFont(UiElementH1));
+
+ d->searchBox = new SearchBox;
+ d->searchBox->setFixedWidth(itemWidth);
+ d->updateButton = new Button(Tr::tr("Install..."), Button::MediumPrimary);
+
+ d->model = new ExtensionsModel(this);
+
+ d->filterProxyModel = new QSortFilterProxyModel(this);
+ d->filterProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
+ d->filterProxyModel->setFilterRole(RoleSearchText);
+ d->filterProxyModel->setSortRole(RoleItemType);
+ d->filterProxyModel->setSourceModel(d->model);
+
+ d->extensionsView = new QListView;
+ d->extensionsView->setFrameStyle(QFrame::NoFrame);
+ d->extensionsView->setItemDelegate(new ExtensionItemDelegate(this));
+ d->extensionsView->setResizeMode(QListView::Adjust);
+ d->extensionsView->setSelectionMode(QListView::SingleSelection);
+ d->extensionsView->setUniformItemSizes(true);
+ d->extensionsView->setViewMode(QListView::IconMode);
+ d->extensionsView->setModel(d->filterProxyModel);
+ d->extensionsView->setMouseTracking(true);
using namespace Layouting;
Column {
Space(15),
manageLabel,
Space(15),
- Row { m_searchBox, st, m_updateButton, Space(extraListViewWidth() + gapSize) },
+ Row { d->searchBox, st, d->updateButton, Space(extraListViewWidth() + gapSize) },
Space(gapSize),
- m_extensionsView,
- noMargin(), spacing(0),
+ d->extensionsView,
+ noMargin, spacing(0),
}.attachTo(this);
WelcomePageHelpers::setBackgroundColor(this, Theme::Token_Background_Default);
- WelcomePageHelpers::setBackgroundColor(m_extensionsView, Theme::Token_Background_Default);
- WelcomePageHelpers::setBackgroundColor(m_extensionsView->viewport(),
+ WelcomePageHelpers::setBackgroundColor(d->extensionsView, Theme::Token_Background_Default);
+ WelcomePageHelpers::setBackgroundColor(d->extensionsView->viewport(),
Theme::Token_Background_Default);
auto updateModel = [this] {
- m_model.reset(extensionsModel());
- m_filterProxyModel->setSourceModel(m_model.data());
- m_filterProxyModel->sort(0);
-
- if (m_selectionModel == nullptr) {
- m_selectionModel = new QItemSelectionModel(m_filterProxyModel, m_extensionsView);
- m_extensionsView->setSelectionModel(m_selectionModel);
- connect(m_extensionsView->selectionModel(), &QItemSelectionModel::currentChanged,
+ d->filterProxyModel->sort(0);
+
+ if (d->selectionModel == nullptr) {
+ d->selectionModel = new QItemSelectionModel(d->filterProxyModel,
+ d->extensionsView);
+ d->extensionsView->setSelectionModel(d->selectionModel);
+ connect(d->extensionsView->selectionModel(), &QItemSelectionModel::currentChanged,
this, &ExtensionsBrowser::itemSelected);
}
};
- connect(ExtensionSystem::PluginManager::instance(),
- &ExtensionSystem::PluginManager::pluginsChanged, this, updateModel);
- connect(m_searchBox, &QLineEdit::textChanged,
- m_filterProxyModel, &QSortFilterProxyModel::setFilterWildcard);
+ connect(d->updateButton, &QAbstractButton::pressed, this, []() {
+ executePluginInstallWizard();
+ });
+ connect(PluginManager::instance(), &PluginManager::pluginsChanged, this, updateModel);
+ connect(PluginManager::instance(), &PluginManager::initializationDone,
+ this, &ExtensionsBrowser::fetchExtensions);
+ connect(d->searchBox, &QLineEdit::textChanged,
+ d->filterProxyModel, &QSortFilterProxyModel::setFilterWildcard);
+}
+
+ExtensionsBrowser::~ExtensionsBrowser()
+{
+ delete d;
}
void ExtensionsBrowser::adjustToWidth(const int width)
{
const int widthForItems = width - extraListViewWidth();
- m_columnsCount = qMax(1, qFloor(widthForItems / cellSize.width()));
- m_updateButton->setVisible(m_columnsCount > 1);
+ d->columnsCount = qMax(1, qFloor(widthForItems / cellWidth));
+ d->updateButton->setVisible(d->columnsCount > 1);
updateGeometry();
}
QSize ExtensionsBrowser::sizeHint() const
{
- const int columsWidth = m_columnsCount * cellSize.width();
+ const int columsWidth = d->columnsCount * cellWidth;
return { columsWidth + extraListViewWidth(), 0};
}
int ExtensionsBrowser::extraListViewWidth() const
{
// TODO: Investigate "transient" scrollbar, just for this list view.
- return m_extensionsView->style()->pixelMetric(QStyle::PM_ScrollBarExtent)
+ return d->extensionsView->style()->pixelMetric(QStyle::PM_ScrollBarExtent)
+ 1; // Needed
}
+void ExtensionsBrowser::fetchExtensions()
+{
+ // d->model->setExtensionsJson(testData("thirdpartyplugins")); return;
+
+ using namespace Tasking;
+
+ const auto onQuerySetup = [](NetworkQuery &query) {
+ const QString host = "https://qc-extensions.qt.io";
+ const QString url = "%1/api/v1/search?request=";
+ const QString requestTemplate
+ = R"({"version":"%1","host_os":"%2","host_os_version":"%3","host_architecture":"%4","page_size":200})";
+ const QString request = url.arg(host)
+ + requestTemplate
+ .arg("2.2") // .arg(QCoreApplication::applicationVersion())
+ .arg("macOS") // .arg(QSysInfo::productType())
+ .arg("12") // .arg(QSysInfo::productVersion())
+ .arg("arm64"); // .arg(QSysInfo::currentCpuArchitecture());
+
+ query.setRequest(QNetworkRequest(QUrl::fromUserInput(request)));
+ query.setNetworkAccessManager(NetworkAccessManager::instance());
+ };
+ const auto onQueryDone = [this](const NetworkQuery &query, DoneWith result) {
+ if (result != DoneWith::Success) {
+#ifdef WITH_TESTS
+ d->model->setExtensionsJson(testData("defaultpacks"));
+#endif // WITH_TESTS
+ return;
+ }
+ const QByteArray response = query.reply()->readAll();
+ d->model->setExtensionsJson(response);
+ };
+
+ Group group {
+ NetworkQueryTask{onQuerySetup, onQueryDone},
+ };
+
+ d->taskTreeRunner.start(group);
+}
+
} // ExtensionManager::Internal
diff --git a/src/plugins/extensionmanager/extensionsbrowser.h b/src/plugins/extensionmanager/extensionsbrowser.h
index 2daa2362ba..d0467aa216 100644
--- a/src/plugins/extensionmanager/extensionsbrowser.h
+++ b/src/plugins/extensionmanager/extensionsbrowser.h
@@ -3,66 +3,30 @@
#pragma once
-#include <utils/theme/theme.h>
-
-#include <QStandardItemModel>
#include <QWidget>
-QT_BEGIN_NAMESPACE
-class QAbstractButton;
-class QItemSelectionModel;
-class QLineEdit;
-class QListView;
-class QSortFilterProxyModel;
-QT_END_NAMESPACE
-
-namespace ExtensionSystem
-{
-class PluginSpec;
-}
-
namespace ExtensionManager::Internal {
-using PluginSpecList = QList<const ExtensionSystem::PluginSpec *>;
-using Tags = QStringList;
-
-enum ItemType {
- ItemTypePack,
- ItemTypeExtension,
-};
-
-struct ItemData {
- const QString name;
- const ItemType type = ItemTypeExtension;
- const Tags tags;
- const PluginSpecList plugins;
-};
-
-ItemData itemData(const QModelIndex &index);
-
class ExtensionsBrowser final : public QWidget
{
Q_OBJECT
public:
- ExtensionsBrowser();
+ ExtensionsBrowser(QWidget *parent = nullptr);
+ ~ExtensionsBrowser();
void adjustToWidth(const int width);
QSize sizeHint() const override;
+ int extraListViewWidth() const; // Space for scrollbar, etc.
+
signals:
void itemSelected(const QModelIndex &current, const QModelIndex &previous);
private:
- int extraListViewWidth() const; // Space for scrollbar, etc.
+ void fetchExtensions();
- QScopedPointer<QStandardItemModel> m_model;
- QLineEdit *m_searchBox;
- QAbstractButton *m_updateButton;
- QListView *m_extensionsView;
- QItemSelectionModel *m_selectionModel = nullptr;
- QSortFilterProxyModel *m_filterProxyModel;
- int m_columnsCount = 2;
+ class ExtensionsBrowserPrivate *d = nullptr;
};
} // ExtensionManager::Internal
diff --git a/src/plugins/extensionmanager/extensionsmodel.cpp b/src/plugins/extensionmanager/extensionsmodel.cpp
new file mode 100644
index 0000000000..56fcc54ad8
--- /dev/null
+++ b/src/plugins/extensionmanager/extensionsmodel.cpp
@@ -0,0 +1,410 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "extensionsmodel.h"
+
+#include "utils/algorithm.h"
+
+#include <coreplugin/coreconstants.h>
+#include <coreplugin/icore.h>
+
+#include <extensionsystem/iplugin.h>
+#include <extensionsystem/pluginspec.h>
+#include <extensionsystem/pluginview.h>
+#include <extensionsystem/pluginmanager.h>
+
+#include <QJsonArray>
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QStandardItemModel>
+#include <QVersionNumber>
+
+using namespace ExtensionSystem;
+using namespace Core;
+using namespace Utils;
+
+namespace ExtensionManager::Internal {
+
+Q_LOGGING_CATEGORY(modelLog, "qtc.extensionmanager.model", QtWarningMsg)
+
+struct Dependency
+{
+ QString name;
+ QString version;
+};
+using Dependencies = QList<Dependency>;
+
+struct Plugin
+{
+ Dependencies dependencies;
+ QString copyright;
+ bool isInternal = false;
+ QString name;
+ QString packageUrl;
+ QString vendor;
+ QString version;
+};
+using Plugins = QList<Plugin>;
+
+struct Description {
+ ImagesData images;
+ LinksData links;
+ TextData text;
+};
+
+struct Extension {
+ QString copyright;
+ Description description;
+ int downloadCount = -1;
+ QString id;
+ QString license;
+ QString name;
+ QStringList platforms;
+ Plugins plugins;
+ qint64 size = 0;
+ QStringList tags;
+ ItemType type = ItemTypePack;
+ QString vendor;
+ QString version;
+};
+using Extensions = QList<Extension>;
+
+static Plugin pluginFromJson(const QJsonObject &obj)
+{
+ const QJsonObject metaDataObj = obj.value("meta_data").toObject();
+
+ const QJsonArray dependenciesArray = metaDataObj.value("Dependencies").toArray();
+ Dependencies dependencies;
+ for (const QJsonValueConstRef &dependencyVal : dependenciesArray) {
+ const QJsonObject dependencyObj = dependencyVal.toObject();
+ dependencies.append(Dependency{
+ .name = dependencyObj.value("Name").toString(),
+ .version = dependencyObj.value("Version").toString(),
+ });
+ }
+
+ return {
+ .dependencies = dependencies,
+ .copyright = metaDataObj.value("Copyright").toString(),
+ .isInternal = obj.value("is_internal").toBool(false),
+ .name = metaDataObj.value("Name").toString(),
+ .packageUrl = obj.value("url").toString(),
+ .vendor = metaDataObj.value("Vendor").toString(),
+ .version = metaDataObj.value("Version").toString(),
+ };
+}
+
+static Description descriptionFromJson(const QJsonObject &obj)
+{
+ TextData descriptionText;
+ const QJsonArray paragraphsArray = obj.value("paragraphs").toArray();
+ for (const QJsonValueConstRef &paragraphVal : paragraphsArray) {
+ const QJsonObject &paragraphObj = paragraphVal.toObject();
+ const QJsonArray &textArray = paragraphObj.value("text").toArray();
+ QStringList textLines;
+ for (const QJsonValueConstRef &textVal : textArray)
+ textLines.append(textVal.toString());
+ descriptionText.append({
+ paragraphObj.value("header").toString(),
+ textLines,
+ });
+ }
+
+ LinksData links;
+ const QJsonArray linksArray = obj.value("links").toArray();
+ for (const QJsonValueConstRef &linkVal : linksArray) {
+ const QJsonObject &linkObj = linkVal.toObject();
+ links.append({
+ linkObj.value("link_text").toString(),
+ linkObj.value("url").toString(),
+ });
+ }
+
+ ImagesData images;
+ const QJsonArray imagesArray = obj.value("images").toArray();
+ for (const QJsonValueConstRef &imageVal : imagesArray) {
+ const QJsonObject &imageObj = imageVal.toObject();
+ images.append({
+ imageObj.value("image_label").toString(),
+ imageObj.value("url").toString(),
+ });
+ }
+
+ const Description description = {
+ .images = images,
+ .links = links,
+ .text = descriptionText,
+ };
+
+ return description;
+}
+
+static Extension extensionFromJson(const QJsonObject &obj)
+{
+ Plugins plugins;
+ const QJsonArray pluginsArray = obj.value("plugins").toArray();
+ for (const QJsonValueConstRef &pluginVal : pluginsArray)
+ plugins.append(pluginFromJson(pluginVal.toObject()));
+
+ QStringList tags;
+ const QJsonArray tagsArray = obj.value("tags").toArray();
+ for (const QJsonValueConstRef &tagVal : tagsArray)
+ tags.append(tagVal.toString());
+
+ QStringList platforms;
+ const QJsonArray platformsArray = obj.value("platforms").toArray();
+ for (const QJsonValueConstRef &platformsVal : platformsArray)
+ platforms.append(platformsVal.toString());
+
+ const QJsonObject descriptionObj = obj.value("description").toObject();
+ const Description description = descriptionFromJson(descriptionObj);
+
+ const Extension extension = {
+ .copyright = obj.value("copyright").toString(),
+ .description = description,
+ .downloadCount = obj.value("download_count").toInt(-1),
+ .id = obj.value("id").toString(),
+ .license = obj.value("license").toString(),
+ .name = obj.value("name").toString(),
+ .platforms = platforms,
+ .plugins = plugins,
+ .size = obj.value("total_size").toInteger(),
+ .tags = tags,
+ .type = obj.value("is_pack").toBool(true) ? ItemTypePack : ItemTypeExtension,
+ .vendor = obj.value("vendor").toString(),
+ .version = obj.value("version").toString(),
+ };
+
+ return extension;
+}
+
+static Extensions parseExtensionsRepoReply(const QByteArray &jsonData)
+{
+ // https://qc-extensions.qt.io/api-docs
+ Extensions parsedExtensions;
+ const QJsonObject jsonObj = QJsonDocument::fromJson(jsonData).object();
+ const QJsonArray items = jsonObj.value("items").toArray();
+ for (const QJsonValueConstRef &itemVal : items) {
+ const QJsonObject itemObj = itemVal.toObject();
+ const Extension extension = extensionFromJson(itemObj);
+ parsedExtensions.append(extension);
+ }
+ return parsedExtensions;
+}
+
+class ExtensionsModelPrivate
+{
+public:
+ void setExtensions(const Extensions &extensions);
+ void removeLocalExtensions();
+
+ Extensions allExtensions; // Original, complete extensions entries
+ Extensions absentExtensions; // All packs + plugin extensions that are not (yet) installed
+};
+
+void ExtensionsModelPrivate::setExtensions(const Extensions &extensions)
+{
+ allExtensions = extensions;
+ removeLocalExtensions();
+}
+
+void ExtensionsModelPrivate::removeLocalExtensions()
+{
+ const QStringList installedPlugins = transform(PluginManager::plugins(), &PluginSpec::name);
+ absentExtensions.clear();
+ for (const Extension &extension : allExtensions) {
+ if (extension.type == ItemTypePack || !installedPlugins.contains(extension.name))
+ absentExtensions.append(extension);
+ }
+}
+
+ExtensionsModel::ExtensionsModel(QObject *parent)
+ : QAbstractListModel(parent)
+ , d(new ExtensionsModelPrivate)
+{
+}
+
+ExtensionsModel::~ExtensionsModel()
+{
+ delete d;
+}
+
+int ExtensionsModel::rowCount([[maybe_unused]] const QModelIndex &parent) const
+{
+ const int remoteExtnsionsCount = d->absentExtensions.count();
+ const int installedPluginsCount = PluginManager::plugins().count();
+ return remoteExtnsionsCount + installedPluginsCount;
+}
+
+static QVariant dataFromPluginSpec(const PluginSpec *pluginSpec, int role)
+{
+ switch (role) {
+ case Qt::DisplayRole:
+ case RoleName:
+ return pluginSpec->name();
+ case RoleCopyright:
+ return pluginSpec->copyright();
+ case RoleDependencies: {
+ QStringList dependencies = transform(pluginSpec->dependencies(),
+ &PluginDependency::toString);
+ dependencies.sort();
+ return dependencies;
+ }
+ case RoleDescriptionImages:
+ break;
+ case RoleDescriptionLinks: {
+ const QString url = pluginSpec->url();
+ if (!url.isEmpty()) {
+ const LinksData links = {{{}, url}};
+ return QVariant::fromValue(links);
+ }
+ break;
+ }
+ case RoleDescriptionText: {
+ QStringList lines = pluginSpec->description().split('\n', Qt::SkipEmptyParts);
+ lines.append(pluginSpec->longDescription().split('\n', Qt::SkipEmptyParts));
+ const TextData text = {{ pluginSpec->name(), lines }};
+ return QVariant::fromValue(text);
+ }
+ case RoleItemType:
+ return ItemTypeExtension;
+ case RoleLicense:
+ return pluginSpec->license();
+ case RoleLocation:
+ return pluginSpec->filePath().toVariant();
+ case RolePlatforms: {
+ const QString pattern = pluginSpec->platformSpecification().pattern();
+ const QStringList platforms = pattern.isEmpty()
+ ? QStringList({"macOS", "Windows", "Linux"})
+ : QStringList(pattern);
+ return platforms;
+ }
+ case RoleSize:
+ return pluginSpec->filePath().fileSize();
+ case RoleTags:
+ break;
+ case RoleVendor:
+ return pluginSpec->vendor();
+ case RoleVersion:
+ return pluginSpec->version();
+ default:
+ break;
+ }
+ return {};
+}
+
+static QStringList dependenciesFromExtension(const Extension &extension)
+{
+ QStringList dependencies;
+ for (const Plugin &plugin : extension.plugins) {
+ for (const Dependency &dependency : plugin.dependencies) {
+ const QString withVersion = QString::fromLatin1("%1 (%2)").arg(dependency.name)
+ .arg(dependency.version);
+ dependencies.append(withVersion);
+ }
+ }
+ dependencies.sort();
+ dependencies.removeDuplicates();
+ return dependencies;
+}
+
+static QVariant dataFromExtension(const Extension &extension, int role)
+{
+ switch (role) {
+ case Qt::DisplayRole:
+ case RoleName:
+ return extension.name;
+ case RoleCopyright:
+ return !extension.copyright.isEmpty() ? extension.copyright : QVariant();
+ case RoleDependencies:
+ return dependenciesFromExtension(extension);
+ case RoleDescriptionImages:
+ return QVariant::fromValue(extension.description.images);
+ case RoleDescriptionLinks:
+ return QVariant::fromValue(extension.description.links);
+ case RoleDescriptionText:
+ return QVariant::fromValue(extension.description.text);
+ case RoleDownloadCount:
+ return extension.downloadCount;
+ case RoleId:
+ return extension.id;
+ case RoleItemType:
+ return extension.type;
+ case RoleLicense:
+ return extension.license;
+ case RoleLocation:
+ break;
+ case RolePlatforms:
+ return extension.platforms;
+ case RolePlugins: {
+ PluginsData plugins;
+ for (const Plugin &plugin : extension.plugins)
+ plugins.append(qMakePair(plugin.name, plugin.packageUrl));
+ return QVariant::fromValue(plugins);
+ }
+ case RoleSize:
+ return extension.size;
+ case RoleTags:
+ return extension.tags;
+ case RoleVendor:
+ return !extension.vendor.isEmpty() ? extension.vendor : QVariant();
+ case RoleVersion:
+ return !extension.version.isEmpty() ? extension.version : QVariant();
+ default:
+ break;
+ }
+ return {};
+}
+
+static QString searchText(const QModelIndex &index)
+{
+ QStringList searchTexts;
+ searchTexts.append(index.data(RoleName).toString());
+ searchTexts.append(index.data(RoleTags).toStringList());
+ searchTexts.append(index.data(RoleDescriptionText).toStringList());
+ searchTexts.append(index.data(RoleVendor).toString());
+ return searchTexts.join(" ");
+}
+
+QVariant ExtensionsModel::data(const QModelIndex &index, int role) const
+{
+ if (role == RoleSearchText)
+ return searchText(index);
+
+ const bool itemIsLocalPlugin = index.row() >= d->absentExtensions.count();
+ if (itemIsLocalPlugin) {
+ const PluginSpecs &pluginSpecs = PluginManager::plugins();
+ const int pluginIndex = index.row() - d->absentExtensions.count();
+ QTC_ASSERT(pluginIndex >= 0 && pluginIndex <= pluginSpecs.size(), return {});
+ const PluginSpec *plugin = pluginSpecs.at(pluginIndex);
+ return dataFromPluginSpec(plugin, role);
+ } else {
+ const Extension &extension = d->absentExtensions.at(index.row());
+ const QVariant extensionData = dataFromExtension(extension, role);
+
+ // If data is unavailable, retrieve it from the first contained plugin
+ if (extensionData.isNull() && !extension.plugins.isEmpty()) {
+ const PluginSpec *pluginSpec = ExtensionsModel::pluginSpecForName(
+ extension.plugins.constFirst().name);
+ if (pluginSpec)
+ return dataFromPluginSpec(pluginSpec, role);
+ }
+ return extensionData;
+ }
+ return {};
+}
+
+void ExtensionsModel::setExtensionsJson(const QByteArray &json)
+{
+ const Extensions extensions = parseExtensionsRepoReply(json);
+ beginResetModel();
+ d->setExtensions(extensions);
+ endResetModel();
+}
+
+PluginSpec *ExtensionsModel::pluginSpecForName(const QString &pluginName)
+{
+ return findOrDefault(PluginManager::plugins(), equal(&PluginSpec::name, pluginName));
+}
+
+} // ExtensionManager::Internal
diff --git a/src/plugins/extensionmanager/extensionsmodel.h b/src/plugins/extensionmanager/extensionsmodel.h
new file mode 100644
index 0000000000..1fc86d3afd
--- /dev/null
+++ b/src/plugins/extensionmanager/extensionsmodel.h
@@ -0,0 +1,70 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <QAbstractListModel>
+
+namespace ExtensionSystem {
+class PluginSpec;
+}
+
+namespace ExtensionManager::Internal {
+
+using QPairList = QList<QPair<QString, QString> >;
+
+using ImagesData = QPairList; // { <caption, url>, ... }
+using LinksData = QPairList; // { <name, url>, ... }
+using PluginsData = QPairList; // { <name, url>, ... }
+using TextData = QList<QPair<QString, QStringList> >; // { <header, text>, ... }
+
+enum ItemType {
+ ItemTypePack,
+ ItemTypeExtension,
+};
+
+enum Role {
+ RoleName = Qt::UserRole,
+ RoleCopyright,
+ RoleDependencies,
+ RoleDescriptionImages,
+ RoleDescriptionLinks,
+ RoleDescriptionText,
+ RoleDownloadCount,
+ RoleId,
+ RoleItemType,
+ RoleLicense,
+ RoleLocation,
+ RolePlatforms,
+ RolePlugins,
+ RoleSearchText,
+ RoleSize,
+ RoleTags,
+ RoleVendor,
+ RoleVersion,
+};
+
+class ExtensionsModel : public QAbstractListModel
+{
+public:
+ ExtensionsModel(QObject *parent = nullptr);
+ ~ExtensionsModel();
+
+ int rowCount(const QModelIndex &parent = {}) const;
+ QVariant data(const QModelIndex &index, int role) const;
+
+ void setExtensionsJson(const QByteArray &json);
+ static ExtensionSystem::PluginSpec *pluginSpecForName(const QString &pluginName);
+
+private:
+ class ExtensionsModelPrivate *d = nullptr;
+};
+
+#ifdef WITH_TESTS
+QObject *createExtensionsModelTest();
+#endif
+
+} // ExtensionManager::Internal
+
+Q_DECLARE_METATYPE(ExtensionManager::Internal::QPairList)
+Q_DECLARE_METATYPE(ExtensionManager::Internal::TextData)
diff --git a/src/plugins/extensionmanager/images/download.png b/src/plugins/extensionmanager/images/download.png
new file mode 100644
index 0000000000..d328e062fc
--- /dev/null
+++ b/src/plugins/extensionmanager/images/download.png
Binary files differ
diff --git a/src/plugins/extensionmanager/images/download@2x.png b/src/plugins/extensionmanager/images/download@2x.png
new file mode 100644
index 0000000000..404df01da0
--- /dev/null
+++ b/src/plugins/extensionmanager/images/download@2x.png
Binary files differ
diff --git a/src/plugins/extensionmanager/images/extensionsmall.png b/src/plugins/extensionmanager/images/extensionsmall.png
index 6cdb8df12c..b3d270c514 100644
--- a/src/plugins/extensionmanager/images/extensionsmall.png
+++ b/src/plugins/extensionmanager/images/extensionsmall.png
Binary files differ
diff --git a/src/plugins/extensionmanager/images/extensionsmall@2x.png b/src/plugins/extensionmanager/images/extensionsmall@2x.png
index f788b5565a..414f7197ae 100644
--- a/src/plugins/extensionmanager/images/extensionsmall@2x.png
+++ b/src/plugins/extensionmanager/images/extensionsmall@2x.png
Binary files differ
diff --git a/src/plugins/extensionmanager/images/packsmall.png b/src/plugins/extensionmanager/images/packsmall.png
index 7a1c5e09bc..92c8ab99d6 100644
--- a/src/plugins/extensionmanager/images/packsmall.png
+++ b/src/plugins/extensionmanager/images/packsmall.png
Binary files differ
diff --git a/src/plugins/extensionmanager/images/packsmall@2x.png b/src/plugins/extensionmanager/images/packsmall@2x.png
index a58632c756..281ccd8207 100644
--- a/src/plugins/extensionmanager/images/packsmall@2x.png
+++ b/src/plugins/extensionmanager/images/packsmall@2x.png
Binary files differ
diff --git a/src/plugins/extensionmanager/testdata/defaultpacks.json b/src/plugins/extensionmanager/testdata/defaultpacks.json
new file mode 100644
index 0000000000..0323b08d27
--- /dev/null
+++ b/src/plugins/extensionmanager/testdata/defaultpacks.json
@@ -0,0 +1,161 @@
+{
+ "items": [
+ {
+ "name": "Essentials",
+ "tags": [ "Essentials" ],
+ "platforms": [ "macOS", "Windows", "Linux" ],
+ "license": "os",
+ "is_pack": true,
+ "description": {
+ "paragraphs": [
+ {
+ "text": [
+ "Basic services, such as editing and debugging code, viewing images, and adding resources to applications."
+ ],
+ "header": "Get started"
+ }
+ ],
+ "links": [
+ {
+ "url": "https://doc.qt.io/qtcreator/creator-coding-navigating.html",
+ "link_text": "Online documentation"
+ }
+ ]
+ },
+ "plugins": [
+ { "meta_data": { "Name": "BinEditor" } },
+ { "meta_data": { "Name": "Debugger" } },
+ { "meta_data": { "Name": "DiffEditor" } },
+ { "meta_data": { "Name": "ImageViewer" } },
+ { "meta_data": { "Name": "Macros" } },
+ { "meta_data": { "Name": "LanguageClient" } },
+ { "meta_data": { "Name": "ResourceEditor" } }
+ ]
+ },
+
+ {
+ "name": "C++ Support",
+ "tags": [ "Programming Language", "C++" ],
+ "platforms": [ "macOS", "Windows", "Linux" ],
+ "license": "os",
+ "is_pack": true,
+ "description": {
+ "paragraphs": [
+ {
+ "text": [
+ "Tools for developing Qt C++ applications."
+ ],
+ "header": "Get started"
+ }
+ ]
+ },
+ "plugins": [
+ { "meta_data": { "Name": "ClangCodeModel" } },
+ { "meta_data": { "Name": "ClangFormat" } },
+ { "meta_data": { "Name": "ClassView" } },
+ { "meta_data": { "Name": "CppEditor" } }
+ ]
+ },
+
+ {
+ "name": "QML Support",
+ "tags": [ "Programming Language", "QML" ],
+ "platforms": [ "macOS", "Windows", "Linux" ],
+ "license": "os",
+ "is_pack": true,
+ "description": {
+ "paragraphs": [
+ {
+ "text": [
+ "Tools for developing Qt Quick applications."
+ ],
+ "header": "Get started"
+ }
+ ]
+ },
+ "plugins": [
+ { "meta_data": { "Name": "QmlJSEditor" } },
+ { "meta_data": { "Name": "QmlJSTools" } },
+ { "meta_data": { "Name": "QmlPreview" } },
+ { "meta_data": { "Name": "QmlProfiler" } }
+ ]
+ },
+
+ {
+ "name": "Visual QML Editor",
+ "tags": [ "Visual UI editor", "qml", "Quick" ],
+ "platforms": [ "macOS", "Windows", "Linux" ],
+ "license": "os",
+ "is_pack": true,
+ "description": {
+ "paragraphs": [
+ {
+ "text": [
+ "Tools for creating Qt Quick UIs."
+ ],
+ "header": "Get started"
+ }
+ ]
+ },
+ "plugins": [
+ { "meta_data": { "Name": "QmlDesigner" } }
+ ]
+ },
+
+ {
+ "name": "Visual Widget Editor",
+ "tags": [ "Visual UI editor", "C++", "Widgets" ],
+ "platforms": [ "macOS", "Windows", "Linux" ],
+ "license": "os",
+ "is_pack": true,
+ "description": {
+ "paragraphs": [
+ {
+ "text": [
+ "Visual tool for creating Qt widget-based UIs."
+ ],
+ "header": "Get started"
+ }
+ ]
+ },
+ "plugins": [
+ { "meta_data": { "Name": "Designer" } }
+ ]
+ },
+
+ {
+ "name": "SpellChecker",
+ "tags": [ "Editor" ],
+ "platforms": [ "macOS", "Windows", "Linux" ],
+ "license": "os",
+ "is_pack": false,
+ "description": {
+ "paragraphs": [
+ {
+ "text": [
+ "Spellcheck comments in source files."
+ ],
+ "header": "Get started"
+ }
+ ],
+ "links": [
+ {
+ "url": "https://github.com/CJCombrink/SpellChecker-Plugin",
+ "link_text": "GitHub page"
+ }
+ ]
+ },
+ "plugins": [
+ {
+ "meta_data": {
+ "Name": "SpellChecker",
+ "Copyright": "(C) 2015 - 2024 Carel Combrink"
+ },
+ "url": "https://github.com/CJCombrink/SpellChecker-Plugin/releases/download/v3.6.0/SpellChecker-Plugin_QtC13.0.0_macos_x64.tar.gz"
+ }
+ ],
+ "vendor": "Carel Combrink",
+ "copyright": "(C) 2015 - 2024 Carel Combrink"
+ }
+ ]
+}
diff --git a/src/plugins/extensionmanager/testdata/thirdpartyplugins.json b/src/plugins/extensionmanager/testdata/thirdpartyplugins.json
new file mode 100644
index 0000000000..910854a68f
--- /dev/null
+++ b/src/plugins/extensionmanager/testdata/thirdpartyplugins.json
@@ -0,0 +1,38 @@
+{
+ "items": [
+ {
+ "name": "SpellChecker",
+ "tags": [ "Editor" ],
+ "platforms": [ "macOS", "Windows", "Linux" ],
+ "license": "os",
+ "is_pack": false,
+ "description": {
+ "paragraphs": [
+ {
+ "text": [
+ "Spellcheck comments in source files."
+ ],
+ "header": "Get started"
+ }
+ ],
+ "links": [
+ {
+ "url": "https://github.com/CJCombrink/SpellChecker-Plugin",
+ "link_text": "GitHub page"
+ }
+ ]
+ },
+ "plugins": [
+ {
+ "meta_data": {
+ "Name": "SpellChecker",
+ "Copyright": "(C) 2015 - 2024 Carel Combrink"
+ },
+ "url": "https://github.com/CJCombrink/SpellChecker-Plugin/releases/download/v3.6.0/SpellChecker-Plugin_QtC13.0.0_macos_x64.tar.gz"
+ }
+ ],
+ "vendor": "Carel Combrink",
+ "copyright": "(C) 2015 - 2024 Carel Combrink"
+ }
+ ]
+}
diff --git a/src/plugins/fakevim/FakeVim.json.in b/src/plugins/fakevim/FakeVim.json.in
index 915b50331b..b2ae7f2a87 100644
--- a/src/plugins/fakevim/FakeVim.json.in
+++ b/src/plugins/fakevim/FakeVim.json.in
@@ -12,7 +12,8 @@
"",
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
- "Description" : "VI-style keyboard navigation.",
- "Url" : "http://www.qt.io",
+ "Description" : "Run the main editor in a manner similar to the Vim editor",
+ "LongDescription" : [],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/fakevim/fakevimactions.cpp b/src/plugins/fakevim/fakevimactions.cpp
index 3fd3e9c31f..e6a2a661d5 100644
--- a/src/plugins/fakevim/fakevimactions.cpp
+++ b/src/plugins/fakevim/fakevimactions.cpp
@@ -139,7 +139,7 @@ FakeVimSettings::FakeVimSettings()
startOfLine,
passKeys,
blinkingCursor,
- HostOsInfo::isWindowsHost() ? LayoutItem(systemEncoding) : empty
+ If { HostOsInfo::isWindowsHost(), { systemEncoding } }
},
Column {
incSearch,
@@ -199,7 +199,7 @@ FakeVimSettings::FakeVimSettings()
autoIndent.setValue(true);
smartIndent.setValue(tps.m_autoIndent);
incSearch.setValue(true);
- }),
+ }, nullptr),
},
PushButton {
text(Tr::tr("Set Qt Style")),
@@ -213,7 +213,7 @@ FakeVimSettings::FakeVimSettings()
incSearch.setVolatileValue(true);
backspace.setVolatileValue(QString("indent,eol,start"));
passKeys.setVolatileValue(true);
- }),
+ }, nullptr),
},
PushButton {
text(Tr::tr("Set Plain Style")),
@@ -227,7 +227,7 @@ FakeVimSettings::FakeVimSettings()
incSearch.setVolatileValue(false);
backspace.setVolatileValue(QString());
passKeys.setVolatileValue(false);
- }),
+ }, nullptr),
},
st
},
diff --git a/src/plugins/fakevim/fakevimhandler.cpp b/src/plugins/fakevim/fakevimhandler.cpp
index 1e25113fe3..29257505f2 100644
--- a/src/plugins/fakevim/fakevimhandler.cpp
+++ b/src/plugins/fakevim/fakevimhandler.cpp
@@ -6123,7 +6123,7 @@ bool FakeVimHandler::Private::handleExSetCommand(const ExCommand &cmd)
FvBaseAspect *act = s.item(Utils::keyFromString(optionName));
if (!act) {
showMessage(MessageError, Tr::tr("Unknown option:") + ' ' + cmd.args);
- } else if (act->defaultVariantValue().type() == QVariant::Bool) {
+ } else if (act->defaultVariantValue().typeId() == QMetaType::Bool) {
bool oldValue = act->variantValue().toBool();
if (printOption) {
showMessage(MessageInfo, QLatin1String(oldValue ? "" : "no")
diff --git a/src/plugins/fossil/Fossil.json.in b/src/plugins/fossil/Fossil.json.in
index d83238ccca..02bc83e599 100644
--- a/src/plugins/fossil/Fossil.json.in
+++ b/src/plugins/fossil/Fossil.json.in
@@ -14,8 +14,12 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Version Control",
- "Description" : "Fossil SCM integration.",
- "Url" : "http://www.qt.io",
+ "Description" : "Access the Fossil version control system",
+ "LongDescription" : [
+ "You also need:",
+ "- Fossil"
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES},
"JsonWizardPaths" : [":/fossil/wizard"]
diff --git a/src/plugins/fossil/fossilclient.cpp b/src/plugins/fossil/fossilclient.cpp
index 662785930d..2c077a3b59 100644
--- a/src/plugins/fossil/fossilclient.cpp
+++ b/src/plugins/fossil/fossilclient.cpp
@@ -766,7 +766,7 @@ unsigned int FossilClient::binaryVersion() const
static unsigned int cachedBinaryVersion = 0;
static FilePath cachedBinaryPath;
- const FilePath currentBinaryPath = settings().binaryPath();
+ const FilePath currentBinaryPath = settings().binaryPath.effectiveBinary();
if (currentBinaryPath.isEmpty())
return 0;
diff --git a/src/plugins/fossil/fossilplugin.cpp b/src/plugins/fossil/fossilplugin.cpp
index d627d13fda..9821607033 100644
--- a/src/plugins/fossil/fossilplugin.cpp
+++ b/src/plugins/fossil/fossilplugin.cpp
@@ -958,12 +958,9 @@ VcsCommand *FossilPluginPrivate::createInitialCheckoutCommand(const QString &sou
//
// So here we want Fossil to save the remote details when specified.
- QStringList args;
- args << fossilClient().vcsCommandString(FossilClient::CloneCommand)
- << extraOptions
- << sourceUrl
- << fossilFileNative;
- command->addJob({fossilClient().vcsBinary(checkoutPath), args}, -1);
+ command->addJob({fossilClient().vcsBinary(checkoutPath),
+ {fossilClient().vcsCommandString(FossilClient::CloneCommand), extraOptions,
+ sourceUrl, fossilFileNative}}, -1);
}
// check out the cloned repository file into the working copy directory;
@@ -977,15 +974,14 @@ VcsCommand *FossilPluginPrivate::createInitialCheckoutCommand(const QString &sou
// set user default to admin user if specified
if (!isLocalRepository
&& !adminUser.isEmpty()) {
- const QStringList args({ "user", "default", adminUser, "--user", adminUser});
- command->addJob({fossilClient().vcsBinary(checkoutPath), args}, -1);
+ command->addJob({fossilClient().vcsBinary(checkoutPath),
+ {"user", "default", adminUser, "--user", adminUser}}, -1);
}
// turn-off autosync if requested
- if (!isLocalRepository
- && disableAutosync) {
- const QStringList args({"settings", "autosync", "off"});
- command->addJob({fossilClient().vcsBinary(checkoutPath), args}, -1);
+ if (!isLocalRepository && disableAutosync) {
+ command->addJob({fossilClient().vcsBinary(checkoutPath), {"settings", "autosync", "off"}},
+ -1);
}
return command;
@@ -993,11 +989,11 @@ VcsCommand *FossilPluginPrivate::createInitialCheckoutCommand(const QString &sou
void FossilPluginPrivate::changed(const QVariant &v)
{
- switch (v.type()) {
- case QVariant::String:
+ switch (v.typeId()) {
+ case QMetaType::QString:
emit repositoryChanged(FilePath::fromVariant(v));
break;
- case QVariant::StringList:
+ case QMetaType::QStringList:
emit filesChanged(v.toStringList());
break;
default:
diff --git a/src/plugins/genericprojectmanager/GenericProjectManager.json.in b/src/plugins/genericprojectmanager/GenericProjectManager.json.in
index ec4eae552e..40a4f88829 100644
--- a/src/plugins/genericprojectmanager/GenericProjectManager.json.in
+++ b/src/plugins/genericprojectmanager/GenericProjectManager.json.in
@@ -14,7 +14,7 @@
],
"Category" : "Build Systems",
"Description" : "Generic support.",
- "Url" : "http://www.qt.io",
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES},
"Mimetypes" : [
diff --git a/src/plugins/git/Git.json.in b/src/plugins/git/Git.json.in
index db0663a876..af1f550a7a 100644
--- a/src/plugins/git/Git.json.in
+++ b/src/plugins/git/Git.json.in
@@ -13,8 +13,12 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Version Control",
- "Description" : "Git integration.",
- "Url" : "http://www.qt.io",
+ "Description" : "Access the Git version control system",
+ "LongDescription" : [
+ "You also need:",
+ "- Git"
+ ],
+ "Url" : "https://www.qt.io",
"Arguments" : [
{
"Name" : "-git-show",
diff --git a/src/plugins/git/changeselectiondialog.cpp b/src/plugins/git/changeselectiondialog.cpp
index 24daa93829..1eb8ba7d65 100644
--- a/src/plugins/git/changeselectiondialog.cpp
+++ b/src/plugins/git/changeselectiondialog.cpp
@@ -172,18 +172,16 @@ void ChangeSelectionDialog::acceptCommand(ChangeCommand command)
//! Set commit message in details
void ChangeSelectionDialog::setDetails()
{
- Theme *theme = creatorTheme();
-
QPalette palette;
if (m_process->result() == ProcessResult::FinishedWithSuccess) {
m_detailsText->setPlainText(m_process->cleanedStdOut());
- palette.setColor(QPalette::Text, theme->color(Theme::TextColorNormal));
+ palette.setColor(QPalette::Text, creatorColor(Theme::TextColorNormal));
m_changeNumberEdit->setPalette(palette);
} else if (m_process->result() == ProcessResult::StartFailed) {
m_detailsText->setPlainText(Tr::tr("Error: Could not start Git."));
} else {
m_detailsText->setPlainText(Tr::tr("Error: Unknown reference"));
- palette.setColor(QPalette::Text, theme->color(Theme::TextColorError));
+ palette.setColor(QPalette::Text, creatorColor(Theme::TextColorError));
m_changeNumberEdit->setPalette(palette);
enableButtons(false);
}
diff --git a/src/plugins/git/gerrit/gerritpushdialog.cpp b/src/plugins/git/gerrit/gerritpushdialog.cpp
index acbf62c9ee..1a29273d09 100644
--- a/src/plugins/git/gerrit/gerritpushdialog.cpp
+++ b/src/plugins/git/gerrit/gerritpushdialog.cpp
@@ -228,7 +228,7 @@ void GerritPushDialog::setChangeRange()
const int currentRange = range.toInt();
QPalette palette = QApplication::palette();
if (currentRange > ReasonableDistance) {
- const QColor errorColor = Utils::creatorTheme()->color(Utils::Theme::TextColorError);
+ const QColor errorColor = Utils::creatorColor(Utils::Theme::TextColorError);
palette.setColor(QPalette::WindowText, errorColor);
palette.setColor(QPalette::ButtonText, errorColor);
labelText.append("\n" + Git::Tr::tr("Are you sure you selected the right target branch?"));
diff --git a/src/plugins/git/gitclient.cpp b/src/plugins/git/gitclient.cpp
index 9d4d133ee4..5c60523359 100644
--- a/src/plugins/git/gitclient.cpp
+++ b/src/plugins/git/gitclient.cpp
@@ -1626,7 +1626,7 @@ QString GitClient::synchronousCurrentLocalBranch(const FilePath &workingDirector
if (!branch.isEmpty()) {
const QString refsHeadsPrefix = "refs/heads/";
if (branch.startsWith(refsHeadsPrefix)) {
- branch.remove(0, refsHeadsPrefix.count());
+ branch.remove(0, refsHeadsPrefix.size());
return branch;
}
}
@@ -2531,7 +2531,7 @@ bool GitClient::launchGitBash(const FilePath &workingDirectory)
success = false;
} else {
const FilePath gitBash = git.absolutePath().parentDir() / "git-bash.exe";
- success = Process::startDetached({gitBash, {}}, workingDirectory);
+ success = Process::startDetached(CommandLine{gitBash}, workingDirectory);
}
if (!success)
@@ -3154,7 +3154,7 @@ void GitClient::push(const FilePath &workingDirectory, const QStringList &pushAr
return;
if (pushFailure == PushFailure::NonFastForward) {
- const QColor warnColor = Utils::creatorTheme()->color(Theme::TextColorError);
+ const QColor warnColor = Utils::creatorColor(Theme::TextColorError);
if (QMessageBox::question(
Core::ICore::dialogParent(), Tr::tr("Force Push"),
Tr::tr("Push failed. Would you like to force-push <span style=\"color:#%1\">"
diff --git a/src/plugins/git/gitgrep.cpp b/src/plugins/git/gitgrep.cpp
index 73feffd68f..84bf26238f 100644
--- a/src/plugins/git/gitgrep.cpp
+++ b/src/plugins/git/gitgrep.cpp
@@ -245,14 +245,15 @@ GitGrepParameters GitGrep::gitParameters() const
return {m_treeLineEdit->text(), m_recurseSubmodules && m_recurseSubmodules->isChecked()};
}
-void GitGrep::readSettings(QtcSettings *settings)
+void GitGrep::readSettings(const Store &s)
{
- m_treeLineEdit->setText(settings->value(GitGrepRef).toString());
+ m_treeLineEdit->setText(s.value(GitGrepRef).toString());
}
-void GitGrep::writeSettings(QtcSettings *settings) const
+void GitGrep::writeSettings(Store &s) const
{
- settings->setValue(GitGrepRef, m_treeLineEdit->text());
+ if (!m_treeLineEdit->text().isEmpty())
+ s.insert(GitGrepRef, m_treeLineEdit->text());
}
SearchExecutor GitGrep::searchExecutor() const
diff --git a/src/plugins/git/gitgrep.h b/src/plugins/git/gitgrep.h
index bb6967eb67..4e5c75d97a 100644
--- a/src/plugins/git/gitgrep.h
+++ b/src/plugins/git/gitgrep.h
@@ -24,8 +24,8 @@ public:
QString title() const override;
QString toolTip() const override;
QWidget *widget() const override;
- void readSettings(Utils::QtcSettings *settings) override;
- void writeSettings(Utils::QtcSettings *settings) const override;
+ void readSettings(const Utils::Store &settings) override;
+ void writeSettings(Utils::Store &settings) const override;
TextEditor::SearchExecutor searchExecutor() const override;
TextEditor::EditorOpener editorOpener() const override;
diff --git a/src/plugins/git/gitplugin.cpp b/src/plugins/git/gitplugin.cpp
index af5b267101..d238f7113e 100644
--- a/src/plugins/git/gitplugin.cpp
+++ b/src/plugins/git/gitplugin.cpp
@@ -1766,13 +1766,11 @@ VcsCommand *GitPluginPrivate::createInitialCheckoutCommand(const QString &url,
const QString &localName,
const QStringList &extraArgs)
{
- QStringList args = {"clone", "--progress"};
- args << extraArgs << url << localName;
-
auto command = VcsBaseClient::createVcsCommand(this, baseDirectory,
gitClient().processEnvironment(baseDirectory));
command->addFlags(RunFlags::SuppressStdErr);
- command->addJob({gitClient().vcsBinary(baseDirectory), args}, -1);
+ command->addJob({gitClient().vcsBinary(baseDirectory),
+ {"clone", "--progress", extraArgs, url, localName}}, -1);
return command;
}
diff --git a/src/plugins/git/gitsettings.cpp b/src/plugins/git/gitsettings.cpp
index 2e7f8bf2de..f8056d9a00 100644
--- a/src/plugins/git/gitsettings.cpp
+++ b/src/plugins/git/gitsettings.cpp
@@ -151,7 +151,7 @@ GitSettings::GitSettings()
Group {
title(Tr::tr("Instant Blame")),
- instantBlame.groupChecker(),
+ groupChecker(instantBlame.groupChecker()),
Row { instantBlameIgnoreSpaceChanges, instantBlameIgnoreLineMoves, st },
},
diff --git a/src/plugins/git/gitsubmiteditor.cpp b/src/plugins/git/gitsubmiteditor.cpp
index 3a968a1e86..1e09d6ec9d 100644
--- a/src/plugins/git/gitsubmiteditor.cpp
+++ b/src/plugins/git/gitsubmiteditor.cpp
@@ -11,7 +11,6 @@
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/iversioncontrol.h>
#include <coreplugin/progressmanager/progressmanager.h>
-#include <extensionsystem/pluginmanager.h>
#include <utils/async.h>
#include <utils/qtcassert.h>
#include <vcsbase/submitfilemodel.h>
@@ -210,7 +209,7 @@ void GitSubmitEditor::updateFileModel()
Core::ProgressManager::addTask(m_fetchWatcher.future(), Tr::tr("Refreshing Commit Data"),
TASK_UPDATE_COMMIT);
- ExtensionSystem::PluginManager::futureSynchronizer()->addFuture(m_fetchWatcher.future());
+ Utils::futureSynchronizer()->addFuture(m_fetchWatcher.future());
}
void GitSubmitEditor::forceUpdateFileModel()
diff --git a/src/plugins/git/gitsubmiteditorwidget.cpp b/src/plugins/git/gitsubmiteditorwidget.cpp
index c9b716df26..a6635104ea 100644
--- a/src/plugins/git/gitsubmiteditorwidget.cpp
+++ b/src/plugins/git/gitsubmiteditorwidget.cpp
@@ -120,8 +120,7 @@ void GitSubmitEditorWidget::setPanelInfo(const GitSubmitEditorPanelInfo &info)
{
m_gitSubmitPanel->repositoryLabel->setText(info.repository.toUserOutput());
if (info.branch.contains("(no branch)")) {
- const QString errorColor =
- Utils::creatorTheme()->color(Utils::Theme::TextColorError).name();
+ const QString errorColor = Utils::creatorColor(Utils::Theme::TextColorError).name();
m_gitSubmitPanel->branchLabel->setText(QString::fromLatin1("<span style=\"color:%1\">%2</span>")
.arg(errorColor, Tr::tr("Detached HEAD")));
} else {
diff --git a/src/plugins/git/instantblame.cpp b/src/plugins/git/instantblame.cpp
index 5921518c34..f932c78698 100644
--- a/src/plugins/git/instantblame.cpp
+++ b/src/plugins/git/instantblame.cpp
@@ -166,6 +166,13 @@ void InstantBlame::setup()
connect(EditorManager::instance(), &EditorManager::currentEditorChanged,
this, setupBlameForEditor);
+ connect(EditorManager::instance(), &EditorManager::documentClosed,
+ this, [this](IDocument *doc) {
+ if (m_document != doc)
+ return;
+ disconnect(m_documentChangedConn);
+ m_document = nullptr;
+ });
}
// Porcelain format of git blame output
diff --git a/src/plugins/git/mergetool.cpp b/src/plugins/git/mergetool.cpp
index d0a102c9df..138943ef2c 100644
--- a/src/plugins/git/mergetool.cpp
+++ b/src/plugins/git/mergetool.cpp
@@ -35,9 +35,7 @@ MergeTool::MergeTool(QObject *parent) : QObject(parent)
void MergeTool::start(const FilePath &workingDirectory, const QStringList &files)
{
- QStringList arguments;
- arguments << "mergetool" << "-y" << files;
- const CommandLine cmd = {gitClient().vcsBinary(workingDirectory), arguments};
+ const CommandLine cmd{gitClient().vcsBinary(workingDirectory), {"mergetool", "-y", files}};
VcsOutputWindow::appendCommand(workingDirectory, cmd);
m_process.setCommand(cmd);
m_process.setWorkingDirectory(workingDirectory);
diff --git a/src/plugins/gitlab/GitLab.json.in b/src/plugins/gitlab/GitLab.json.in
index ad55a21d7c..e446654bc4 100644
--- a/src/plugins/gitlab/GitLab.json.in
+++ b/src/plugins/gitlab/GitLab.json.in
@@ -13,7 +13,11 @@
"",
"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."
],
-"Description" : "GitLab plugin.",
-"Url" : "http://www.qt.io",
+"Description" : "Create connections to GitLab servers",
+"LongDescription" : [
+ "You also need:",
+ "- GitLab account"
+],
+"Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/gitlab/gitlaboptionspage.cpp b/src/plugins/gitlab/gitlaboptionspage.cpp
index d550dd4845..181d5fba82 100644
--- a/src/plugins/gitlab/gitlaboptionspage.cpp
+++ b/src/plugins/gitlab/gitlaboptionspage.cpp
@@ -102,7 +102,7 @@ GitLabServerWidget::GitLabServerWidget(Mode m, QWidget *parent)
m_token, br,
m_port, br,
m_secure,
- m == Edit ? normalMargin : noMargin
+ m == Edit ? &Layout::setNormalMargins : &Layout::setNoMargins
},
}.attachTo(this);
}
diff --git a/src/plugins/glsleditor/GLSLEditor.json.in b/src/plugins/glsleditor/GLSLEditor.json.in
index 54ef8d548b..dd3b752969 100644
--- a/src/plugins/glsleditor/GLSLEditor.json.in
+++ b/src/plugins/glsleditor/GLSLEditor.json.in
@@ -13,8 +13,11 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Other Languages",
- "Description" : "Editor for GLSL.",
- "Url" : "http://www.qt.io",
+ "Description" : "Create OpenGL fragment and vertex shaders",
+ "LongDescription" : [
+ "Use the shaders to display hardware-accelerated 3D graphics alongside a more conventional UI."
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES},
"Mimetypes" : [
diff --git a/src/plugins/haskell/Haskell.json.in b/src/plugins/haskell/Haskell.json.in
index 7f99cdf3fe..1473874ad9 100644
--- a/src/plugins/haskell/Haskell.json.in
+++ b/src/plugins/haskell/Haskell.json.in
@@ -6,7 +6,8 @@
"Vendor" : "Eike Ziller",
"Copyright" : "(C) Eike Ziller",
"License" : "MIT",
- "Description" : "Haskell support",
+ "Description" : "Write source code in the Haskell language",
+ "LongDescription" : [],
"Url" : "https://haskell.org",
${IDE_PLUGIN_DEPENDENCIES},
diff --git a/src/plugins/helloworld/HelloWorld.json.in b/src/plugins/helloworld/HelloWorld.json.in
index bf6fa1587b..9438334fdd 100644
--- a/src/plugins/helloworld/HelloWorld.json.in
+++ b/src/plugins/helloworld/HelloWorld.json.in
@@ -14,6 +14,6 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Description" : "Hello World sample plugin.",
- "Url" : "http://www.qt.io",
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/help/Help.json.in b/src/plugins/help/Help.json.in
index e6ecc9eeb3..cbb172e3b9 100644
--- a/src/plugins/help/Help.json.in
+++ b/src/plugins/help/Help.json.in
@@ -13,7 +13,11 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Core",
- "Description" : "Help system.",
- "Url" : "http://www.qt.io",
+ "Description" : "Get help for Qt API and read Qt and other documentation",
+ "LongDescription" : [
+ "You also need:",
+ "- Qt documentation as help files (.qch)"
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/help/generalsettingspage.cpp b/src/plugins/help/generalsettingspage.cpp
index b0c22ca0f7..a87633a840 100644
--- a/src/plugins/help/generalsettingspage.cpp
+++ b/src/plugins/help/generalsettingspage.cpp
@@ -63,7 +63,6 @@ private:
QFont m_font;
int m_fontZoom = 100;
- QFontDatabase m_fontDatabase;
QString m_homePage;
int m_contextOption;
@@ -400,9 +399,9 @@ void GeneralSettingsPageWidget::exportBookmarks()
void GeneralSettingsPageWidget::updateFontSizeSelector()
{
const QString &family = m_font.family();
- const QString &fontStyle = m_fontDatabase.styleString(m_font);
+ const QString &fontStyle = QFontDatabase::styleString(m_font);
- QList<int> pointSizes = m_fontDatabase.pointSizes(family, fontStyle);
+ QList<int> pointSizes = QFontDatabase::pointSizes(family, fontStyle);
if (pointSizes.empty())
pointSizes = QFontDatabase::standardSizes();
@@ -424,8 +423,8 @@ void GeneralSettingsPageWidget::updateFontSizeSelector()
void GeneralSettingsPageWidget::updateFontStyleSelector()
{
- const QString &fontStyle = m_fontDatabase.styleString(m_font);
- const QStringList &styles = m_fontDatabase.styles(m_font.family());
+ const QString &fontStyle = QFontDatabase::styleString(m_font);
+ const QStringList &styles = QFontDatabase::styles(m_font.family());
QSignalBlocker blocker(styleComboBox);
styleComboBox->clear();
diff --git a/src/plugins/help/helpindexfilter.cpp b/src/plugins/help/helpindexfilter.cpp
index c535fe31b0..2820c634a3 100644
--- a/src/plugins/help/helpindexfilter.cpp
+++ b/src/plugins/help/helpindexfilter.cpp
@@ -10,7 +10,6 @@
#include <coreplugin/helpmanager.h>
#include <coreplugin/icore.h>
-#include <extensionsystem/pluginmanager.h>
#include <utils/async.h>
#include <utils/utilsicons.h>
@@ -95,7 +94,6 @@ LocatorMatcherTasks HelpIndexFilter::matchers()
}
const QStringList cache = m_lastEntry.isEmpty() || !storage->input().contains(m_lastEntry)
? m_allIndicesCache : m_lastIndicesCache;
- async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
async.setConcurrentCallData(matches, *storage, cache, m_icon);
};
const auto onDone = [this, storage](const Async<QStringList> &async) {
diff --git a/src/plugins/help/helpmanager.cpp b/src/plugins/help/helpmanager.cpp
index a117afd916..eee8f8fcd4 100644
--- a/src/plugins/help/helpmanager.cpp
+++ b/src/plugins/help/helpmanager.cpp
@@ -2,14 +2,13 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "helpmanager.h"
+#include "localhelpmanager.h"
#include "helptr.h"
#include <coreplugin/icore.h>
#include <coreplugin/progressmanager/progressmanager.h>
-#include <extensionsystem/pluginmanager.h>
-
#include <utils/algorithm.h>
#include <utils/async.h>
#include <utils/filesystemwatcher.h>
@@ -140,7 +139,7 @@ void HelpManager::registerDocumentation(const QStringList &files)
}
QFuture<bool> future = Utils::asyncRun(&registerDocumentationNow, collectionFilePath(), files);
- ExtensionSystem::PluginManager::futureSynchronizer()->addFuture(future);
+ Utils::futureSynchronizer()->addFuture(future);
Utils::onResultReady(future, this, [](bool docsChanged){
if (docsChanged) {
d->m_helpEngine->setupData();
@@ -203,7 +202,7 @@ void HelpManager::unregisterDocumentation(const QStringList &files)
d->m_userRegisteredFiles.subtract(Utils::toSet(files));
QFuture<bool> future = Utils::asyncRun(&unregisterDocumentationNow, collectionFilePath(), files);
- ExtensionSystem::PluginManager::futureSynchronizer()->addFuture(future);
+ Utils::futureSynchronizer()->addFuture(future);
Utils::onResultReady(future, this, [](bool docsChanged){
if (docsChanged) {
d->m_helpEngine->setupData();
@@ -284,6 +283,11 @@ void HelpManager::showHelpUrl(const QUrl &url, Core::HelpManager::HelpViewerLoca
emit m_instance->helpRequested(url, location);
}
+void HelpManager::addOnlineHelpHandler(const Core::HelpManager::OnlineHelpHandler &handler)
+{
+ LocalHelpManager::addOnlineHelpHandler(handler);
+}
+
QStringList HelpManager::registeredNamespaces()
{
QTC_ASSERT(!d->m_needsSetup, return {});
diff --git a/src/plugins/help/helpmanager.h b/src/plugins/help/helpmanager.h
index 5f0efe5620..8842586315 100644
--- a/src/plugins/help/helpmanager.h
+++ b/src/plugins/help/helpmanager.h
@@ -57,6 +57,8 @@ public:
const QUrl &url,
Core::HelpManager::HelpViewerLocation location = Core::HelpManager::HelpModeAlways) override;
+ void addOnlineHelpHandler(const Core::HelpManager::OnlineHelpHandler &handler) override;
+
static void setupHelpManager();
signals:
diff --git a/src/plugins/help/helpplugin.cpp b/src/plugins/help/helpplugin.cpp
index 49b4624b26..c729ff575d 100644
--- a/src/plugins/help/helpplugin.cpp
+++ b/src/plugins/help/helpplugin.cpp
@@ -507,8 +507,8 @@ void HelpPluginPrivate::showContextHelp(const HelpItem &contextHelp)
"<font color=\"%3\">%5</font>"
"</center></body></html>")
.arg(Tr::tr("No Documentation"))
- .arg(creatorTheme()->color(Theme::BackgroundColorNormal).name())
- .arg(creatorTheme()->color(Theme::TextColorNormal).name())
+ .arg(creatorColor(Theme::BackgroundColorNormal).name())
+ .arg(creatorColor(Theme::TextColorNormal).name())
.arg(contextHelp.helpIds().join(", "))
.arg(Tr::tr("No documentation available.")));
}
diff --git a/src/plugins/help/helpwidget.cpp b/src/plugins/help/helpwidget.cpp
index 0aeb0b0fd8..08f66b155c 100644
--- a/src/plugins/help/helpwidget.cpp
+++ b/src/plugins/help/helpwidget.cpp
@@ -52,6 +52,24 @@ static const char kModeSideBarSettingsKey[] = "Help/ModeSideBar";
namespace Help {
namespace Internal {
+class ButtonWithMenu : public QToolButton
+{
+public:
+ ButtonWithMenu(QWidget *parent = nullptr)
+ : QToolButton(parent)
+ {}
+
+protected:
+ void mousePressEvent(QMouseEvent *e) override
+ {
+ if (e->button() == Qt::RightButton) {
+ showMenu();
+ return;
+ }
+ QToolButton::mousePressEvent(e);
+ }
+};
+
OpenPagesModel::OpenPagesModel(HelpWidget *parent)
: m_parent(parent)
{}
@@ -282,7 +300,9 @@ HelpWidget::HelpWidget(const Core::Context &context, WidgetStyle style, QWidget
m_backAction->setMenu(m_backMenu);
cmd = Core::ActionManager::registerAction(m_backAction, Constants::HELP_PREVIOUS, context);
cmd->setDefaultKeySequence(QKeySequence::Back);
- button = Core::Command::toolButtonWithAppendedShortcut(m_backAction, cmd);
+ button = new ButtonWithMenu;
+ button->setDefaultAction(m_backAction);
+ cmd->augmentActionWithShortcutToolTip(m_backAction);
button->setPopupMode(QToolButton::DelayedPopup);
layout->addWidget(button);
@@ -293,7 +313,9 @@ HelpWidget::HelpWidget(const Core::Context &context, WidgetStyle style, QWidget
m_forwardAction->setMenu(m_forwardMenu);
cmd = Core::ActionManager::registerAction(m_forwardAction, Constants::HELP_NEXT, context);
cmd->setDefaultKeySequence(QKeySequence::Forward);
- button = Core::Command::toolButtonWithAppendedShortcut(m_forwardAction, cmd);
+ button = new ButtonWithMenu;
+ button->setDefaultAction(m_forwardAction);
+ cmd->augmentActionWithShortcutToolTip(m_forwardAction);
button->setPopupMode(QToolButton::DelayedPopup);
layout->addWidget(button);
diff --git a/src/plugins/help/localhelpmanager.cpp b/src/plugins/help/localhelpmanager.cpp
index 4bca01bf79..738e3337a1 100644
--- a/src/plugins/help/localhelpmanager.cpp
+++ b/src/plugins/help/localhelpmanager.cpp
@@ -53,6 +53,8 @@ QHelpEngine* LocalHelpManager::m_guiEngine = nullptr;
QMutex LocalHelpManager::m_bkmarkMutex;
BookmarkManager* LocalHelpManager::m_bookmarkManager = nullptr;
+QList<Core::HelpManager::OnlineHelpHandler> LocalHelpManager::m_onlineHelpHandlerList;
+
const char kHelpHomePageKey[] = "Help/HomePage";
const char kFontFamilyKey[] = "Help/FallbackFontFamily";
const char kFontStyleNameKey[] = "Help/FallbackFontStyleName";
@@ -86,7 +88,7 @@ static QString defaultFallbackFontFamily()
static QString defaultFallbackFontStyleName(const QString &fontFamily)
{
- const QStringList styles = QFontDatabase().styles(fontFamily);
+ const QStringList styles = QFontDatabase::styles(fontFamily);
QTC_ASSERT(!styles.isEmpty(), return QString("Regular"));
return styles.first();
}
@@ -96,6 +98,8 @@ LocalHelpManager::LocalHelpManager(QObject *parent)
{
m_instance = this;
qRegisterMetaType<Help::Internal::LocalHelpManager::HelpData>("Help::Internal::LocalHelpManager::HelpData");
+
+ addOnlineHelpHandler({LocalHelpManager::isQtUrl, LocalHelpManager::openQtUrl});
}
LocalHelpManager::~LocalHelpManager()
@@ -508,43 +512,64 @@ QHelpFilterEngine *LocalHelpManager::filterEngine()
bool LocalHelpManager::canOpenOnlineHelp(const QUrl &url)
{
+ return Utils::anyOf(
+ m_onlineHelpHandlerList, [url](const Core::HelpManager::OnlineHelpHandler &handler) {
+ return handler.handlesUrl(url);
+ });
+}
+
+bool LocalHelpManager::isQtUrl(const QUrl &url)
+{
const QString address = url.toString();
return address.startsWith("qthelp://org.qt-project.")
|| address.startsWith("qthelp://com.nokia.")
|| address.startsWith("qthelp://com.trolltech.");
}
-bool LocalHelpManager::openOnlineHelp(const QUrl &url)
+void LocalHelpManager::openQtUrl(const QUrl &url)
{
static const QString unversionedLocalDomainName
= QString("org.qt-project.%1").arg(Utils::appInfo().id);
- if (canOpenOnlineHelp(url)) {
- QString urlPrefix = "http://doc.qt.io/";
- if (url.authority().startsWith(unversionedLocalDomainName)) {
- urlPrefix.append(Utils::appInfo().id);
+ QString urlPrefix = "http://doc.qt.io/";
+ if (url.authority().startsWith(unversionedLocalDomainName)) {
+ urlPrefix.append(Utils::appInfo().id);
+ } else {
+ const auto host = url.host();
+ const auto dot = host.lastIndexOf('.');
+ if (dot < 0) {
+ urlPrefix.append("qt-5");
} else {
- const auto host = url.host();
- const auto dot = host.lastIndexOf('.');
- if (dot < 0) {
- urlPrefix.append("qt-5");
+ const auto version = host.mid(dot + 1);
+ if (version.startsWith('6')) {
+ urlPrefix.append("qt-6");
} else {
- const auto version = host.mid(dot + 1);
- if (version.startsWith('6')) {
- urlPrefix.append("qt-6");
- } else {
- urlPrefix.append("qt-5");
- }
+ urlPrefix.append("qt-5");
}
}
- const QString address = url.toString();
- QDesktopServices::openUrl(QUrl(urlPrefix + address.mid(address.lastIndexOf(QLatin1Char('/')))));
- return true;
}
- return false;
+ const QString address = url.toString();
+ QDesktopServices::openUrl(QUrl(urlPrefix + address.mid(address.lastIndexOf(QLatin1Char('/')))));
+}
+
+bool LocalHelpManager::openOnlineHelp(const QUrl &url)
+{
+ return Utils::anyOf(
+ m_onlineHelpHandlerList, [url](const Core::HelpManager::OnlineHelpHandler &handler) {
+ if (handler.handlesUrl(url)) {
+ handler.openUrl(url);
+ return true;
+ }
+ return false;
+ });
}
QMultiMap<QString, QUrl> LocalHelpManager::linksForKeyword(const QString &keyword)
{
return HelpManager::linksForKeyword(&LocalHelpManager::helpEngine(), keyword, std::nullopt);
}
+
+void LocalHelpManager::addOnlineHelpHandler(const Core::HelpManager::OnlineHelpHandler &handler)
+{
+ LocalHelpManager::m_onlineHelpHandlerList.push_back(handler);
+}
diff --git a/src/plugins/help/localhelpmanager.h b/src/plugins/help/localhelpmanager.h
index 5195e7f1b7..13a30555c1 100644
--- a/src/plugins/help/localhelpmanager.h
+++ b/src/plugins/help/localhelpmanager.h
@@ -101,9 +101,13 @@ public:
static bool canOpenOnlineHelp(const QUrl &url);
static bool openOnlineHelp(const QUrl &url);
+ static bool isQtUrl(const QUrl &url);
+ static void openQtUrl(const QUrl &url);
static QMultiMap<QString, QUrl> linksForKeyword(const QString &keyword);
+ static void addOnlineHelpHandler(const Core::HelpManager::OnlineHelpHandler &handler);
+
signals:
void fallbackFontChanged(const QFont &font);
void fontZoomChanged(int percentage);
@@ -121,6 +125,8 @@ private:
static QMutex m_bkmarkMutex;
static BookmarkManager *m_bookmarkManager;
+
+ static QList<Core::HelpManager::OnlineHelpHandler> m_onlineHelpHandlerList;
};
} // Internal
diff --git a/src/plugins/imageviewer/ImageViewer.json.in b/src/plugins/imageviewer/ImageViewer.json.in
index 90ffbcc0c6..4062ba072b 100644
--- a/src/plugins/imageviewer/ImageViewer.json.in
+++ b/src/plugins/imageviewer/ImageViewer.json.in
@@ -12,8 +12,9 @@
"",
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
- "Description" : "Image Viewer component.",
- "Url" : "http://www.qt.io",
+ "Description" : "View images, and create pixmaps from SVG images",
+ "LongDescription" : [],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES},
"Mimetypes" : [
diff --git a/src/plugins/incredibuild/IncrediBuild.json.in b/src/plugins/incredibuild/IncrediBuild.json.in
index d7fb5f242b..a9bdab7ae3 100644
--- a/src/plugins/incredibuild/IncrediBuild.json.in
+++ b/src/plugins/incredibuild/IncrediBuild.json.in
@@ -6,7 +6,7 @@
"Vendor" : "IncrediBuild",
"Copyright" : "(C) IncrediBuild",
"Category" : "Build Systems",
- "Url" : "http://www.IncrediBuild.com",
+ "Url" : "https://www.IncrediBuild.com",
"License" : [ "Commercial Usage",
"",
"Licensees holding valid Qt Commercial licenses may use this plugin in accordance with the Qt 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.",
@@ -15,6 +15,10 @@
"",
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
- "Description" : "Support for Incredibuild.",
+ "Description" : "Spend less time on building C++ code",
+ "LongDescription" : [
+ "You also need:",
+ "- IncrediBuild Agent"
+ ],
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/incredibuild/commandbuilderaspect.cpp b/src/plugins/incredibuild/commandbuilderaspect.cpp
index 99c29cd269..38d0d858e8 100644
--- a/src/plugins/incredibuild/commandbuilderaspect.cpp
+++ b/src/plugins/incredibuild/commandbuilderaspect.cpp
@@ -112,7 +112,7 @@ void CommandBuilderAspectPrivate::tryToMigrate()
}
}
-void CommandBuilderAspect::addToLayout(Layouting::LayoutItem &parent)
+void CommandBuilderAspect::addToLayout(Layouting::Layout &parent)
{
if (!d->commandBuilder) {
d->commandBuilder = new QComboBox;
diff --git a/src/plugins/incredibuild/commandbuilderaspect.h b/src/plugins/incredibuild/commandbuilderaspect.h
index 3282a18415..2035a1c309 100644
--- a/src/plugins/incredibuild/commandbuilderaspect.h
+++ b/src/plugins/incredibuild/commandbuilderaspect.h
@@ -23,7 +23,7 @@ public:
QString fullCommandFlag(bool keepJobNum) const;
private:
- void addToLayout(Layouting::LayoutItem &parent) final;
+ void addToLayout(Layouting::Layout &parent) final;
void fromMap(const Utils::Store &map) final;
void toMap(Utils::Store &map) const final;
diff --git a/src/plugins/insight/Insight.json.in b/src/plugins/insight/Insight.json.in
index 096cc0569f..1f60965d80 100644
--- a/src/plugins/insight/Insight.json.in
+++ b/src/plugins/insight/Insight.json.in
@@ -14,8 +14,13 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Qt Quick",
- "Description" : "Qt Insight Support for Design Studio.",
+ "Description" : "Collect information about how an application is used",
+ "LongDescription" : [
+ "Use Qt Insight with Qt Design Studio.",
+ "You also need:",
+ "- Qt Insight"
+ ],
"DisabledByDefault" : true,
- "Url" : "http://www.qt.io",
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/ios/Ios.json.in b/src/plugins/ios/Ios.json.in
index 2b2e4bae77..2d3eedfef9 100644
--- a/src/plugins/ios/Ios.json.in
+++ b/src/plugins/ios/Ios.json.in
@@ -14,7 +14,13 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Device Support",
- "Description" : "Support for deployment to and execution on iOS Devices.",
- "Url" : "http://www.qt.io",
+ "Description" : "Develop applications for iOS devices",
+ "LongDescription" : [
+ "Connect devices with USB or use the iOS simulator to run, debug, and analyze applications built for iOS.",
+ "You also need:",
+ "- Qt for iOS",
+ "- Xcode"
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/ios/iosconfigurations.cpp b/src/plugins/ios/iosconfigurations.cpp
index d7ad6bf3be..6d7d559d48 100644
--- a/src/plugins/ios/iosconfigurations.cpp
+++ b/src/plugins/ios/iosconfigurations.cpp
@@ -12,8 +12,6 @@
#include <coreplugin/icore.h>
-#include <extensionsystem/pluginmanager.h>
-
#include <projectexplorer/kitaspects.h>
#include <projectexplorer/kitmanager.h>
#include <projectexplorer/devicesupport/devicemanager.h>
@@ -34,8 +32,8 @@
#include <utils/algorithm.h>
#include <utils/futuresynchronizer.h>
-#include <utils/qtcprocess.h>
#include <utils/qtcassert.h>
+#include <utils/qtcprocess.h>
#include <QDir>
#include <QDomDocument>
@@ -386,8 +384,7 @@ void IosConfigurations::updateSimulators()
dev = IDevice::ConstPtr(new IosSimulator(devId));
devManager->addDevice(dev);
}
- ExtensionSystem::PluginManager::futureSynchronizer()->addFuture(
- SimulatorControl::updateAvailableSimulators(this));
+ Utils::futureSynchronizer()->addFuture(SimulatorControl::updateAvailableSimulators(this));
}
void IosConfigurations::setDeveloperPath(const FilePath &devPath)
diff --git a/src/plugins/ios/iosrunconfiguration.cpp b/src/plugins/ios/iosrunconfiguration.cpp
index 23a27e85f6..3fc8c75950 100644
--- a/src/plugins/ios/iosrunconfiguration.cpp
+++ b/src/plugins/ios/iosrunconfiguration.cpp
@@ -337,7 +337,7 @@ IosDeviceTypeAspect::IosDeviceTypeAspect(AspectContainer *container, IosRunConfi
this, &IosDeviceTypeAspect::deviceChanges);
}
-void IosDeviceTypeAspect::addToLayout(Layouting::LayoutItem &parent)
+void IosDeviceTypeAspect::addToLayout(Layouting::Layout &parent)
{
m_deviceTypeComboBox = new QComboBox;
m_deviceTypeComboBox->setModel(&m_deviceTypeModel);
diff --git a/src/plugins/ios/iosrunconfiguration.h b/src/plugins/ios/iosrunconfiguration.h
index 524369605c..4defcf36ee 100644
--- a/src/plugins/ios/iosrunconfiguration.h
+++ b/src/plugins/ios/iosrunconfiguration.h
@@ -31,7 +31,7 @@ public:
void fromMap(const Utils::Store &map) override;
void toMap(Utils::Store &map) const override;
- void addToLayout(Layouting::LayoutItem &parent) override;
+ void addToLayout(Layouting::Layout &parent) override;
IosDeviceType deviceType() const;
void setDeviceType(const IosDeviceType &deviceType);
diff --git a/src/plugins/ios/iosrunner.cpp b/src/plugins/ios/iosrunner.cpp
index e5b03e5229..863a2867b8 100644
--- a/src/plugins/ios/iosrunner.cpp
+++ b/src/plugins/ios/iosrunner.cpp
@@ -237,17 +237,17 @@ GroupItem DeviceCtlRunner::launchTask(const QString &bundleIdentifier)
return SetupResult::StopWithError;
}
process.setCommand({FilePath::fromString("/usr/bin/xcrun"),
- QStringList{"devicectl",
- "device",
- "process",
- "launch",
- "--device",
- m_device->uniqueInternalDeviceId(),
- "--quiet",
- "--json-output",
- "-",
- bundleIdentifier}
- + m_arguments});
+ {"devicectl",
+ "device",
+ "process",
+ "launch",
+ "--device",
+ m_device->uniqueInternalDeviceId(),
+ "--quiet",
+ "--json-output",
+ "-",
+ bundleIdentifier,
+ m_arguments}});
return SetupResult::Continue;
};
const auto onDone = [this](const Process &process, DoneWith result) {
diff --git a/src/plugins/ios/simulatorcontrol.cpp b/src/plugins/ios/simulatorcontrol.cpp
index 4def4c0e47..b702767251 100644
--- a/src/plugins/ios/simulatorcontrol.cpp
+++ b/src/plugins/ios/simulatorcontrol.cpp
@@ -86,20 +86,18 @@ static expected_str<void> runCommand(
}
static expected_str<void> runSimCtlCommand(
- QStringList args,
+ const QStringList &args,
QString *output,
QString *allOutput = nullptr,
std::function<bool()> shouldStop = [] { return false; })
{
- args.prepend("simctl");
-
// Cache xcrun's path, as this function will be called often.
static FilePath xcrun = FilePath::fromString("xcrun").searchInPath();
if (xcrun.isEmpty())
return make_unexpected(Tr::tr("Cannot find xcrun."));
else if (!xcrun.isExecutableFile())
return make_unexpected(Tr::tr("xcrun is not executable."));
- return runCommand({xcrun, args}, output, allOutput, shouldStop);
+ return runCommand({xcrun, {"simctl", args}}, output, allOutput, shouldStop);
}
static expected_str<void> launchSimulator(const QString &simUdid, std::function<bool()> shouldStop)
diff --git a/src/plugins/languageclient/LanguageClient.json.in b/src/plugins/languageclient/LanguageClient.json.in
index 8ccc43c725..7b3ae96052 100644
--- a/src/plugins/languageclient/LanguageClient.json.in
+++ b/src/plugins/languageclient/LanguageClient.json.in
@@ -13,8 +13,11 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Other Languages",
- "Description" : "Language Server Protocol client component. See https://microsoft.github.io/language-server-protocol/overview for an overview on Language Servers.",
- "Url" : "http://www.qt.io",
+ "Description" : "Get code editing services",
+ "LongDescription" : [
+ "Offers code completion, highlighting of the symbol under cursor, and jumping to the symbol definition, as well as diagnostics from the language server."
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES},
"Mimetypes" : [
diff --git a/src/plugins/languageclient/client.cpp b/src/plugins/languageclient/client.cpp
index 0d7826307a..c12693e480 100644
--- a/src/plugins/languageclient/client.cpp
+++ b/src/plugins/languageclient/client.cpp
@@ -903,7 +903,8 @@ void ClientPrivate::requestDocumentHighlightsNow(TextEditor::TextEditorWidget *w
= m_serverCapabilities.documentHighlightProvider();
if (!provider.has_value())
return;
- if (std::holds_alternative<bool>(*provider) && !std::get<bool>(*provider))
+ const auto boolvalue = std::get_if<bool>(&*provider);
+ if (boolvalue && !*boolvalue)
return;
}
@@ -935,7 +936,8 @@ void ClientPrivate::requestDocumentHighlightsNow(TextEditor::TextEditorWidget *w
const QTextCharFormat &format =
widget->textDocument()->fontSettings().toTextCharFormat(TextEditor::C_OCCURRENCES);
QTextDocument *document = widget->document();
- for (const auto &highlight : std::get<QList<DocumentHighlight>>(*result)) {
+ const auto highlights = std::get_if<QList<DocumentHighlight>>(&*result);
+ for (const auto &highlight : *highlights) {
QTextEdit::ExtraSelection selection{widget->textCursor(), format};
const int &start = highlight.range().start().toPositionInDocument(document);
const int &end = highlight.range().end().toPositionInDocument(document);
@@ -1171,7 +1173,7 @@ void Client::documentContentsSaved(TextEditor::TextDocument *document)
includeText = saveOptions->includeText().value_or(includeText);
}
}
- if (!send)
+ if (!send || !shouldSendDidSave(document))
return;
DidSaveTextDocumentParams params(
TextDocumentIdentifier(hostPathToServerUri(document->filePath())));
@@ -1435,7 +1437,8 @@ void Client::requestCodeActions(const CodeActionRequest &request)
} else {
std::variant<bool, CodeActionOptions> provider
= d->m_serverCapabilities.codeActionProvider().value_or(false);
- if (!(std::holds_alternative<CodeActionOptions>(provider) || std::get<bool>(provider)))
+ const auto boolvalue = std::get_if<bool>(&provider);
+ if (boolvalue && !*boolvalue)
return;
}
@@ -1661,8 +1664,8 @@ bool Client::supportsDocumentSymbols(const TextEditor::TextDocument *doc) const
= capabilities().documentSymbolProvider();
if (!provider.has_value())
return false;
- if (std::holds_alternative<bool>(*provider))
- return std::get<bool>(*provider);
+ if (const auto boolvalue = std::get_if<bool>(&*provider))
+ return *boolvalue;
return true;
}
@@ -2233,10 +2236,10 @@ bool ClientPrivate::sendWorkspceFolderChanges() const
if (auto folder = workspace->workspaceFolders()) {
if (folder->supported().value_or(false)) {
// holds either the Id for deregistration or whether it is registered
- auto notification = folder->changeNotifications().value_or(false);
- return std::holds_alternative<QString>(notification)
- || (std::holds_alternative<bool>(notification)
- && std::get<bool>(notification));
+ const std::variant<QString, bool> notification
+ = folder->changeNotifications().value_or(false);
+ const auto boolvalue = std::get_if<bool>(&notification);
+ return !boolvalue || *boolvalue;
}
}
}
diff --git a/src/plugins/languageclient/client.h b/src/plugins/languageclient/client.h
index a881d17824..c80e458a47 100644
--- a/src/plugins/languageclient/client.h
+++ b/src/plugins/languageclient/client.h
@@ -235,6 +235,7 @@ private:
const Utils::FilePath &candidate);
virtual QList<Utils::Text::Range> additionalDocumentHighlights(
TextEditor::TextEditorWidget *, const QTextCursor &) { return {}; }
+ virtual bool shouldSendDidSave(const TextEditor::TextDocument *) const { return true; }
};
} // namespace LanguageClient
diff --git a/src/plugins/languageclient/clientrequest.cpp b/src/plugins/languageclient/clientrequest.cpp
index 4e221f1b9d..bb853f80e4 100644
--- a/src/plugins/languageclient/clientrequest.cpp
+++ b/src/plugins/languageclient/clientrequest.cpp
@@ -29,7 +29,8 @@ bool ClientWorkspaceSymbolRequest::preStartCheck()
= client()->capabilities().workspaceSymbolProvider();
if (!capability.has_value())
return false;
- if (std::holds_alternative<bool>(*capability) && !std::get<bool>(*capability))
+ const auto boolvalue = std::get_if<bool>(&*capability);
+ if (boolvalue && !*boolvalue)
return false;
return true;
diff --git a/src/plugins/languageclient/languageclientcompletionassist.cpp b/src/plugins/languageclient/languageclientcompletionassist.cpp
index d6a2904fa4..7486ca102c 100644
--- a/src/plugins/languageclient/languageclientcompletionassist.cpp
+++ b/src/plugins/languageclient/languageclientcompletionassist.cpp
@@ -123,11 +123,11 @@ QString LanguageClientCompletionItem::detail() const
if (auto _doc = m_item.documentation()) {
auto doc = *_doc;
QString detailDocText;
- if (std::holds_alternative<QString>(doc)) {
- detailDocText = std::get<QString>(doc);
- } else if (std::holds_alternative<MarkupContent>(doc)) {
+ if (const auto s = std::get_if<QString>(&doc)) {
+ detailDocText = *s;
+ } else if (const auto m = std::get_if<MarkupContent>(&doc)) {
// TODO markdown parser?
- detailDocText = std::get<MarkupContent>(doc).content();
+ detailDocText = m->content();
}
if (!detailDocText.isEmpty())
return detailDocText;
@@ -495,12 +495,10 @@ void LanguageClientCompletionAssistProcessor::handleCompletionResponse(
}
QList<CompletionItem> items;
- if (std::holds_alternative<CompletionList>(*result)) {
- const auto &list = std::get<CompletionList>(*result);
- items = list.items().value_or(QList<CompletionItem>());
- } else if (std::holds_alternative<QList<CompletionItem>>(*result)) {
- items = std::get<QList<CompletionItem>>(*result);
- }
+ if (const auto list = std::get_if<CompletionList>(&*result))
+ items = list->items().value_or(QList<CompletionItem>());
+ else if (const auto l = std::get_if<QList<CompletionItem>>(&*result))
+ items = *l;
auto proposalItems = generateCompletionItems(items);
if (!m_snippetsGroup.isEmpty()) {
proposalItems << TextEditor::SnippetAssistCollector(m_snippetsGroup,
diff --git a/src/plugins/languageclient/languageclientformatter.cpp b/src/plugins/languageclient/languageclientformatter.cpp
index 52f35856af..acebc3184b 100644
--- a/src/plugins/languageclient/languageclientformatter.cpp
+++ b/src/plugins/languageclient/languageclientformatter.cpp
@@ -69,7 +69,8 @@ QFutureWatcher<ChangeSet> *LanguageClientFormatter::format(
= m_client->capabilities().documentRangeFormattingProvider();
if (!provider.has_value())
return nullptr;
- if (std::holds_alternative<bool>(*provider) && !std::get<bool>(*provider))
+ const auto boolvalue = std::get_if<bool>(&*provider);
+ if (boolvalue && !*boolvalue)
return nullptr;
}
DocumentRangeFormattingParams params;
diff --git a/src/plugins/languageclient/languageclienthoverhandler.cpp b/src/plugins/languageclient/languageclienthoverhandler.cpp
index e878a62749..429c89e511 100644
--- a/src/plugins/languageclient/languageclienthoverhandler.cpp
+++ b/src/plugins/languageclient/languageclienthoverhandler.cpp
@@ -87,9 +87,8 @@ void HoverHandler::identifyMatch(TextEditor::TextEditorWidget *editorWidget,
const std::optional<std::variant<bool, WorkDoneProgressOptions>> &provider
= m_client->capabilities().hoverProvider();
- bool sendMessage = provider.has_value();
- if (sendMessage && std::holds_alternative<bool>(*provider))
- sendMessage = std::get<bool>(*provider);
+ const bool *boolvalue = provider.has_value() ? std::get_if<bool>(&*provider) : nullptr;
+ bool sendMessage = provider.has_value() && (!boolvalue || *boolvalue);
if (std::optional<bool> registered = m_client->dynamicCapabilities().isRegistered(
HoverRequest::methodName)) {
sendMessage = *registered;
diff --git a/src/plugins/languageclient/languageclientmanager.cpp b/src/plugins/languageclient/languageclientmanager.cpp
index ae3c52f897..8ee2254c74 100644
--- a/src/plugins/languageclient/languageclientmanager.cpp
+++ b/src/plugins/languageclient/languageclientmanager.cpp
@@ -194,7 +194,7 @@ void LanguageClientManager::clientFinished(Client *client)
openDocumentWithClient(document, nullptr);
}
- deleteClient(client);
+ deleteClient(client, unexpectedFinish);
if (isShutdownFinished())
emit managerInstance->shutdownFinished();
}
@@ -234,7 +234,7 @@ void LanguageClientManager::shutdownClient(Client *client)
deleteClient(client);
}
-void LanguageClientManager::deleteClient(Client *client)
+void LanguageClientManager::deleteClient(Client *client, bool unexpected)
{
QTC_ASSERT(managerInstance, return);
QTC_ASSERT(client, return);
@@ -252,7 +252,7 @@ void LanguageClientManager::deleteClient(Client *client)
managerInstance->trackClientDeletion(client);
if (!PluginManager::isShuttingDown())
- emit instance()->clientRemoved(client);
+ emit instance()->clientRemoved(client, unexpected);
}
void LanguageClientManager::shutdown()
diff --git a/src/plugins/languageclient/languageclientmanager.h b/src/plugins/languageclient/languageclientmanager.h
index c288ef453b..12bef4f6fe 100644
--- a/src/plugins/languageclient/languageclientmanager.h
+++ b/src/plugins/languageclient/languageclientmanager.h
@@ -41,7 +41,7 @@ public:
static void restartClient(Client *client);
static void shutdownClient(Client *client);
- static void deleteClient(Client *client);
+ static void deleteClient(Client *client, bool unexpected = false);
static void shutdown();
static bool isShutdownFinished();
@@ -87,7 +87,7 @@ public slots:
signals:
void clientAdded(Client *client);
void clientInitialized(Client *client);
- void clientRemoved(Client *client);
+ void clientRemoved(Client *client, bool unexpected);
void shutdownFinished();
void openCallHierarchy();
diff --git a/src/plugins/languageclient/languageclientoutline.cpp b/src/plugins/languageclient/languageclientoutline.cpp
index 8c45513ce9..30dea87688 100644
--- a/src/plugins/languageclient/languageclientoutline.cpp
+++ b/src/plugins/languageclient/languageclientoutline.cpp
@@ -228,10 +228,10 @@ void LanguageClientOutlineWidget::handleResponse(const DocumentUri &uri,
{
if (uri != m_uri)
return;
- if (std::holds_alternative<QList<SymbolInformation>>(result))
- m_model.setInfo(std::get<QList<SymbolInformation>>(result));
- else if (std::holds_alternative<QList<DocumentSymbol>>(result))
- m_model.setInfo(std::get<QList<DocumentSymbol>>(result));
+ if (const auto i = std::get_if<QList<SymbolInformation>>(&result))
+ m_model.setInfo(*i);
+ else if (const auto s = std::get_if<QList<DocumentSymbol>>(&result))
+ m_model.setInfo(*s);
else
m_model.clear();
@@ -369,10 +369,10 @@ void OutlineComboBox::updateModel(const DocumentUri &resultUri, const DocumentSy
{
if (m_uri != resultUri)
return;
- if (std::holds_alternative<QList<SymbolInformation>>(result))
- m_model.setInfo(std::get<QList<SymbolInformation>>(result));
- else if (std::holds_alternative<QList<DocumentSymbol>>(result))
- m_model.setInfo(std::get<QList<DocumentSymbol>>(result));
+ if (const auto i = std::get_if<QList<SymbolInformation>>(&result))
+ m_model.setInfo(*i);
+ else if (const auto s = std::get_if<QList<DocumentSymbol>>(&result))
+ m_model.setInfo(*s);
else
m_model.clear();
diff --git a/src/plugins/languageclient/languageclientsettings.cpp b/src/plugins/languageclient/languageclientsettings.cpp
index b994da6096..4176918015 100644
--- a/src/plugins/languageclient/languageclientsettings.cpp
+++ b/src/plugins/languageclient/languageclientsettings.cpp
@@ -40,6 +40,7 @@
#include <QDialogButtonBox>
#include <QDir>
#include <QFileInfo>
+#include <QFormLayout>
#include <QGroupBox>
#include <QHeaderView>
#include <QJsonDocument>
@@ -817,8 +818,8 @@ static QString startupBehaviorString(BaseSettings::StartBehavior behavior)
return {};
}
-BaseSettingsWidget::BaseSettingsWidget(
- const BaseSettings *settings, QWidget *parent, Layouting::LayoutItems additionalItems)
+BaseSettingsWidget::BaseSettingsWidget(const BaseSettings *settings, QWidget *parent,
+ Layouting::LayoutModifier additionalItems)
: QWidget(parent)
, m_name(new QLineEdit(settings->m_name, this))
, m_mimeTypes(new QLabel(settings->m_languageFilter.mimeTypes.join(filterSeparator), this))
@@ -878,7 +879,10 @@ BaseSettingsWidget::BaseSettingsWidget(
Tr::tr("Initialization options:"), m_initializationOptions, br
};
- form.addItems(additionalItems);
+
+ if (additionalItems)
+ additionalItems(&form);
+
form.attachTo(this);
// clang-format on
}
diff --git a/src/plugins/languageclient/languageclientsettings.h b/src/plugins/languageclient/languageclientsettings.h
index ad2f01ac74..a6055577bc 100644
--- a/src/plugins/languageclient/languageclientsettings.h
+++ b/src/plugins/languageclient/languageclientsettings.h
@@ -92,9 +92,6 @@ protected:
BaseSettings(BaseSettings &&other) = default;
BaseSettings &operator=(const BaseSettings &other) = default;
BaseSettings &operator=(BaseSettings &&other) = default;
-
-private:
- bool canStart(QList<const Core::IDocument *> documents) const;
};
class LANGUAGECLIENT_EXPORT StdIOSettings : public BaseSettings
@@ -162,7 +159,7 @@ public:
explicit BaseSettingsWidget(
const BaseSettings *settings,
QWidget *parent = nullptr,
- Layouting::LayoutItems additionalItems = {});
+ Layouting::LayoutModifier additionalItems = {});
~BaseSettingsWidget() override = default;
diff --git a/src/plugins/languageclient/languageclientsymbolsupport.cpp b/src/plugins/languageclient/languageclientsymbolsupport.cpp
index 9bc7cd1027..71b41bbcbf 100644
--- a/src/plugins/languageclient/languageclientsymbolsupport.cpp
+++ b/src/plugins/languageclient/languageclientsymbolsupport.cpp
@@ -100,9 +100,8 @@ static MessageId sendTextDocumentPositionParamsRequest(Client *client,
sendMessage = supportedFile;
} else {
const auto provider = std::mem_fn(member)(serverCapability);
- sendMessage = provider.has_value();
- if (sendMessage && std::holds_alternative<bool>(*provider))
- sendMessage = std::get<bool>(*provider);
+ const bool *boolvalue = provider.has_value() ? std::get_if<bool>(&*provider) : nullptr;
+ sendMessage = provider.has_value() && (!boolvalue || *boolvalue);
}
if (sendMessage) {
client->sendMessage(request);
@@ -191,9 +190,8 @@ bool SymbolSupport::supportsFindLink(TextEditor::TextDocument *document, LinkTar
else
supported = m_client->isSupportedUri(uri);
} else {
- supported = provider.has_value();
- if (supported && std::holds_alternative<bool>(*provider))
- supported = std::get<bool>(*provider);
+ const bool *boolvalue = provider.has_value() ? std::get_if<bool>(&*provider) : nullptr;
+ supported = provider.has_value() && (!boolvalue || *boolvalue);
}
return supported;
}
@@ -259,8 +257,8 @@ bool SymbolSupport::supportsFindUsages(TextEditor::TextDocument *document) const
return false;
}
} else if (auto referencesProvider = m_client->capabilities().referencesProvider()) {
- if (std::holds_alternative<bool>(*referencesProvider)) {
- if (!std::get<bool>(*referencesProvider))
+ if (const auto b = std::get_if<bool>(&*referencesProvider)) {
+ if (!*b)
return false;
}
} else {
@@ -447,13 +445,11 @@ static bool supportsRename(Client *client,
}
}
if (auto renameProvider = client->capabilities().renameProvider()) {
- if (std::holds_alternative<bool>(*renameProvider)) {
- if (!std::get<bool>(*renameProvider))
+ if (const auto b = std::get_if<bool>(&*renameProvider)) {
+ if (!*b)
return false;
- } else if (std::holds_alternative<ServerCapabilities::RenameOptions>(*renameProvider)) {
- prepareSupported = std::get<ServerCapabilities::RenameOptions>(*renameProvider)
- .prepareProvider()
- .value_or(false);
+ } else if (const auto opt = std::get_if<ServerCapabilities::RenameOptions>(&*renameProvider)) {
+ prepareSupported = opt->prepareProvider().value_or(false);
}
} else {
return false;
@@ -524,19 +520,17 @@ void SymbolSupport::requestPrepareRename(TextEditor::TextDocument *document,
const std::optional<PrepareRenameResult> &result = response.result();
if (result.has_value()) {
- if (std::holds_alternative<PlaceHolderResult>(*result)) {
- auto placeHolderResult = std::get<PlaceHolderResult>(*result);
- startRenameSymbol(params,
- placeholder.isEmpty() ? placeHolderResult.placeHolder()
- : placeholder,
- oldSymbolName,
- callback,
- preferLowerCaseFileNames);
- } else if (std::holds_alternative<Range>(*result)) {
- auto range = std::get<Range>(*result);
+ if (const auto placeHolderResult = std::get_if<PlaceHolderResult>(&*result)) {
+ startRenameSymbol(
+ params,
+ placeholder.isEmpty() ? placeHolderResult->placeHolder() : placeholder,
+ oldSymbolName,
+ callback,
+ preferLowerCaseFileNames);
+ } else if (const auto range = std::get_if<Range>(&*result)) {
if (document) {
- const int start = range.start().toPositionInDocument(document->document());
- const int end = range.end().toPositionInDocument(document->document());
+ const int start = range->start().toPositionInDocument(document->document());
+ const int end = range->end().toPositionInDocument(document->document());
const QString reportedSymbolName = document->textAt(start, end - start);
startRenameSymbol(params,
derivePlaceholder(reportedSymbolName, placeholder),
@@ -586,28 +580,24 @@ Utils::SearchResultItems generateReplaceItems(const WorkspaceEdit &edits,
const DocumentUri::PathMapper &pathMapper = client->hostPathMapper();
if (!documentChanges.isEmpty()) {
for (const DocumentChange &documentChange : std::as_const(documentChanges)) {
- if (std::holds_alternative<TextDocumentEdit>(documentChange)) {
- const TextDocumentEdit edit = std::get<TextDocumentEdit>(documentChange);
- rangesInDocument[edit.textDocument().uri().toFilePath(pathMapper)] = convertEdits(
- edit.edits());
+ if (const auto edit = std::get_if<TextDocumentEdit>(&documentChange)) {
+ rangesInDocument[edit->textDocument().uri().toFilePath(pathMapper)] = convertEdits(
+ edit->edits());
} else {
Utils::SearchResultItem item;
- if (std::holds_alternative<CreateFileOperation>(documentChange)) {
- auto op = std::get<CreateFileOperation>(documentChange);
- item.setLineText(op.message(pathMapper));
- item.setFilePath(op.uri().toFilePath(pathMapper));
- item.setUserData(QVariant(op));
- } else if (std::holds_alternative<RenameFileOperation>(documentChange)) {
- auto op = std::get<RenameFileOperation>(documentChange);
- item.setLineText(op.message(pathMapper));
- item.setFilePath(op.oldUri().toFilePath(pathMapper));
- item.setUserData(QVariant(op));
- } else if (std::holds_alternative<DeleteFileOperation>(documentChange)) {
- auto op = std::get<DeleteFileOperation>(documentChange);
- item.setLineText(op.message(pathMapper));
- item.setFilePath(op.uri().toFilePath(pathMapper));
- item.setUserData(QVariant(op));
+ if (const auto op = std::get_if<CreateFileOperation>(&documentChange)) {
+ item.setLineText(op->message(pathMapper));
+ item.setFilePath(op->uri().toFilePath(pathMapper));
+ item.setUserData(QVariant(*op));
+ } else if (const auto op = std::get_if<RenameFileOperation>(&documentChange)) {
+ item.setLineText(op->message(pathMapper));
+ item.setFilePath(op->oldUri().toFilePath(pathMapper));
+ item.setUserData(QVariant(*op));
+ } else if (const auto op = std::get_if<DeleteFileOperation>(&documentChange)) {
+ item.setLineText(op->message(pathMapper));
+ item.setFilePath(op->uri().toFilePath(pathMapper));
+ item.setUserData(QVariant(*op));
}
items << item;
diff --git a/src/plugins/languageclient/languageclientutils.cpp b/src/plugins/languageclient/languageclientutils.cpp
index 3df7622548..f7b52fdc9b 100644
--- a/src/plugins/languageclient/languageclientutils.cpp
+++ b/src/plugins/languageclient/languageclientutils.cpp
@@ -97,8 +97,7 @@ bool applyTextEdits(const Client *client,
if (edits.isEmpty())
return true;
const RefactoringFilePtr file = client->createRefactoringFile(filePath);
- file->setChangeSet(editsToChangeSet(edits, file->document()));
- return file->apply();
+ return file->apply(editsToChangeSet(edits, file->document()));
}
void applyTextEdit(TextDocumentManipulatorInterface &manipulator,
@@ -349,13 +348,12 @@ bool applyDocumentChange(const Client *client, const DocumentChange &change)
if (!client)
return false;
- if (std::holds_alternative<TextDocumentEdit>(change)) {
- return applyTextDocumentEdit(client, std::get<TextDocumentEdit>(change));
- } else if (std::holds_alternative<CreateFileOperation>(change)) {
- const auto createOperation = std::get<CreateFileOperation>(change);
- const FilePath filePath = createOperation.uri().toFilePath(client->hostPathMapper());
+ if (const auto e = std::get_if<TextDocumentEdit>(&change)) {
+ return applyTextDocumentEdit(client, *e);
+ } else if (const auto createOperation = std::get_if<CreateFileOperation>(&change)) {
+ const FilePath filePath = createOperation->uri().toFilePath(client->hostPathMapper());
if (filePath.exists()) {
- if (const std::optional<CreateFileOptions> options = createOperation.options()) {
+ if (const std::optional<CreateFileOptions> options = createOperation->options()) {
if (options->overwrite().value_or(false)) {
if (!filePath.removeFile())
return false;
@@ -365,16 +363,15 @@ bool applyDocumentChange(const Client *client, const DocumentChange &change)
}
}
return filePath.ensureExistingFile();
- } else if (std::holds_alternative<RenameFileOperation>(change)) {
- const RenameFileOperation renameOperation = std::get<RenameFileOperation>(change);
- const FilePath oldPath = renameOperation.oldUri().toFilePath(client->hostPathMapper());
+ } else if (const auto renameOperation = std::get_if<RenameFileOperation>(&change)) {
+ const FilePath oldPath = renameOperation->oldUri().toFilePath(client->hostPathMapper());
if (!oldPath.exists())
return false;
- const FilePath newPath = renameOperation.newUri().toFilePath(client->hostPathMapper());
+ const FilePath newPath = renameOperation->newUri().toFilePath(client->hostPathMapper());
if (oldPath == newPath)
return true;
if (newPath.exists()) {
- if (const std::optional<CreateFileOptions> options = renameOperation.options()) {
+ if (const std::optional<CreateFileOptions> options = renameOperation->options()) {
if (options->overwrite().value_or(false)) {
if (!newPath.removeFile())
return false;
@@ -384,10 +381,9 @@ bool applyDocumentChange(const Client *client, const DocumentChange &change)
}
}
return oldPath.renameFile(newPath);
- } else if (std::holds_alternative<DeleteFileOperation>(change)) {
- const auto deleteOperation = std::get<DeleteFileOperation>(change);
- const FilePath filePath = deleteOperation.uri().toFilePath(client->hostPathMapper());
- if (const std::optional<DeleteFileOptions> options = deleteOperation.options()) {
+ } else if (const auto deleteOperation = std::get_if<DeleteFileOperation>(&change)) {
+ const FilePath filePath = deleteOperation->uri().toFilePath(client->hostPathMapper());
+ if (const std::optional<DeleteFileOptions> options = deleteOperation->options()) {
if (!filePath.exists())
return options->ignoreIfNotExists().value_or(false);
if (filePath.isDir() && options->recursive().value_or(false))
diff --git a/src/plugins/languageclient/locatorfilter.cpp b/src/plugins/languageclient/locatorfilter.cpp
index ccebcd7679..4fb8cea19a 100644
--- a/src/plugins/languageclient/locatorfilter.cpp
+++ b/src/plugins/languageclient/locatorfilter.cpp
@@ -8,8 +8,6 @@
#include "languageclientmanager.h"
#include "languageclienttr.h"
-#include <extensionsystem/pluginmanager.h>
-
#include <utils/async.h>
#include <utils/fuzzymatcher.h>
@@ -70,7 +68,6 @@ LocatorMatcherTask locatorMatcher(Client *client, int maxResultCount,
const QList<SymbolInformation> results = *resultStorage;
if (results.isEmpty())
return SetupResult::StopWithSuccess;
- async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
async.setConcurrentCallData(filterResults, *storage, client, results, filter);
return SetupResult::Continue;
};
@@ -130,7 +127,6 @@ LocatorMatcherTask currentDocumentMatcher()
};
const auto onFilterSetup = [storage, resultStorage](Async<void> &async) {
- async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
async.setConcurrentCallData(filterCurrentResults, *storage, *resultStorage);
};
diff --git a/src/plugins/languageclient/lualanguageclient/LuaLanguageClient.json.in b/src/plugins/languageclient/lualanguageclient/LuaLanguageClient.json.in
index a74e87f34c..9497e53668 100644
--- a/src/plugins/languageclient/lualanguageclient/LuaLanguageClient.json.in
+++ b/src/plugins/languageclient/lualanguageclient/LuaLanguageClient.json.in
@@ -16,6 +16,6 @@
],
"Category" : "Scripting",
"Description" : "Lua Language Client scripting support",
- "Url" : "http://www.qt.io",
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/languageclient/lualanguageclient/lualanguageclient.cpp b/src/plugins/languageclient/lualanguageclient/lualanguageclient.cpp
index bcd9ade763..44fd187a72 100644
--- a/src/plugins/languageclient/lualanguageclient/lualanguageclient.cpp
+++ b/src/plugins/languageclient/lualanguageclient/lualanguageclient.cpp
@@ -1,9 +1,12 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#include <coreplugin/editormanager/editormanager.h>
+
#include <languageclient/languageclientinterface.h>
#include <languageclient/languageclientmanager.h>
#include <languageclient/languageclientsettings.h>
+#include <languageclient/languageclienttr.h>
#include <lua/bindings/inheritance.h>
#include <lua/luaengine.h>
@@ -27,6 +30,19 @@ namespace LanguageClient::Lua {
static void registerLuaApi();
+class LuaClient : public LanguageClient::Client
+{
+ Q_OBJECT
+
+public:
+ Utils::Id m_settingsId;
+
+ LuaClient(BaseClientInterface *interface, Utils::Id settingsId)
+ : LanguageClient::Client(interface)
+ , m_settingsId(settingsId)
+ {}
+};
+
class LuaLanguageClientPlugin final : public ExtensionSystem::IPlugin
{
Q_OBJECT
@@ -135,6 +151,8 @@ public:
BaseSettings *copy() const override { return new LuaClientSettings(*this); }
protected:
+ Client *createClient(BaseClientInterface *interface) const final;
+
BaseClientInterface *createInterface(ProjectExplorer::Project *project) const override;
};
enum class TransportType { StdIO, LocalSocket };
@@ -154,6 +172,7 @@ public:
BaseSettings::StartBehavior m_startBehavior = BaseSettings::RequiresFile;
std::optional<sol::protected_function> m_onInstanceStart;
+ std::optional<sol::protected_function> m_startFailedCallback;
QMap<QString, sol::protected_function> m_messageCallbacks;
QList<Client *> m_clients;
@@ -190,6 +209,8 @@ public:
m_startBehavior = startBehaviorFromString(
options.get_or<QString>("startBehavior", "AlwaysOn"));
+ m_startFailedCallback = options.get<sol::protected_function>("onStartFailed");
+
QString transportType = options.get_or<QString>("transport", "stdio");
if (transportType == "stdio")
m_transportType = TransportType::StdIO;
@@ -231,35 +252,46 @@ public:
// get<sol::optional<>> because on MSVC, get_or(..., nullptr) fails to compile
m_aspects = options.get<sol::optional<AspectContainer *>>("settings").value_or(nullptr);
+ if (m_aspects) {
+ connect(m_aspects, &AspectContainer::applied, this, [this] {
+ updateOptions();
+ LanguageClientManager::applySettings();
+ });
+ }
+
connect(
LanguageClientManager::instance(),
&LanguageClientManager::clientInitialized,
this,
[this](Client *c) {
- if (m_onInstanceStart) {
- if (auto settings = LanguageClientManager::settingForClient(c)) {
- if (settings->m_settingsTypeId == m_settingsTypeId) {
- auto result = m_onInstanceStart->call();
-
- if (!result.valid()) {
- qWarning() << "Error calling instance start callback:"
- << (result.get<sol::error>().what());
- }
-
- m_clients.push_back(c);
- updateMessageCallbacks();
- }
- }
+ auto luaClient = qobject_cast<LuaClient *>(c);
+ if (luaClient && luaClient->m_settingsId == m_settingsTypeId && m_onInstanceStart) {
+ QTC_CHECK(::Lua::LuaEngine::void_safe_call(*m_onInstanceStart, c));
+
+ m_clients.push_back(c);
+ updateMessageCallbacks();
}
});
+
connect(
LanguageClientManager::instance(),
&LanguageClientManager::clientRemoved,
this,
- [this](Client *c) {
- if (m_clients.contains(c))
- m_clients.removeOne(c);
- });
+ &LuaClientWrapper::onClientRemoved);
+ }
+
+ void onClientRemoved(Client *c, bool unexpected)
+ {
+ auto luaClient = qobject_cast<LuaClient *>(c);
+ if (!luaClient || luaClient->m_settingsId != m_settingsTypeId)
+ return;
+
+ if (m_clients.contains(c))
+ m_clients.removeOne(c);
+
+ if (unexpected && m_startFailedCallback) {
+ QTC_CHECK_EXPECTED(::Lua::LuaEngine::void_safe_call(*m_startFailedCallback));
+ }
}
// TODO: Unregister Client settings from LanguageClientManager
@@ -288,10 +320,10 @@ public:
m_aspects->toMap(map);
}
- std::optional<Layouting::LayoutItem> settingsLayout()
+ Layouting::LayoutModifier settingsLayout()
{
- if (m_aspects && m_aspects->layouter())
- return m_aspects->layouter()();
+ if (m_aspects)
+ return [this](Layouting::Layout *iface) { m_aspects->addToLayout(*iface); };
return {};
}
@@ -451,12 +483,17 @@ QWidget *LuaClientSettings::createSettingsWidget(QWidget *parent) const
using namespace Layouting;
if (auto w = m_wrapper.lock())
- if (std::optional<LayoutItem> layout = w->settingsLayout())
- return new BaseSettingsWidget(this, parent, layout->subItems);
+ return new BaseSettingsWidget(this, parent, w->settingsLayout());
return new BaseSettingsWidget(this, parent);
}
+Client *LuaClientSettings::createClient(BaseClientInterface *interface) const
+{
+ Client *client = new LuaClient(interface, m_settingsTypeId);
+ return client;
+}
+
BaseClientInterface *LuaClientSettings::createInterface(ProjectExplorer::Project *project) const
{
if (auto w = m_wrapper.lock())
diff --git a/src/plugins/languageclient/progressmanager.cpp b/src/plugins/languageclient/progressmanager.cpp
index 6b82d289df..4ef7d0b60d 100644
--- a/src/plugins/languageclient/progressmanager.cpp
+++ b/src/plugins/languageclient/progressmanager.cpp
@@ -69,10 +69,10 @@ bool ProgressManager::isProgressEndMessage(const LanguageServerProtocol::Progres
Utils::Id languageClientProgressId(const ProgressToken &token)
{
constexpr char k_LanguageClientProgressId[] = "LanguageClient.ProgressId.";
- auto toString = [](const ProgressToken &token){
- if (std::holds_alternative<int>(token))
- return QString::number(std::get<int>(token));
- return std::get<QString>(token);
+ auto toString = [](const ProgressToken &token) {
+ if (const auto i = std::get_if<int>(&token))
+ return QString::number(*i);
+ return *std::get_if<QString>(&token);
};
return Utils::Id(k_LanguageClientProgressId).withSuffix(toString(token));
}
diff --git a/src/plugins/languageclient/snippet.cpp b/src/plugins/languageclient/snippet.cpp
index 7a0fd21131..b401123ef8 100644
--- a/src/plugins/languageclient/snippet.cpp
+++ b/src/plugins/languageclient/snippet.cpp
@@ -251,11 +251,11 @@ void SnippetParsingTest::testSnippetParsing()
QFETCH(Parts, parts);
SnippetParseResult result = LanguageClient::parseSnippet(input);
- QCOMPARE(std::holds_alternative<ParsedSnippet>(result), success);
+ const auto snippet = std::get_if<ParsedSnippet>(&result);
+ QCOMPARE(snippet != nullptr, success);
if (!success)
return;
- ParsedSnippet snippet = std::get<ParsedSnippet>(result);
auto rangesCompare = [&](const ParsedSnippet::Part &actual, const SnippetPart &expected) {
QCOMPARE(actual.text, expected.text);
@@ -264,10 +264,10 @@ void SnippetParsingTest::testSnippetParsing()
QCOMPARE(manglerId, expected.manglerId);
};
- QCOMPARE(snippet.parts.count(), parts.count());
+ QCOMPARE(snippet->parts.count(), parts.count());
for (int i = 0; i < parts.count(); ++i)
- rangesCompare(snippet.parts.at(i), parts.at(i));
+ rangesCompare(snippet->parts.at(i), parts.at(i));
}
QObject *createSnippetParsingTest()
diff --git a/src/plugins/lua/CMakeLists.txt b/src/plugins/lua/CMakeLists.txt
index 9663ef5081..c7a62e3a8b 100644
--- a/src/plugins/lua/CMakeLists.txt
+++ b/src/plugins/lua/CMakeLists.txt
@@ -8,9 +8,10 @@ add_qtc_plugin(Lua
bindings/async.cpp
bindings/core.cpp
bindings/fetch.cpp
+ bindings/gui.cpp
bindings/hook.cpp
bindings/inheritance.h
- bindings/layout.cpp
+ bindings/install.cpp
bindings/messagemanager.cpp
bindings/qtcprocess.cpp
bindings/settings.cpp
@@ -23,9 +24,30 @@ add_qtc_plugin(Lua
luaqttypes.cpp
luaqttypes.h
luatr.h
+ meta/action.lua
+ meta/async.lua
+ meta/core.lua
+ meta/fetch.lua
+ meta/gui.lua
+ meta/install.lua
+ meta/lsp.lua
+ meta/messagemanager.lua
+ meta/process.lua
+ meta/qt.lua
+ meta/qtc.lua
+ meta/settings.lua
+ meta/simpletypes.lua
+ meta/utils.lua
+ meta/widgets.lua
# generateqtbindings.cpp # Use this if you need to generate some code.
)
+qt_add_resources(Lua lua_images_rcc
+ PREFIX "/lua"
+ FILES
+ images/settingscategory_lua.png
+ images/settingscategory_lua@2x.png
+)
set_source_files_properties(luauibindings.cpp PROPERTY SKIP_AUTOMOC ON PROPERTY SKIP_AUTOGEN ON)
@@ -33,3 +55,30 @@ if (MSVC)
# Prevent fatal error C1128
set_property(SOURCE bindings/settings.cpp PROPERTY COMPILE_FLAGS /bigobj)
endif()
+
+set(META_FILES
+ meta/action.lua
+ meta/async.lua
+ meta/core.lua
+ meta/fetch.lua
+ meta/gui.lua
+ meta/lsp.lua
+ meta/messagemanager.lua
+ meta/process.lua
+ meta/qt.lua
+ meta/qtc.lua
+ meta/settings.lua
+ meta/simpletypes.lua
+ meta/utils.lua
+ meta/widgets.lua
+)
+
+qtc_copy_to_builddir(copy_lua_meta_files
+ DESTINATION ${IDE_DATA_PATH}/lua
+ FILES ${META_FILES}
+)
+
+install(
+ FILES ${META_FILES}
+ DESTINATION ${IDE_DATA_PATH}/lua/meta
+)
diff --git a/src/plugins/lua/Lua.json.in b/src/plugins/lua/Lua.json.in
index 7007945934..68b54fd1ad 100644
--- a/src/plugins/lua/Lua.json.in
+++ b/src/plugins/lua/Lua.json.in
@@ -16,6 +16,14 @@
],
"Category" : "Scripting",
"Description" : "Support for Lua based plugins.",
- "Url" : "http://www.qt.io",
+ "Url" : "https://www.qt.io",
+ "JsonWizardPaths" : [":/lua/wizards"],
+ "Arguments" : [
+ {
+ "Name" : "-loadluaplugin",
+ "Parameter" : "path",
+ "Description" : "Load an additional lua plugin from the specified path"
+ }
+ ],
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/lua/bindings/async.cpp b/src/plugins/lua/bindings/async.cpp
index 31b3cbcd36..8d7d4f4750 100644
--- a/src/plugins/lua/bindings/async.cpp
+++ b/src/plugins/lua/bindings/async.cpp
@@ -69,10 +69,14 @@ local join = function(thunks)
end
-- sugar over coroutine
local await = function(defer)
+ local _, isMain = coroutine.running()
+ assert(not isMain, "a.wait was called outside of a running coroutine. You need to start one using a.sync(my_function)() first")
assert(type(defer) == "function", "type error :: expected func :: was: " .. type(defer))
return co.yield(defer)
end
local await_all = function(defer)
+ local _, isMain = coroutine.running()
+ assert(not isMain, "a.wait_all was called outside of a running coroutine. You need to start one using a.sync(my_function)() first")
assert(type(defer) == "table", "type error :: expected table")
return co.yield(join(defer))
end
diff --git a/src/plugins/lua/bindings/fetch.cpp b/src/plugins/lua/bindings/fetch.cpp
index 0790ed8e29..22d1bc64cb 100644
--- a/src/plugins/lua/bindings/fetch.cpp
+++ b/src/plugins/lua/bindings/fetch.cpp
@@ -3,11 +3,23 @@
#include "../luaengine.h"
#include "../luaqttypes.h"
+#include "../luatr.h"
+#include <coreplugin/dialogs/ioptionspage.h>
+#include <coreplugin/icore.h>
+
+#include <utils/aspects.h>
+#include <utils/infobar.h>
+#include <utils/layoutbuilder.h>
#include <utils/networkaccessmanager.h>
+#include <utils/stylehelper.h>
+#include <QApplication>
+#include <QCheckBox>
#include <QJsonArray>
#include <QJsonDocument>
+#include <QMessageBox>
+#include <QMetaEnum>
#include <QNetworkAccessManager>
#include <QNetworkReply>
@@ -36,32 +48,205 @@ static QString opToString(QNetworkAccessManager::Operation op)
void addFetchModule()
{
- LuaEngine::registerProvider(
- "Fetch", [](sol::state_view lua) -> sol::object {
- sol::table async = lua.script("return require('async')", "_fetch_").get<sol::table>();
- sol::function wrap = async["wrap"];
-
- sol::table fetch = lua.create_table();
-
- auto networkReplyType = lua.new_usertype<QNetworkReply>(
- "QNetworkReply",
- "error",
- sol::property([](QNetworkReply *self) -> int { return self->error(); }),
- "readAll",
- [](QNetworkReply *r) { return r->readAll().toStdString(); },
- "__tostring",
- [](QNetworkReply *r) {
- return QString("QNetworkReply(%1 \"%2\") => %3")
- .arg(opToString(r->operation()))
- .arg(r->url().toString())
- .arg(r->error());
- });
-
- fetch["fetch_cb"] = [](const sol::table &options,
- const sol::function &callback,
- const sol::this_state &thisState) {
- auto url = options.get<QString>("url");
+ class Module : Utils::AspectContainer
+ {
+ Utils::StringListAspect pluginsAllowedToFetch{this};
+ Utils::StringListAspect pluginsNotAllowedToFetch{this};
+
+ class LuaOptionsPage : public Core::IOptionsPage
+ {
+ public:
+ LuaOptionsPage(Module *module)
+ {
+ setId("BB.Lua.Fetch");
+ setDisplayName(Tr::tr("Network access"));
+ setCategory("ZY.Lua");
+ setDisplayCategory("Lua");
+ setCategoryIconPath(":/lua/images/settingscategory_lua.png");
+ setSettingsProvider(
+ [module] { return static_cast<Utils::AspectContainer *>(module); });
+ }
+ };
+
+ LuaOptionsPage settingsPage{this};
+
+ public:
+ Module()
+ {
+ setSettingsGroup("Lua.Fetch");
+
+ pluginsAllowedToFetch.setSettingsKey("pluginsAllowedToFetch");
+ pluginsAllowedToFetch.setLabelText("Plugins allowed to fetch data from the internet");
+ pluginsAllowedToFetch.setToolTip(
+ "List of plugins that are allowed to fetch data from the internet");
+ pluginsAllowedToFetch.setUiAllowAdding(false);
+ pluginsAllowedToFetch.setUiAllowEditing(false);
+
+ pluginsNotAllowedToFetch.setSettingsKey("pluginsNotAllowedToFetch");
+ pluginsNotAllowedToFetch.setLabelText(
+ "Plugins not allowed to fetch data from the internet");
+ pluginsNotAllowedToFetch.setToolTip(
+ "List of plugins that are not allowed to fetch data from the internet");
+ pluginsNotAllowedToFetch.setUiAllowAdding(false);
+ pluginsNotAllowedToFetch.setUiAllowEditing(false);
+
+ setLayouter([this] {
+ using namespace Layouting;
+ // clang-format off
+ return Form {
+ pluginsAllowedToFetch, br,
+ pluginsNotAllowedToFetch, br,
+ };
+ // clang-format on
+ });
+
+ readSettings();
+ }
+
+ ~Module() { writeSettings(); }
+
+ enum class IsAllowed { Yes, No, NeedsToAsk };
+
+ IsAllowed isAllowedToFetch(const QString &pluginName) const
+ {
+ if (pluginsAllowedToFetch().contains(pluginName))
+ return IsAllowed::Yes;
+ if (pluginsNotAllowedToFetch().contains(pluginName))
+ return IsAllowed::No;
+ return IsAllowed::NeedsToAsk;
+ }
+
+ void setAllowedToFetch(const QString &pluginName, IsAllowed allowed)
+ {
+ if (allowed == IsAllowed::Yes)
+ pluginsAllowedToFetch.appendValue(pluginName);
+ else if (allowed == IsAllowed::No)
+ pluginsNotAllowedToFetch.appendValue(pluginName);
+
+ if (allowed == IsAllowed::Yes)
+ pluginsNotAllowedToFetch.removeValue(pluginName);
+ else if (allowed == IsAllowed::No)
+ pluginsAllowedToFetch.removeValue(pluginName);
+ }
+ };
+
+ std::shared_ptr<Module> module = std::make_shared<Module>();
+
+ LuaEngine::registerProvider("Fetch", [mod = std::move(module)](sol::state_view lua) -> sol::object {
+ const ScriptPluginSpec *pluginSpec = lua.get<ScriptPluginSpec *>("PluginSpec");
+ sol::table async = lua.script("return require('async')", "_fetch_").get<sol::table>();
+ sol::function wrap = async["wrap"];
+
+ sol::table fetch = lua.create_table();
+
+ auto networkReplyType = lua.new_usertype<QNetworkReply>(
+ "QNetworkReply",
+ "error",
+ sol::property([](QNetworkReply *self) -> int { return self->error(); }),
+ "readAll",
+ [](QNetworkReply *r) { return r->readAll().toStdString(); },
+ "__tostring",
+ [](QNetworkReply *r) {
+ return QString("QNetworkReply(%1 \"%2\") => %3")
+ .arg(opToString(r->operation()))
+ .arg(r->url().toString())
+ .arg(r->error());
+ });
+
+ auto checkPermission = [mod,
+ pluginName = pluginSpec->name,
+ guard = pluginSpec->connectionGuard.get()](
+ QString url,
+ std::function<void()> fetch,
+ std::function<void()> notAllowed) {
+ auto isAllowed = mod->isAllowedToFetch(pluginName);
+ if (isAllowed == Module::IsAllowed::Yes) {
+ fetch();
+ return;
+ }
+
+ if (isAllowed == Module::IsAllowed::No) {
+ notAllowed();
+ return;
+ }
+
+ if (QApplication::activeModalWidget()) {
+ // We are already showing a modal dialog,
+ // so we have to use a QMessageBox instead of the info bar
+ auto msgBox = new QMessageBox(
+ QMessageBox::Question,
+ Tr::tr("Allow Internet access"),
+ Tr::tr("The plugin \"%1\" would like to fetch from the following url:\n%2")
+ .arg(pluginName)
+ .arg(url),
+ QMessageBox::Yes | QMessageBox::No,
+ Core::ICore::dialogParent());
+ msgBox->setCheckBox(new QCheckBox(Tr::tr("Remember choice")));
+
+ QObject::connect(
+ msgBox, &QMessageBox::accepted, guard, [mod, fetch, pluginName, msgBox]() {
+ if (msgBox->checkBox()->isChecked())
+ mod->setAllowedToFetch(pluginName, Module::IsAllowed::Yes);
+ fetch();
+ });
+
+ QObject::connect(
+ msgBox, &QMessageBox::rejected, guard, [mod, notAllowed, pluginName, msgBox]() {
+ if (msgBox->checkBox()->isChecked())
+ mod->setAllowedToFetch(pluginName, Module::IsAllowed::No);
+ notAllowed();
+ });
+
+ msgBox->show();
+
+ return;
+ }
+
+ Utils::InfoBarEntry entry{
+ Utils::Id::fromString("Fetch" + pluginName),
+ Tr::tr("The plugin \"%1\" would like to fetch data from the internet. Do "
+ "you want to allow this?")
+ .arg(pluginName)};
+ entry.setDetailsWidgetCreator([pluginName, url] {
+ const QString markdown = Tr::tr("The plugin \"**%1**\" would like to fetch "
+ "from the following url:\n\n")
+ .arg(pluginName)
+ + QString("* [%3](%3)").arg(url);
+
+ QLabel *list = new QLabel();
+ list->setTextFormat(Qt::TextFormat::MarkdownText);
+ list->setText(markdown);
+ list->setMargin(Utils::StyleHelper::SpacingTokens::ExPaddingGapS);
+ return list;
+ });
+ entry.addCustomButton(Tr::tr("Always Allow"), [mod, pluginName, fetch]() {
+ mod->setAllowedToFetch(pluginName, Module::IsAllowed::Yes);
+ Core::ICore::infoBar()->removeInfo(Utils::Id::fromString("Fetch" + pluginName));
+ fetch();
+ });
+ entry.addCustomButton(Tr::tr("Allow once"), [pluginName, fetch]() {
+ Core::ICore::infoBar()->removeInfo(Utils::Id::fromString("Fetch" + pluginName));
+ fetch();
+ });
+
+ entry.setCancelButtonInfo(Tr::tr("Deny"), [mod, notAllowed, pluginName]() {
+ Core::ICore::infoBar()->removeInfo(Utils::Id::fromString("Fetch" + pluginName));
+ mod->setAllowedToFetch(pluginName, Module::IsAllowed::No);
+ notAllowed();
+ });
+ Core::ICore::infoBar()->addInfo(entry);
+ };
+
+ fetch["fetch_cb"] = [checkPermission,
+ pluginName = pluginSpec->name,
+ guard = pluginSpec->connectionGuard.get(),
+ mod](
+ const sol::table &options,
+ const sol::function &callback,
+ const sol::this_state &thisState) {
+ auto url = options.get<QString>("url");
+ auto actualFetch = [guard, url, options, callback, thisState]() {
auto method = (options.get_or<QString>("method", "GET")).toLower();
auto headers = options.get_or<sol::table>("headers", {});
auto data = options.get_or<QString>("body", {});
@@ -84,15 +269,16 @@ void addFetchModule()
if (convertToTable) {
QObject::connect(
- reply,
- &QNetworkReply::finished,
- &LuaEngine::instance(),
- [reply, thisState, callback]() {
+ reply, &QNetworkReply::finished, guard, [reply, thisState, callback]() {
reply->deleteLater();
if (reply->error() != QNetworkReply::NoError) {
- callback(
- QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
+ callback(QString("%1 (%2):\n%3")
+ .arg(reply->errorString())
+ .arg(QString::fromLatin1(
+ QMetaEnum::fromType<QNetworkReply::NetworkError>()
+ .valueToKey(reply->error())))
+ .arg(QString::fromUtf8(reply->readAll())));
return;
}
@@ -115,7 +301,7 @@ void addFetchModule()
} else {
QObject::connect(
- reply, &QNetworkReply::finished, &LuaEngine::instance(), [reply, callback]() {
+ reply, &QNetworkReply::finished, guard, [reply, callback]() {
// We don't want the network reply to be deleted by the manager, but
// by the Lua GC
reply->setParent(nullptr);
@@ -124,10 +310,17 @@ void addFetchModule()
}
};
- fetch["fetch"] = wrap(fetch["fetch_cb"]);
+ checkPermission(url, actualFetch, [callback, pluginName]() {
+ callback(Tr::tr("Fetching is not allowed for the plugin \"%1\" (You can edit "
+ "permissions in Preferences => Lua)")
+ .arg(pluginName));
+ });
+ };
+
+ fetch["fetch"] = wrap(fetch["fetch_cb"]);
- return fetch;
- });
+ return fetch;
+ });
}
} // namespace Lua::Internal
diff --git a/src/plugins/lua/bindings/gui.cpp b/src/plugins/lua/bindings/gui.cpp
new file mode 100644
index 0000000000..aaba86f461
--- /dev/null
+++ b/src/plugins/lua/bindings/gui.cpp
@@ -0,0 +1,392 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "../luaengine.h"
+
+#include "inheritance.h"
+
+#include <utils/aspects.h>
+#include <utils/layoutbuilder.h>
+
+using namespace Layouting;
+using namespace Utils;
+
+namespace Lua::Internal {
+
+template<class T>
+static void processChildren(T *item, const sol::table &children)
+{
+ for (size_t i = 1; i <= children.size(); ++i) {
+ const auto &child = children[i];
+ if (child.is<Layout *>()) {
+ if (Layout *layout = child.get<Layout *>())
+ item->addItem(*layout);
+ else
+ item->addItem("ERROR");
+ } else if (child.is<Widget *>()) {
+ if (Widget *widget = child.get<Widget *>())
+ item->addItem(*widget);
+ else
+ item->addItem("ERROR");
+ } else if (child.is<BaseAspect>()) {
+ child.get<BaseAspect *>()->addToLayout(*item);
+ } else if (child.is<QString>()) {
+ item->addItem(child.get<QString>());
+ } else if (child.is<sol::function>()) {
+ const sol::function f = child.get<sol::function>();
+ auto res = LuaEngine::void_safe_call(f, item);
+ QTC_ASSERT_EXPECTED(res, continue);
+ } else if (child.is<Span>()) {
+ const Span &span = child.get<Span>();
+ item->addItem(span);
+ } else if (child.is<Space>()) {
+ const Space &space = child.get<Space>();
+ item->addItem(space);
+ } else if (child.is<Stretch>()) {
+ const Stretch &stretch = child.get<Stretch>();
+ item->addItem(stretch);
+ } else {
+ qWarning() << "Incompatible object added to layout item: " << (int) child.get_type()
+ << " (expected LayoutItem, Aspect or function returning LayoutItem)";
+ }
+ }
+}
+
+template<class T>
+static std::unique_ptr<T> construct(const sol::table &children)
+{
+ std::unique_ptr<T> item(new T({}));
+ processChildren(item.get(), children);
+ return item;
+}
+
+template<class T>
+void constructWidget(std::unique_ptr<T> &widget, const sol::table &children)
+{
+ widget->setWindowTitle(children.get_or<QString>("windowTitle", ""));
+ widget->setToolTip(children.get_or<QString>("toolTip", ""));
+
+ for (size_t i = 1; i <= children.size(); ++i) {
+ const auto &child = children[i];
+ if (child.is<Layout>())
+ widget->setLayout(*child.get<Layout *>());
+ }
+}
+
+#define HAS_MEM_FUNC(func, name) \
+ template<typename T, typename Sign> \
+ struct name \
+ { \
+ typedef char yes[1]; \
+ typedef char no[2]; \
+ template<typename U, U> \
+ struct type_check; \
+ template<typename _1> \
+ static yes &chk(type_check<Sign, &_1::func> *); \
+ template<typename> \
+ static no &chk(...); \
+ static bool const value = sizeof(chk<T>(0)) == sizeof(yes); \
+ }
+
+HAS_MEM_FUNC(onTextChanged, hasOnTextChanged);
+HAS_MEM_FUNC(onClicked, hasOnClicked);
+HAS_MEM_FUNC(setText, hasSetText);
+HAS_MEM_FUNC(setTitle, hasSetTitle);
+HAS_MEM_FUNC(setValue, hasSetValue);
+
+template<class T>
+void setProperties(std::unique_ptr<T> &item, const sol::table &children, QObject *guard)
+{
+ if constexpr (hasOnTextChanged<T, void (T::*)(const QString &)>::value) {
+ sol::optional<sol::protected_function> onTextChanged
+ = children.get<sol::optional<sol::protected_function>>("onTextChanged");
+ if (onTextChanged) {
+ item->onTextChanged(
+ [f = *onTextChanged](const QString &text) {
+ auto res = LuaEngine::void_safe_call(f, text);
+ QTC_CHECK_EXPECTED(res);
+ },
+ guard);
+ }
+ }
+ if constexpr (hasOnClicked<T, void (T::*)(const std::function<void()> &, QObject *guard)>::value) {
+ sol::optional<sol::protected_function> onClicked
+ = children.get<sol::optional<sol::protected_function>>("onClicked");
+ if (onClicked) {
+ item->onClicked(
+ [f = *onClicked]() {
+ auto res = LuaEngine::void_safe_call(f);
+ QTC_CHECK_EXPECTED(res);
+ },
+ guard);
+ }
+ }
+ if constexpr (hasSetText<T, void (T::*)(const QString &)>::value) {
+ item->setText(children.get_or<QString>("text", ""));
+ }
+ if constexpr (hasSetTitle<T, void (T::*)(const QString &)>::value) {
+ item->setTitle(children.get_or<QString>("title", ""));
+ }
+ if constexpr (hasSetValue<T, void (T::*)(int)>::value) {
+ sol::optional<int> value = children.get<sol::optional<int>>("value");
+ if (value)
+ item->setValue(*value);
+ }
+}
+
+template<class T>
+std::unique_ptr<T> constructWidgetType(const sol::table &children, QObject *guard)
+{
+ std::unique_ptr<T> item(new T({}));
+ constructWidget(item, children);
+ setProperties(item, children, guard);
+ return item;
+}
+
+std::unique_ptr<Tab> constructTabFromTable(const sol::table &children)
+{
+ if (children.size() != 2)
+ throw sol::error("Tab must have exactly two children");
+
+ auto tabName = children[1];
+ if (tabName.get_type() != sol::type::string)
+ throw sol::error("Tab name (first argument) must be a string");
+
+ const auto &layout = children[2];
+ if (!layout.is<Layout *>())
+ throw sol::error("Tab child (second argument) must be a Layout");
+
+ std::unique_ptr<Tab> item = std::make_unique<Tab>(tabName, *layout.get<Layout *>());
+ return item;
+}
+
+std::unique_ptr<Tab> constructTab(const QString &tabName, const Layout &layout)
+{
+ std::unique_ptr<Tab> item = std::make_unique<Tab>(tabName, layout);
+ return item;
+}
+
+std::unique_ptr<Span> constructSpanFromTable(const sol::table &children)
+{
+ if (children.size() != 2)
+ throw sol::error("Span must have exactly two children");
+
+ auto spanSize = children[1];
+ if (spanSize.get_type() != sol::type::number)
+ throw sol::error("Span size (first argument) must be a number");
+
+ const auto &layout = children[2];
+ if (!layout.is<Layout *>())
+ throw sol::error("Span child (second argument) must be a Layout");
+
+ std::unique_ptr<Span> item = std::make_unique<Span>(spanSize, *layout.get<Layout *>());
+ return item;
+}
+
+std::unique_ptr<Span> constructSpan(int n, const Layout &layout)
+{
+ std::unique_ptr<Span> item = std::make_unique<Span>(n, layout);
+ return item;
+}
+
+std::unique_ptr<TabWidget> constructTabWidget(const sol::table &children, QObject *guard)
+{
+ std::unique_ptr<TabWidget> item(new TabWidget({}));
+ setProperties(item, children, guard);
+ for (size_t i = 1; i <= children.size(); ++i) {
+ const auto &child = children[i];
+ if (child.is<Tab *>())
+ addToTabWidget(item.get(), *child.get<Tab *>());
+ }
+ return item;
+}
+
+std::unique_ptr<Splitter> constructSplitter(const sol::table &children)
+{
+ std::unique_ptr<Splitter> item(new Splitter({}));
+ constructWidget(item, children);
+
+ for (size_t i = 1; i <= children.size(); ++i) {
+ const auto &child = children[i];
+ if (child.is<Layout *>()) {
+ addToSplitter(item.get(), *child.get<Layout *>());
+ } else if (child.is<Widget *>()) {
+ addToSplitter(item.get(), *child.get<Widget *>());
+ } else {
+ qWarning() << "Incompatible object added to Splitter: " << (int) child.get_type()
+ << " (expected Layout or Widget)";
+ }
+ }
+ return item;
+}
+
+void addGuiModule()
+{
+ LuaEngine::registerProvider("Gui", [](sol::state_view l) -> sol::object {
+ const ScriptPluginSpec *pluginSpec = l.get<ScriptPluginSpec *>("PluginSpec");
+ QObject *guard = pluginSpec->connectionGuard.get();
+
+ sol::table gui = l.create_table();
+
+ gui.new_usertype<Span>(
+ "Span", sol::call_constructor, sol::factories(&constructSpan, &constructSpanFromTable));
+
+ gui.new_usertype<Space>("Space", sol::call_constructor, sol::constructors<Space(int)>());
+
+ gui.new_usertype<Stretch>(
+ "Stretch", sol::call_constructor, sol::constructors<Stretch(int)>());
+
+ // Layouts
+ gui.new_usertype<Layout>(
+ "Layout",
+ sol::call_constructor,
+ sol::factories(&construct<Layout>),
+ "show",
+ &Layout::show,
+ sol::base_classes,
+ sol::bases<Object, Thing>());
+
+ gui.new_usertype<Form>(
+ "Form",
+ sol::call_constructor,
+ sol::factories(&construct<Form>),
+ sol::base_classes,
+ sol::bases<Layout, Object, Thing>());
+
+ gui.new_usertype<Column>(
+ "Column",
+ sol::call_constructor,
+ sol::factories(&construct<Column>),
+ sol::base_classes,
+ sol::bases<Layout, Object, Thing>());
+
+ gui.new_usertype<Row>(
+ "Row",
+ sol::call_constructor,
+ sol::factories(&construct<Row>),
+ sol::base_classes,
+ sol::bases<Layout, Object, Thing>());
+ gui.new_usertype<Flow>(
+ "Flow",
+ sol::call_constructor,
+ sol::factories(&construct<Flow>),
+ sol::base_classes,
+ sol::bases<Layout, Object, Thing>());
+ gui.new_usertype<Grid>(
+ "Grid",
+ sol::call_constructor,
+ sol::factories(&construct<Grid>),
+ sol::base_classes,
+ sol::bases<Layout, Object, Thing>());
+
+ // Widgets
+ gui.new_usertype<PushButton>(
+ "PushButton",
+ sol::call_constructor,
+ sol::factories([guard](const sol::table &children) {
+ return constructWidgetType<PushButton>(children, guard);
+ }),
+ sol::base_classes,
+ sol::bases<Widget, Object, Thing>());
+
+ gui.new_usertype<Label>(
+ "Label",
+ sol::call_constructor,
+ sol::factories([guard](const sol::table &children) {
+ return constructWidgetType<Label>(children, guard);
+ }),
+ sol::base_classes,
+ sol::bases<Widget, Object, Thing>());
+
+ gui.new_usertype<Widget>(
+ "Widget",
+ sol::call_constructor,
+ sol::factories([guard](const sol::table &children) {
+ return constructWidgetType<Widget>(children, guard);
+ }),
+ "show",
+ &Widget::show,
+ "setSize",
+ &Widget::setSize,
+ sol::base_classes,
+ sol::bases<Object, Thing>());
+
+ gui.new_usertype<Stack>(
+ "Stack",
+ sol::call_constructor,
+ sol::factories([guard](const sol::table &children) {
+ return constructWidgetType<Stack>(children, guard);
+ }),
+ sol::base_classes,
+ sol::bases<Widget, Object, Thing>());
+
+ gui.new_usertype<Tab>(
+ "Tab",
+ sol::call_constructor,
+ sol::factories(&constructTab, &constructTabFromTable),
+ sol::base_classes,
+ sol::bases<Widget, Object, Thing>());
+
+ gui.new_usertype<TextEdit>(
+ "TextEdit",
+ sol::call_constructor,
+ sol::factories([guard](const sol::table &children) {
+ return constructWidgetType<TextEdit>(children, guard);
+ }),
+ sol::base_classes,
+ sol::bases<Widget, Object, Thing>());
+
+ gui.new_usertype<SpinBox>(
+ "SpinBox",
+ sol::call_constructor,
+ sol::factories([guard](const sol::table &children) {
+ return constructWidgetType<SpinBox>(children, guard);
+ }),
+ sol::base_classes,
+ sol::bases<Widget, Object, Thing>());
+ gui.new_usertype<Splitter>(
+ "Splitter",
+ sol::call_constructor,
+ sol::factories(&constructSplitter),
+ sol::base_classes,
+ sol::bases<Widget, Object, Thing>());
+ gui.new_usertype<ToolBar>(
+ "ToolBar",
+ sol::call_constructor,
+ sol::factories([guard](const sol::table &children) {
+ return constructWidgetType<ToolBar>(children, guard);
+ }),
+ sol::base_classes,
+ sol::bases<Widget, Object, Thing>());
+ gui.new_usertype<TabWidget>(
+ "TabWidget",
+ sol::call_constructor,
+ sol::factories([guard](const sol::table &children) {
+ return constructTabWidget(children, guard);
+ }),
+ sol::base_classes,
+ sol::bases<Widget, Object, Thing>());
+
+ gui.new_usertype<Group>(
+ "Group",
+ sol::call_constructor,
+ sol::factories([guard](const sol::table &children) {
+ return constructWidgetType<Group>(children, guard);
+ }),
+ sol::base_classes,
+ sol::bases<Widget, Object, Thing>());
+
+ gui["br"] = &br;
+ gui["st"] = &st;
+ gui["empty"] = &empty;
+ gui["hr"] = &hr;
+ gui["noMargin"] = &noMargin;
+ gui["normalMargin"] = &normalMargin;
+ gui["withFormAlignment"] = &withFormAlignment;
+ gui["spacing"] = &spacing;
+
+ return gui;
+ });
+}
+
+} // namespace Lua::Internal
diff --git a/src/plugins/lua/bindings/hook.cpp b/src/plugins/lua/bindings/hook.cpp
index 261e6fe115..b626a83308 100644
--- a/src/plugins/lua/bindings/hook.cpp
+++ b/src/plugins/lua/bindings/hook.cpp
@@ -46,15 +46,21 @@ void addHookModule()
[](Hook *, QMetaObject::Connection con) { QObject::disconnect(con); });
});
- LuaEngine::registerHook("editors.documentOpened", [](const sol::function &func) {
- QObject::connect(Core::EditorManager::instance(),
- &Core::EditorManager::documentOpened,
- [func](Core::IDocument *document) { func(document); });
+ LuaEngine::registerHook("editors.documentOpened", [](const sol::protected_function &func) {
+ QObject::connect(
+ Core::EditorManager::instance(),
+ &Core::EditorManager::documentOpened,
+ [func](Core::IDocument *document) {
+ QTC_CHECK_EXPECTED(LuaEngine::void_safe_call(func, document));
+ });
});
- LuaEngine::registerHook("editors.documentClosed", [](const sol::function &func) {
- QObject::connect(Core::EditorManager::instance(),
- &Core::EditorManager::documentClosed,
- [func](Core::IDocument *document) { func(document); });
+ LuaEngine::registerHook("editors.documentClosed", [](const sol::protected_function &func) {
+ QObject::connect(
+ Core::EditorManager::instance(),
+ &Core::EditorManager::documentClosed,
+ [func](Core::IDocument *document) {
+ QTC_CHECK_EXPECTED(LuaEngine::void_safe_call(func, document));
+ });
});
}
diff --git a/src/plugins/lua/bindings/inheritance.h b/src/plugins/lua/bindings/inheritance.h
index 6336ff4d2d..5e6703e75f 100644
--- a/src/plugins/lua/bindings/inheritance.h
+++ b/src/plugins/lua/bindings/inheritance.h
@@ -72,43 +72,47 @@ SOL_DERIVED_CLASSES(
Utils::AspectList);
namespace Layouting {
-class LayoutItem;
+class Object;
+
+class Layout;
class Column;
-class Row;
class Flow;
-class Grid;
class Form;
+class Grid;
+
class Widget;
-class Stack;
-class Tab;
class Group;
-class TextEdit;
class PushButton;
+class Row;
class SpinBox;
class Splitter;
-class ToolBar;
+class Stack;
+class Tab;
class TabWidget;
-class Group;
+class TextEdit;
+class ToolBar;
} // namespace Layouting
-SOL_BASE_CLASSES(Layouting::Column, Layouting::LayoutItem);
-SOL_BASE_CLASSES(Layouting::Row, Layouting::LayoutItem);
-SOL_BASE_CLASSES(Layouting::Flow, Layouting::LayoutItem);
-SOL_BASE_CLASSES(Layouting::Grid, Layouting::LayoutItem);
-SOL_BASE_CLASSES(Layouting::Form, Layouting::LayoutItem);
-SOL_BASE_CLASSES(Layouting::Widget, Layouting::LayoutItem);
-SOL_BASE_CLASSES(Layouting::Stack, Layouting::LayoutItem);
-SOL_BASE_CLASSES(Layouting::Tab, Layouting::LayoutItem);
-SOL_BASE_CLASSES(Layouting::Group, Layouting::LayoutItem);
-SOL_BASE_CLASSES(Layouting::TextEdit, Layouting::LayoutItem);
-SOL_BASE_CLASSES(Layouting::PushButton, Layouting::LayoutItem);
-SOL_BASE_CLASSES(Layouting::SpinBox, Layouting::LayoutItem);
-SOL_BASE_CLASSES(Layouting::Splitter, Layouting::LayoutItem);
-SOL_BASE_CLASSES(Layouting::ToolBar, Layouting::LayoutItem);
-SOL_BASE_CLASSES(Layouting::TabWidget, Layouting::LayoutItem);
+SOL_BASE_CLASSES(Layouting::Layout, Layouting::Object);
+SOL_BASE_CLASSES(Layouting::Column, Layouting::Layout);
+SOL_BASE_CLASSES(Layouting::Row, Layouting::Layout);
+SOL_BASE_CLASSES(Layouting::Flow, Layouting::Layout);
+SOL_BASE_CLASSES(Layouting::Grid, Layouting::Layout);
+SOL_BASE_CLASSES(Layouting::Form, Layouting::Layout);
+SOL_BASE_CLASSES(Layouting::Widget, Layouting::Object);
+SOL_BASE_CLASSES(Layouting::Stack, Layouting::Widget);
+SOL_BASE_CLASSES(Layouting::Tab, Layouting::Widget);
+SOL_BASE_CLASSES(Layouting::Group, Layouting::Widget);
+SOL_BASE_CLASSES(Layouting::TextEdit, Layouting::Widget);
+SOL_BASE_CLASSES(Layouting::PushButton, Layouting::Widget);
+SOL_BASE_CLASSES(Layouting::SpinBox, Layouting::Widget);
+SOL_BASE_CLASSES(Layouting::Splitter, Layouting::Widget);
+SOL_BASE_CLASSES(Layouting::ToolBar, Layouting::Widget);
+SOL_BASE_CLASSES(Layouting::TabWidget, Layouting::Widget);
SOL_DERIVED_CLASSES(
- Layouting::LayoutItem,
+ Layouting::Object,
+ Layouting::Layout,
Layouting::Column,
Layouting::Row,
Layouting::Flow,
@@ -124,3 +128,4 @@ SOL_DERIVED_CLASSES(
Layouting::Splitter,
Layouting::ToolBar,
Layouting::TabWidget);
+
diff --git a/src/plugins/lua/bindings/install.cpp b/src/plugins/lua/bindings/install.cpp
new file mode 100644
index 0000000000..a044ff2fb8
--- /dev/null
+++ b/src/plugins/lua/bindings/install.cpp
@@ -0,0 +1,390 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "../luaengine.h"
+#include "../luatr.h"
+
+#include <coreplugin/icore.h>
+#include <coreplugin/progressmanager/progressmanager.h>
+#include <coreplugin/progressmanager/taskprogress.h>
+
+#include <solutions/tasking/networkquery.h>
+#include <solutions/tasking/tasktree.h>
+
+#include <utils/algorithm.h>
+#include <utils/infobar.h>
+#include <utils/networkaccessmanager.h>
+#include <utils/stylehelper.h>
+#include <utils/unarchiver.h>
+
+#include <QApplication>
+#include <QJsonDocument>
+#include <QLabel>
+#include <QMessageBox>
+#include <QTemporaryFile>
+#include <QtConcurrent>
+
+using namespace Core;
+using namespace Tasking;
+using namespace Utils;
+
+namespace Lua::Internal {
+
+expected_str<QJsonDocument> getPackageInfo(const FilePath &appDataPath)
+{
+ const FilePath packageInfoPath = appDataPath / "package.json";
+
+ if (!packageInfoPath.exists())
+ return QJsonDocument();
+
+ expected_str<QByteArray> json = packageInfoPath.fileContents();
+ if (!json)
+ return make_unexpected(json.error());
+
+ if (json->isEmpty())
+ return QJsonDocument();
+
+ QJsonParseError error;
+ QJsonDocument doc(QJsonDocument::fromJson(*json, &error));
+ if (error.error != QJsonParseError::NoError)
+ return make_unexpected(error.errorString());
+
+ if (!doc.isObject())
+ return make_unexpected(Tr::tr("Package info is not an object"));
+
+ return doc;
+}
+
+expected_str<QJsonObject> getInstalledPackageInfo(const FilePath &appDataPath, const QString &name)
+{
+ auto packageDoc = getPackageInfo(appDataPath);
+ if (!packageDoc)
+ return make_unexpected(packageDoc.error());
+
+ QJsonObject root = packageDoc->object();
+
+ if (root.contains(name)) {
+ QJsonValue v = root[name];
+ if (!v.isObject())
+ return make_unexpected(Tr::tr("Installed package info is not an object"));
+ return v.toObject();
+ }
+
+ return QJsonObject();
+}
+
+expected_str<QJsonDocument> getOrCreatePackageInfo(const FilePath &appDataPath)
+{
+ expected_str<QJsonDocument> doc = getPackageInfo(appDataPath);
+ if (doc && doc->isObject())
+ return doc;
+
+ QJsonObject obj;
+ return QJsonDocument(obj);
+}
+
+expected_str<void> savePackageInfo(const FilePath &appDataPath, const QJsonDocument &doc)
+{
+ if (!appDataPath.ensureWritableDir())
+ return make_unexpected(Tr::tr("Could not create app data directory"));
+
+ const FilePath packageInfoPath = appDataPath / "package.json";
+ return packageInfoPath.writeFileContents(doc.toJson())
+ .transform_error([](const QString &error) {
+ return Tr::tr("Could not write to package info: %1").arg(error);
+ })
+ .transform([](qint64) { return; });
+}
+
+struct InstallOptions
+{
+ QUrl url;
+ QString name;
+ QString version;
+};
+
+size_t qHash(const InstallOptions &item, size_t seed = 0)
+{
+ return qHash(item.url, seed) ^ qHash(item.name, seed) ^ qHash(item.version, seed);
+}
+
+static FilePath destination(const FilePath &appDataPath, const InstallOptions &options)
+{
+ return appDataPath / "packages" / options.name / options.version;
+}
+
+static Group installRecipe(
+ const FilePath &appDataPath,
+ const QList<InstallOptions> &installOptions,
+ const sol::protected_function &callback)
+{
+ Storage<QFile> storage;
+
+ const LoopList<InstallOptions> installOptionsIt(installOptions);
+
+ const auto emitResult = [callback](const QString &error = QString()) {
+ if (error.isEmpty()) {
+ LuaEngine::void_safe_call(callback, true);
+ return DoneResult::Success;
+ }
+ LuaEngine::void_safe_call(callback, false, error);
+ return DoneResult::Error;
+ };
+
+ const auto onDownloadSetup = [installOptionsIt](NetworkQuery &query) {
+ query.setRequest(QNetworkRequest(installOptionsIt->url));
+ query.setNetworkAccessManager(NetworkAccessManager::instance());
+ return SetupResult::Continue;
+ };
+
+ const auto onDownloadDone = [emitResult, storage](const NetworkQuery &query, DoneWith result) {
+ if (result == DoneWith::Error)
+ return emitResult(query.reply()->errorString());
+ if (result == DoneWith::Cancel)
+ return DoneResult::Error;
+
+ QNetworkReply *reply = query.reply();
+ const auto size = reply->size();
+ const auto written = storage->write(reply->readAll());
+ if (written != size)
+ return emitResult(Tr::tr("Could not write to temporary file"));
+ storage->close();
+ return DoneResult::Success;
+ };
+
+ const auto onUnarchiveSetup =
+ [appDataPath, installOptionsIt, storage, emitResult](Unarchiver &unarchiver) {
+ const auto sourceAndCommand = Unarchiver::sourceAndCommand(
+ FilePath::fromUserInput(storage->fileName()));
+
+ if (!sourceAndCommand) {
+ emitResult(sourceAndCommand.error());
+ return SetupResult::StopWithError;
+ }
+ unarchiver.setGZipFileDestName(installOptionsIt->name);
+ unarchiver.setSourceAndCommand(*sourceAndCommand);
+ unarchiver.setDestDir(destination(appDataPath, *installOptionsIt));
+ return SetupResult::Continue;
+ };
+
+ const auto onUnarchiverDone = [appDataPath, installOptionsIt, emitResult](DoneWith result) {
+ if (result == DoneWith::Error)
+ return emitResult(Tr::tr("Unarchiving failed"));
+ if (result == DoneWith::Cancel)
+ return DoneResult::Error;
+
+ const FilePath destDir = destination(appDataPath, *installOptionsIt);
+ const FilePath binary = destDir / installOptionsIt->name;
+
+ if (binary.isFile())
+ binary.setPermissions(QFile::ExeUser | QFile::ExeGroup | QFile::ExeOther);
+
+ expected_str<QJsonDocument> doc = getOrCreatePackageInfo(appDataPath);
+ if (!doc)
+ return emitResult(doc.error());
+
+ QJsonObject obj = doc->object();
+ QJsonObject installedPackage;
+ installedPackage["version"] = installOptionsIt->version;
+ installedPackage["name"] = installOptionsIt->name;
+ installedPackage["path"] = destDir.toFSPathString();
+ obj[installOptionsIt->name] = installedPackage;
+
+ expected_str<void> res = savePackageInfo(appDataPath, QJsonDocument(obj));
+ if (!res)
+ return emitResult(res.error());
+ return DoneResult::Success;
+ };
+
+ return Group{
+ storage,
+ parallelIdealThreadCountLimit,
+ installOptionsIt,
+ Group{
+ onGroupSetup([emitResult, storage, installOptionsIt] {
+ const QString fileName = installOptionsIt->url.fileName();
+ const QString ext = fileName.mid(fileName.indexOf('.'));
+ {
+ QTemporaryFile tempFile(QDir::tempPath() + "/XXXXXX" + ext);
+ tempFile.setAutoRemove(false);
+ tempFile.open();
+ (*storage).setFileName(tempFile.fileName());
+ }
+
+ if (!storage->open(QIODevice::WriteOnly)) {
+ emitResult(Tr::tr("Could not open temporary file"));
+ return SetupResult::StopWithError;
+ }
+ return SetupResult::Continue;
+ }),
+ NetworkQueryTask(onDownloadSetup, onDownloadDone),
+ UnarchiverTask(onUnarchiveSetup, onUnarchiverDone),
+ onGroupDone([storage, emitResult] { storage->remove(); }),
+ },
+ onGroupDone([emitResult](DoneWith result) {
+ if (result == DoneWith::Cancel)
+ emitResult("Installation was canceled");
+ else if (result == DoneWith::Success)
+ emitResult();
+ }),
+ };
+}
+
+void addInstallModule()
+{
+ class State
+ {
+ public:
+ State() = default;
+ State(const State &) {}
+ ~State()
+ {
+ for (auto tree : m_trees)
+ delete tree;
+ }
+
+ TaskTree *createTree()
+ {
+ auto tree = new TaskTree();
+ m_trees.append(tree);
+ QObject::connect(tree, &TaskTree::done, tree, &QObject::deleteLater);
+ return tree;
+ };
+
+ private:
+ QList<QPointer<TaskTree>> m_trees;
+ };
+
+ LuaEngine::registerProvider(
+ "Install", [state = State()](sol::state_view lua) mutable -> sol::object {
+ sol::table async
+ = lua.script("return require('async')", "_install_async_").get<sol::table>();
+ sol::function wrap = async["wrap"];
+
+ sol::table install = lua.create_table();
+ const ScriptPluginSpec *pluginSpec = lua.get<ScriptPluginSpec *>("PluginSpec");
+
+ install["packageInfo"] =
+ [pluginSpec](const QString &name, sol::this_state l) -> sol::optional<sol::table> {
+ expected_str<QJsonObject> obj
+ = getInstalledPackageInfo(pluginSpec->appDataPath, name);
+ if (!obj)
+ throw sol::error(obj.error().toStdString());
+
+ return sol::table::create_with(
+ l.lua_state(),
+ "name",
+ obj->value("name").toString(),
+ "version",
+ obj->value("version").toString(),
+ "path",
+ FilePath::fromUserInput(obj->value("path").toString()));
+ };
+
+ install["install_cb"] =
+ [pluginSpec, &state](
+ const QString &msg,
+ const sol::table &installOptions,
+ const sol::function &callback) {
+ QList<InstallOptions> installOptionsList;
+ if (installOptions.size() > 0) {
+ for (const auto &pair : installOptions) {
+ const sol::object &value = pair.second;
+ if (value.get_type() != sol::type::table)
+ throw sol::error("Install options must be a table");
+ const sol::table &table = value.as<sol::table>();
+ const QString name = table["name"];
+ const QUrl url = QUrl::fromUserInput(table["url"]);
+ if (url.scheme() != "https")
+ throw sol::error("Only HTTPS is supported");
+
+ const QString version = table["version"];
+ installOptionsList.append({url, name, version});
+ }
+ } else {
+ const QString name = installOptions["name"];
+ const QUrl url = QUrl::fromUserInput(installOptions["url"]);
+ const QString version = installOptions["version"];
+ if (url.scheme() != "https")
+ throw sol::error("Only HTTPS is supported");
+ installOptionsList.append({url, name, version});
+ }
+
+ auto install = [&state, pluginSpec, installOptionsList, callback]() {
+ auto tree = state.createTree();
+
+ auto progress = new TaskProgress(tree);
+ progress->setDisplayName(Tr::tr("Installing package(s) %1").arg("..."));
+
+ tree->setRecipe(
+ installRecipe(pluginSpec->appDataPath, installOptionsList, callback));
+ tree->start();
+ };
+
+ auto denied = [callback]() { callback(false, "User denied installation"); };
+
+ if (QApplication::activeModalWidget()) {
+ auto msgBox = new QMessageBox(
+ QMessageBox::Question,
+ Tr::tr("Install package"),
+ msg,
+ QMessageBox::Yes | QMessageBox::No,
+ Core::ICore::dialogParent());
+
+ const QString details
+ = Tr::tr("The plugin \"%1\" would like to install the following "
+ "package(s):\n\n")
+ .arg(pluginSpec->name)
+ + Utils::transform(installOptionsList, [](const InstallOptions &options) {
+ return QString("* %1 - %2 (from: %3)")
+ .arg(options.name, options.version, options.url.toString());
+ }).join("\n");
+
+ msgBox->setDetailedText(details);
+
+ auto guard = pluginSpec->connectionGuard.get();
+ QObject::connect(msgBox, &QMessageBox::accepted, guard, install);
+ QObject::connect(msgBox, &QMessageBox::rejected, guard, denied);
+
+ msgBox->show();
+ return;
+ }
+
+ const Utils::Id infoBarId = Utils::Id::fromString(
+ "Install" + pluginSpec->name + QString::number(qHash(installOptionsList)));
+
+ InfoBarEntry entry(infoBarId, msg, InfoBarEntry::GlobalSuppression::Enabled);
+
+ entry.addCustomButton(Tr::tr("Install"), [install, infoBarId]() {
+ install();
+ Core::ICore::infoBar()->removeInfo(infoBarId);
+ });
+
+ entry.setCancelButtonInfo(denied);
+
+ const QString details
+ = Tr::tr("The plugin \"**%1**\" would like to install the following "
+ "package(s):\n\n")
+ .arg(pluginSpec->name)
+ + Utils::transform(installOptionsList, [](const InstallOptions &options) {
+ return QString("* %1 - %2 (from: [%3](%3))")
+ .arg(options.name, options.version, options.url.toString());
+ }).join("\n");
+
+ entry.setDetailsWidgetCreator([details]() -> QWidget * {
+ QLabel *list = new QLabel();
+ list->setTextFormat(Qt::TextFormat::MarkdownText);
+ list->setText(details);
+ list->setMargin(StyleHelper::SpacingTokens::ExPaddingGapS);
+ return list;
+ });
+ Core::ICore::infoBar()->addInfo(entry);
+ };
+
+ install["install"] = wrap(install["install_cb"]);
+
+ return install;
+ });
+}
+
+} // namespace Lua::Internal
diff --git a/src/plugins/lua/bindings/layout.cpp b/src/plugins/lua/bindings/layout.cpp
deleted file mode 100644
index 6225bce9f3..0000000000
--- a/src/plugins/lua/bindings/layout.cpp
+++ /dev/null
@@ -1,177 +0,0 @@
-// Copyright (C) 2024 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#include "../luaengine.h"
-
-#include "inheritance.h"
-
-#include <utils/aspects.h>
-#include <utils/layoutbuilder.h>
-
-using namespace Layouting;
-using namespace Utils;
-
-namespace Lua::Internal {
-
-static void processChildren(LayoutItem *item, const sol::table &children)
-{
- for (size_t i = 1; i <= children.size(); ++i) {
- const sol::object v = children[i];
-
- if (v.is<LayoutItem *>()) {
- item->addItem(*v.as<LayoutItem *>());
- } else if (v.is<BaseAspect>()) {
- v.as<BaseAspect *>()->addToLayout(*item);
- } else if (v.is<QString>()) {
- item->addItem(v.as<QString>());
- } else if (v.is<sol::function>()) {
- const sol::function f = v.as<sol::function>();
- auto res = LuaEngine::safe_call<LayoutItem *>(f);
- QTC_ASSERT_EXPECTED(res, continue);
- item->addItem(**res);
- } else {
- qWarning() << "Incompatible object added to layout item: " << (int) v.get_type()
- << " (expected LayoutItem, Aspect or function returning LayoutItem)";
- }
- }
-}
-
-template<class T, typename... Args>
-static std::unique_ptr<T> construct(Args &&...args, const sol::table &children)
-{
- std::unique_ptr<T> item(new T(std::forward<Args>(args)..., {}));
-
- processChildren(item.get(), children);
-
- return item;
-}
-
-void addLayoutModule()
-{
- LuaEngine::registerProvider("Layout", [](sol::state_view l) -> sol::object {
- sol::table layout = l.create_table();
-
- layout.new_usertype<LayoutItem>("LayoutItem", "attachTo", &LayoutItem::attachTo);
-
- layout["Span"] = [](int span, LayoutItem *item) {
- return createItem(item, Span(span, *item));
- };
- layout["Space"] = [](int space) { return createItem(nullptr, Space(space)); };
- layout["Stretch"] = [](int stretch) { return createItem(nullptr, Stretch(stretch)); };
-
- layout.new_usertype<Column>("Column",
- sol::call_constructor,
- sol::factories(&construct<Column>),
- sol::base_classes,
- sol::bases<LayoutItem>());
- layout.new_usertype<Row>("Row",
- sol::call_constructor,
- sol::factories(&construct<Row>),
- sol::base_classes,
- sol::bases<LayoutItem>());
- layout.new_usertype<Flow>("Flow",
- sol::call_constructor,
- sol::factories(&construct<Flow>),
- sol::base_classes,
- sol::bases<LayoutItem>());
- layout.new_usertype<Grid>("Grid",
- sol::call_constructor,
- sol::factories(&construct<Grid>),
- sol::base_classes,
- sol::bases<LayoutItem>());
- layout.new_usertype<Form>("Form",
- sol::call_constructor,
- sol::factories(&construct<Form>),
- sol::base_classes,
- sol::bases<LayoutItem>());
- layout.new_usertype<Widget>("Widget",
- sol::call_constructor,
- sol::factories(&construct<Widget>),
- sol::base_classes,
- sol::bases<LayoutItem>());
- layout.new_usertype<Stack>("Stack",
- sol::call_constructor,
- sol::factories(&construct<Stack>),
- sol::base_classes,
- sol::bases<LayoutItem>());
- layout.new_usertype<Tab>(
- "Tab",
- sol::call_constructor,
- sol::factories(&construct<Tab, QString>),
- sol::base_classes,
- sol::bases<LayoutItem>());
- layout.new_usertype<TextEdit>("TextEdit",
- sol::call_constructor,
- sol::factories(&construct<TextEdit>),
- sol::base_classes,
- sol::bases<LayoutItem>());
- layout.new_usertype<PushButton>("PushButton",
- sol::call_constructor,
- sol::factories(&construct<PushButton>),
- sol::base_classes,
- sol::bases<LayoutItem>());
- layout.new_usertype<SpinBox>("SpinBox",
- sol::call_constructor,
- sol::factories(&construct<SpinBox>),
- sol::base_classes,
- sol::bases<LayoutItem>());
- layout.new_usertype<Splitter>("Splitter",
- sol::call_constructor,
- sol::factories(&construct<Splitter>),
- sol::base_classes,
- sol::bases<LayoutItem>());
- layout.new_usertype<ToolBar>("ToolBar",
- sol::call_constructor,
- sol::factories(&construct<ToolBar>),
- sol::base_classes,
- sol::bases<LayoutItem>());
- layout.new_usertype<TabWidget>("TabWidget",
- sol::call_constructor,
- sol::factories(&construct<TabWidget>),
- sol::base_classes,
- sol::bases<LayoutItem>());
-
- layout.new_usertype<Group>("Group",
- sol::call_constructor,
- sol::factories(&construct<Group>),
- sol::base_classes,
- sol::bases<LayoutItem>());
-
- layout["br"] = &br;
- layout["st"] = &st;
- layout["empty"] = &empty;
- layout["hr"] = &hr;
- layout["noMargin"] = &noMargin;
- layout["normalMargin"] = &normalMargin;
- layout["customMargin"] = [](int left, int top, int right, int bottom) {
- return customMargin(QMargins(left, top, right, bottom));
- };
- layout["withFormAlignment"] = &withFormAlignment;
- layout["title"] = &title;
- layout["text"] = &text;
- layout["tooltip"] = &tooltip;
- layout["resize"] = &resize;
- layout["columnStretch"] = &columnStretch;
- layout["spacing"] = &spacing;
- layout["windowTitle"] = &windowTitle;
- layout["fieldGrowthPolicy"] = &fieldGrowthPolicy;
- layout["id"] = &id;
- layout["setText"] = &setText;
- layout["onClicked"] = [](const sol::function &f) {
- return onClicked([f]() {
- auto res = LuaEngine::void_safe_call(f);
- QTC_CHECK_EXPECTED(res);
- });
- };
- layout["onTextChanged"] = [](const sol::function &f) {
- return onTextChanged([f](const QString &text) {
- auto res = LuaEngine::void_safe_call(f, text);
- QTC_CHECK_EXPECTED(res);
- });
- };
-
- return layout;
- });
-}
-
-} // namespace Lua::Internal
diff --git a/src/plugins/lua/bindings/qtcprocess.cpp b/src/plugins/lua/bindings/qtcprocess.cpp
index 93d3af2e0a..ff2726a96e 100644
--- a/src/plugins/lua/bindings/qtcprocess.cpp
+++ b/src/plugins/lua/bindings/qtcprocess.cpp
@@ -12,30 +12,42 @@ namespace Lua::Internal {
void addProcessModule()
{
- LuaEngine::registerProvider(
- "Process", [](sol::state_view lua) -> sol::object {
- sol::table async = lua.script("return require('async')", "_process_").get<sol::table>();
- sol::function wrap = async["wrap"];
+ LuaEngine::registerProvider("Process", [](sol::state_view lua) -> sol::object {
+ const ScriptPluginSpec *pluginSpec = lua.get<ScriptPluginSpec *>("PluginSpec");
- sol::table process = lua.create_table();
+ sol::table async = lua.script("return require('async')", "_process_").get<sol::table>();
+ sol::function wrap = async["wrap"];
- process["runInTerminal_cb"] = [](const QString &cmdline, const sol::function &cb) {
+ sol::table process = lua.create_table();
+
+ process["runInTerminal_cb"] =
+ [guard
+ = pluginSpec->connectionGuard.get()](const QString &cmdline, const sol::function &cb) {
Process *p = new Process;
p->setTerminalMode(TerminalMode::Run);
p->setCommand(CommandLine::fromUserInput((cmdline)));
p->setEnvironment(Environment::systemEnvironment());
- QObject::connect(p, &Process::done, &LuaEngine::instance(), [p, cb]() {
- cb(p->exitCode());
- });
+ QObject::connect(p, &Process::done, guard, [p, cb]() { cb(p->exitCode()); });
+ };
+
+ process["commandOutput_cb"] =
+ [guard
+ = pluginSpec->connectionGuard.get()](const QString &cmdline, const sol::function &cb) {
+ Process *p = new Process;
+ p->setCommand(CommandLine::fromUserInput((cmdline)));
+ p->setEnvironment(Environment::systemEnvironment());
+
+ QObject::connect(p, &Process::done, guard, [p, cb]() { cb(p->allOutput()); });
p->start();
};
- process["runInTerminal"] = wrap(process["runInTerminal_cb"]);
+ process["runInTerminal"] = wrap(process["runInTerminal_cb"]);
+ process["commandOutput"] = wrap(process["commandOutput_cb"]);
- return process;
- });
+ return process;
+ });
}
} // namespace Lua::Internal
diff --git a/src/plugins/lua/bindings/settings.cpp b/src/plugins/lua/bindings/settings.cpp
index b1c388605f..241b1970c6 100644
--- a/src/plugins/lua/bindings/settings.cpp
+++ b/src/plugins/lua/bindings/settings.cpp
@@ -62,8 +62,8 @@ std::unique_ptr<LuaAspectContainer> aspectContainerCreate(const sol::table &opti
} else if (key == "layouter") {
if (v.is<sol::function>())
container->setLayouter(
- [func = v.as<sol::function>()]() -> Layouting::LayoutItem {
- auto res = Lua::LuaEngine::safe_call<Layouting::LayoutItem>(func);
+ [func = v.as<sol::function>()]() -> Layouting::Layout {
+ auto res = Lua::LuaEngine::safe_call<Layouting::Layout>(func);
QTC_ASSERT_EXPECTED(res, return {});
return *res;
});
@@ -300,6 +300,13 @@ void addSettingsModule()
addTypedAspect<StringAspect>(settings, "StringAspect");
auto filePathAspectType = addTypedAspect<FilePathAspect>(settings, "FilePathAspect");
+ filePathAspectType.set(
+ "setValue",
+ sol::overload(
+ [](FilePathAspect &self, const QString &value) {
+ self.setValue(FilePath::fromUserInput(value));
+ },
+ [](FilePathAspect &self, const FilePath &value) { self.setValue(value); }));
filePathAspectType.set("expandedValue", sol::property(&FilePathAspect::expandedValue));
filePathAspectType.set(
"defaultPath",
diff --git a/src/plugins/lua/bindings/utils.cpp b/src/plugins/lua/bindings/utils.cpp
index d8f860cc68..2ed5a4601e 100644
--- a/src/plugins/lua/bindings/utils.cpp
+++ b/src/plugins/lua/bindings/utils.cpp
@@ -19,23 +19,19 @@ void addUtilsModule()
LuaEngine::registerProvider(
"Utils",
[futureSync = Utils::FutureSynchronizer()](sol::state_view lua) mutable -> sol::object {
+ const ScriptPluginSpec *pluginSpec = lua.get<ScriptPluginSpec *>("PluginSpec");
+
auto async = lua.script("return require('async')", "_utils_").get<sol::table>();
sol::table utils = lua.create_table();
- utils.set_function("waitms_cb", [](int ms, const sol::function &cb) {
- QTimer *timer = new QTimer();
- timer->setSingleShot(true);
- timer->setInterval(ms);
- QObject::connect(timer, &QTimer::timeout, &LuaEngine::instance(), [cb, timer]() {
- cb();
- timer->deleteLater();
- });
- timer->start();
+ utils.set_function("waitms_cb", [guard = pluginSpec->connectionGuard.get()](int ms, const sol::function &cb) {
+ QTimer::singleShot(ms, guard, [cb]() { cb(); });
});
auto dirEntries_cb =
- [&futureSync](const FilePath &p, const sol::table &options, const sol::function &cb) {
+ [&futureSync, guard = pluginSpec->connectionGuard.get()](
+ const FilePath &p, const sol::table &options, const sol::function &cb) {
const QStringList nameFilters = options.get_or<QStringList>("nameFilters", {});
QDir::Filters fileFilters
= (QDir::Filters) options.get_or<int>("fileFilters", QDir::NoFilter);
@@ -60,22 +56,22 @@ void addUtilsModule()
futureSync.addFuture<FilePath>(future);
- Utils::onFinished<FilePath>(
- future, &LuaEngine::instance(), [cb](const QFuture<FilePath> &future) {
- cb(future.results());
- });
+ Utils::onFinished<FilePath>(future, guard, [cb](const QFuture<FilePath> &future) {
+ cb(future.results());
+ });
};
- auto searchInPath_cb = [&futureSync](const FilePath &p, const sol::function &cb) {
+ auto searchInPath_cb = [&futureSync,
+ guard = pluginSpec->connectionGuard
+ .get()](const FilePath &p, const sol::function &cb) {
QFuture<FilePath> future = Utils::asyncRun(
[p](QPromise<FilePath> &promise) { promise.addResult(p.searchInPath()); });
futureSync.addFuture<FilePath>(future);
- Utils::onFinished<FilePath>(
- future, &LuaEngine::instance(), [cb](const QFuture<FilePath> &future) {
- cb(future.result());
- });
+ Utils::onFinished<FilePath>(future, guard, [cb](const QFuture<FilePath> &future) {
+ cb(future.result());
+ });
};
utils.set_function("__dirEntries_cb__", dirEntries_cb);
@@ -99,6 +95,24 @@ void addUtilsModule()
else
return "unknown";
}());
+ hostOsInfoType["architecture"] = sol::var([]() {
+ switch (HostOsInfo::hostArchitecture()) {
+ case OsArchUnknown:
+ return "unknown";
+ case OsArchX86:
+ return "x86";
+ case OsArchAMD64:
+ return "x86_64";
+ case OsArchItanium:
+ return "itanium";
+ case OsArchArm:
+ return "arm";
+ case OsArchArm64:
+ return "arm64";
+ default:
+ return "unknown";
+ }
+ }());
sol::usertype<FilePath> filePathType = utils.new_usertype<FilePath>(
"FilePath",
@@ -126,6 +140,12 @@ void addUtilsModule()
&FilePath::currentWorkingPath,
"parentDir",
&FilePath::parentDir,
+ "suffix",
+ &FilePath::suffix,
+ "completeSuffix",
+ &FilePath::completeSuffix,
+ "isAbsolutePath",
+ &FilePath::isAbsolutePath,
"resolvePath",
sol::overload(
[](const FilePath &p, const QString &path) { return p.resolvePath(path); },
diff --git a/src/plugins/lua/images/settingscategory_lua.png b/src/plugins/lua/images/settingscategory_lua.png
new file mode 100644
index 0000000000..7f1b6808fe
--- /dev/null
+++ b/src/plugins/lua/images/settingscategory_lua.png
Binary files differ
diff --git a/src/plugins/lua/images/settingscategory_lua@2x.png b/src/plugins/lua/images/settingscategory_lua@2x.png
new file mode 100644
index 0000000000..a70400124d
--- /dev/null
+++ b/src/plugins/lua/images/settingscategory_lua@2x.png
Binary files differ
diff --git a/src/plugins/lua/lua.qbs b/src/plugins/lua/lua.qbs
index e7f1507ba7..1b49d94f51 100644
--- a/src/plugins/lua/lua.qbs
+++ b/src/plugins/lua/lua.qbs
@@ -32,9 +32,10 @@ QtcPlugin {
"async.cpp",
"core.cpp",
"fetch.cpp",
+ "gui.cpp",
"hook.cpp",
"inheritance.h",
- "layout.cpp",
+ "install.cpp",
"messagemanager.cpp",
"qtcprocess.cpp",
"settings.cpp",
@@ -56,7 +57,8 @@ QtcPlugin {
"async.lua",
"core.lua",
"fetch.lua",
- "layout.lua",
+ "gui.lua",
+ "install.lua",
"lsp.lua",
"messagemanager.lua",
"process.lua",
@@ -66,8 +68,9 @@ QtcPlugin {
"simpletypes.lua",
"utils.lua",
"widgets.lua",
- "wizard.lua",
]
+ qbs.install: true
+ qbs.installDir: qtc.ide_data_path + "/lua/meta/"
}
Group {
diff --git a/src/plugins/lua/luaengine.cpp b/src/plugins/lua/luaengine.cpp
index 67dea9be53..29ca17fad6 100644
--- a/src/plugins/lua/luaengine.cpp
+++ b/src/plugins/lua/luaengine.cpp
@@ -4,16 +4,43 @@
#include "luaengine.h"
#include "luapluginspec.h"
+#include "luatr.h"
+
+#include <coreplugin/icore.h>
+#include <coreplugin/messagemanager.h>
#include <utils/algorithm.h>
+#include <utils/lua.h>
+#include <utils/stringutils.h>
+#include <utils/theme/theme.h>
#include <QJsonArray>
#include <QJsonObject>
+#include <QStandardPaths>
using namespace Utils;
namespace Lua {
+class LuaInterfaceImpl : public Utils::LuaInterface
+{
+ LuaEngine *m_engine;
+
+public:
+ LuaInterfaceImpl(LuaEngine *engine)
+ : m_engine(engine)
+ {
+ Utils::setLuaInterface(this);
+ }
+ ~LuaInterfaceImpl() override { Utils::setLuaInterface(nullptr); }
+
+ expected_str<std::unique_ptr<LuaState>> runScript(
+ const QString &script, const QString &name) override
+ {
+ return m_engine->runScript(script, name);
+ }
+};
+
class LuaEnginePrivate
{
public:
@@ -23,6 +50,8 @@ public:
QList<std::function<void(sol::state_view)>> m_autoProviders;
QMap<QString, std::function<void(sol::function)>> m_hooks;
+
+ std::unique_ptr<LuaInterfaceImpl> m_luaInterface;
};
static LuaEngine *s_instance = nullptr;
@@ -37,6 +66,7 @@ LuaEngine::LuaEngine()
: d(new LuaEnginePrivate())
{
s_instance = this;
+ d->m_luaInterface.reset(new LuaInterfaceImpl(this));
}
LuaEngine::~LuaEngine()
@@ -44,6 +74,70 @@ LuaEngine::~LuaEngine()
s_instance = nullptr;
}
+class LuaStateImpl : public Utils::LuaState
+{
+public:
+ sol::state lua;
+};
+
+// Runs the gives script in a new Lua state. The returned Object manages the lifetime of the state.
+std::unique_ptr<Utils::LuaState> LuaEngine::runScript(const QString &script, const QString &name)
+{
+ std::unique_ptr<LuaStateImpl> opaque = std::make_unique<LuaStateImpl>();
+
+ opaque->lua.open_libraries(
+ sol::lib::base,
+ sol::lib::bit32,
+ sol::lib::coroutine,
+ sol::lib::debug,
+ sol::lib::io,
+ sol::lib::math,
+ sol::lib::os,
+ sol::lib::package,
+ sol::lib::string,
+ sol::lib::table,
+ sol::lib::utf8);
+
+ opaque->lua["print"] = [prefix = name, printToOutputPane = true](sol::variadic_args va) {
+ const QString msg = variadicToStringList(va).join("\t");
+
+ qDebug().noquote() << "[" << prefix << "]" << msg;
+ if (printToOutputPane) {
+ static const QString p
+ = ansiColoredText("[" + prefix + "]", creatorColor(Theme::Token_Text_Muted));
+ Core::MessageManager::writeSilently(QString("%1 %2").arg(p, msg));
+ }
+ };
+
+ opaque->lua.new_usertype<ScriptPluginSpec>(
+ "PluginSpec", sol::no_constructor, "name", sol::property([](ScriptPluginSpec &self) {
+ return self.name;
+ }));
+
+ opaque->lua["PluginSpec"] = ScriptPluginSpec{name, {}, std::make_unique<QObject>()};
+
+ for (const auto &[name, func] : d->m_providers.asKeyValueRange()) {
+ opaque->lua["package"]["preload"][name.toStdString()] =
+ [func = func](const sol::this_state &s) { return func(s); };
+ }
+
+ for (const auto &func : d->m_autoProviders)
+ func(opaque->lua);
+
+ auto result
+ = opaque->lua
+ .safe_script(script.toStdString(), sol::script_pass_on_error, name.toStdString());
+
+ if (!result.valid()) {
+ sol::error err = result;
+ qWarning() << "Failed to run script" << name << ":" << QString::fromUtf8(err.what());
+ Core::MessageManager::writeFlashing(
+ tr("Failed to run script %1: %2").arg(name, QString::fromUtf8(err.what())));
+ }
+
+ return opaque;
+}
+
void LuaEngine::registerProvider(const QString &packageName, const PackageProvider &provider)
{
QTC_ASSERT(!instance().d->m_providers.contains(packageName), return);
@@ -79,14 +173,6 @@ expected_str<void> LuaEngine::connectHooks(
return {};
}
-expected_str<void> LuaEngine::connectHooks(sol::state_view lua, const sol::table &hookTable)
-{
- if (!hookTable)
- return {};
-
- return instance().connectHooks(lua, hookTable, "");
-}
-
expected_str<LuaPluginSpec *> LuaEngine::loadPlugin(const Utils::FilePath &path)
{
auto contents = path.fileContents();
@@ -95,10 +181,6 @@ expected_str<LuaPluginSpec *> LuaEngine::loadPlugin(const Utils::FilePath &path)
sol::state lua;
- lua["print"] = [prefix = path.fileName()](sol::variadic_args va) {
- qDebug().noquote() << "[" << prefix << "]" << variadicToStringList(va).join("\t");
- };
-
auto result = lua.safe_script(
std::string_view(contents->data(), contents->size()),
sol::script_pass_on_error,
@@ -115,12 +197,16 @@ expected_str<LuaPluginSpec *> LuaEngine::loadPlugin(const Utils::FilePath &path)
sol::table pluginInfo = result.get<sol::table>();
if (!pluginInfo.valid())
return make_unexpected(QString("Script did not return a table with plugin info"));
- return LuaPluginSpec::create(path, std::move(lua), pluginInfo);
+ return LuaPluginSpec::create(path, pluginInfo);
}
-expected_str<void> LuaEngine::prepareSetup(
- sol::state_view &lua, const LuaPluginSpec &pluginSpec, sol::optional<sol::table> hookTable)
+expected_str<sol::protected_function> LuaEngine::prepareSetup(
+ sol::state_view lua, const LuaPluginSpec &pluginSpec)
{
+ auto contents = pluginSpec.filePath().fileContents();
+ if (!contents)
+ return make_unexpected(contents.error());
+
// TODO: Only open libraries requested by the plugin
lua.open_libraries(
sol::lib::base,
@@ -135,10 +221,33 @@ expected_str<void> LuaEngine::prepareSetup(
sol::lib::table,
sol::lib::utf8);
- const QString searchPath
- = (FilePath::fromUserInput(pluginSpec.filePath()).parentDir() / "?.lua").toUserOutput();
+ const bool printToOutputPane = pluginSpec.printToOutputPane();
+ const QString prefix = pluginSpec.filePath().fileName();
+ lua["print"] = [prefix, printToOutputPane](sol::variadic_args va) {
+ const QString msg = variadicToStringList(va).join("\t");
+
+ qDebug().noquote() << "[" << prefix << "]" << msg;
+ if (printToOutputPane) {
+ static const QString p
+ = ansiColoredText("[" + prefix + "]", creatorColor(Theme::Token_Text_Muted));
+ Core::MessageManager::writeSilently(QString("%1 %2").arg(p, msg));
+ }
+ };
+
+ const QString searchPath = (pluginSpec.location() / "?.lua").toUserOutput();
lua["package"]["path"] = searchPath.toStdString();
+ const FilePath appDataPath = Core::ICore::userResourcePath() / "plugin-data" / "lua"
+ / pluginSpec.location().fileName();
+
+ lua.new_usertype<ScriptPluginSpec>(
+ "PluginSpec", sol::no_constructor, "name", sol::property([](ScriptPluginSpec &self) {
+ return self.name;
+ }));
+
+ lua["PluginSpec"]
+ = ScriptPluginSpec{pluginSpec.name(), appDataPath, std::make_unique<QObject>()};
+
// TODO: only register what the plugin requested
for (const auto &[name, func] : d->m_providers.asKeyValueRange()) {
lua["package"]["preload"][name.toStdString()] = [func = func](const sol::this_state &s) {
@@ -149,10 +258,29 @@ expected_str<void> LuaEngine::prepareSetup(
for (const auto &func : d->m_autoProviders)
func(lua);
- if (hookTable)
- return LuaEngine::connectHooks(lua, *hookTable);
+ sol::protected_function_result result = lua.safe_script(
+ std::string_view(contents->data(), contents->size()),
+ sol::script_pass_on_error,
+ pluginSpec.filePath().fileName().toUtf8().constData());
- return {};
+ auto pluginTable = result.get<sol::optional<sol::table>>();
+ if (!pluginTable)
+ return make_unexpected(Tr::tr("Script did not return a table"));
+
+ auto hookTable = pluginTable->get<sol::optional<sol::table>>("hooks");
+
+ if (hookTable) {
+ auto connectResult = connectHooks(lua, *hookTable, {});
+ if (!connectResult)
+ return make_unexpected(connectResult.error());
+ }
+
+ auto setupFunction = pluginTable->get_or<sol::function>("setup", {});
+
+ if (!setupFunction)
+ return make_unexpected(Tr::tr("Plugin info table did not contain a setup function"));
+
+ return setupFunction;
}
bool LuaEngine::isCoroutine(lua_State *state)
diff --git a/src/plugins/lua/luaengine.h b/src/plugins/lua/luaengine.h
index a683f43f2d..f8fa085d6d 100644
--- a/src/plugins/lua/luaengine.h
+++ b/src/plugins/lua/luaengine.h
@@ -10,6 +10,7 @@
#include <utils/expected.h>
#include <utils/filepath.h>
+#include <utils/lua.h>
#include <sol/sol.hpp>
@@ -33,6 +34,13 @@ struct CoroutineState
bool isMainThread;
};
+struct ScriptPluginSpec
+{
+ QString name;
+ Utils::FilePath appDataPath;
+ std::unique_ptr<QObject> connectionGuard;
+};
+
class LUA_EXPORT LuaEngine final : public QObject
{
friend class Internal::LuaPlugin;
@@ -47,15 +55,13 @@ public:
static LuaEngine &instance();
Utils::expected_str<LuaPluginSpec *> loadPlugin(const Utils::FilePath &path);
- Utils::expected_str<void> prepareSetup(
- sol::state_view &lua, const LuaPluginSpec &pluginSpec, sol::optional<sol::table> hookTable);
+ Utils::expected_str<sol::protected_function> prepareSetup(
+ sol::state_view lua, const LuaPluginSpec &pluginSpec);
static void registerProvider(const QString &packageName, const PackageProvider &provider);
static void autoRegister(const std::function<void(sol::state_view)> &registerFunction);
static void registerHook(QString name, const std::function<void(sol::function)> &hookProvider);
- static Utils::expected_str<void> connectHooks(sol::state_view lua, const sol::table &hookTable);
-
static bool isCoroutine(lua_State *state);
static sol::table toTable(const sol::state_view &lua, const QJsonValue &v);
@@ -101,6 +107,9 @@ public:
return {};
}
+ // Runs the given script in a new Lua state. The returned Object manages the lifetime of the state.
+ std::unique_ptr<Utils::LuaState> runScript(const QString &script, const QString &name);
+
protected:
Utils::expected_str<void> connectHooks(
sol::state_view lua, const sol::table &table, const QString &path);
diff --git a/src/plugins/lua/luaplugin.cpp b/src/plugins/lua/luaplugin.cpp
index 68b151c2fd..60505ade85 100644
--- a/src/plugins/lua/luaplugin.cpp
+++ b/src/plugins/lua/luaplugin.cpp
@@ -4,6 +4,8 @@
#include "luaengine.h"
#include "luapluginspec.h"
+#include <coreplugin/icore.h>
+#include <coreplugin/jsexpander.h>
#include <coreplugin/messagemanager.h>
#include <extensionsystem/iplugin.h>
@@ -26,10 +28,44 @@ void addUtilsModule();
void addMessageManagerModule();
void addProcessModule();
void addSettingsModule();
-void addLayoutModule();
+void addGuiModule();
void addQtModule();
void addCoreModule();
void addHookModule();
+void addInstallModule();
+
+class LuaJsExtension : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit LuaJsExtension(QObject *parent = nullptr)
+ : QObject(parent)
+ {}
+
+ Q_INVOKABLE QString metaFolder() const
+ {
+ return Core::ICore::resourcePath("lua/meta").toFSPathString();
+ }
+};
+
+struct Arguments
+{
+ std::optional<FilePath> loadPlugin;
+};
+
+Arguments parseArguments(const QStringList &arguments)
+{
+ Arguments args;
+ for (int i = 0; i < arguments.size() - 1; ++i) {
+ if (arguments.at(i) == QLatin1String("-loadluaplugin")) {
+ const QString path(arguments.at(i + 1));
+ args.loadPlugin = FilePath::fromUserInput(path);
+ i++; // skip the argument
+ }
+ }
+ return args;
+}
class LuaPlugin : public IPlugin
{
@@ -38,15 +74,18 @@ class LuaPlugin : public IPlugin
private:
std::unique_ptr<LuaEngine> m_luaEngine;
+ Arguments m_arguments;
public:
LuaPlugin() {}
~LuaPlugin() override = default;
- void initialize() final
+ bool initialize(const QStringList &arguments, QString *) final
{
m_luaEngine.reset(new LuaEngine());
+ m_arguments = parseArguments(arguments);
+
addAsyncModule();
addFetchModule();
addActionModule();
@@ -54,16 +93,21 @@ public:
addMessageManagerModule();
addProcessModule();
addSettingsModule();
- addLayoutModule();
+ addGuiModule();
addQtModule();
addCoreModule();
addHookModule();
+ addInstallModule();
+
+ Core::JsExpander::registerGlobalObject("Lua", [] { return new LuaJsExtension(); });
+
+ return true;
}
bool delayedInitialize() final
{
- scanForPlugins(transform(PluginManager::pluginPaths(), [](const QString &path) -> FilePath {
- return FilePath::fromUserInput(path) / "lua-plugins";
+ scanForPlugins(transform(PluginManager::pluginPaths(), [](const FilePath &path) {
+ return path / "lua-plugins";
}));
return true;
@@ -73,8 +117,7 @@ public:
{
QSet<PluginSpec *> plugins;
for (const FilePath &path : paths) {
- const FilePaths folders = path.dirEntries(
- FileFilter({}, QDir::Dirs | QDir::NoDotAndDotDot));
+ FilePaths folders = path.dirEntries(FileFilter({}, QDir::Dirs | QDir::NoDotAndDotDot));
for (const FilePath &folder : folders) {
const FilePath script = folder / (folder.baseName() + ".lua");
@@ -92,6 +135,23 @@ public:
}
}
+ if (m_arguments.loadPlugin) {
+ const FilePath folder = *m_arguments.loadPlugin;
+ const FilePath script = folder / (folder.baseName() + ".lua");
+ const expected_str<LuaPluginSpec *> result = m_luaEngine->loadPlugin(script);
+
+ if (!result) {
+ qWarning() << "Failed to load plugin" << script << ":" << result.error();
+ MessageManager::writeFlashing(tr("Failed to load plugin %1: %2")
+ .arg(script.toUserOutput())
+ .arg(result.error()));
+
+ } else {
+ (*result)->setEnabledBySettings(true);
+ plugins.insert(*result);
+ }
+ }
+
PluginManager::addPlugins({plugins.begin(), plugins.end()});
PluginManager::loadPluginsAtRuntime(plugins);
}
diff --git a/src/plugins/lua/luapluginspec.cpp b/src/plugins/lua/luapluginspec.cpp
index dfbb54a41d..c3fcd558a5 100644
--- a/src/plugins/lua/luapluginspec.cpp
+++ b/src/plugins/lua/luapluginspec.cpp
@@ -34,28 +34,19 @@ class LuaPluginSpecPrivate
{
public:
FilePath pluginScriptPath;
-
- sol::state lua;
- sol::table pluginTable;
-
- sol::function setupFunction;
+ bool printToOutputPane = false;
+ std::unique_ptr<sol::state> activeLuaState;
};
LuaPluginSpec::LuaPluginSpec()
: d(new LuaPluginSpecPrivate())
{}
-expected_str<LuaPluginSpec *> LuaPluginSpec::create(const FilePath &filePath,
- sol::state lua,
- sol::table pluginTable)
+expected_str<LuaPluginSpec *> LuaPluginSpec::create(const FilePath &filePath, sol::table pluginTable)
{
std::unique_ptr<LuaPluginSpec> pluginSpec(new LuaPluginSpec());
- pluginSpec->d->lua = std::move(lua);
- pluginSpec->d->pluginTable = pluginTable;
-
- pluginSpec->d->setupFunction = pluginTable.get_or<sol::function>("setup", {});
- if (!pluginSpec->d->setupFunction)
+ if (!pluginTable.get_or<sol::function>("setup", {}))
return make_unexpected(QString("Plugin info table did not contain a setup function"));
QJsonValue v = LuaEngine::toJson(pluginTable);
@@ -71,10 +62,11 @@ expected_str<LuaPluginSpec *> LuaPluginSpec::create(const FilePath &filePath,
if (!r)
return make_unexpected(r.error());
- pluginSpec->setFilePath(filePath.toUserOutput());
- pluginSpec->setLocation(filePath.parentDir().toUserOutput());
+ pluginSpec->setFilePath(filePath);
+ pluginSpec->setLocation(filePath.parentDir());
pluginSpec->d->pluginScriptPath = filePath;
+ pluginSpec->d->printToOutputPane = pluginTable.get_or("printToOutputPane", false);
return pluginSpec.release();
}
@@ -94,16 +86,19 @@ bool LuaPluginSpec::loadLibrary()
}
bool LuaPluginSpec::initializePlugin()
{
- expected_str<void> setupResult
- = LuaEngine::instance()
- .prepareSetup(d->lua, *this, d->pluginTable.get<sol::optional<sol::table>>("hooks"));
+ QTC_ASSERT(!d->activeLuaState, return false);
+
+ std::unique_ptr<sol::state> activeLuaState = std::make_unique<sol::state>();
+
+ expected_str<sol::protected_function> setupResult
+ = LuaEngine::instance().prepareSetup(*activeLuaState, *this);
if (!setupResult) {
setError(Lua::Tr::tr("Failed to prepare plugin setup: %1").arg(setupResult.error()));
return false;
}
- auto result = d->setupFunction.call();
+ auto result = setupResult->call();
if (result.get_type() == sol::type::boolean && result.get<bool>() == false) {
setError(Lua::Tr::tr("Plugin setup function returned false"));
@@ -117,6 +112,8 @@ bool LuaPluginSpec::initializePlugin()
}
}
+ d->activeLuaState = std::move(activeLuaState);
+
setState(PluginSpec::State::Initialized);
return true;
}
@@ -133,9 +130,18 @@ bool LuaPluginSpec::delayedInitialize()
}
ExtensionSystem::IPlugin::ShutdownFlag LuaPluginSpec::stop()
{
+ d->activeLuaState.reset();
return ExtensionSystem::IPlugin::ShutdownFlag::SynchronousShutdown;
}
-void LuaPluginSpec::kill() {}
+void LuaPluginSpec::kill()
+{
+ d->activeLuaState.reset();
+}
+
+bool LuaPluginSpec::printToOutputPane() const
+{
+ return d->printToOutputPane;
+}
} // namespace Lua
diff --git a/src/plugins/lua/luapluginspec.h b/src/plugins/lua/luapluginspec.h
index 208aa8f196..7c2ab3318b 100644
--- a/src/plugins/lua/luapluginspec.h
+++ b/src/plugins/lua/luapluginspec.h
@@ -39,9 +39,8 @@ class LuaPluginSpec : public ExtensionSystem::PluginSpec
LuaPluginSpec();
public:
- static Utils::expected_str<LuaPluginSpec *> create(const Utils::FilePath &filePath,
- sol::state lua,
- sol::table pluginTable);
+ static Utils::expected_str<LuaPluginSpec *> create(
+ const Utils::FilePath &filePath, sol::table pluginTable);
ExtensionSystem::IPlugin *plugin() const override;
@@ -52,6 +51,9 @@ public:
bool delayedInitialize() override;
ExtensionSystem::IPlugin::ShutdownFlag stop() override;
void kill() override;
+
+public:
+ bool printToOutputPane() const;
};
} // namespace Lua
diff --git a/src/plugins/lua/meta/gui.lua b/src/plugins/lua/meta/gui.lua
new file mode 100644
index 0000000000..6913e5f1eb
--- /dev/null
+++ b/src/plugins/lua/meta/gui.lua
@@ -0,0 +1,181 @@
+---@meta Layout
+
+local gui = {}
+
+---The base class of all ui related classes
+---@class Object
+gui.Object = {}
+
+---The base class of all gui layout classes
+---@class Layout : Object
+gui.Layout = {}
+
+---The base class of all widget classes, an empty widget itself.
+---@class Widget : Object
+gui.Widget = {}
+
+---@param children Layout
+---@return Widget
+function gui.Widget(children) end
+
+---Column layout
+---@class Column : Layout
+local column = {}
+
+---@param children Layout|string|BaseAspect|function
+---@return Column
+function gui.Column(children) end
+
+---A group box with a title
+---@class Group : Widget
+local group = {}
+
+---@return Group
+function gui.Group(children) end
+
+---Row layout
+---@class Row : Layout
+local row = {}
+
+---@param children Layout|string|BaseAspect|function
+---@return Row
+function gui.Row(children) end
+
+---Flow layout
+---@class Flow : Layout
+local flow = {}
+
+---@param children Layout|string|BaseAspect|function
+---@return Flow
+function gui.Flow(children) end
+
+---Grid layout
+---@class Grid : Layout
+local grid = {}
+
+---@param children Layout|string|BaseAspect|function
+---@return Grid
+function gui.Grid(children) end
+
+---Form layout
+---@class Form : Layout
+local form = {}
+
+---@param children Layout|string|BaseAspect|function
+---@return Form
+function gui.Form(children) end
+
+
+---A stack of multiple widgets
+---@class Stack : Widget
+local stack = {}
+
+---@param children Layout|string|BaseAspect|function
+---@return Stack
+function gui.Stack(children) end
+
+---A Tab widget
+---@class Tab : Widget
+local tab = {}
+
+---@param children Layout|string|BaseAspect|function
+---@return Tab
+function gui.Tab(children) end
+
+---A Multiline text edit
+---@class TextEdit : Widget
+local textEdit = {}
+
+---@param children Layout|string|BaseAspect|function
+---@return TextEdit
+function gui.TextEdit(children) end
+
+---A PushButton
+---@class PushButton : Widget
+local pushButton = {}
+
+---@param children Layout|string|BaseAspect|function
+---@return PushButton
+function gui.PushButton(children) end
+
+---A Label
+---@class Label : LayoutItem
+local label = {}
+
+---@param children LayoutItem|string|BaseAspect|function
+---@return Label
+function gui.Label(children) end
+
+---A SpinBox
+---@class SpinBox : Widget
+local spinBox = {}
+
+---@param children Layout|string|BaseAspect|function
+---@return SpinBox
+function gui.SpinBox(children) end
+
+---A Splitter
+---@class Splitter : Widget
+local splitter = {}
+
+---@param children Layout|string|BaseAspect|function
+---@return Splitter
+function gui.Splitter(children) end
+
+---A Toolbar
+---@class ToolBar : Widget
+local toolBar = {}
+
+---@param children Layout|string|BaseAspect|function
+---@return ToolBar
+function gui.ToolBar(children) end
+
+---A TabWidget
+---@class TabWidget : Widget
+local tabWidget = {}
+
+---@param children Layout|string|BaseAspect|function
+---@return TabWidget
+function gui.TabWidget(children) end
+
+---@param name string
+---@param child Layout|string|BaseAspect|function
+---@return TabWidget
+function gui.TabWidget(name, child) end
+---A "Line break" in the gui
+function gui.br() end
+
+---A "Stretch" in the layout
+function gui.st() end
+
+---An empty grid cell in a grid layout
+function gui.empty() end
+
+---A horizontal line in the layout
+function gui.hr() end
+
+---Clears the margin of the layout
+function gui.noMargin() end
+
+---Sets the margin of the layout to the default value
+function gui.normalMargin() end
+
+---Sets the alignment of a Grid layout according to the Form layout rules
+function gui.withFormAlignment() end
+
+---Sets the size of the parent object if possible
+function gui.resize(width, height) end
+
+---Sets the spacing of the gui
+function gui.spacing(spacing) end
+
+---Sets the field growth policy of the gui
+function gui.fieldGrowthPolicy(policy) end
+
+---Sets the onClicked handler of the parent object if possible
+function gui.onClicked(f) end
+
+---Sets the onTextChanged handler of the parent object if possible
+function gui.onTextChanged(f) end
+
+return gui
diff --git a/src/plugins/lua/meta/install.lua b/src/plugins/lua/meta/install.lua
new file mode 100644
index 0000000000..1775206374
--- /dev/null
+++ b/src/plugins/lua/meta/install.lua
@@ -0,0 +1,29 @@
+---@meta Install
+
+local Install = {}
+
+---@class PackageInfo
+---@field name string The name of the package
+---@field version string The version of the package
+---@field path FilePath The path to the package
+
+local PackageInfo = {}
+---@class InstallOptions
+---@field name string The name of the package to install
+---@field url string The url to fetch the package from
+---@field version string The version of the package to install
+local InstallOptions = {}
+
+---Install something
+---@param msg string The message to display to the user asking for permission to install
+---@param options InstallOptions|[InstallOptions] The options to install
+---@return boolean Result Whether the installation was successful
+---@return string Error The error message if the installation failed.
+function Install.install(msg, options) end
+
+---Get the package info
+---@param name any The name of the package
+---@return PackageInfo
+function Install.packageInfo(name) end
+
+return Install
diff --git a/src/plugins/lua/meta/layout.lua b/src/plugins/lua/meta/layout.lua
deleted file mode 100644
index 272171beff..0000000000
--- a/src/plugins/lua/meta/layout.lua
+++ /dev/null
@@ -1,186 +0,0 @@
----@meta Layout
-
-local layout = {}
-
----The base class of all layout items
----@class LayoutItem
-layout.LayoutItem = {
- ---Attaches the layout to the specified widget
- ---@param widget QWidget
- attachTo = function(widget) end
-}
-
----Column layout
----@class Column : LayoutItem
-local column = {}
-
----@param children LayoutItem|string|BaseAspect|function
----@return Column
-function layout.Column(children) end
-
----A group box with a title
----@class Group : LayoutItem
-local group = {}
-
----@return Group
-function layout.Group(children) end
-
----Row layout
----@class Row : LayoutItem
-local row = {}
-
----@param children LayoutItem|string|BaseAspect|function
----@return Row
-function layout.Row(children) end
-
----Flow layout
----@class Flow : LayoutItem
-local flow = {}
-
----@param children LayoutItem|string|BaseAspect|function
----@return Flow
-function layout.Flow(children) end
-
----Grid layout
----@class Grid : LayoutItem
-local grid = {}
-
----@param children LayoutItem|string|BaseAspect|function
----@return Grid
-function layout.Grid(children) end
-
----Form layout
----@class Form : LayoutItem
-local form = {}
-
----@param children LayoutItem|string|BaseAspect|function
----@return Form
-function layout.Form(children) end
-
----An empty widget
----@class Widget : LayoutItem
-local widget = {}
-
----@param children LayoutItem|string|BaseAspect|function
----@return Widget
-function layout.Widget(children) end
-
----A stack of multiple widgets
----@class Stack : LayoutItem
-local stack = {}
-
----@param children LayoutItem|string|BaseAspect|function
----@return Stack
-function layout.Stack(children) end
-
----A Tab widget
----@class Tab : LayoutItem
-local tab = {}
-
----@param children LayoutItem|string|BaseAspect|function
----@return Tab
-function layout.Tab(children) end
-
----A Multiline text edit
----@class TextEdit : LayoutItem
-local textEdit = {}
-
----@param children LayoutItem|string|BaseAspect|function
----@return TextEdit
-function layout.TextEdit(children) end
-
----A PushButton
----@class PushButton : LayoutItem
-local pushButton = {}
-
----@param children LayoutItem|string|BaseAspect|function
----@return PushButton
-function layout.PushButton(children) end
-
----A SpinBox
----@class SpinBox : LayoutItem
-local spinBox = {}
-
----@param children LayoutItem|string|BaseAspect|function
----@return SpinBox
-function layout.SpinBox(children) end
-
----A Splitter
----@class Splitter : LayoutItem
-local splitter = {}
-
----@param children LayoutItem|string|BaseAspect|function
----@return Splitter
-function layout.Splitter(children) end
-
----A Toolbar
----@class ToolBar : LayoutItem
-local toolBar = {}
-
----@param children LayoutItem|string|BaseAspect|function
----@return ToolBar
-function layout.ToolBar(children) end
-
----A TabWidget
----@class TabWidget : LayoutItem
-local tabWidget = {}
-
----@param children LayoutItem|string|BaseAspect|function
----@return TabWidget
-function layout.TabWidget(children) end
-
----A "Line break" in the layout
-function layout.br() end
-
----A "Stretch" in the layout
-function layout.st() end
-
----An empty space in the layout
-function layout.empty() end
-
----A horizontal line in the layout
-function layout.hr() end
-
----Clears the margin of the layout
-function layout.noMargin() end
-
----Sets the margin of the layout to the default value
-function layout.normalMargin() end
-
----Sets the margin of the layout to a custom value
-function layout.customMargin(left, top, right, bottom) end
-
----Sets the alignment of the layout to "Form"
-function layout.withFormAlignment() end
-
----Sets the title of the parent object if possible
-function layout.title(text) end
-
----Sets the text of the parent object if possible
-function layout.text(text) end
-
----Sets the tooltip of the parent object if possible
-function layout.tooltip(text) end
-
----Sets the size of the parent object if possible
-function layout.resize(width, height) end
-
----Sets the stretch of the column at `index`
-function layout.columnStretch(index, stretch) end
-
----Sets the spacing of the layout
-function layout.spacing(spacing) end
-
----Sets the window title of the parent object if possible
-function layout.windowTitle(text) end
-
----Sets the field growth policy of the layout
-function layout.fieldGrowthPolicy(policy) end
-
----Sets the onClicked handler of the parent object if possible
-function layout.onClicked(f) end
-
----Sets the onTextChanged handler of the parent object if possible
-function layout.onTextChanged(f) end
-
-return layout
diff --git a/src/plugins/lua/meta/lsp.lua b/src/plugins/lua/meta/lsp.lua
index 65b1ef8361..1c404c167f 100644
--- a/src/plugins/lua/meta/lsp.lua
+++ b/src/plugins/lua/meta/lsp.lua
@@ -11,6 +11,7 @@ local lsp = {}
---@field startBehavior? "AlwaysOn"|"RequiresFile"|"RequiresProject"
---@field initializationOptions? table|string The initialization options to pass to the language server, either a json string, or a table
---@field settings? AspectContainer
+---@field onStartFailed? function This callback is called when client failed to start.
local ClientOptions = {}
---@class LanguageFilter
diff --git a/src/plugins/lua/meta/process.lua b/src/plugins/lua/meta/process.lua
index 0519990770..53b4f35b7d 100644
--- a/src/plugins/lua/meta/process.lua
+++ b/src/plugins/lua/meta/process.lua
@@ -8,4 +8,10 @@ local process = {}
---@return number The exit code of the command
function process.runInTerminal(cmd) end
+---@async
+---Runs a command and returns the output!
+---@param cmd string The command to run
+---@return string The output of the command
+function process.commandOutput(cmd) end
+
return process
diff --git a/src/plugins/lua/meta/qtc.lua b/src/plugins/lua/meta/qtc.lua
index bfcfd457eb..cc7de85f05 100644
--- a/src/plugins/lua/meta/qtc.lua
+++ b/src/plugins/lua/meta/qtc.lua
@@ -23,6 +23,7 @@ Qtc = {}
---@field hooks? Hooks The hooks of the plugin.
---@field Mimetypes? string XML MIME-info for registering additional or adapting built-in MIME types.
---@field JsonWizardPaths? string[] A list of paths relative to the plugin location or paths to the Qt resource system that are searched for template-based wizards.
+---@field printToOutputPane? boolean Whether the `print(...)` function should print to the output pane or not. ( Default: false )
QtcPlugin = {}
---@class QtcPluginDependency
diff --git a/src/plugins/lua/meta/settings.lua b/src/plugins/lua/meta/settings.lua
index 2c947f4250..09af050d92 100644
--- a/src/plugins/lua/meta/settings.lua
+++ b/src/plugins/lua/meta/settings.lua
@@ -145,6 +145,10 @@ settings.FilePathAspect = {}
---@return FilePathAspect
function settings.FilePathAspect.create(options) end
+---Set the value of the aspect
+---@param value string|FilePath The value to set
+function settings.FilePathAspect:setValue(value) end
+
settings.IntegerAspect = {}
function settings.IntegerAspect.create(options) end
diff --git a/src/plugins/lua/meta/utils.lua b/src/plugins/lua/meta/utils.lua
index 083b87adc1..409946b711 100644
--- a/src/plugins/lua/meta/utils.lua
+++ b/src/plugins/lua/meta/utils.lua
@@ -70,4 +70,34 @@ function utils.FilePath:parentDir() end
---@return FilePath The resolved path
function utils.FilePath:resolveSymlinks() end
+---Returns the suffix of the path (e.g. "test.ui.qml" -> ".qml")
+---@return string
+function utils.FilePath:suffix() end
+
+---Returns the complete suffix of the path (e.g. "test.ui.qml" -> "ui.qml")
+---@return string
+function utils.FilePath:completeSuffix() end
+
+---Returns whether the path is absolute
+---@return boolean
+function utils.FilePath:isAbsolutePath() end
+
+---@class HostOsInfo
+---@field os "mac"|"windows"|"linux" The current host operating system
+---@field architecture "unknown"|"x86"|"x86_64"|"itanium"|"arm"|"arm64" The current host architecture
+utils.HostOsInfo = {}
+
+---Returns whether the host operating system is windows
+---@return boolean
+function utils.HostOsInfo.isWindowsHost() end
+
+---Returns whether the host operating system is mac
+---@return boolean
+function utils.HostOsInfo.isMacHost() end
+
+---Returns whether the host operating system is linux
+---@return boolean
+function utils.HostOsInfo.isLinuxHost() end
+
+
return utils
diff --git a/src/plugins/lua/meta/wizard.lua b/src/plugins/lua/meta/wizard.lua
deleted file mode 100644
index fb5b000d51..0000000000
--- a/src/plugins/lua/meta/wizard.lua
+++ /dev/null
@@ -1,61 +0,0 @@
----@meta Wizard
-
----@module "Layout"
-local Layout = require("Layout")
-
----@module "Core"
-local Core = require("Core")
-
-local wizard = {}
-
----@class (exact) WizardFactoryOptions
----@field id string
----@field displayName string
----@field description string
----@field category string
----@field displayCategory string
----@field icon? string
----@field iconText? string
----@field factory function A function returning a Wizard
-
---- Registers a wizard factory.
----@param options WizardFactoryOptions
-function wizard.registerFactory(options) end
-
----@class Wizard
-Wizard = {}
-
----@class (exact) WizardPageOptions
----@field title string
----@field layout LayoutItem
----@field initializePage? function The function called before showing the page
-
----Add a page to the wizard
----@param options WizardPageOptions
-function Wizard:addPage(options) end
-
----@class SummaryPage
-Wizard.SummaryPage = {}
-
----Set the files to be shown on the summary page
----@param generatedFiles Core.GeneratedFile[]
-function Wizard.SummaryPage:setFiles(generatedFiles) end
-
----@class SummaryPageOptions
----@field title? string
----@field initializePage? function The function called before showing the page
-
----Add a summary page to the wizard
----@param options SummaryPageOptions
----@return SummaryPage
-function Wizard:addSummaryPage(options) end
-
----@class WizardOptions
----@field fileFactory function A function returning a GeneratedFile[]
-
----Create a wizard
----@param options WizardOptions
----@return Wizard
-function wizard.create(options) end
-
-return wizard
diff --git a/src/plugins/luals/CMakeLists.txt b/src/plugins/luals/CMakeLists.txt
new file mode 100644
index 0000000000..cec9a052de
--- /dev/null
+++ b/src/plugins/luals/CMakeLists.txt
@@ -0,0 +1,4 @@
+add_qtc_lua_plugin(luals
+ SOURCES luals/luals.lua
+ luals/init.lua
+)
diff --git a/src/plugins/luals/luals.qbs b/src/plugins/luals/luals.qbs
new file mode 100644
index 0000000000..ea05595f80
--- /dev/null
+++ b/src/plugins/luals/luals.qbs
@@ -0,0 +1,8 @@
+QtcLuaPlugin {
+ name: "luals"
+
+ luafiles: [
+ "init.lua",
+ "luals.lua",
+ ]
+}
diff --git a/src/plugins/lualsp/lualsp/init.lua b/src/plugins/luals/luals/init.lua
index 75162c95c0..e7bc13629c 100644
--- a/src/plugins/lualsp/lualsp/init.lua
+++ b/src/plugins/luals/luals/init.lua
@@ -3,10 +3,11 @@
local LSP = require('LSP')
local mm = require('MessageManager')
local Utils = require('Utils')
-local Process = require('Process')
local S = require('Settings')
-local Layout = require('Layout')
+local Gui = require('Gui')
local a = require('async')
+local fetch = require('Fetch').fetch
+local Install = require('Install')
Settings = {}
@@ -24,6 +25,73 @@ local function createCommand()
return cmd
end
+local function filter(tbl, callback)
+ for i = #tbl, 1, -1 do
+ if not callback(tbl[i]) then
+ table.remove(tbl, i)
+ end
+ end
+end
+
+local function installOrUpdateServer()
+ local data = a.wait(fetch({
+ url = "https://qtccache.qt.io/LuaLanguageServer/LatestRelease",
+ convertToTable = true
+ }))
+
+ if type(data) == "table" and #data > 0 then
+ local r = data[1]
+ local lspPkgInfo = Install.packageInfo("lua-language-server")
+ if not lspPkgInfo or lspPkgInfo.version ~= r.tag_name then
+ local osTr = { mac = "darwin", windows = "win32", linux = "linux" }
+ local archTr = { unknown = "", x86 = "ia32", x86_64 = "x64", itanium = "", arm = "", arm64 = "arm64" }
+ local os = osTr[Utils.HostOsInfo.os]
+ local arch = archTr[Utils.HostOsInfo.architecture]
+
+ local expectedFileName = "lua-language-server-" .. r.tag_name .. "-" .. os .. "-" .. arch
+
+ filter(r.assets, function(asset)
+ return string.find(asset.name, expectedFileName, 1, true) == 1
+ end)
+
+ if #r.assets == 0 then
+ print("No assets found for this platform")
+ return
+ end
+ local res, err = a.wait(Install.install(
+ "Do you want to install the lua-language-server?", {
+ name = "lua-language-server",
+ url = r.assets[1].browser_download_url,
+ version = r.tag_name
+ }))
+
+ if not res then
+ mm.writeFlashing("Failed to install lua-language-server: " .. err)
+ return
+ end
+
+ lspPkgInfo = Install.packageInfo("lua-language-server")
+ print("Installed:", lspPkgInfo.name, "version:", lspPkgInfo.version, "at", lspPkgInfo.path)
+ end
+
+ local binary = "bin/lua-language-server"
+ if Utils.HostOsInfo.isWindowsHost() then
+ binary = "bin/lua-language-server.exe"
+ end
+
+ Settings.binary:setValue(lspPkgInfo.path:resolvePath(binary))
+ Settings:apply()
+ return
+ end
+
+ if type(data) == "string" then
+ print("Failed to fetch:", data)
+ else
+ print("No lua-language-server release found.")
+ end
+end
+IsTryingToInstall = false
+
local function setupClient()
Client = LSP.Client.create({
name = 'Lua Language Server',
@@ -35,6 +103,17 @@ local function setupClient()
},
settings = Settings,
startBehavior = "RequiresFile",
+ onStartFailed = function()
+ a.sync(function()
+ if IsTryingToInstall == true then
+ mm.writeFlashing("RECURSION!");
+ return
+ end
+ IsTryingToInstall = true
+ installOrUpdateServer()
+ IsTryingToInstall = false
+ end)()
+ end
})
Client.on_instance_start = function()
@@ -46,59 +125,62 @@ local function setupClient()
end)
end
-local function installServer()
- print("Lua Language Server not found, installing ...")
- local cmds = {
- mac = "brew install lua-language-server",
- windows = "winget install lua-language-server",
- linux = "sudo apt install lua-language-server"
- }
- if a.wait(Process.runInTerminal(cmds[Utils.HostOsInfo.os])) == 0 then
- print("Lua Language Server installed!")
- Settings.binary.defaultPath = Utils.FilePath.fromUserInput("lua-language-server"):resolveSymlinks()
- Settings:apply()
- return true
- end
-
- print("Lua Language Server installation failed!")
- return false
-end
-
local function using(tbl)
local result = _G
for k, v in pairs(tbl) do result[k] = v end
return result
end
local function layoutSettings()
- --- "using namespace Layout"
- local _ENV = using(Layout)
-
- local installButton = {}
+ --- "using namespace Gui"
+ local _ENV = using(Gui)
- if Settings.binary.expandedValue:isExecutableFile() == false then
- installButton = {
- "Language server not found:",
- Row {
- PushButton {
- text("Try to install lua language server"),
- onClicked(function() a.sync(installServer)() end),
- br,
- },
- st
- }
- }
- end
local layout = Form {
Settings.binary, br,
Settings.developMode, br,
Settings.showSource, br,
Settings.showNode, br,
- table.unpack(installButton)
+ Row {
+ PushButton {
+ text = "Update Lua Language Server",
+ onClicked = function() a.sync(installOrUpdateServer)() end,
+ },
+ st
+ }
}
-
return layout
end
+local function binaryFromPkg()
+ local lspPkgInfo = Install.packageInfo("lua-language-server")
+ if lspPkgInfo then
+ local binary = "bin/lua-language-server"
+ if Utils.HostOsInfo.isWindowsHost() then
+ binary = "bin/lua-language-server.exe"
+ end
+ local binaryPath = lspPkgInfo.path:resolvePath(binary)
+ if binaryPath:isExecutableFile() == true then
+ return binaryPath
+ end
+ end
+
+ return nil
+end
+
+local function findBinary()
+ local binary = binaryFromPkg()
+ if binary then
+ return binary
+ end
+
+ -- Search for the binary in the PATH
+ local serverPath = Utils.FilePath.fromUserInput("lua-language-server")
+ local absolute = a.wait(serverPath:searchInPath()):resolveSymlinks()
+ if absolute:isExecutableFile() == true then
+ return absolute
+ end
+ return serverPath
+end
+
local function setupAspect()
---@class Settings: AspectContainer
Settings = S.AspectContainer.create({
@@ -112,8 +194,9 @@ local function setupAspect()
labelText = "Binary:",
toolTip = "The path to the lua-language-server binary.",
expectedKind = S.Kind.ExistingCommand,
- defaultPath = Utils.FilePath.fromUserInput("lua-language-server"):resolveSymlinks(),
+ defaultPath = findBinary(),
})
+
Settings.developMode = S.BoolAspect.create({
settingsKey = "LuaCopilot.DevelopMode",
displayName = "Enable Develop Mode",
@@ -143,15 +226,7 @@ local function setupAspect()
return Settings
end
local function setup(parameters)
- print("Setting up Lua Language Server ...")
setupAspect()
- local serverPath = Utils.FilePath.fromUserInput("lua-language-server")
- local absolute = a.wait(serverPath:searchInPath()):resolveSymlinks()
- if absolute:isExecutableFile() == true then
- Settings.binary.defaultPath = absolute
- else
- installServer()
- end
setupClient()
end
diff --git a/src/plugins/lualsp/lualsp/lualsp.lua b/src/plugins/luals/luals/luals.lua
index ba2de87414..843d18189e 100644
--- a/src/plugins/lualsp/lualsp/lualsp.lua
+++ b/src/plugins/luals/luals/luals.lua
@@ -21,10 +21,4 @@ It will try to install it if it is not found.
setup = function()
require 'init'.setup()
end,
- hooks = {
- editors = {
- documentOpened = function(doc) print("documentOpened", doc) end,
- documentClosed = function(doc) print("documentClosed", doc) end,
- }
- }
} --[[@as QtcPlugin]]
diff --git a/src/plugins/lualsp/CMakeLists.txt b/src/plugins/lualsp/CMakeLists.txt
deleted file mode 100644
index bd794c48b1..0000000000
--- a/src/plugins/lualsp/CMakeLists.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-add_qtc_lua_plugin(lualsp
- SOURCES lualsp/lualsp.lua
- lualsp/init.lua
-)
diff --git a/src/plugins/luatemplates/CMakeLists.txt b/src/plugins/luatemplates/CMakeLists.txt
deleted file mode 100644
index 87088b7058..0000000000
--- a/src/plugins/luatemplates/CMakeLists.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-add_qtc_plugin(LuaTemplates
- CONDITION TARGET Lua
- PLUGIN_DEPENDS Lua TextEditor ProjectExplorer Core
- SOURCES
- luatemplates.cpp
-)
-
-add_subdirectory(templates)
diff --git a/src/plugins/luatemplates/luatemplates.cpp b/src/plugins/luatemplates/luatemplates.cpp
deleted file mode 100644
index 3adea77040..0000000000
--- a/src/plugins/luatemplates/luatemplates.cpp
+++ /dev/null
@@ -1,402 +0,0 @@
-// Copyright (C) 2024 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#include <lua/bindings/inheritance.h>
-#include <lua/luaengine.h>
-
-#include <coreplugin/dialogs/promptoverwritedialog.h>
-#include <coreplugin/editormanager/editormanager.h>
-#include <coreplugin/icore.h>
-#include <coreplugin/iwizardfactory.h>
-
-#include <extensionsystem/iplugin.h>
-#include <extensionsystem/pluginmanager.h>
-
-#include <utils/algorithm.h>
-#include <utils/expected.h>
-#include <utils/layoutbuilder.h>
-#include <utils/mimeutils.h>
-#include <utils/wizard.h>
-#include <utils/wizardpage.h>
-
-#include <projectexplorer/editorconfiguration.h>
-#include <projectexplorer/project.h>
-#include <projectexplorer/projectexplorertr.h>
-#include <projectexplorer/projectwizardpage.h>
-
-#include <texteditor/icodestylepreferences.h>
-#include <texteditor/icodestylepreferencesfactory.h>
-#include <texteditor/storagesettings.h>
-#include <texteditor/tabsettings.h>
-#include <texteditor/texteditorsettings.h>
-#include <texteditor/textindenter.h>
-
-#include <QMessageBox>
-
-using namespace Utils;
-using namespace Layouting;
-using namespace Core;
-using namespace ProjectExplorer;
-using namespace TextEditor;
-
-namespace LuaTemplates {
-
-static ICodeStylePreferences *codeStylePreferences(Project *project, Id languageId)
-{
- if (!languageId.isValid())
- return nullptr;
-
- if (project)
- return project->editorConfiguration()->codeStyle(languageId);
-
- return TextEditorSettings::codeStyle(languageId);
-}
-
-class LuaWizard : public Wizard
-{
-public:
- LuaWizard()
- : Wizard(Core::ICore::dialogParent())
- {}
-
- expected_str<bool> promptForOverwrite(GeneratedFiles &files)
- {
- FilePaths existingFiles;
- bool oddStuffFound = false;
-
- for (const auto &f : files) {
- if (f.filePath().exists() && !(f.attributes() & GeneratedFile::ForceOverwrite)
- && !(f.attributes() & GeneratedFile::KeepExistingFileAttribute))
- existingFiles.append(f.filePath());
- }
- if (existingFiles.isEmpty())
- return true;
-
- // Before prompting to overwrite existing files, loop over files and check
- // if there is anything blocking overwriting them (like them being links or folders).
- // Format a file list message as ( "<file1> [readonly], <file2> [folder]").
- const QString commonExistingPath = FileUtils::commonPath(existingFiles).toUserOutput();
- const int commonPathSize = commonExistingPath.size();
- QString fileNamesMsgPart;
- for (const FilePath &filePath : std::as_const(existingFiles)) {
- if (filePath.exists()) {
- if (!fileNamesMsgPart.isEmpty())
- fileNamesMsgPart += QLatin1String(", ");
- const QString namePart = filePath.toUserOutput().mid(commonPathSize);
- if (filePath.isDir()) {
- oddStuffFound = true;
- fileNamesMsgPart += Tr::tr("%1 [folder]").arg(namePart);
- } else if (filePath.isSymLink()) {
- oddStuffFound = true;
- fileNamesMsgPart += Tr::tr("%1 [symbolic link]").arg(namePart);
- } else if (!filePath.isWritableDir() && !filePath.isWritableFile()) {
- oddStuffFound = true;
- fileNamesMsgPart += Tr::tr("%1 [read only]").arg(namePart);
- }
- }
- }
-
- if (oddStuffFound) {
- return make_unexpected(
- Tr::tr("The directory %1 contains files which cannot be overwritten:\n%2.")
- .arg(commonExistingPath)
- .arg(fileNamesMsgPart));
- }
-
- // Prompt to overwrite existing files.
- PromptOverwriteDialog overwriteDialog;
-
- // Scripts cannot handle overwrite
- overwriteDialog.setFiles(existingFiles);
- for (const auto &file : files) {
- if (!allowKeepingExistingFiles)
- overwriteDialog.setFileEnabled(file.filePath(), false);
- }
- if (overwriteDialog.exec() != QDialog::Accepted)
- return false;
-
- const QSet<FilePath> existingFilesToKeep = Utils::toSet(overwriteDialog.uncheckedFiles());
- if (existingFilesToKeep.size() == files.size()) // All exist & all unchecked->Cancel.
- return false;
-
- // Set 'keep' attribute in files
- for (auto &file : files) {
- if (!existingFilesToKeep.contains(file.filePath()))
- continue;
-
- file.setAttributes(file.attributes() | GeneratedFile::KeepExistingFileAttribute);
- }
- return true;
- }
-
- void formatFile(GeneratedFile &file)
- {
- if (file.isBinary() || file.contents().isEmpty())
- return; // nothing to do
-
- Id languageId = TextEditorSettings::languageId(
- Utils::mimeTypeForFile(file.filePath()).name());
-
- if (!languageId.isValid())
- return; // don't modify files like *.ui, *.pro
-
- // TODO:
- auto baseProject
- = nullptr; // qobject_cast<Project *>( wizard->property("SelectedProject").value<QObject *>());
-
- ICodeStylePreferencesFactory *factory = TextEditorSettings::codeStyleFactory(languageId);
-
- QTextDocument doc(file.contents());
- QTextCursor cursor(&doc);
- Indenter *indenter = nullptr;
- if (factory) {
- indenter = factory->createIndenter(&doc);
- indenter->setFileName(file.filePath());
- }
- if (!indenter)
- indenter = new TextIndenter(&doc);
- ICodeStylePreferences *codeStylePrefs = codeStylePreferences(baseProject, languageId);
- indenter->setCodeStylePreferences(codeStylePrefs);
-
- cursor.select(QTextCursor::Document);
- indenter->indent(cursor, QChar::Null, codeStylePrefs->currentTabSettings());
- delete indenter;
- if (TextEditor::globalStorageSettings().m_cleanWhitespace) {
- QTextBlock block = doc.firstBlock();
- while (block.isValid()) {
- TabSettings::removeTrailingWhitespace(cursor, block);
- block = block.next();
- }
- }
- file.setContents(doc.toPlainText());
- }
-
- void formatFiles(GeneratedFiles &files)
- {
- for (auto &file : files)
- formatFile(file);
- }
-
- void accept() override
- {
- auto files = Lua::LuaEngine::safe_call<GeneratedFiles>(fileFactory);
- QTC_ASSERT_EXPECTED(files, return);
-
- auto result = promptForOverwrite(*files);
- if (!result) {
- QMessageBox::warning(this, Tr::tr("Failed to Overwrite Files"), result.error());
- return;
- }
-
- formatFiles(*files);
-
- if (*result) {
- for (const auto &file : *files) {
- QString errorMsg;
- if (file.attributes().testFlag(GeneratedFile::KeepExistingFileAttribute))
- continue;
-
- if (!file.write(&errorMsg)) {
- qWarning() << "Failed writing file:" << errorMsg;
- continue;
- } else if (file.attributes().testFlag(GeneratedFile::OpenEditorAttribute)) {
- EditorManager::openEditor(file.filePath());
- }
- }
- }
-
- Wizard::accept();
- return;
- }
-
- void reject() override { Wizard::reject(); }
-
- sol::protected_function fileFactory;
- bool allowKeepingExistingFiles{true};
-};
-
-class WizardFactory : public IWizardFactory
-{
-public:
- WizardFactory() {}
-
- Wizard *runWizardImpl(
- const FilePath &path,
- QWidget *parent,
- Id /*platform*/,
- const QVariantMap & /*variables*/,
- bool showWizard = true) override
- {
- // We assume that the parent is always "dialogParent".
- QTC_CHECK(parent == Core::ICore::dialogParent());
- auto wizard = Lua::LuaEngine::safe_call<LuaWizard *>(m_setupFunction, path);
- QTC_ASSERT_EXPECTED(wizard, return nullptr);
-
- if (showWizard)
- (*wizard)->show();
- return (*wizard);
- }
-
- sol::protected_function m_setupFunction;
-};
-
-class LuaWizardPage : public WizardPage
-{
-public:
- void initializePage() override
- {
- if (m_initializePage) {
- auto res = Lua::LuaEngine::void_safe_call(*m_initializePage, this);
- QTC_CHECK_EXPECTED(res);
- }
- WizardPage::initializePage();
- }
- std::optional<sol::function> m_initializePage;
-};
-
-class SummaryPage : public ProjectWizardPage
-{
-public:
- void initializePage() override
- {
- if (m_initializePage) {
- auto res = Lua::LuaEngine::void_safe_call(*m_initializePage, this);
- QTC_CHECK_EXPECTED(res);
- }
-
- FilePaths paths = Utils::transform(m_files,
- [](const GeneratedFile &f) { return f.filePath(); });
- initializeProjectTree(nullptr,
- paths,
- IWizardFactory::WizardKind::FileWizard,
- ProjectAction::AddNewFile);
-
- initializeVersionControls();
-
- ProjectWizardPage::initializePage();
- }
-
- void setFiles(const GeneratedFiles &files)
- {
- m_files = std::move(files);
- FilePaths paths = Utils::transform(m_files,
- [](const GeneratedFile &f) { return f.filePath(); });
- ProjectWizardPage::setFiles(paths);
- }
-
- GeneratedFiles m_files;
- std::optional<sol::protected_function> m_initializePage;
-};
-
-class LuaTemplatesPlugin final : public ExtensionSystem::IPlugin
-{
- Q_OBJECT
- Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "LuaTemplates.json")
-
-public:
- LuaTemplatesPlugin() {}
-
- using Factories = QList<std::function<IWizardFactory *()>>;
- using WeakFactories = std::weak_ptr<QList<std::function<IWizardFactory *()>>>;
-
-private:
- void initialize() final
- {
- Lua::LuaEngine::registerProvider(
- "Wizard", [](sol::state_view lua) -> sol::object {
- sol::table wizard = lua.create_table();
- wizard.new_usertype<WizardFactory>("Factory", sol::no_constructor);
-
- wizard.new_usertype<SummaryPage>(
- "SummaryPage", "setFiles", [](SummaryPage *page, QList<GeneratedFile> files) {
- page->setFiles(std::move(files));
- });
-
- wizard.new_usertype<LuaWizard>(
- "Wizard",
- sol::no_constructor,
- "addPage",
- [](LuaWizard *wizard, const sol::table &options) {
- LuaWizardPage *page = new LuaWizardPage();
-
- page->m_initializePage = options.get<std::optional<sol::function>>(
- "initializePage");
-
- page->setTitle(options.get<QString>("title"));
- auto item = options.get<Layouting::LayoutItem *>("layout");
- item->attachTo(page);
- wizard->addPage(page);
- return page;
- },
- "addSummaryPage",
- [](LuaWizard *wizard, const sol::table &options) {
- SummaryPage *page = new SummaryPage();
-
- page->m_initializePage = options.get<std::optional<sol::function>>(
- "initializePage");
-
- wizard->addPage(page);
- return page;
- });
-
- wizard.set_function("create", [](const sol::table &options) {
- std::unique_ptr<LuaWizard> wizard(new LuaWizard());
- wizard->fileFactory = options.get<sol::function>("fileFactory");
- wizard->allowKeepingExistingFiles
- = options.get<std::optional<bool>>("allowKeepingExistingFiles")
- .value_or(true);
- return wizard.release();
- });
-
- wizard.set_function(
- "registerFactory",
- [factories = std::make_shared<Factories>()](const sol::table &options) mutable {
- // We need to make sure that all options are available before registering
- // the factory.
- Lua::LuaEngine::checkKey<QString>(options, "id");
- Lua::LuaEngine::checkKey<QString>(options, "displayName");
- Lua::LuaEngine::checkKey<QString>(options, "description");
- Lua::LuaEngine::checkKey<QString>(options, "category");
- Lua::LuaEngine::checkKey<QString>(options, "displayCategory");
- Lua::LuaEngine::checkKey<sol::function>(options, "factory");
-
- // We have to make sure that no lua object is accessed after the lua_state
- // is destroyed. Therefore we store the factory in a shared_ptr and
- // only give a weak pointer to the actual registered factory function.
- // That way we can make sure that the factory list is destroyed when the
- // lua_state is destroyed, as the current function is stored inside the table
- // "wizard" which is automatically destroyed when the lua_state is destroyed.
- factories->push_back([options]() -> IWizardFactory * {
- std::unique_ptr<WizardFactory> factory(new WizardFactory());
-
- factory->setId(Utils::Id::fromString(options.get<QString>("id")));
- factory->setDisplayName(options.get<QString>("displayName"));
- factory->setDescription(options.get<QString>("description"));
- factory->setCategory(options.get<QString>("category"));
- factory->setDisplayCategory(options.get<QString>("displayCategory"));
- factory->setFlags(IWizardFactory::PlatformIndependent);
- factory->setIcon(
- QIcon(options.get_or<QString>("icon", {})),
- options.get_or<QString>("iconText", {}));
- factory->m_setupFunction = options.get<sol::function>("factory");
-
- return factory.release();
- });
-
- IWizardFactory::registerFactoryCreator(
- [weakFactories = WeakFactories(factories),
- index = factories->size() - 1]() -> IWizardFactory * {
- if (auto factories = weakFactories.lock())
- return (*factories)[index]();
- return nullptr;
- });
- });
-
- return wizard;
- });
- }
-};
-
-} // namespace LuaTemplates
-
-#include "luatemplates.moc"
diff --git a/src/plugins/luatemplates/templates/CMakeLists.txt b/src/plugins/luatemplates/templates/CMakeLists.txt
deleted file mode 100644
index 96e6dee944..0000000000
--- a/src/plugins/luatemplates/templates/CMakeLists.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-add_qtc_lua_plugin(lua_basic_templates
- SOURCES basic_templates/basic_templates.lua
- basic_templates/init.lua
-)
diff --git a/src/plugins/luatemplates/templates/basic_templates/basic_templates.lua b/src/plugins/luatemplates/templates/basic_templates/basic_templates.lua
deleted file mode 100644
index d3668daf4d..0000000000
--- a/src/plugins/luatemplates/templates/basic_templates/basic_templates.lua
+++ /dev/null
@@ -1,13 +0,0 @@
--- Copyright (C) 2024 The Qt Company Ltd.
--- SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-return {
- Name = "BasicTemplates",
- Version = "1.0.0",
- CompatVersion = "1.0.0",
- Vendor = "The Qt Company",
- Category = "Templates",
- Dependencies = {
- { Name = "LuaTemplates", Version = "13.0.82", Required = true },
- },
- setup = function() require "init".setup() end,
-} --[[@as QtcPlugin]]
diff --git a/src/plugins/luatemplates/templates/basic_templates/init.lua b/src/plugins/luatemplates/templates/basic_templates/init.lua
deleted file mode 100644
index 60399467bd..0000000000
--- a/src/plugins/luatemplates/templates/basic_templates/init.lua
+++ /dev/null
@@ -1,65 +0,0 @@
--- Copyright (C) 2024 The Qt Company Ltd.
--- SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-local Wizard = require('Wizard')
-local Layout = require('Layout')
-local Settings = require('Settings')
-local Core = require('Core')
-local Utils = require('Utils')
-
----@param settings AspectContainer
-local function generateFiles(settings)
- local mainFile = Core.GeneratedFile.new()
- mainFile.filePath = settings.path.expandedValue:resolvePath(settings.fileName.value)
- mainFile.contents = [[print("Hello world!")]]
- mainFile.attributes = Core.GeneratedFile.Attribute.OpenEditorAttribute
- return { mainFile }
-end
-
-local function createWizard(path)
- ---@class AspectContainer
- local settings = Settings.AspectContainer.create({
- autoApply = true,
- })
- settings.fileName = Settings.StringAspect.create({
- defaultValue = "script.lua",
- displayStyle = Settings.StringDisplayStyle.LineEdit,
- historyId = "BasicTemplate.FileName",
- })
- settings.path = Settings.FilePathAspect.create({
- defaultPath = path,
- expectedKind = Settings.Kind.ExistingDirectory,
- })
-
- local wizard = Wizard.create({
- fileFactory = function() return generateFiles(settings) end,
- })
-
- wizard:addPage({
- title = "Location",
- layout = Layout.Form {
- "File name:", settings.fileName, Layout.br,
- "Path:", settings.path, Layout.br,
- },
- })
-
- wizard:addSummaryPage({
- initializePage = function(page)
- page:setFiles(generateFiles(settings))
- end
- })
- return wizard
-end
-
-local function setup()
- Wizard.registerFactory({
- id = "org.qtproject.Qt.QtCreator.Plugin.LuaTemplates.BasicTemplates",
- displayName = "Basic Templates",
- description = "Basic Template for Lua",
- category = "Lua",
- displayCategory = "Lua",
- iconText = "lua",
- factory = createWizard,
- })
-end
-
-return { setup = setup }
diff --git a/src/plugins/luatests/luatests.qbs b/src/plugins/luatests/luatests.qbs
new file mode 100644
index 0000000000..d521ed9b71
--- /dev/null
+++ b/src/plugins/luatests/luatests.qbs
@@ -0,0 +1,13 @@
+QtcLuaPlugin {
+ name: "luatests"
+
+ luafiles: [
+ "inspect.lua",
+ "luatests.lua",
+ "qtctest.lua",
+ "tests.lua",
+ "tst_aspectcontainer.lua",
+ "tst_fetch.lua",
+ "tst_utils.lua",
+ ]
+}
diff --git a/src/plugins/luatests/luatests/luatests.lua b/src/plugins/luatests/luatests/luatests.lua
index db0fb7f0c6..4d22738f3b 100644
--- a/src/plugins/luatests/luatests/luatests.lua
+++ b/src/plugins/luatests/luatests/luatests.lua
@@ -17,4 +17,5 @@ return {
{ Name = "Lua", Version = "13.0.82", Required = true }
},
setup = function() require 'tests'.setup() end,
+ printToOutputPane = true,
} --[[@as QtcPlugin]]
diff --git a/src/plugins/luatests/luatests/tst_utils.lua b/src/plugins/luatests/luatests/tst_utils.lua
index c6bbfcf084..940f6ff7e9 100644
--- a/src/plugins/luatests/luatests/tst_utils.lua
+++ b/src/plugins/luatests/luatests/tst_utils.lua
@@ -20,7 +20,15 @@ local function testSearchInPath()
print("Hostname found at:", result)
end
+local function testWaitMs()
+ local u = require("Utils")
+ print("Starting to wait ...")
+ a.wait(u.waitms(1000))
+ print("About a second should have elapsed now.")
+end
+
return {
testDirEntries = testDirEntries,
testSearchInPath = testSearchInPath,
+ testWaitMs = testWaitMs,
}
diff --git a/src/plugins/macros/Macros.json.in b/src/plugins/macros/Macros.json.in
index 1c5d949c1c..c43396152b 100644
--- a/src/plugins/macros/Macros.json.in
+++ b/src/plugins/macros/Macros.json.in
@@ -12,7 +12,10 @@
"",
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
- "Description" : "Macros in text editors.",
- "Url" : "http://www.qt.io",
+ "Description" : "Record and play macros in text editors",
+ "LongDescription" : [
+ "When you have a file open in the code editor, you can record a keyboard sequence as a macro. You can then play the macro to repeat the sequence. You can save the latest macro and assign a keyboard shortcut for running it or run it from the locator."
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/marketplace/Marketplace.json.in b/src/plugins/marketplace/Marketplace.json.in
index 1122b40a67..d6422b2cd9 100644
--- a/src/plugins/marketplace/Marketplace.json.in
+++ b/src/plugins/marketplace/Marketplace.json.in
@@ -12,7 +12,8 @@
"",
"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."
],
-"Description" : "Qt Marketplace plugin.",
-"Url" : "http://www.qt.io",
+"Description" : "Install applications from Qt Marketplace",
+"LongDescription" : [],
+"Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/marketplace/qtmarketplacewelcomepage.cpp b/src/plugins/marketplace/qtmarketplacewelcomepage.cpp
index e315ce7efb..f26a618a10 100644
--- a/src/plugins/marketplace/qtmarketplacewelcomepage.cpp
+++ b/src/plugins/marketplace/qtmarketplacewelcomepage.cpp
@@ -62,11 +62,11 @@ public:
Column {
Row {
m_searcher,
- customMargin({0, 0, ExVPaddingGapXl, 0}),
+ customMargins(0, 0, ExVPaddingGapXl, 0),
},
m_sectionedProducts,
spacing(VPaddingL),
- customMargin({ExVPaddingGapXl, ExVPaddingGapXl, 0, 0}),
+ customMargins(ExVPaddingGapXl, ExVPaddingGapXl, 0, 0),
}.attachTo(this);
connect(m_sectionedProducts, &SectionedProducts::toggleProgressIndicator,
diff --git a/src/plugins/mcusupport/McuSupport.json.in b/src/plugins/mcusupport/McuSupport.json.in
index bac0409ee7..1064975989 100644
--- a/src/plugins/mcusupport/McuSupport.json.in
+++ b/src/plugins/mcusupport/McuSupport.json.in
@@ -14,8 +14,13 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Device Support",
- "Description" : "Helper for MCU related projects.",
- "Url" : "http://www.qt.io",
+ "Description" : "Develop for microcontrollers",
+ "LongDescription" : [
+ "Use subsets of QML and Qt Quick Controls to create user interfaces for devices that are powered by microcontrollers (MCU).",
+ "You also need:",
+ "- Qt for MCUs"
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES},
"JsonWizardPaths" : [":/mcusupport/wizards/"]
diff --git a/src/plugins/mcusupport/mcukitaspect.cpp b/src/plugins/mcusupport/mcukitaspect.cpp
index 8d497f209d..1ab803b243 100644
--- a/src/plugins/mcusupport/mcukitaspect.cpp
+++ b/src/plugins/mcusupport/mcukitaspect.cpp
@@ -24,7 +24,7 @@ public:
void makeReadOnly() override {}
void refresh() override {}
- void addToLayoutImpl(Layouting::LayoutItem &) override {}
+ void addToLayoutImpl(Layouting::Layout &) override {}
};
Utils::Id McuDependenciesKitAspect::id()
@@ -78,7 +78,7 @@ public:
const QVariant checkFormat = kit->value(McuDependenciesKitAspect::id());
if (!checkFormat.isValid() || checkFormat.isNull())
return result;
- if (!checkFormat.canConvert(QVariant::List))
+ if (!checkFormat.canConvert(QMetaType::QVariantList))
return {BuildSystemTask(Task::Error, Tr::tr("The MCU dependencies setting value is invalid."))};
// check paths defined in cmake variables for given dependencies exist
@@ -105,7 +105,7 @@ public:
QTC_ASSERT(kit, return );
const QVariant variant = kit->value(McuDependenciesKitAspect::id());
- if (!variant.isNull() && !variant.canConvert(QVariant::List)) {
+ if (!variant.isNull() && !variant.canConvert(QMetaType::QVariantList)) {
qWarning("Kit \"%s\" has a wrong mcu dependencies value set.",
qPrintable(kit->displayName()));
McuDependenciesKitAspect::setDependencies(kit, Utils::EnvironmentItems());
diff --git a/src/plugins/mcusupport/mcupackage.cpp b/src/plugins/mcusupport/mcupackage.cpp
index 2ff913c680..2ccc4bcc1a 100644
--- a/src/plugins/mcusupport/mcupackage.cpp
+++ b/src/plugins/mcusupport/mcupackage.cpp
@@ -43,11 +43,11 @@ McuPackage::McuPackage(const SettingsHandler::Ptr &settingsHandler,
const McuPackageVersionDetector *versionDetector,
const bool addToSystemPath,
const Utils::PathChooser::Kind &valueType,
- const bool useNewestVersionKey)
+ const bool allowNewerVersionKey)
: settingsHandler(settingsHandler)
, m_label(label)
, m_detectionPaths(detectionPaths)
- , m_settingsKey(settingsHandler->getVersionedKey(settingsKey, QSettings::SystemScope, versions, useNewestVersionKey))
+ , m_settingsKey(settingsKey)
, m_versionDetector(versionDetector)
, m_versions(versions)
, m_cmakeVariableName(cmakeVarName)
@@ -56,8 +56,18 @@ McuPackage::McuPackage(const SettingsHandler::Ptr &settingsHandler,
, m_addToSystemPath(addToSystemPath)
, m_valueType(valueType)
{
- m_defaultPath = settingsHandler->getPath(m_settingsKey, QSettings::SystemScope, defaultPath);
- m_path = settingsHandler->getPath(m_settingsKey, QSettings::UserScope, m_defaultPath);
+ // The installer writes versioned keys as well as the plain key as found in the kits.
+ // Use the versioned key in case the plain key was removed by an uninstall operation
+ const Utils::Key versionedKey = settingsHandler->getVersionedKey(settingsKey,
+ QSettings::SystemScope,
+ versions,
+ allowNewerVersionKey);
+ m_defaultPath = settingsHandler->getPath(versionedKey, QSettings::SystemScope, defaultPath);
+ m_path = settingsHandler->getPath(m_settingsKey, QSettings::UserScope, "");
+ // The user settings may have been written with a versioned key in older versions of QtCreator
+ if (m_path.isEmpty()) {
+ m_path = settingsHandler->getPath(versionedKey, QSettings::UserScope, m_defaultPath);
+ }
if (m_path.isEmpty()) {
m_path = FilePath::fromUserInput(qtcEnvironmentVariable(m_environmentVariableName));
}
@@ -559,6 +569,10 @@ QVariant McuToolchainPackage::debuggerId() const
switch (m_type) {
case ToolchainType::ArmGcc: {
sub = QString::fromLatin1("bin/arm-none-eabi-gdb-py");
+ const FilePath command = (path() / sub).withExecutableSuffix();
+ if (!command.exists()) {
+ sub = QString::fromLatin1("bin/arm-none-eabi-gdb");
+ }
displayName = Tr::tr("Arm GDB at %1");
engineType = Debugger::GdbEngineType;
break;
diff --git a/src/plugins/mcusupport/mcupackage.h b/src/plugins/mcusupport/mcupackage.h
index a74c08bcf2..7b9ef64650 100644
--- a/src/plugins/mcusupport/mcupackage.h
+++ b/src/plugins/mcusupport/mcupackage.h
@@ -41,7 +41,7 @@ public:
const bool addToPath = false,
const Utils::PathChooser::Kind &valueType
= Utils::PathChooser::Kind::ExistingDirectory,
- const bool useNewestVersionKey = false);
+ const bool allowNewerVersionKey = false);
~McuPackage() override = default;
diff --git a/src/plugins/mcusupport/settingshandler.cpp b/src/plugins/mcusupport/settingshandler.cpp
index 1ecec030b4..939ba125d1 100644
--- a/src/plugins/mcusupport/settingshandler.cpp
+++ b/src/plugins/mcusupport/settingshandler.cpp
@@ -105,12 +105,24 @@ FilePath SettingsHandler::getPath(const Key &settingsKey,
bool SettingsHandler::write(const Key &settingsKey,
const FilePath &path,
- const FilePath &defaultPath) const
+ const FilePath &maybeDefaultPath) const
{
const FilePath savedPath = packagePathFromSettings(settingsKey,
*Core::ICore::settings(QSettings::UserScope),
- defaultPath);
+ maybeDefaultPath);
const Key key = Key(Constants::SETTINGS_GROUP) + '/' + Constants::SETTINGS_KEY_PACKAGE_PREFIX + settingsKey;
+
+ FilePath defaultPath = maybeDefaultPath;
+ if (path == maybeDefaultPath) {
+ // If the installer has overwritten the non-versioned key with an older version than the
+ // newest versioned key, and the user wants to manually return to the newest installed
+ // version, the defaultPath will match the desired path, and the settings object will
+ // assume it can simply remove the key instead of writing a new value to it.
+ // To work around this, pretend like the default value is the value found from the global scope
+ defaultPath = packagePathFromSettings(settingsKey,
+ *Core::ICore::settings(QSettings::SystemScope),
+ maybeDefaultPath);;
+ }
Core::ICore::settings()->setValueWithDefault(key,
path.toUserOutput(),
defaultPath.toUserOutput());
diff --git a/src/plugins/mercurial/Mercurial.json.in b/src/plugins/mercurial/Mercurial.json.in
index 5c897570e5..e6be967df6 100644
--- a/src/plugins/mercurial/Mercurial.json.in
+++ b/src/plugins/mercurial/Mercurial.json.in
@@ -13,7 +13,11 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Version Control",
- "Description" : "Mercurial integration.",
- "Url" : "http://www.qt.io",
+ "Description" : "Access the Mercurial source control management tool",
+ "LongDescription" : [
+ "You also need:",
+ "- Mercurial"
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/mercurial/mercurialplugin.cpp b/src/plugins/mercurial/mercurialplugin.cpp
index f87d181da4..6d7a1ef730 100644
--- a/src/plugins/mercurial/mercurialplugin.cpp
+++ b/src/plugins/mercurial/mercurialplugin.cpp
@@ -669,7 +669,7 @@ bool MercurialPluginPrivate::managesFile(const FilePath &workingDirectory, const
bool MercurialPluginPrivate::isConfigured() const
{
- const FilePath binary = settings().binaryPath();
+ const FilePath binary = settings().binaryPath.effectiveBinary();
if (binary.isEmpty())
return false;
QFileInfo fi = binary.toFileInfo();
@@ -734,11 +734,9 @@ VcsCommand *MercurialPluginPrivate::createInitialCheckoutCommand(const QString &
const QString &localName,
const QStringList &extraArgs)
{
- QStringList args;
- args << QLatin1String("clone") << extraArgs << url << localName;
auto command = VcsBaseClient::createVcsCommand(this, baseDirectory,
mercurialClient().processEnvironment(baseDirectory));
- command->addJob({settings().binaryPath(), args}, -1);
+ command->addJob({settings().binaryPath(), {"clone", extraArgs, url, localName}}, -1);
return command;
}
@@ -755,11 +753,11 @@ bool MercurialPluginPrivate::sccManaged(const QString &filename)
void MercurialPluginPrivate::changed(const QVariant &v)
{
- switch (v.type()) {
- case QVariant::String:
+ switch (v.typeId()) {
+ case QMetaType::QString:
emit repositoryChanged(FilePath::fromVariant(v));
break;
- case QVariant::StringList:
+ case QMetaType::QStringList:
emit filesChanged(v.toStringList());
break;
default:
diff --git a/src/plugins/mercurial/mercurialsettings.cpp b/src/plugins/mercurial/mercurialsettings.cpp
index 05c96672f9..79423ab774 100644
--- a/src/plugins/mercurial/mercurialsettings.cpp
+++ b/src/plugins/mercurial/mercurialsettings.cpp
@@ -30,7 +30,7 @@ MercurialSettings::MercurialSettings()
binaryPath.setExpectedKind(PathChooser::ExistingCommand);
binaryPath.setDefaultValue(Constants::MERCURIALDEFAULT);
binaryPath.setDisplayName(Tr::tr("Mercurial Command"));
- binaryPath.setHistoryCompleter("Bazaar.Command.History");
+ binaryPath.setHistoryCompleter("Mercurial.Command.History");
binaryPath.setLabelText(Tr::tr("Command:"));
userName.setDisplayStyle(StringAspect::LineEditDisplay);
diff --git a/src/plugins/mesonprojectmanager/MesonProjectManager.json.in b/src/plugins/mesonprojectmanager/MesonProjectManager.json.in
index 169ac45e62..0dbf60653f 100644
--- a/src/plugins/mesonprojectmanager/MesonProjectManager.json.in
+++ b/src/plugins/mesonprojectmanager/MesonProjectManager.json.in
@@ -15,7 +15,13 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Build Systems",
- "Description" : "Meson support.",
- "Url" : "http://www.mesonbuild.com",
+ "Description" : "Generate build systems with Meson",
+ "LongDescription" : [
+ "Generate build systems with Meson using Ninja as the main backend.",
+ "You also need:",
+ "- Meson",
+ "- Ninja"
+ ],
+ "Url" : "https://www.mesonbuild.com",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/mesonprojectmanager/buildoptionsmodel.cpp b/src/plugins/mesonprojectmanager/buildoptionsmodel.cpp
index 1f8e7acd9b..f8db40b749 100644
--- a/src/plugins/mesonprojectmanager/buildoptionsmodel.cpp
+++ b/src/plugins/mesonprojectmanager/buildoptionsmodel.cpp
@@ -118,25 +118,25 @@ bool BuidOptionsModel::hasChanges() const
QWidget *BuildOptionDelegate::makeWidget(QWidget *parent, const QVariant &data)
{
- auto type = data.userType();
+ const int type = data.typeId();
switch (type) {
- case QVariant::Int: {
+ case QMetaType::Int: {
auto w = new QSpinBox{parent};
w->setValue(data.toInt());
return w;
}
- case QVariant::Bool: {
+ case QMetaType::Bool: {
auto w = new QComboBox{parent};
w->addItems({"false", "true"});
w->setCurrentIndex(data.toBool());
return w;
}
- case QVariant::StringList: {
+ case QMetaType::QStringList: {
auto w = new ArrayOptionLineEdit{parent};
w->setPlainText(data.toStringList().join(" "));
return w;
}
- case QVariant::String: {
+ case QMetaType::QString: {
auto w = new QLineEdit{parent};
w->setText(data.toString());
return w;
diff --git a/src/plugins/mesonprojectmanager/mesonbuildconfiguration.cpp b/src/plugins/mesonprojectmanager/mesonbuildconfiguration.cpp
index c936ac3130..090f48257f 100644
--- a/src/plugins/mesonprojectmanager/mesonbuildconfiguration.cpp
+++ b/src/plugins/mesonprojectmanager/mesonbuildconfiguration.cpp
@@ -191,7 +191,7 @@ public:
Column {
noMargin,
Form {
- Tr::tr("Parameters:"), parametersLineEdit, br,
+ Tr::tr("Parameters:"), parametersLineEdit, br,
buildCfg->buildDirectoryAspect(), br
},
optionsFilterLineEdit,
diff --git a/src/plugins/mesonprojectmanager/mesonoutputparser.cpp b/src/plugins/mesonprojectmanager/mesonoutputparser.cpp
index 9ba777f075..241f05a916 100644
--- a/src/plugins/mesonprojectmanager/mesonoutputparser.cpp
+++ b/src/plugins/mesonprojectmanager/mesonoutputparser.cpp
@@ -56,7 +56,7 @@ inline Utils::OutputLineParser::LinkSpecs MesonOutputParser::addTask(
fileName,
match.captured(lineNumberCapIndex).toInt());
addTask(task);
- addLinkSpecForAbsoluteFilePath(linkSpecs, task.file, task.line, match, 1);
+ addLinkSpecForAbsoluteFilePath(linkSpecs, task.file, task.line, task.column, match, 1);
#else
Q_UNUSED(type);
Q_UNUSED(line);
diff --git a/src/plugins/mesonprojectmanager/toolkitaspectwidget.h b/src/plugins/mesonprojectmanager/toolkitaspectwidget.h
index 252e351ff7..594e1ceec1 100644
--- a/src/plugins/mesonprojectmanager/toolkitaspectwidget.h
+++ b/src/plugins/mesonprojectmanager/toolkitaspectwidget.h
@@ -36,7 +36,7 @@ private:
void makeReadOnly() override { m_toolsComboBox->setEnabled(false); }
- void addToLayoutImpl(Layouting::LayoutItem &parent) override
+ void addToLayoutImpl(Layouting::Layout &parent) override
{
addMutableAction(m_toolsComboBox);
parent.addItem(m_toolsComboBox);
@@ -49,11 +49,7 @@ private:
return MesonToolKitAspect::mesonToolId(m_kit);
return NinjaToolKitAspect::ninjaToolId(m_kit);
}();
- if (id.isValid())
- m_toolsComboBox->setCurrentIndex(indexOf(id));
- else {
- setToDefault();
- }
+ m_toolsComboBox->setCurrentIndex(indexOf(id));
}
QComboBox *m_toolsComboBox;
diff --git a/src/plugins/modeleditor/ModelEditor.json.in b/src/plugins/modeleditor/ModelEditor.json.in
index 6237bce22f..bbf2d204d4 100644
--- a/src/plugins/modeleditor/ModelEditor.json.in
+++ b/src/plugins/modeleditor/ModelEditor.json.in
@@ -13,8 +13,11 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Modeling",
- "Description" : "Graphical modeling with structured diagrams.",
- "Url" : "http://www.qt.io",
+ "Description" : "Create Universal Modeling Language (UML) style models",
+ "LongDescription" : [
+ "Create structured and behavioral diagrams that offer different views to your system. However, the editor uses a variant of UML and has only a subset of properties for specifying the appearance of model elements."
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES},
"Mimetypes" : [
diff --git a/src/plugins/modeleditor/componentviewcontroller.cpp b/src/plugins/modeleditor/componentviewcontroller.cpp
index 684d104fd9..cb98ca21ed 100644
--- a/src/plugins/modeleditor/componentviewcontroller.cpp
+++ b/src/plugins/modeleditor/componentviewcontroller.cpp
@@ -31,6 +31,7 @@
using namespace ProjectExplorer;
using namespace Utils;
+using Utils::FilePath;
namespace ModelEditor {
namespace Internal {
@@ -52,9 +53,9 @@ private:
void FindComponentFromFilePath::setFilePath(const QString &filePath)
{
- m_elementName = qmt::NameController::convertFileNameToElementName(filePath);
+ m_elementName = qmt::NameController::convertFileNameToElementName(FilePath::fromString(filePath));
QFileInfo fileInfo(filePath);
- m_elementsPath = qmt::NameController::buildElementsPath(fileInfo.path(), false);
+ m_elementsPath = qmt::NameController::buildElementsPath(FilePath::fromString(fileInfo.path()), false);
}
void FindComponentFromFilePath::visitMComponent(qmt::MComponent *component)
@@ -154,7 +155,7 @@ void UpdateIncludeDependenciesVisitor::visitMComponent(qmt::MComponent *componen
if (document) {
const QList<CPlusPlus::Document::Include> includes = document->resolvedIncludes();
for (const CPlusPlus::Document::Include &include : includes) {
- Utils::FilePath includeFilePath = include.resolvedFileName();
+ FilePath includeFilePath = include.resolvedFileName();
// replace proxy header with real one
CPlusPlus::Document::Ptr includeDocument = snapshot.document(includeFilePath);
if (includeDocument) {
@@ -218,10 +219,10 @@ void UpdateIncludeDependenciesVisitor::collectElementPaths(const ProjectExplorer
QMultiHash<QString, Node> *filePathsMap)
{
folderNode->forEachFileNode([&](FileNode *fileNode) {
- QString elementName = qmt::NameController::convertFileNameToElementName(fileNode->filePath().toString());
+ QString elementName = qmt::NameController::convertFileNameToElementName(fileNode->filePath());
QFileInfo fileInfo = fileNode->filePath().toFileInfo();
QString nodePath = fileInfo.path();
- QStringList elementsPath = qmt::NameController::buildElementsPath(nodePath, false);
+ QStringList elementsPath = qmt::NameController::buildElementsPath(FilePath::fromString(nodePath), false);
filePathsMap->insert(elementName, Node(fileNode->filePath().toString(), elementsPath));
});
folderNode->forEachFolderNode([&](FolderNode *subNode) {
@@ -309,7 +310,7 @@ void ComponentViewController::doCreateComponentModel(const QString &filePath, qm
{
for (const QString &fileName : QDir(filePath).entryList(QDir::Files)) {
QString file = filePath + "/" + fileName;
- QString componentName = qmt::NameController::convertFileNameToElementName(file);
+ QString componentName = qmt::NameController::convertFileNameToElementName(FilePath::fromString(file));
qmt::MComponent *component = nullptr;
bool isSource = false;
CppEditor::ProjectFile::Kind kind = CppEditor::ProjectFile::classify(file);
@@ -341,7 +342,7 @@ void ComponentViewController::doCreateComponentModel(const QString &filePath, qm
}
if (component) {
QStringList relativeElements = qmt::NameController::buildElementsPath(
- d->pxnodeUtilities->calcRelativePath(file, anchorFolder), false);
+ FilePath::fromString(d->pxnodeUtilities->calcRelativePath(file, anchorFolder)), false);
if (d->pxnodeUtilities->findSameObject(relativeElements, component)) {
delete component;
} else {
diff --git a/src/plugins/modeleditor/elementtasks.cpp b/src/plugins/modeleditor/elementtasks.cpp
index 173ccd6524..7bbdd7b3e0 100644
--- a/src/plugins/modeleditor/elementtasks.cpp
+++ b/src/plugins/modeleditor/elementtasks.cpp
@@ -40,6 +40,7 @@
using namespace Core;
using namespace CppEditor;
+using Utils::FilePath;
namespace ModelEditor {
namespace Internal {
@@ -401,17 +402,22 @@ void ElementTasks::createAndOpenDiagram(const qmt::DElement *element, const qmt:
createAndOpenDiagram(melement);
}
+FilePath ElementTasks::linkedFile(const qmt::MObject *mobject) const
+{
+ FilePath filepath = mobject->linkedFileName();
+ if (!filepath.isEmpty()) {
+ FilePath projectName = d->documentController->projectController()->project()->fileName();
+ filepath = projectName.absolutePath().resolvePath(filepath).canonicalPath();
+ }
+ return filepath;
+}
+
bool ElementTasks::hasLinkedFile(const qmt::MElement *element) const
{
if (auto mobject = dynamic_cast<const qmt::MObject *>(element)) {
- QString filename = mobject->linkedFileName();
- if (!filename.isEmpty()) {
- QString projectName = d->documentController->projectController()->project()->fileName();
- Utils::FilePath relativePath = Utils::FilePath::fromString(filename);
- Utils::FilePath projectPath = Utils::FilePath::fromString(projectName);
- QString filepath = relativePath.resolvePath(projectPath).toString();
- return QFileInfo::exists(filepath);
- }
+ FilePath filepath = linkedFile(mobject);
+ if (!filepath.isEmpty())
+ return filepath.exists();
}
return false;
}
@@ -429,25 +435,20 @@ bool ElementTasks::hasLinkedFile(const qmt::DElement *element, const qmt::MDiagr
void ElementTasks::openLinkedFile(const qmt::MElement *element)
{
if (auto mobject = dynamic_cast<const qmt::MObject *>(element)) {
- QString filename = mobject->linkedFileName();
- if (!filename.isEmpty()) {
- QString projectName = d->documentController->projectController()->project()->fileName();
- QString filepath;
- if (QFileInfo(filename).isRelative())
- filepath = QFileInfo(QFileInfo(projectName).path() + "/" + filename).canonicalFilePath();
- else
- filepath = filename;
- if (QFileInfo::exists(filepath)) {
- Core::EditorFactories list = Core::IEditorFactory::preferredEditorFactories(Utils::FilePath::fromString(filepath));
+ FilePath filepath = linkedFile(mobject);
+ if (!filepath.isEmpty()) {
+ if (filepath.exists()) {
+ Core::EditorFactories list = Core::IEditorFactory::preferredEditorFactories(filepath);
if (list.empty() || (list.count() <= 1 && list.at(0)->id() == "Core.BinaryEditor")) {
// intentionally ignore return code
- (void) Core::EditorManager::instance()->openExternalEditor(Utils::FilePath::fromString(filepath), "CorePlugin.OpenWithSystemEditor");
+ (void) Core::EditorManager::openExternalEditor(filepath, "CorePlugin.OpenWithSystemEditor");
} else {
// intentionally ignore return code
- (void) Core::EditorManager::instance()->openEditor(Utils::FilePath::fromString(filepath));
+ (void) Core::EditorManager::openEditor(filepath);
}
} else {
- QMessageBox::critical(Core::ICore::dialogParent(), Tr::tr("Opening File"), Tr::tr("File %1 does not exist.").arg(filepath));
+ QMessageBox::critical(Core::ICore::dialogParent(), Tr::tr("Opening File"),
+ Tr::tr("File %1 does not exist.").arg(filepath.toUserOutput()));
}
}
}
diff --git a/src/plugins/modeleditor/elementtasks.h b/src/plugins/modeleditor/elementtasks.h
index caee1e9e6f..2eb2d3a272 100644
--- a/src/plugins/modeleditor/elementtasks.h
+++ b/src/plugins/modeleditor/elementtasks.h
@@ -6,7 +6,12 @@
#include <QObject>
#include "qmt/tasks/ielementtasks.h"
-namespace qmt { class DocumentController; }
+#include <utils/filepath.h>
+
+namespace qmt {
+class DocumentController;
+class MObject;
+}
namespace ModelEditor {
namespace Internal {
@@ -75,6 +80,8 @@ public:
bool handleContextMenuAction(qmt::DElement *element, qmt::MDiagram *diagram, const QString &id) override;
private:
+ Utils::FilePath linkedFile(const qmt::MObject *mobject) const;
+
ElementTasksPrivate *d;
};
diff --git a/src/plugins/modeleditor/extdocumentcontroller.cpp b/src/plugins/modeleditor/extdocumentcontroller.cpp
index 46df6f7472..f62d1c173b 100644
--- a/src/plugins/modeleditor/extdocumentcontroller.cpp
+++ b/src/plugins/modeleditor/extdocumentcontroller.cpp
@@ -9,7 +9,7 @@
#include "qmt/project_controller/projectcontroller.h"
#include "qmt/tasks/diagramscenecontroller.h"
-#include <QFileInfo>
+using Utils::FilePath;
namespace ModelEditor {
namespace Internal {
@@ -51,10 +51,9 @@ PxNodeController *ExtDocumentController::pxNodeController() const
return d->pxNodeController;
}
-void ExtDocumentController::onProjectFileNameChanged(const QString &fileName)
+void ExtDocumentController::onProjectFileNameChanged(const FilePath &fileName)
{
- QFileInfo fileInfo(fileName);
- d->pxNodeController->setAnchorFolder(fileInfo.path());
+ d->pxNodeController->setAnchorFolder(fileName.path());
}
} // namespace Internal
diff --git a/src/plugins/modeleditor/extdocumentcontroller.h b/src/plugins/modeleditor/extdocumentcontroller.h
index 8a0f2a28ec..0fc50aca17 100644
--- a/src/plugins/modeleditor/extdocumentcontroller.h
+++ b/src/plugins/modeleditor/extdocumentcontroller.h
@@ -5,6 +5,8 @@
#include "qmt/document_controller/documentcontroller.h"
+#include <utils/filepath.h>
+
namespace ModelEditor {
namespace Internal {
@@ -25,7 +27,7 @@ public:
PxNodeController *pxNodeController() const;
private:
- void onProjectFileNameChanged(const QString &fileName);
+ void onProjectFileNameChanged(const Utils::FilePath &fileName);
private:
ExtDocumentControllerPrivate *d;
diff --git a/src/plugins/modeleditor/extpropertiesmview.cpp b/src/plugins/modeleditor/extpropertiesmview.cpp
index fcae9ada22..b105554e6f 100644
--- a/src/plugins/modeleditor/extpropertiesmview.cpp
+++ b/src/plugins/modeleditor/extpropertiesmview.cpp
@@ -21,6 +21,8 @@
#include <QMimeDatabase>
#include <QImageReader>
+using Utils::FilePath;
+
namespace ModelEditor {
namespace Internal {
@@ -52,7 +54,7 @@ static QString imageNameFilterString()
/// Constructs an absolute FilePath from \a relativePath which
/// is interpreted as being relative to \a anchor.
-Utils::FilePath absoluteFromRelativePath(const Utils::FilePath &relativePath,
+FilePath absoluteFromRelativePath(const Utils::FilePath &relativePath,
const Utils::FilePath &anchor)
{
QDir anchorDir = QFileInfo(anchor.path()).absoluteDir();
@@ -83,8 +85,7 @@ void ExtPropertiesMView::visitMPackage(const qmt::MPackage *package)
m_configPath = new Utils::PathChooser(m_topWidget);
m_configPath->setPromptDialogTitle(Tr::tr("Select Custom Configuration Folder"));
m_configPath->setExpectedKind(Utils::PathChooser::ExistingDirectory);
- m_configPath->setInitialBrowsePathBackup(
- Utils::FilePath::fromString(project->fileName()).absolutePath());
+ m_configPath->setInitialBrowsePathBackup(project->fileName().absolutePath());
addRow(Tr::tr("Config path:"), m_configPath, "configpath");
connect(m_configPath, &Utils::PathChooser::textChanged,
this, &ExtPropertiesMView::onConfigPathChanged,
@@ -95,8 +96,8 @@ void ExtPropertiesMView::visitMPackage(const qmt::MPackage *package)
m_configPath->setFilePath({});
} else {
// make path absolute (may be relative to current project's directory)
- QDir projectDir = QFileInfo(project->fileName()).absoluteDir();
- m_configPath->setPath(QFileInfo(projectDir, project->configPath()).canonicalFilePath());
+ auto projectDir = project->fileName().absolutePath();
+ m_configPath->setPath(projectDir.resolvePath(project->configPath()).toUserOutput());
}
}
if (!m_configPathInfo) {
@@ -115,7 +116,7 @@ void ExtPropertiesMView::visitMObjectBehind(const qmt::MObject *object)
m_filelinkPathChooser = new Utils::PathChooser(m_topWidget);
m_filelinkPathChooser->setPromptDialogTitle((Tr::tr("Select File Target")));
m_filelinkPathChooser->setExpectedKind(Utils::PathChooser::File);
- m_filelinkPathChooser->setInitialBrowsePathBackup(Utils::FilePath::fromString(QFileInfo(project->fileName()).absolutePath()));
+ m_filelinkPathChooser->setInitialBrowsePathBackup(project->fileName().absolutePath());
addRow(Tr::tr("Linked file:"), m_filelinkPathChooser, "filelink");
connect(m_filelinkPathChooser, &Utils::PathChooser::textChanged,
this, &ExtPropertiesMView::onFileLinkPathChanged,
@@ -123,13 +124,13 @@ void ExtPropertiesMView::visitMObjectBehind(const qmt::MObject *object)
}
if (isSingleSelection) {
if (!m_filelinkPathChooser->hasFocus()) {
- QString path = object->linkedFileName();
+ Utils::FilePath path = object->linkedFileName();
if (path.isEmpty()) {
m_filelinkPathChooser->setPath(QString());
} else {
- Utils::FilePath relativePath = Utils::FilePath::fromString(path);
- Utils::FilePath projectPath = Utils::FilePath::fromString(project->fileName());
- QString filePath = absoluteFromRelativePath(relativePath, projectPath).toString();
+ Utils::FilePath relativePath = path;
+ Utils::FilePath projectPath = project->fileName();
+ QString filePath = absoluteFromRelativePath(relativePath, projectPath).toFSPathString();
m_filelinkPathChooser->setPath(filePath);
}
}
@@ -150,8 +151,7 @@ void ExtPropertiesMView::visitDObjectBefore(const qmt::DObject *object)
m_imagePathChooser->setPromptDialogTitle(Tr::tr("Select Image File"));
m_imagePathChooser->setExpectedKind(Utils::PathChooser::File);
m_imagePathChooser->setPromptDialogFilter(imageNameFilterString());
- m_imagePathChooser->setInitialBrowsePathBackup(
- Utils::FilePath::fromString(QFileInfo(project->fileName()).absolutePath()));
+ m_imagePathChooser->setInitialBrowsePathBackup(project->fileName().absolutePath());
addRow(Tr::tr("Image:"), m_imagePathChooser, "imagepath");
connect(m_imagePathChooser, &Utils::PathChooser::textChanged,
this, &ExtPropertiesMView::onImagePathChanged,
@@ -159,13 +159,12 @@ void ExtPropertiesMView::visitDObjectBefore(const qmt::DObject *object)
}
if (isSingleSelection) {
if (!m_imagePathChooser->hasFocus()) {
- QString path = object->imagePath();
+ Utils::FilePath path = object->imagePath();
if (path.isEmpty()) {
m_imagePathChooser->setPath(QString());
} else {
- Utils::FilePath relativePath = Utils::FilePath::fromString(path);
- Utils::FilePath projectPath = Utils::FilePath::fromString(project->fileName());
- QString filePath = absoluteFromRelativePath(relativePath, projectPath).toString();
+ Utils::FilePath relativePath = path;
+ QString filePath = absoluteFromRelativePath(relativePath, project->fileName()).toFSPathString();
m_imagePathChooser->setPath(filePath);
}
}
@@ -182,16 +181,15 @@ void ExtPropertiesMView::onConfigPathChanged(const QString &path)
qmt::Project *project = m_projectController->project();
if (path.isEmpty()) {
if (!project->configPath().isEmpty()) {
- project->setConfigPath(QString());
+ project->setConfigPath({ });
m_projectController->setModified();
modified = true;
}
} else {
// make path relative to current project's directory
- QFileInfo absConfigPath = Utils::FilePath::fromString(path).toFileInfo();
- absConfigPath.makeAbsolute();
- QDir projectDir = QFileInfo(project->fileName()).dir();
- QString configPath = projectDir.relativeFilePath(absConfigPath.filePath());
+ Utils::FilePath absConfigPath = Utils::FilePath::fromString(path).absoluteFilePath();
+ Utils::FilePath projectDir = project->fileName().absolutePath();
+ Utils::FilePath configPath = absConfigPath.relativePathFrom(projectDir);
if (configPath != project->configPath()) {
project->setConfigPath(configPath);
m_projectController->setModified();
@@ -206,16 +204,24 @@ void ExtPropertiesMView::onFileLinkPathChanged(const QString &path)
{
qmt::Project *project = m_projectController->project();
if (path.isEmpty()) {
- assignModelElement<qmt::MObject, QString>(m_modelElements, SelectionSingle, QString(),
- &qmt::MObject::linkedFileName, &qmt::MObject::setLinkedFileName);
+ assignModelElement<qmt::MObject, Utils::FilePath>(
+ m_modelElements,
+ SelectionSingle,
+ {},
+ &qmt::MObject::linkedFileName,
+ &qmt::MObject::setLinkedFileName);
} else {
// make path relative to current project's directory
Utils::FilePath filePath = Utils::FilePath::fromString(path);
- Utils::FilePath projectPath = Utils::FilePath::fromString(QFileInfo(project->fileName()).path());
- QString relativeFilePath = filePath.relativePathFrom(projectPath).toString();
+ Utils::FilePath projectPath = project->fileName().absolutePath();
+ Utils::FilePath relativeFilePath = filePath.relativePathFrom(projectPath);
if (!relativeFilePath.isEmpty()) {
- assignModelElement<qmt::MObject, QString>(m_modelElements, SelectionSingle, relativeFilePath,
- &qmt::MObject::linkedFileName, &qmt::MObject::setLinkedFileName);
+ assignModelElement<qmt::MObject, Utils::FilePath>(
+ m_modelElements,
+ SelectionSingle,
+ relativeFilePath,
+ &qmt::MObject::linkedFileName,
+ &qmt::MObject::setLinkedFileName);
}
}
}
@@ -224,24 +230,30 @@ void ExtPropertiesMView::onImagePathChanged(const QString &path)
{
qmt::Project *project = m_projectController->project();
if (path.isEmpty()) {
- assignModelElement<qmt::DObject, QString>(m_diagramElements, SelectionSingle, QString(),
- &qmt::DObject::imagePath, &qmt::DObject::setImagePath);
+ assignModelElement<qmt::DObject, Utils::FilePath>(
+ m_diagramElements,
+ SelectionSingle,
+ {},
+ &qmt::DObject::imagePath,
+ &qmt::DObject::setImagePath);
assignModelElement<qmt::DObject, QImage>(m_diagramElements, SelectionSingle, QImage(),
&qmt::DObject::image, &qmt::DObject::setImage);
} else {
// make path relative to current project's directory
Utils::FilePath filePath = Utils::FilePath::fromString(path);
- Utils::FilePath projectPath = Utils::FilePath::fromString(
- QFileInfo(project->fileName()).path());
- QString relativeFilePath = filePath.relativePathFrom(projectPath).toString();
+ Utils::FilePath projectPath = project->fileName().absolutePath();
+ Utils::FilePath relativeFilePath = filePath.relativePathFrom(projectPath);
if (!relativeFilePath.isEmpty()
- && isValueChanged<qmt::DObject, QString>(m_diagramElements, SelectionSingle, relativeFilePath,
- &qmt::DObject::imagePath))
- {
+ && isValueChanged<qmt::DObject, Utils::FilePath>(
+ m_diagramElements, SelectionSingle, relativeFilePath, &qmt::DObject::imagePath)) {
QImage image;
if (image.load(path)) {
- assignModelElement<qmt::DObject, QString>(m_diagramElements, SelectionSingle, relativeFilePath,
- &qmt::DObject::imagePath, &qmt::DObject::setImagePath);
+ assignModelElement<qmt::DObject, Utils::FilePath>(
+ m_diagramElements,
+ SelectionSingle,
+ relativeFilePath,
+ &qmt::DObject::imagePath,
+ &qmt::DObject::setImagePath);
assignModelElement<qmt::DObject, QImage>(m_diagramElements, SelectionSingle, image,
&qmt::DObject::image, &qmt::DObject::setImage);
} else {
diff --git a/src/plugins/modeleditor/jsextension.cpp b/src/plugins/modeleditor/jsextension.cpp
index cfe31ddea7..455814a49f 100644
--- a/src/plugins/modeleditor/jsextension.cpp
+++ b/src/plugins/modeleditor/jsextension.cpp
@@ -5,7 +5,9 @@
#include <qmt/controller/namecontroller.h>
-QString ModelEditor::Internal::JsExtension::fileNameToElementName(const QString &file)
+using Utils::FilePath;
+
+QString ModelEditor::Internal::JsExtension::fileNameToElementName(const FilePath &file)
{
return qmt::NameController::convertFileNameToElementName(file);
}
diff --git a/src/plugins/modeleditor/jsextension.h b/src/plugins/modeleditor/jsextension.h
index 1cc9c262f2..d9f3484919 100644
--- a/src/plugins/modeleditor/jsextension.h
+++ b/src/plugins/modeleditor/jsextension.h
@@ -5,6 +5,8 @@
#include <QObject>
+#include <utils/filepath.h>
+
namespace ModelEditor {
namespace Internal {
@@ -15,7 +17,7 @@ class JsExtension : public QObject
public:
JsExtension() {}
- Q_INVOKABLE QString fileNameToElementName(const QString &file);
+ Q_INVOKABLE QString fileNameToElementName(const Utils::FilePath &file);
Q_INVOKABLE QString elementNameToFileName(const QString &element);
};
diff --git a/src/plugins/modeleditor/modeldocument.cpp b/src/plugins/modeleditor/modeldocument.cpp
index a1c271539c..00630341f9 100644
--- a/src/plugins/modeleditor/modeldocument.cpp
+++ b/src/plugins/modeleditor/modeldocument.cpp
@@ -19,8 +19,7 @@
#include <utils/id.h>
#include <utils/fileutils.h>
-#include <QFileInfo>
-#include <QDir>
+using Utils::FilePath;
namespace ModelEditor {
namespace Internal {
@@ -46,23 +45,23 @@ ModelDocument::~ModelDocument()
}
Core::IDocument::OpenResult ModelDocument::open(QString *errorString,
- const Utils::FilePath &filePath,
- const Utils::FilePath &realFilePath)
+ const FilePath &filePath,
+ const FilePath &realFilePath)
{
Q_UNUSED(filePath)
- OpenResult result = load(errorString, realFilePath.toString());
+ OpenResult result = load(errorString, realFilePath);
return result;
}
-bool ModelDocument::saveImpl(QString *errorString, const Utils::FilePath &filePath, bool autoSave)
+bool ModelDocument::saveImpl(QString *errorString, const FilePath &filePath, bool autoSave)
{
if (!d->documentController) {
*errorString = Tr::tr("No model loaded. Cannot save.");
return false;
}
- d->documentController->projectController()->setFileName(filePath.toString());
+ d->documentController->projectController()->setFileName(filePath);
try {
d->documentController->projectController()->save();
} catch (const qmt::Exception &ex) {
@@ -73,7 +72,7 @@ bool ModelDocument::saveImpl(QString *errorString, const Utils::FilePath &filePa
if (autoSave) {
d->documentController->projectController()->setModified();
} else {
- setFilePath(Utils::FilePath::fromString(d->documentController->projectController()->project()->fileName()));
+ setFilePath(d->documentController->projectController()->project()->fileName());
emit changed();
}
@@ -102,12 +101,13 @@ bool ModelDocument::reload(QString *errorString, Core::IDocument::ReloadFlag fla
if (flag == FlagIgnore)
return true;
try {
- d->documentController->loadProject(filePath().toString());
+ d->documentController->loadProject(filePath());
} catch (const qmt::FileNotFoundException &ex) {
*errorString = ex.errorMessage();
return false;
} catch (const qmt::Exception &ex) {
- *errorString = Tr::tr("Could not open \"%1\" for reading: %2.").arg(filePath().toString()).arg(ex.errorMessage());
+ *errorString = Tr::tr("Could not open \"%1\" for reading: %2.")
+ .arg(filePath().toUserOutput(), ex.errorMessage());
return false;
}
emit contentSet();
@@ -119,25 +119,25 @@ ExtDocumentController *ModelDocument::documentController() const
return d->documentController;
}
-Core::IDocument::OpenResult ModelDocument::load(QString *errorString, const QString &fileName)
+Core::IDocument::OpenResult ModelDocument::load(QString *errorString, const FilePath &fileName)
{
d->documentController = ModelEditorPlugin::modelsManager()->createModel(this);
connect(d->documentController, &qmt::DocumentController::changed, this, &IDocument::changed);
try {
d->documentController->loadProject(fileName);
- setFilePath(Utils::FilePath::fromString(d->documentController->projectController()->project()->fileName()));
+ setFilePath(d->documentController->projectController()->project()->fileName());
} catch (const qmt::FileNotFoundException &ex) {
*errorString = ex.errorMessage();
return OpenResult::ReadError;
} catch (const qmt::Exception &ex) {
- *errorString = Tr::tr("Could not open \"%1\" for reading: %2.").arg(fileName).arg(ex.errorMessage());
+ *errorString = Tr::tr("Could not open \"%1\" for reading: %2.").arg(fileName.toUserOutput(), ex.errorMessage());
return OpenResult::CannotHandle;
}
- QString configPath = d->documentController->projectController()->project()->configPath();
+ FilePath configPath = d->documentController->projectController()->project()->configPath();
if (!configPath.isEmpty()) {
- QString canonicalPath = QFileInfo(QDir(QFileInfo(fileName).path()).filePath(configPath)).canonicalFilePath();
+ FilePath canonicalPath =fileName.absolutePath().resolvePath(configPath);
if (!canonicalPath.isEmpty()) {
// TODO error output on reading definition files
d->documentController->configController()->readStereotypeDefinitions(canonicalPath);
diff --git a/src/plugins/modeleditor/modeldocument.h b/src/plugins/modeleditor/modeldocument.h
index e606420805..6b2037e007 100644
--- a/src/plugins/modeleditor/modeldocument.h
+++ b/src/plugins/modeleditor/modeldocument.h
@@ -4,6 +4,7 @@
#pragma once
#include <coreplugin/idocument.h>
+#include <utils/filepath.h>
namespace qmt { class Uid; }
@@ -36,7 +37,7 @@ public:
ExtDocumentController *documentController() const;
- OpenResult load(QString *errorString, const QString &fileName);
+ OpenResult load(QString *errorString, const Utils::FilePath &fileName);
protected:
bool saveImpl(QString *errorString, const Utils::FilePath &filePath, bool autoSave) override;
diff --git a/src/plugins/modeleditor/modeleditor.cpp b/src/plugins/modeleditor/modeleditor.cpp
index bd39057f45..dcf2c5b518 100644
--- a/src/plugins/modeleditor/modeleditor.cpp
+++ b/src/plugins/modeleditor/modeleditor.cpp
@@ -62,7 +62,6 @@
#include <QAction>
#include <QActionGroup>
#include <QComboBox>
-#include <QDir>
#include <QEvent>
#include <QFileDialog>
#include <QFileInfo>
@@ -616,7 +615,7 @@ void ModelEditor::exportToImage(bool selectedElements)
QString fileName = FileUtils::getSaveFilePath(
nullptr,
selectedElements ? Tr::tr("Export Selected Elements") : Tr::tr("Export Diagram"),
- FilePath::fromString(d->lastExportDirPath), filter).toString();
+ FilePath::fromString(d->lastExportDirPath), filter).toFSPathString();
if (!fileName.isEmpty()) {
qmt::DocumentController *documentController = d->document->documentController();
qmt::DiagramSceneModel *sceneModel = documentController->diagramsManager()->diagramSceneModel(diagram);
@@ -1151,8 +1150,13 @@ void ModelEditor::initToolbars()
if (!tool.m_stereotype.isEmpty() && stereotypeIconElement != qmt::StereotypeIcon::ElementAny) {
const qmt::Style *style = documentController->styleController()->adaptStyle(styleEngineElementType);
icon = stereotypeController->createIcon(
- stereotypeIconElement, {tool.m_stereotype},
- QString(), style, QSize(128, 128), QMarginsF(6.0, 4.0, 6.0, 8.0), 8.0);
+ stereotypeIconElement,
+ {tool.m_stereotype},
+ {},
+ style,
+ QSize(128, 128),
+ QMarginsF(6.0, 4.0, 6.0, 8.0),
+ 8.0);
if (!icon.isNull()) {
QString stereotypeIconId = stereotypeController->findStereotypeIconId(
stereotypeIconElement, {tool.m_stereotype});
diff --git a/src/plugins/modeleditor/modelindexer.cpp b/src/plugins/modeleditor/modelindexer.cpp
index 163d0d60b5..a6de1c4008 100644
--- a/src/plugins/modeleditor/modelindexer.cpp
+++ b/src/plugins/modeleditor/modelindexer.cpp
@@ -36,6 +36,8 @@
#include <QPointer>
using namespace ProjectExplorer;
+using Utils::FilePath;
+using Utils::FilePaths;
namespace ModelEditor {
namespace Internal {
@@ -275,7 +277,7 @@ void ModelIndexer::IndexerThread::onFilesQueued()
qmt::ProjectSerializer projectSerializer;
qmt::Project project;
try {
- projectSerializer.load(queuedFile.file(), &project);
+ projectSerializer.load(FilePath::fromString(queuedFile.file()), &project);
} catch (const qmt::Exception &e) {
qWarning() << e.errorMessage();
return;
@@ -375,13 +377,13 @@ void ModelIndexer::scanProject(ProjectExplorer::Project *project)
return;
// TODO harmonize following code with findFirstModel()?
- const Utils::FilePaths files = project->files(ProjectExplorer::Project::SourceFiles);
+ const FilePaths files = project->files(ProjectExplorer::Project::SourceFiles);
QQueue<QueuedFile> filesQueue;
QSet<QueuedFile> filesSet;
const Utils::MimeType modelMimeType = Utils::mimeTypeForName(Constants::MIME_TYPE_MODEL);
if (modelMimeType.isValid()) {
- for (const Utils::FilePath &file : files) {
+ for (const FilePath &file : files) {
if (modelMimeType.suffixes().contains(file.completeSuffix())) {
QueuedFile queuedFile(file.toString(), project, file.lastModified());
filesQueue.append(queuedFile);
@@ -466,10 +468,10 @@ QString ModelIndexer::findFirstModel(ProjectExplorer::FolderNode *folderNode,
void ModelIndexer::forgetProject(ProjectExplorer::Project *project)
{
- const Utils::FilePaths files = project->files(ProjectExplorer::Project::SourceFiles);
+ const FilePaths files = project->files(ProjectExplorer::Project::SourceFiles);
QMutexLocker locker(&d->indexerMutex);
- for (const Utils::FilePath &file : files) {
+ for (const FilePath &file : files) {
const QString fileString = file.toString();
// remove file from queue
QueuedFile queuedFile(fileString, project);
diff --git a/src/plugins/modeleditor/modelsmanager.cpp b/src/plugins/modeleditor/modelsmanager.cpp
index 787c5556fd..02c481fa5d 100644
--- a/src/plugins/modeleditor/modelsmanager.cpp
+++ b/src/plugins/modeleditor/modelsmanager.cpp
@@ -38,11 +38,11 @@
#include <projectexplorer/projecttree.h>
#include <utils/fileutils.h>
-#include <QFileInfo>
-#include <QDir>
#include <QTimer>
#include <QAction>
+using Utils::FilePath;
+
namespace ModelEditor {
namespace Internal {
@@ -121,7 +121,7 @@ ExtDocumentController *ModelsManager::createModel(ModelDocument *modelDocument)
auto documentController = new ExtDocumentController(this);
// TODO error output on reading definition files
documentController->configController()->readStereotypeDefinitions(
- Core::ICore::resourcePath("modeleditor").toString());
+ Core::ICore::resourcePath("modeleditor"));
d->managedModels.append(ManagedModel(documentController, modelDocument));
return documentController;
@@ -238,7 +238,7 @@ void ModelsManager::onOpenDiagramFromProjectExplorer()
void ModelsManager::onOpenDefaultModel(const qmt::Uid &modelUid)
{
- const auto modelFile = Utils::FilePath::fromString(d->modelIndexer->findModel(modelUid));
+ const FilePath modelFile = FilePath::fromString(d->modelIndexer->findModel(modelUid));
if (!modelFile.isEmpty())
Core::EditorManager::openEditor(modelFile);
}
diff --git a/src/plugins/modeleditor/pxnodecontroller.cpp b/src/plugins/modeleditor/pxnodecontroller.cpp
index f659e46f3d..50bbf901e6 100644
--- a/src/plugins/modeleditor/pxnodecontroller.cpp
+++ b/src/plugins/modeleditor/pxnodecontroller.cpp
@@ -30,7 +30,7 @@
#include <QMenu>
#include <QQueue>
-using namespace Utils;
+using Utils::FilePath;
namespace ModelEditor {
namespace Internal {
@@ -146,7 +146,7 @@ void PxNodeController::addFileSystemEntry(const QString &filePath, int line, int
{
QMT_ASSERT(diagram, return);
- QString elementName = qmt::NameController::convertFileNameToElementName(filePath);
+ QString elementName = qmt::NameController::convertFileNameToElementName(FilePath::fromString(filePath));
QFileInfo fileInfo(filePath);
if (fileInfo.exists() && fileInfo.isFile()) {
@@ -212,7 +212,7 @@ qmt::MDiagram *PxNodeController::findDiagramForExplorerNode(const ProjectExplore
return nullptr;
QStringList relativeElements = qmt::NameController::buildElementsPath(
- d->pxnodeUtilities->calcRelativePath(node, d->anchorFolder), false);
+ FilePath::fromString(d->pxnodeUtilities->calcRelativePath(node, d->anchorFolder)), false);
QQueue<qmt::MPackage *> roots;
roots.append(d->diagramSceneController->modelController()->rootPackage());
@@ -322,7 +322,7 @@ void PxNodeController::onMenuActionTriggered(PxNodeController::MenuAction *actio
package->setStereotypes({action->stereotype});
d->diagramSceneController->modelController()->undoController()->beginMergeSequence(Tr::tr("Create Component Model"));
QStringList relativeElements = qmt::NameController::buildElementsPath(
- d->pxnodeUtilities->calcRelativePath(filePath, d->anchorFolder), true);
+ FilePath::fromString(d->pxnodeUtilities->calcRelativePath(filePath, d->anchorFolder)), true);
if (qmt::MObject *existingObject = d->pxnodeUtilities->findSameObject(relativeElements, package)) {
delete package;
package = dynamic_cast<qmt::MPackage *>(existingObject);
@@ -346,8 +346,8 @@ void PxNodeController::onMenuActionTriggered(PxNodeController::MenuAction *actio
item->setName(action->elementName);
item->setVariety(action->stereotype);
item->setVarietyEditable(false);
- Utils::FilePath filePath = Utils::FilePath::fromString(action->filePath);
- item->setLinkedFileName(filePath.relativePathFrom(Utils::FilePath::fromString(d->anchorFolder)).toString());
+ FilePath filePath = FilePath::fromString(action->filePath);
+ item->setLinkedFileName(filePath.relativePathFrom(FilePath::fromString(d->anchorFolder)));
newObject = item;
dropInCurrentDiagram = true;
break;
@@ -363,8 +363,9 @@ void PxNodeController::onMenuActionTriggered(PxNodeController::MenuAction *actio
} else {
qmt::MObject *parentForDiagram = nullptr;
QStringList relativeElements = qmt::NameController::buildElementsPath(
- d->pxnodeUtilities->calcRelativePath(filePath, d->anchorFolder),
- dynamic_cast<qmt::MPackage *>(newObject) != nullptr);
+ FilePath::fromString(
+ d->pxnodeUtilities->calcRelativePath(filePath, d->anchorFolder)),
+ dynamic_cast<qmt::MPackage *>(newObject) != nullptr);
if (qmt::MObject *existingObject = d->pxnodeUtilities->findSameObject(relativeElements, newObject)) {
delete newObject;
newObject = nullptr;
diff --git a/src/plugins/modeleditor/pxnodeutilities.cpp b/src/plugins/modeleditor/pxnodeutilities.cpp
index c9e58d5052..5b8b27c830 100644
--- a/src/plugins/modeleditor/pxnodeutilities.cpp
+++ b/src/plugins/modeleditor/pxnodeutilities.cpp
@@ -20,6 +20,8 @@
#include <typeinfo>
+using Utils::FilePath;
+
namespace ModelEditor {
namespace Internal {
@@ -51,7 +53,8 @@ QString PxNodeUtilities::calcRelativePath(const ProjectExplorer::Node *node,
? node->filePath().toFileInfo().path()
: node->filePath().toString();
- return qmt::NameController::calcRelativePath(nodePath, anchorFolder);
+ return qmt::NameController::calcRelativePath(FilePath::fromString(nodePath),
+ FilePath::fromString(anchorFolder)).toString();
}
QString PxNodeUtilities::calcRelativePath(const QString &filePath, const QString &anchorFolder)
@@ -63,7 +66,8 @@ QString PxNodeUtilities::calcRelativePath(const QString &filePath, const QString
path = fileInfo.path();
else
path = filePath;
- return qmt::NameController::calcRelativePath(path, anchorFolder);
+ return qmt::NameController::calcRelativePath(FilePath::fromString(path),
+ FilePath::fromString(anchorFolder)).toString();
}
qmt::MPackage *PxNodeUtilities::createBestMatchingPackagePath(
@@ -218,7 +222,7 @@ bool PxNodeUtilities::isProxyHeader(const QString &file) const
{
CPlusPlus::Snapshot snapshot = CppEditor::CppModelManager::snapshot();
- CPlusPlus::Document::Ptr document = snapshot.document(Utils::FilePath::fromString(file));
+ CPlusPlus::Document::Ptr document = snapshot.document(FilePath::fromString(file));
if (document) {
QList<CPlusPlus::Document::Include> includes = document->resolvedIncludes();
if (includes.count() != 1)
diff --git a/src/plugins/nim/Nim.json.in b/src/plugins/nim/Nim.json.in
index 622e460a0b..82369b316b 100644
--- a/src/plugins/nim/Nim.json.in
+++ b/src/plugins/nim/Nim.json.in
@@ -13,8 +13,12 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Other Languages",
- "Description" : "Plugin for supporting the Nim programming language.",
- "Url" : "http://www.qt.io",
+ "Description" : "Develop applications using the Nim programming language",
+ "LongDescription" : [
+ "You also need:",
+ "- Nimble packaging manager"
+ ],
+ "Url" : "https://www.qt.io",
"Experimental" : true,
${IDE_PLUGIN_DEPENDENCIES},
diff --git a/src/plugins/nim/project/nimbuildsystem.cpp b/src/plugins/nim/project/nimbuildsystem.cpp
index ef7d6c4732..373a263ba2 100644
--- a/src/plugins/nim/project/nimbuildsystem.cpp
+++ b/src/plugins/nim/project/nimbuildsystem.cpp
@@ -25,14 +25,6 @@ const char EXCLUDED_FILES_KEY[] = "ExcludedFiles";
NimProjectScanner::NimProjectScanner(Project *project)
: m_project(project)
{
- m_scanner.setFilter([this](const Utils::MimeType &, const FilePath &fp) {
- const QString path = fp.toString();
- return excludedFiles().contains(path)
- || path.endsWith(".nimproject")
- || path.contains(".nimproject.user")
- || path.contains(".nimble.user");
- });
-
connect(&m_directoryWatcher, &FileSystemWatcher::directoryChanged,
this, &NimProjectScanner::directoryChanged);
connect(&m_directoryWatcher, &FileSystemWatcher::fileChanged,
@@ -91,6 +83,13 @@ void NimProjectScanner::saveSettings()
void NimProjectScanner::startScan()
{
+ m_scanner.setFilter(
+ [excludedFiles = excludedFiles()](const Utils::MimeType &, const FilePath &fp) {
+ const QString path = fp.toString();
+ return excludedFiles.contains(path) || path.endsWith(".nimproject")
+ || path.contains(".nimproject.user") || path.contains(".nimble.user");
+ });
+
m_scanner.asyncScanForFiles(m_project->projectDirectory());
}
diff --git a/src/plugins/nim/project/nimoutputtaskparser.cpp b/src/plugins/nim/project/nimoutputtaskparser.cpp
index 31fc1e8e77..1fde71d3c2 100644
--- a/src/plugins/nim/project/nimoutputtaskparser.cpp
+++ b/src/plugins/nim/project/nimoutputtaskparser.cpp
@@ -39,7 +39,7 @@ NimParser::Result NimParser::handleLine(const QString &lne, OutputFormat)
const CompileTask t(type, message, absoluteFilePath(FilePath::fromUserInput(filename)),
lineNumber);
LinkSpecs linkSpecs;
- addLinkSpecForAbsoluteFilePath(linkSpecs, t.file, t.line, match, 1);
+ addLinkSpecForAbsoluteFilePath(linkSpecs, t.file, t.line, t.column, match, 1);
scheduleTask(t, 1);
return {Status::Done, linkSpecs};
}
diff --git a/src/plugins/nim/settings/nimsettings.cpp b/src/plugins/nim/settings/nimsettings.cpp
index 9df9acd73b..ce5f406f5b 100644
--- a/src/plugins/nim/settings/nimsettings.cpp
+++ b/src/plugins/nim/settings/nimsettings.cpp
@@ -30,7 +30,7 @@ NimSettings::NimSettings()
using namespace Layouting;
return Column {
Group {
- title("Nimsuggest"),
+ title(QString("Nimsuggest")),
Column { nimSuggestPath }
},
st
diff --git a/src/plugins/perforce/Perforce.json.in b/src/plugins/perforce/Perforce.json.in
index 8a8e1e5099..1bfaa701ed 100644
--- a/src/plugins/perforce/Perforce.json.in
+++ b/src/plugins/perforce/Perforce.json.in
@@ -14,8 +14,12 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Version Control",
- "Description" : "Perforce integration.",
- "Url" : "http://www.qt.io",
+ "Description" : "Access the Perforce software configuration management system",
+ "LongDescription" : [
+ "You also need:",
+ "- Perforce"
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES},
"Mimetypes" : [
diff --git a/src/plugins/perforce/perforcechecker.cpp b/src/plugins/perforce/perforcechecker.cpp
index cf63d9e691..9eb66ef45c 100644
--- a/src/plugins/perforce/perforcechecker.cpp
+++ b/src/plugins/perforce/perforcechecker.cpp
@@ -52,8 +52,7 @@ void PerforceChecker::resetOverrideCursor()
}
void PerforceChecker::start(const FilePath &binary, const FilePath &workingDirectory,
- const QStringList &basicArgs,
- int timeoutMS)
+ const QStringList &basicArgs, int timeoutMS)
{
if (isRunning()) {
emitFailed(QLatin1String("Internal error: process still running"));
@@ -64,13 +63,10 @@ void PerforceChecker::start(const FilePath &binary, const FilePath &workingDirec
return;
}
m_binary = binary;
- QStringList args = basicArgs;
- args << QLatin1String("client") << QLatin1String("-o");
-
if (!workingDirectory.isEmpty())
m_process.setWorkingDirectory(workingDirectory);
- m_process.setCommand({m_binary, args});
+ m_process.setCommand({m_binary, {basicArgs, "client", "-o"}});
m_process.start();
// Timeout handling
m_timeOutMS = timeoutMS;
diff --git a/src/plugins/perforce/perforcesettings.cpp b/src/plugins/perforce/perforcesettings.cpp
index 6f7691c054..65b00a19dc 100644
--- a/src/plugins/perforce/perforcesettings.cpp
+++ b/src/plugins/perforce/perforcesettings.cpp
@@ -125,7 +125,7 @@ PerforceSettings::PerforceSettings()
Group environment {
title(Tr::tr("Environment Variables")),
- customEnv.groupChecker(),
+ groupChecker(customEnv.groupChecker()),
Row { p4Port, p4Client, p4User }
};
diff --git a/src/plugins/perfprofiler/PerfProfiler.json.in b/src/plugins/perfprofiler/PerfProfiler.json.in
index c3a439b1a5..eeca090e04 100644
--- a/src/plugins/perfprofiler/PerfProfiler.json.in
+++ b/src/plugins/perfprofiler/PerfProfiler.json.in
@@ -13,7 +13,12 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Code Analyzer",
- "Description" : "Perf Profiler Plugin.",
- "Url" : "http://www.qt.io",
+ "Description" : "Analyze the CPU and memory usage of an application",
+ "LongDescription" : [
+ "Works on embedded devices and, to a limited extent, on Linux desktop platforms.",
+ "You also need:",
+ "- Perf tool (bundled with the Linux kernel)"
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/perfprofiler/perfprofilerflamegraphview.cpp b/src/plugins/perfprofiler/perfprofilerflamegraphview.cpp
index 076c977888..2d0c451589 100644
--- a/src/plugins/perfprofiler/perfprofilerflamegraphview.cpp
+++ b/src/plugins/perfprofiler/perfprofilerflamegraphview.cpp
@@ -28,7 +28,7 @@ PerfProfilerFlameGraphView::PerfProfilerFlameGraphView(QWidget *parent)
rootContext()->setContextProperty(QStringLiteral("flameGraphModel"), m_model);
setSource(QUrl(QStringLiteral(
"qrc:/qt/qml/QtCreator/PerfProfiler/PerfProfilerFlameGraphView.qml")));
- setClearColor(Utils::creatorTheme()->color(Utils::Theme::Timeline_BackgroundColor1));
+ setClearColor(Utils::creatorColor(Utils::Theme::Timeline_BackgroundColor1));
setResizeMode(QQuickWidget::SizeRootObjectToView);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
diff --git a/src/plugins/perfprofiler/perfprofilerruncontrol.cpp b/src/plugins/perfprofiler/perfprofilerruncontrol.cpp
index 4c51b82b3f..a0927260c1 100644
--- a/src/plugins/perfprofiler/perfprofilerruncontrol.cpp
+++ b/src/plugins/perfprofiler/perfprofilerruncontrol.cpp
@@ -183,15 +183,21 @@ public:
&PerfProfilerTool::onRunControlFinished);
PerfDataReader *reader = m_perfParserWorker->reader();
+ Process *perfProcess = nullptr;
if (auto prw = qobject_cast<LocalPerfRecordWorker *>(m_perfRecordWorker)) {
// That's the local case.
- Process *recorder = prw->recorder();
- connect(recorder, &Process::readyReadStandardError, this, [this, recorder] {
- appendMessage(QString::fromLocal8Bit(recorder->readAllRawStandardError()),
+ perfProcess = prw->recorder();
+ } else {
+ perfProcess = runControl()->property("PerfProcess").value<Process *>();
+ }
+
+ if (perfProcess) {
+ connect(perfProcess, &Process::readyReadStandardError, this, [this, perfProcess] {
+ appendMessage(QString::fromLocal8Bit(perfProcess->readAllRawStandardError()),
StdErrFormat);
});
- connect(recorder, &Process::readyReadStandardOutput, this, [this, reader, recorder] {
- if (!reader->feedParser(recorder->readAllRawStandardOutput()))
+ connect(perfProcess, &Process::readyReadStandardOutput, this, [this, reader, perfProcess] {
+ if (!reader->feedParser(perfProcess->readAllRawStandardOutput()))
reportFailure(Tr::tr("Failed to transfer Perf data to perfparser."));
});
}
diff --git a/src/plugins/perfprofiler/perftimelinemodel.cpp b/src/plugins/perfprofiler/perftimelinemodel.cpp
index fac136024b..904a73fe43 100644
--- a/src/plugins/perfprofiler/perftimelinemodel.cpp
+++ b/src/plugins/perfprofiler/perftimelinemodel.cpp
@@ -86,12 +86,12 @@ QVariantList PerfTimelineModel::labels() const
QString prettyPrintTraceData(const QVariant &data)
{
- switch (data.type()) {
- case QVariant::ULongLong:
+ switch (data.typeId()) {
+ case QMetaType::ULongLong:
return QString::fromLatin1("0x%1").arg(data.toULongLong(), 16, 16, QLatin1Char('0'));
- case QVariant::UInt:
+ case QMetaType::UInt:
return QString::fromLatin1("0x%1").arg(data.toUInt(), 8, 16, QLatin1Char('0'));
- case QVariant::List: {
+ case QMetaType::QVariantList: {
QStringList ret;
for (const QVariant &item : data.toList())
ret.append(prettyPrintTraceData(item));
diff --git a/src/plugins/perfprofiler/perftimelineresourcesrenderpass.cpp b/src/plugins/perfprofiler/perftimelineresourcesrenderpass.cpp
index 8930573f32..8ccada8f4b 100644
--- a/src/plugins/perfprofiler/perftimelineresourcesrenderpass.cpp
+++ b/src/plugins/perfprofiler/perftimelineresourcesrenderpass.cpp
@@ -210,7 +210,7 @@ ResourcesRenderPassState::ResourcesRenderPassState() :
node = new QSGNode;
node->setFlag(QSGNode::OwnedByParent, false);
m_expandedRows.append(node);
- m_material.setColor(Utils::creatorTheme()->color(Utils::Theme::Timeline_HighlightColor));
+ m_material.setColor(Utils::creatorColor(Utils::Theme::Timeline_HighlightColor));
// Disable blending
m_material.setFlag(QSGMaterial::Blending, false);
diff --git a/src/plugins/perfprofiler/perftracepointdialog.cpp b/src/plugins/perfprofiler/perftracepointdialog.cpp
index d726aa7d86..66a6208c1e 100644
--- a/src/plugins/perfprofiler/perftracepointdialog.cpp
+++ b/src/plugins/perfprofiler/perftracepointdialog.cpp
@@ -101,7 +101,7 @@ void PerfTracePointDialog::runScript()
if (elevate != QLatin1String(ELEVATE_METHOD_NA))
m_process->setCommand({m_device->filePath(elevate), {"sh"}});
else
- m_process->setCommand({m_device->filePath("sh"), {}});
+ m_process->setCommand(CommandLine{m_device->filePath("sh")});
connect(m_process.get(), &Process::done, this, &PerfTracePointDialog::handleProcessDone);
m_process->start();
diff --git a/src/plugins/plugins.qbs b/src/plugins/plugins.qbs
index b16970c07c..820ff89122 100644
--- a/src/plugins/plugins.qbs
+++ b/src/plugins/plugins.qbs
@@ -5,6 +5,7 @@ Project {
references: [
"android/android.qbs",
+ "appstatisticsmonitor/appstatisticsmonitor.qbs",
"autotest/autotest.qbs",
"autotoolsprojectmanager/autotoolsprojectmanager.qbs",
"axivion/axivion.qbs",
@@ -54,6 +55,8 @@ Project {
"languageclient/languageclient.qbs",
"languageclient/lualanguageclient/lualanguageclient.qbs",
"lua/lua.qbs",
+ "luals/luals.qbs",
+ "luatests/luatests.qbs",
"macros/macros.qbs",
"marketplace/marketplace.qbs",
"mcusupport/mcusupport.qbs",
@@ -74,9 +77,11 @@ Project {
"qnx/qnx.qbs",
"qmakeprojectmanager/qmakeprojectmanager.qbs",
"qmldesignerbase/qmldesignerbase.qbs",
+ "qtapplicationmanager/qtapplicationmanager.qbs",
"qtsupport/qtsupport.qbs",
"remotelinux/remotelinux.qbs",
"resourceeditor/resourceeditor.qbs",
+ "rustls/rustls.qbs",
"saferenderer/saferenderer.qbs",
"screenrecorder/screenrecorder.qbs",
"scxmleditor/scxmleditor.qbs",
@@ -85,6 +90,7 @@ Project {
"squish/squish.qbs",
"studiowelcome/studiowelcome.qbs",
"subversion/subversion.qbs",
+ "tellajoke/tellajoke.qbs",
"terminal/terminal.qbs",
"texteditor/texteditor.qbs",
"todo/todo.qbs",
@@ -94,6 +100,5 @@ Project {
"vcsbase/vcsbase.qbs",
"webassembly/webassembly.qbs",
"welcome/welcome.qbs",
- "qtapplicationmanager/qtapplicationmanager.qbs",
].concat(project.additionalPlugins)
}
diff --git a/src/plugins/projectexplorer/CMakeLists.txt b/src/plugins/projectexplorer/CMakeLists.txt
index b449aababf..ea7a079d90 100644
--- a/src/plugins/projectexplorer/CMakeLists.txt
+++ b/src/plugins/projectexplorer/CMakeLists.txt
@@ -140,6 +140,7 @@ add_qtc_plugin(ProjectExplorer
projectmanager.cpp projectmanager.h
projectmodels.cpp projectmodels.h
projectnodes.cpp projectnodes.h
+ projectnodeshelper.h
projectpanelfactory.cpp projectpanelfactory.h
projectsettingswidget.cpp projectsettingswidget.h
projecttree.cpp projecttree.h
@@ -179,6 +180,7 @@ add_qtc_plugin(ProjectExplorer
vcsannotatetaskhandler.cpp vcsannotatetaskhandler.h
waitforstopdialog.cpp waitforstopdialog.h
windebuginterface.cpp windebuginterface.h
+ workspaceproject.h workspaceproject.cpp
xcodebuildparser.cpp xcodebuildparser.h
)
diff --git a/src/plugins/projectexplorer/ProjectExplorer.json.in b/src/plugins/projectexplorer/ProjectExplorer.json.in
index b5f30d4242..9cb6aac8a1 100644
--- a/src/plugins/projectexplorer/ProjectExplorer.json.in
+++ b/src/plugins/projectexplorer/ProjectExplorer.json.in
@@ -14,7 +14,7 @@
],
"Category" : "Core",
"Description" : "ProjectExplorer framework that can be extended with different kind of project types.",
- "Url" : "http://www.qt.io",
+ "Url" : "https://www.qt.io",
"Arguments" : [
{
"Name" : "-customwizard-verbose",
@@ -37,6 +37,13 @@
" <glob pattern='*.tasks'/>",
" <glob pattern='*.tasks.txt'/>",
" </mime-type>",
+
+ " <mime-type type='text/x-workspace-project'>",
+ " <sub-class-of type='application/json'/>",
+ " <comment>Workspace Qt Creator Project file</comment>",
+ " <glob pattern='project.json' weight='100'/>",
+ " </mime-type>",
+
"</mime-info>"
]
}
diff --git a/src/plugins/projectexplorer/allprojectsfind.cpp b/src/plugins/projectexplorer/allprojectsfind.cpp
index c2cee55e30..5d8f285b00 100644
--- a/src/plugins/projectexplorer/allprojectsfind.cpp
+++ b/src/plugins/projectexplorer/allprojectsfind.cpp
@@ -113,16 +113,22 @@ QWidget *AllProjectsFind::createConfigWidget()
return m_configWidget;
}
-void AllProjectsFind::writeSettings(QtcSettings *settings)
+const char kDefaultInclusion[] = "*";
+const char kDefaultExclusion[] = "";
+
+Store AllProjectsFind::save() const
+{
+ Store s;
+ writeCommonSettings(s, kDefaultInclusion, kDefaultExclusion);
+ return s;
+}
+
+void AllProjectsFind::restore(const Utils::Store &s)
{
- settings->beginGroup("AllProjectsFind");
- writeCommonSettings(settings);
- settings->endGroup();
+ readCommonSettings(s, kDefaultInclusion, kDefaultExclusion);
}
-void AllProjectsFind::readSettings(QtcSettings *settings)
+QByteArray AllProjectsFind::settingsKey() const
{
- settings->beginGroup("AllProjectsFind");
- readCommonSettings(settings, "*", "");
- settings->endGroup();
+ return "AllProjectsFind";
}
diff --git a/src/plugins/projectexplorer/allprojectsfind.h b/src/plugins/projectexplorer/allprojectsfind.h
index 02dfc2a27a..4b43f1ed4b 100644
--- a/src/plugins/projectexplorer/allprojectsfind.h
+++ b/src/plugins/projectexplorer/allprojectsfind.h
@@ -26,8 +26,12 @@ public:
bool isEnabled() const override;
QWidget *createConfigWidget() override;
- void writeSettings(Utils::QtcSettings *settings) override;
- void readSettings(Utils::QtcSettings *settings) override;
+
+ Utils::Store save() const override;
+ void restore(const Utils::Store &s) override;
+
+ // deprecated
+ QByteArray settingsKey() const override;
protected:
static Utils::FileContainer filesForProjects(const QStringList &nameFilters,
diff --git a/src/plugins/projectexplorer/buildaspects.cpp b/src/plugins/projectexplorer/buildaspects.cpp
index 969f0b09bc..82c38c92bc 100644
--- a/src/plugins/projectexplorer/buildaspects.cpp
+++ b/src/plugins/projectexplorer/buildaspects.cpp
@@ -123,7 +123,7 @@ void BuildDirectoryAspect::fromMap(const Store &map)
}
}
-void BuildDirectoryAspect::addToLayout(Layouting::LayoutItem &parent)
+void BuildDirectoryAspect::addToLayout(Layouting::Layout &parent)
{
FilePathAspect::addToLayout(parent);
d->genericProblemSpacer = new QLabel;
diff --git a/src/plugins/projectexplorer/buildaspects.h b/src/plugins/projectexplorer/buildaspects.h
index f19cc39385..73d02b193d 100644
--- a/src/plugins/projectexplorer/buildaspects.h
+++ b/src/plugins/projectexplorer/buildaspects.h
@@ -23,7 +23,7 @@ public:
bool isShadowBuild() const;
void setProblem(const QString &description);
- void addToLayout(Layouting::LayoutItem &parent) override;
+ void addToLayout(Layouting::Layout &parent) override;
static Utils::FilePath fixupDir(const Utils::FilePath &dir);
diff --git a/src/plugins/projectexplorer/buildconfiguration.cpp b/src/plugins/projectexplorer/buildconfiguration.cpp
index dd1e077af4..a69133b219 100644
--- a/src/plugins/projectexplorer/buildconfiguration.cpp
+++ b/src/plugins/projectexplorer/buildconfiguration.cpp
@@ -326,13 +326,13 @@ NamedWidget *BuildConfiguration::createConfigWidget()
}
Layouting::Form form;
+ form.setNoMargins();
for (BaseAspect *aspect : aspects()) {
if (aspect->isVisible()) {
form.addItem(aspect);
- form.addItem(Layouting::br);
+ form.flush();
}
}
- form.addItem(Layouting::noMargin);
form.attachTo(widget);
return named;
diff --git a/src/plugins/projectexplorer/buildstep.cpp b/src/plugins/projectexplorer/buildstep.cpp
index 70c58eedee..a290fc0c69 100644
--- a/src/plugins/projectexplorer/buildstep.cpp
+++ b/src/plugins/projectexplorer/buildstep.cpp
@@ -113,11 +113,13 @@ QWidget *BuildStep::doCreateConfigWidget()
QWidget *BuildStep::createConfigWidget()
{
Layouting::Form form;
+ form.setNoMargins();
for (BaseAspect *aspect : std::as_const(*this)) {
- if (aspect->isVisible())
- form.addItems({aspect, Layouting::br()});
+ if (aspect->isVisible()) {
+ form.addItem(aspect);
+ form.flush();
+ }
}
- form.addItem(Layouting::noMargin);
auto widget = form.emerge();
if (m_addMacroExpander)
diff --git a/src/plugins/projectexplorer/clangparser.cpp b/src/plugins/projectexplorer/clangparser.cpp
index 28679f0ed5..05bcc3e0aa 100644
--- a/src/plugins/projectexplorer/clangparser.cpp
+++ b/src/plugins/projectexplorer/clangparser.cpp
@@ -65,7 +65,7 @@ OutputLineParser::Result ClangParser::handleLine(const QString &line, OutputForm
const int lineNo = match.captured(3).toInt();
const int column = 0;
LinkSpecs linkSpecs;
- addLinkSpecForAbsoluteFilePath(linkSpecs, filePath, lineNo, match, 2);
+ addLinkSpecForAbsoluteFilePath(linkSpecs, filePath, lineNo, column, match, 2);
createOrAmendTask(Task::Unknown, lne.trimmed(), lne, false,
filePath, lineNo, column, linkSpecs);
return {Status::InProgress, linkSpecs};
@@ -84,7 +84,7 @@ OutputLineParser::Result ClangParser::handleLine(const QString &line, OutputForm
const FilePath filePath = absoluteFilePath(FilePath::fromUserInput(match.captured(1)));
LinkSpecs linkSpecs;
- addLinkSpecForAbsoluteFilePath(linkSpecs, filePath, lineNo, match, 1);
+ addLinkSpecForAbsoluteFilePath(linkSpecs, filePath, lineNo, column, match, 1);
createOrAmendTask(taskType(match.captured(8)), match.captured(9), lne, false,
filePath, lineNo, column, linkSpecs);
return {Status::InProgress, linkSpecs};
@@ -235,7 +235,7 @@ void ProjectExplorerTest::testClangOutputParser_data()
68, 10,
QVector<QTextLayout::FormatRange>()
<< formatRange(34, 0)
- << formatRange(34, 28, "olpfile:///usr/include/c++/4.6/utility::68::-1")
+ << formatRange(34, 28, "olpfile:///usr/include/c++/4.6/utility::68::10")
<< formatRange(62, 93)))
<< QString();
@@ -255,7 +255,7 @@ void ProjectExplorerTest::testClangOutputParser_data()
567, 51,
QVector<QTextLayout::FormatRange>()
<< formatRange(74, 0)
- << formatRange(74, 64, "olpfile:///home/code/src/creator/src/plugins/coreplugin/manhattanstyle.cpp::567::-1")
+ << formatRange(74, 64, "olpfile:///home/code/src/creator/src/plugins/coreplugin/manhattanstyle.cpp::567::51")
<< formatRange(138, 202)))
<< QString();
diff --git a/src/plugins/projectexplorer/codestylesettingspropertiespage.cpp b/src/plugins/projectexplorer/codestylesettingspropertiespage.cpp
index 0153a6a195..67e1f4820b 100644
--- a/src/plugins/projectexplorer/codestylesettingspropertiespage.cpp
+++ b/src/plugins/projectexplorer/codestylesettingspropertiespage.cpp
@@ -19,6 +19,7 @@
#include <QComboBox>
#include <QLabel>
+#include <QLayout>
#include <QStackedWidget>
using namespace TextEditor;
diff --git a/src/plugins/projectexplorer/currentprojectfind.cpp b/src/plugins/projectexplorer/currentprojectfind.cpp
index 76439d93b7..5ec53f3dd2 100644
--- a/src/plugins/projectexplorer/currentprojectfind.cpp
+++ b/src/plugins/projectexplorer/currentprojectfind.cpp
@@ -29,8 +29,11 @@ private:
bool isEnabled() const final;
- void writeSettings(Utils::QtcSettings *settings) final;
- void readSettings(Utils::QtcSettings *settings) final;
+ Utils::Store save() const final;
+ void restore(const Utils::Store &s) final;
+
+ // deprecated
+ QByteArray settingsKey() const final;
QString label() const final;
@@ -115,18 +118,24 @@ void CurrentProjectFind::setupSearch(Core::SearchResult *search)
});
}
-void CurrentProjectFind::writeSettings(QtcSettings *settings)
+const char kDefaultInclusion[] = "*";
+const char kDefaultExclusion[] = "";
+
+Store CurrentProjectFind::save() const
+{
+ Store s;
+ writeCommonSettings(s, kDefaultInclusion, kDefaultExclusion);
+ return s;
+}
+
+void CurrentProjectFind::restore(const Store &s)
{
- settings->beginGroup("CurrentProjectFind");
- writeCommonSettings(settings);
- settings->endGroup();
+ readCommonSettings(s, kDefaultInclusion, kDefaultExclusion);
}
-void CurrentProjectFind::readSettings(QtcSettings *settings)
+QByteArray CurrentProjectFind::settingsKey() const
{
- settings->beginGroup("CurrentProjectFind");
- readCommonSettings(settings, "*", "");
- settings->endGroup();
+ return "CurrentProjectFind";
}
void setupCurrentProjectFind()
diff --git a/src/plugins/projectexplorer/customexecutablerunconfiguration.cpp b/src/plugins/projectexplorer/customexecutablerunconfiguration.cpp
index 8639bc00ce..12bc39a7c4 100644
--- a/src/plugins/projectexplorer/customexecutablerunconfiguration.cpp
+++ b/src/plugins/projectexplorer/customexecutablerunconfiguration.cpp
@@ -49,21 +49,6 @@ bool CustomExecutableRunConfiguration::isEnabled(Id) const
return true;
}
-ProcessRunData CustomExecutableRunConfiguration::runnable() const
-{
- ProcessRunData r;
- r.command = commandLine();
- r.environment = environment.environment();
- r.workingDirectory = workingDir();
-
- if (!r.command.isEmpty()) {
- const FilePath expanded = macroExpander()->expand(r.command.executable());
- r.command.setExecutable(expanded);
- }
-
- return r;
-}
-
QString CustomExecutableRunConfiguration::defaultDisplayName() const
{
if (executable().isEmpty())
diff --git a/src/plugins/projectexplorer/customexecutablerunconfiguration.h b/src/plugins/projectexplorer/customexecutablerunconfiguration.h
index ced9892c01..3b9e8996b8 100644
--- a/src/plugins/projectexplorer/customexecutablerunconfiguration.h
+++ b/src/plugins/projectexplorer/customexecutablerunconfiguration.h
@@ -20,7 +20,6 @@ public:
QString defaultDisplayName() const;
private:
- Utils::ProcessRunData runnable() const override;
bool isEnabled(Utils::Id) const override;
Tasks checkForIssues() const override;
diff --git a/src/plugins/projectexplorer/customparser.cpp b/src/plugins/projectexplorer/customparser.cpp
index e10595af78..0e2a360af1 100644
--- a/src/plugins/projectexplorer/customparser.cpp
+++ b/src/plugins/projectexplorer/customparser.cpp
@@ -237,7 +237,7 @@ OutputLineParser::Result CustomParser::hasMatch(
const int lineNumber = match.captured(expression.lineNumberCap()).toInt();
const QString message = match.captured(expression.messageCap());
LinkSpecs linkSpecs;
- addLinkSpecForAbsoluteFilePath(linkSpecs, fileName, lineNumber, match,
+ addLinkSpecForAbsoluteFilePath(linkSpecs, fileName, lineNumber, -1, match,
expression.fileNameCap());
scheduleTask(CompileTask(taskType, message, fileName, lineNumber), 1);
return {Status::Done, linkSpecs};
diff --git a/src/plugins/projectexplorer/customparserconfigdialog.cpp b/src/plugins/projectexplorer/customparserconfigdialog.cpp
index cbfd1adfbc..3793c0de6c 100644
--- a/src/plugins/projectexplorer/customparserconfigdialog.cpp
+++ b/src/plugins/projectexplorer/customparserconfigdialog.cpp
@@ -406,8 +406,8 @@ bool CustomParserConfigDialog::checkPattern(QLineEdit *pattern, const QString &o
QPalette palette;
palette.setColor(QPalette::Text,
- Utils::creatorTheme()->color(rx.isValid() ? Utils::Theme::TextColorNormal
- : Utils::Theme::TextColorError));
+ Utils::creatorColor(rx.isValid() ? Utils::Theme::TextColorNormal
+ : Utils::Theme::TextColorError));
pattern->setPalette(palette);
pattern->setToolTip(rx.isValid() ? QString() : rx.errorString());
@@ -415,7 +415,7 @@ bool CustomParserConfigDialog::checkPattern(QLineEdit *pattern, const QString &o
*match = rx.match(outputText);
if (rx.pattern().isEmpty() || !rx.isValid() || !match->hasMatch()) {
*errorMessage = QString::fromLatin1("<font color=\"%1\">%2 ").arg(
- Utils::creatorTheme()->color(Utils::Theme::TextColorError).name(),
+ Utils::creatorColor(Utils::Theme::TextColorError).name(),
Tr::tr("Not applicable:"));
if (rx.pattern().isEmpty())
*errorMessage += Tr::tr("Pattern is empty.");
diff --git a/src/plugins/projectexplorer/devicesupport/desktopdevice.cpp b/src/plugins/projectexplorer/devicesupport/desktopdevice.cpp
index 2dae1a9b68..b9558f17f1 100644
--- a/src/plugins/projectexplorer/devicesupport/desktopdevice.cpp
+++ b/src/plugins/projectexplorer/devicesupport/desktopdevice.cpp
@@ -6,7 +6,6 @@
#include "../projectexplorerconstants.h"
#include "../projectexplorertr.h"
#include "desktopprocesssignaloperation.h"
-#include "processlist.h"
#include <coreplugin/fileutils.h>
@@ -21,7 +20,6 @@
#include <utils/url.h>
#include <QCoreApplication>
-#include <QDateTime>
#ifdef Q_OS_WIN
#include <cstring>
@@ -65,7 +63,7 @@ DesktopDevice::DesktopDevice()
Process process;
process.setTerminalMode(TerminalMode::Detached);
process.setEnvironment(realEnv);
- process.setCommand({*shell, {}});
+ process.setCommand(CommandLine{*shell});
process.setWorkingDirectory(path);
process.start();
diff --git a/src/plugins/projectexplorer/devicesupport/devicesettingspage.cpp b/src/plugins/projectexplorer/devicesupport/devicesettingspage.cpp
index f1e520c4c6..59ead38b9f 100644
--- a/src/plugins/projectexplorer/devicesupport/devicesettingspage.cpp
+++ b/src/plugins/projectexplorer/devicesupport/devicesettingspage.cpp
@@ -23,6 +23,7 @@
#include <utils/qtcassert.h>
#include <QComboBox>
+#include <QFormLayout>
#include <QGroupBox>
#include <QLabel>
#include <QLineEdit>
@@ -91,7 +92,7 @@ private:
QPushButton *m_defaultDeviceButton;
QVBoxLayout *m_buttonsLayout;
QWidget *m_deviceNameEditWidget;
- QFormLayout *m_generalFormLayout;
+ QLayout *m_generalFormLayout;
};
DeviceSettingsWidget::DeviceSettingsWidget()
@@ -338,7 +339,7 @@ void DeviceSettingsWidget::currentDeviceChanged(int index)
return;
}
- Layouting::Column item{Layouting::noMargin()};
+ Layouting::Column item{Layouting::noMargin};
device->settings()->displayName.addToLayout(item);
QWidget *newEdit = item.emerge();
QLayoutItem *oldItem = m_generalFormLayout->replaceWidget(m_deviceNameEditWidget, newEdit);
diff --git a/src/plugins/projectexplorer/devicesupport/devicetestdialog.cpp b/src/plugins/projectexplorer/devicesupport/devicetestdialog.cpp
index 0277fc827a..4e522f24a0 100644
--- a/src/plugins/projectexplorer/devicesupport/devicetestdialog.cpp
+++ b/src/plugins/projectexplorer/devicesupport/devicetestdialog.cpp
@@ -95,10 +95,8 @@ void DeviceTestDialog::handleTestFinished(DeviceTester::TestResult result)
void DeviceTestDialog::addText(const QString &text, Utils::Theme::Color color, bool bold)
{
- Utils::Theme *theme = Utils::creatorTheme();
-
QTextCharFormat format = d->textEdit->currentCharFormat();
- format.setForeground(QBrush(theme->color(color)));
+ format.setForeground(QBrush(creatorColor(color)));
QFont font = format.font();
font.setBold(bold);
format.setFont(font);
diff --git a/src/plugins/projectexplorer/environmentwidget.cpp b/src/plugins/projectexplorer/environmentwidget.cpp
index a34f8bc256..c438b7b1f2 100644
--- a/src/plugins/projectexplorer/environmentwidget.cpp
+++ b/src/plugins/projectexplorer/environmentwidget.cpp
@@ -197,10 +197,7 @@ EnvironmentWidget::EnvironmentWidget(QWidget *parent, Type type, QWidget *additi
auto horizontalLayout = new QHBoxLayout();
horizontalLayout->setContentsMargins(0, 0, 0, 0);
- auto tree = new Utils::TreeView(this);
- connect(tree, &QAbstractItemView::activated,
- tree, [tree](const QModelIndex &idx) { tree->edit(idx); });
- d->m_environmentView = tree;
+ d->m_environmentView = new Utils::TreeView(this);
d->m_environmentView->setModel(d->m_model);
d->m_environmentView->setMinimumHeight(400);
d->m_environmentView->setRootIsDecorated(false);
@@ -363,7 +360,10 @@ void EnvironmentWidget::updateSummaryText()
return;
}
- Utils::EnvironmentItems list = d->m_model->userChanges();
+ Utils::EnvironmentItems list
+ = Utils::filtered(d->m_model->userChanges(), [](const EnvironmentItem &it) {
+ return it.operation != Utils::EnvironmentItem::Comment;
+ });
Utils::EnvironmentItem::sort(&list);
QString text;
@@ -387,6 +387,8 @@ void EnvironmentWidget::updateSummaryText()
case Utils::EnvironmentItem::SetDisabled:
text.append(Tr::tr("Set <a href=\"%1\"><b>%1</b></a> to <b>%2</b> [disabled]").arg(item.name.toHtmlEscaped(), item.value.toHtmlEscaped()));
break;
+ case Utils::EnvironmentItem::Comment:
+ break;
}
}
}
diff --git a/src/plugins/projectexplorer/extracompiler.cpp b/src/plugins/projectexplorer/extracompiler.cpp
index 932e5c1bd7..b7568d4275 100644
--- a/src/plugins/projectexplorer/extracompiler.cpp
+++ b/src/plugins/projectexplorer/extracompiler.cpp
@@ -11,8 +11,6 @@
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/idocument.h>
-#include <extensionsystem/pluginmanager.h>
-
#include <solutions/tasking/tasktreerunner.h>
#include <utils/async.h>
@@ -326,7 +324,6 @@ GroupItem ProcessExtraCompiler::taskItemImpl(const ContentProvider &provider)
const auto onSetup = [this, provider](Async<FileNameToContentsHash> &async) {
async.setThreadPool(extraCompilerThreadPool());
// The passed synchronizer has cancelOnWait set to true by default.
- async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
async.setConcurrentCallData(&ProcessExtraCompiler::runInThread, this, command(),
workingDirectory(), arguments(), provider, buildEnvironment());
};
@@ -382,7 +379,7 @@ void ProcessExtraCompiler::runInThread(QPromise<FileNameToContentsHash> &promise
process.setEnvironment(env);
if (!workDir.isEmpty())
process.setWorkingDirectory(workDir);
- process.setCommand({ cmd, args });
+ process.setCommand({cmd, args});
process.setWriteData(sourceContents);
process.start();
if (!process.waitForStarted())
diff --git a/src/plugins/projectexplorer/filesinallprojectsfind.cpp b/src/plugins/projectexplorer/filesinallprojectsfind.cpp
index 92c2fc8059..62ac4e4839 100644
--- a/src/plugins/projectexplorer/filesinallprojectsfind.cpp
+++ b/src/plugins/projectexplorer/filesinallprojectsfind.cpp
@@ -29,22 +29,25 @@ QString FilesInAllProjectsFind::displayName() const
}
const char kSettingsKey[] = "FilesInAllProjectDirectories";
+const char kDefaultInclusion[]
+ = "CMakeLists.txt,*.cmake,*.pro,*.pri,*.qbs,*.cpp,*.h,*.mm,*.qml,*.md,*.txt,*.qdoc";
+const char kDefaultExclusion[] = "*/.git/*,*/.cvs/*,*/.svn/*,*.autosave,*/build/*";
-void FilesInAllProjectsFind::writeSettings(QtcSettings *settings)
+Store FilesInAllProjectsFind::save() const
{
- settings->beginGroup(kSettingsKey);
- writeCommonSettings(settings);
- settings->endGroup();
+ Store s;
+ writeCommonSettings(s, kDefaultInclusion, kDefaultExclusion);
+ return s;
}
-void FilesInAllProjectsFind::readSettings(QtcSettings *settings)
+void FilesInAllProjectsFind::restore(const Utils::Store &s)
{
- settings->beginGroup(kSettingsKey);
- readCommonSettings(
- settings,
- "CMakeLists.txt,*.cmake,*.pro,*.pri,*.qbs,*.cpp,*.h,*.mm,*.qml,*.md,*.txt,*.qdoc",
- "*/.git/*,*/.cvs/*,*/.svn/*,*.autosave,*/build/*");
- settings->endGroup();
+ readCommonSettings(s, kDefaultInclusion, kDefaultExclusion);
+}
+
+QByteArray FilesInAllProjectsFind::settingsKey() const
+{
+ return kSettingsKey;
}
FileContainerProvider FilesInAllProjectsFind::fileContainerProvider() const
diff --git a/src/plugins/projectexplorer/filesinallprojectsfind.h b/src/plugins/projectexplorer/filesinallprojectsfind.h
index f96d2a6df9..39d8e2c0ed 100644
--- a/src/plugins/projectexplorer/filesinallprojectsfind.h
+++ b/src/plugins/projectexplorer/filesinallprojectsfind.h
@@ -16,8 +16,11 @@ public:
QString id() const override;
QString displayName() const override;
- void writeSettings(Utils::QtcSettings *settings) override;
- void readSettings(Utils::QtcSettings *settings) override;
+ Utils::Store save() const override;
+ void restore(const Utils::Store &s) override;
+
+ // deprecated
+ QByteArray settingsKey() const override;
protected:
TextEditor::FileContainerProvider fileContainerProvider() const override;
diff --git a/src/plugins/projectexplorer/gccparser.cpp b/src/plugins/projectexplorer/gccparser.cpp
index 4d21d4b8d9..3a43e8ebe9 100644
--- a/src/plugins/projectexplorer/gccparser.cpp
+++ b/src/plugins/projectexplorer/gccparser.cpp
@@ -8,37 +8,123 @@
#include <utils/qtcassert.h>
-#include <numeric>
+#include <QLoggingCategory>
+
+#include <optional>
using namespace Utils;
namespace ProjectExplorer {
-// opt. drive letter + filename: (2 brackets)
-static const char FILE_PATTERN[] = "(<command[ -]line>|([A-Za-z]:)?[^:]+):";
+static Q_LOGGING_CATEGORY(gccParserLog, "qtc.gccparser", QtWarningMsg)
+
+static const QString &filePattern()
+{
+ static const QString pattern = [] {
+ const QString pseudoFile = "<command[ -]line>";
+ const QString driveSpec = "[A-Za-z]:";
+ const QString realFile = QString::fromLatin1("(?:%1)?[^:]+").arg(driveSpec);
+ return QString::fromLatin1("(?<file>%1|%2):").arg(pseudoFile, realFile);
+ }();
+ return pattern;
+}
+
static const char COMMAND_PATTERN[] = "^(.*?[\\\\/])?([a-z0-9]+-[a-z0-9]+-[a-z0-9]+-)?(gcc|g\\+\\+)(-[0-9.]+)?(\\.exe)?: ";
+namespace {
+class MainRegEx
+{
+public:
+ struct Data {
+ QString rawFilePath;
+ QString description;
+ Task::TaskType type = Task::Unknown;
+ int line = -1;
+ int column = -1;
+ int fileOffset = -1;
+ };
+
+ static std::optional<Data> parse(const QString &line)
+ {
+ qCDebug(gccParserLog) << "checking regex" << theRegEx().pattern();
+ const QRegularExpressionMatch match = theRegEx().match(line);
+ if (!match.hasMatch())
+ return {};
+
+ // Quick plausability test: If there's no slashes or dots, it's probably not a file.
+ const QString possibleFile = match.captured("file");
+ if (!possibleFile.contains('/') && !possibleFile.contains("'\\")
+ && !possibleFile.contains('.')) {
+ return {};
+ }
+
+ // Defer to the LdParser for some file types
+ if (possibleFile.endsWith(".o") || possibleFile.endsWith(".a")
+ || possibleFile.endsWith("dll") || possibleFile.contains(".so")
+ || possibleFile.endsWith("ld") || possibleFile.endsWith("ranlib")) {
+ return {};
+ }
+
+ Data data;
+ data.rawFilePath = possibleFile;
+ data.fileOffset = match.capturedStart("file");
+ data.line = match.captured("line").toInt();
+ data.column = match.captured("column").toInt();
+ data.description = match.captured("description");
+ if (match.captured("type") == QLatin1String("warning")) {
+ data.type = Task::Warning;
+ } else if (match.captured("type") == QLatin1String("error") ||
+ data.description.startsWith(QLatin1String("undefined reference to")) ||
+ data.description.startsWith(QLatin1String("multiple definition of"))) {
+ data.type = Task::Error;
+ }
+
+ // Prepend "#warning" or "#error" if that triggered the match on (warning|error)
+ // We want those to show how the warning was triggered
+ if (match.captured("fullTypeString").startsWith(QLatin1Char('#')))
+ data.description.prepend(match.captured("fullTypeString"));
+
+ return data;
+ }
+
+private:
+ static const QRegularExpression &theRegEx()
+ {
+ static const QRegularExpression re = [] {
+ const QRegularExpression re(constructPattern());
+ QTC_CHECK(re.isValid());
+ return re;
+ }();
+ return re;
+ }
+
+ static QString constructPattern()
+ {
+ const QString type = "(?<type>warning|error|note)";
+ const QString typePrefix = "(?:fatal |#)";
+ const QString fullTypeString
+ = QString::fromLatin1("(?<fullTypeString>%1?%2:?\\s)").arg(typePrefix, type);
+ const QString optionalLineAndColumn = "(?:(?:(?<line>\\d+)(?::(?<column>\\d+))?):)?";
+ const QString binaryLocation = "\\(.*\\)"; // E.g. "(.text+0x40)"
+ const QString fullLocation = QString::fromLatin1("%1(?:%2|%3)")
+ .arg(filePattern(), optionalLineAndColumn, binaryLocation);
+ const QString description = "(?<description>[^\\s].+)";
+
+ return QString::fromLatin1("^%1\\s+%2?%3$").arg(fullLocation, fullTypeString, description);
+ }
+};
+} // namespace
+
GccParser::GccParser()
{
setObjectName(QLatin1String("GCCParser"));
- m_regExp.setPattern(QLatin1Char('^') + QLatin1String(FILE_PATTERN)
- + QLatin1String("(?:(?:(\\d+):(?:(\\d+):)?)|\\(.*\\):)\\s+((fatal |#)?(warning|error|note):?\\s)?([^\\s].+)$"));
- QTC_CHECK(m_regExp.isValid());
- m_regExpScope.setPattern(QLatin1Char('^') + FILE_PATTERN
- + "(?:(\\d+):)?(\\d+:)?\\s+((?:In .*(?:function|constructor) .*|At global scope|At top level):)$");
- QTC_CHECK(m_regExpScope.isValid());
-
- m_regExpIncluded.setPattern(QString::fromLatin1("\\bfrom\\s") + QLatin1String(FILE_PATTERN)
+ m_regExpIncluded.setPattern(QString::fromLatin1("\\bfrom\\s") + filePattern()
+ QLatin1String("(\\d+)(:\\d+)?[,:]?$"));
QTC_CHECK(m_regExpIncluded.isValid());
- m_regExpInlined.setPattern(QString::fromLatin1("\\binlined from\\s.* at ")
- + FILE_PATTERN + "(\\d+)(:\\d+)?[,:]?$");
- QTC_CHECK(m_regExpInlined.isValid());
-
m_regExpCc1plus.setPattern(QLatin1Char('^') + "cc1plus.*(error|warning): ((?:"
- + FILE_PATTERN + " No such file or directory)?.*)");
+ + filePattern() + " No such file or directory)?.*)");
QTC_CHECK(m_regExpCc1plus.isValid());
// optional path with trailing slash
@@ -60,82 +146,36 @@ QList<OutputLineParser *> GccParser::gccParserSuite()
return {new GccParser, new Internal::LldParser, new LdParser};
}
-void GccParser::createOrAmendTask(
- Task::TaskType type,
- const QString &description,
- const QString &originalLine,
- bool forceAmend,
- const FilePath &file,
- int line,
- int column,
- const LinkSpecs &linkSpecs
- )
+void GccParser::gccCreateOrAmendTask(
+ Task::TaskType type,
+ const QString &description,
+ const QString &originalLine,
+ bool forceAmend,
+ const Utils::FilePath &file,
+ int line,
+ int column,
+ const LinkSpecs &linkSpecs)
{
- const bool amend = !m_currentTask.isNull() && (forceAmend || isContinuation(originalLine));
- if (!amend) {
- flush();
- m_currentTask = CompileTask(type, description, file, line, column);
- m_currentTask.details.append(originalLine);
- m_linkSpecs = linkSpecs;
- m_lines = 1;
- return;
- }
-
- LinkSpecs adaptedLinkSpecs = linkSpecs;
- const int offset = std::accumulate(m_currentTask.details.cbegin(), m_currentTask.details.cend(),
- 0, [](int total, const QString &line) { return total + line.length() + 1;});
- for (LinkSpec &ls : adaptedLinkSpecs)
- ls.startPos += offset;
- m_linkSpecs << adaptedLinkSpecs;
- m_currentTask.details.append(originalLine);
-
- // Check whether the new line is more relevant than the previous ones.
- if ((m_currentTask.type != Task::Error && type == Task::Error)
- || (m_currentTask.type == Task::Unknown && type != Task::Unknown)) {
- m_currentTask.type = type;
- m_currentTask.summary = description;
- if (!file.isEmpty() && !m_requiredFromHereFound) {
- m_currentTask.setFile(file);
- m_currentTask.line = line;
- m_currentTask.column = column;
- }
- }
+ createOrAmendTask(type, description, originalLine, forceAmend, file, line, column, linkSpecs);
// If a "required from here" line is present, it is almost always the cause of the problem,
// so that's where we should go when the issue is double-clicked.
if ((originalLine.endsWith("required from here") || originalLine.endsWith("requested here")
- || originalLine.endsWith("note: here")) && !file.isEmpty() && line > 0) {
- m_requiredFromHereFound = true;
- m_currentTask.setFile(file);
- m_currentTask.line = line;
- m_currentTask.column = column;
+ || originalLine.endsWith("note: here"))
+ && !file.isEmpty() && line > 0) {
+ fixTargetLink();
+ currentTask().setFile(file);
+ currentTask().line = line;
+ currentTask().column = column;
}
-
- ++m_lines;
-}
-
-void GccParser::flush()
-{
- if (m_currentTask.isNull())
- return;
-
- // If there is only one line of details, then it is the line that we generated
- // the summary from. Remove it, because it does not add any information.
- if (m_currentTask.details.count() == 1)
- m_currentTask.details.clear();
-
- setDetailsFormat(m_currentTask, m_linkSpecs);
- Task t = m_currentTask;
- m_currentTask.clear();
- m_linkSpecs.clear();
- scheduleTask(t, m_lines, 1);
- m_lines = 0;
- m_requiredFromHereFound = false;
}
OutputLineParser::Result GccParser::handleLine(const QString &line, OutputFormat type)
{
+ qCDebug(gccParserLog) << "incoming line" << line;
+
if (type == StdOutFormat) {
+ qCDebug(gccParserLog) << "not parsing stdout";
flush();
return Status::NotHandled;
}
@@ -143,109 +183,95 @@ OutputLineParser::Result GccParser::handleLine(const QString &line, OutputFormat
const QString lne = rightTrimmed(line);
// Blacklist some lines to not handle them:
- if (lne.startsWith(QLatin1String("TeamBuilder ")) ||
- lne.startsWith(QLatin1String("distcc["))) {
+ if (lne.startsWith(QLatin1String("TeamBuilder "))
+ || lne.startsWith(QLatin1String("distcc["))
+ || lne.contains("undefined reference")
+ || lne.contains("undefined symbol")
+ || lne.contains("duplicate symbol")
+ || lne.contains("multiple definition")
+ || lne.contains("ar: creating")) {
return Status::NotHandled;
}
// Handle misc issues:
if (lne.startsWith(QLatin1String("ERROR:")) || lne == QLatin1String("* cpp failed")) {
- createOrAmendTask(Task::Error, lne, lne);
+ gccCreateOrAmendTask(Task::Error, lne, lne);
return Status::InProgress;
}
+ qCDebug(gccParserLog) << "checking regex" << m_regExpGccNames.pattern();
QRegularExpressionMatch match = m_regExpGccNames.match(lne);
if (match.hasMatch()) {
QString description = lne.mid(match.capturedLength());
Task::TaskType type = Task::Error;
if (description.startsWith(QLatin1String("warning: "))) {
type = Task::Warning;
- description = description.mid(9);
+ description = description.mid(8);
} else if (description.startsWith(QLatin1String("fatal: "))) {
- description = description.mid(7);
+ description = description.mid(6);
}
- createOrAmendTask(type, description, lne);
+ gccCreateOrAmendTask(type, description, lne);
return Status::InProgress;
}
+ qCDebug(gccParserLog) << "checking regex" << m_regExpIncluded.pattern();
match = m_regExpIncluded.match(lne);
- if (!match.hasMatch())
- match = m_regExpInlined.match(lne);
if (match.hasMatch()) {
- const FilePath filePath = absoluteFilePath(FilePath::fromUserInput(match.captured(1)));
- const int lineNo = match.captured(3).toInt();
- const int column = match.captured(4).toInt();
+ const FilePath filePath = absoluteFilePath(FilePath::fromUserInput(match.captured("file")));
+ const int lineNo = match.captured(2).toInt();
+ const int column = match.captured(3).toInt();
LinkSpecs linkSpecs;
- addLinkSpecForAbsoluteFilePath(linkSpecs, filePath, lineNo, match, 1);
- createOrAmendTask(Task::Unknown, lne.trimmed(), lne, false, filePath, lineNo, column, linkSpecs);
+ addLinkSpecForAbsoluteFilePath(linkSpecs, filePath, lineNo, column, match, "file");
+ gccCreateOrAmendTask(
+ Task::Unknown, lne.trimmed(), lne, false, filePath, lineNo, column, linkSpecs);
return {Status::InProgress, linkSpecs};
}
+ qCDebug(gccParserLog) << "checking regex" << m_regExpCc1plus.pattern();
match = m_regExpCc1plus.match(lne);
if (match.hasMatch()) {
const Task::TaskType type = match.captured(1) == "error" ? Task::Error : Task::Warning;
const FilePath filePath = absoluteFilePath(FilePath::fromUserInput(match.captured(3)));
LinkSpecs linkSpecs;
if (!filePath.isEmpty())
- addLinkSpecForAbsoluteFilePath(linkSpecs, filePath, -1, match, 3);
- createOrAmendTask(type, match.captured(2), lne, false, filePath, -1, 0, linkSpecs);
- flush();
+ addLinkSpecForAbsoluteFilePath(linkSpecs, filePath, -1, -1, match, 3);
+ gccCreateOrAmendTask(type, match.captured(2), lne, false, filePath, -1, 0, linkSpecs);
return {Status::Done, linkSpecs};
}
- match = m_regExp.match(lne);
- if (match.hasMatch()) {
- int lineno = match.captured(3).toInt();
- int column = match.captured(4).toInt();
- Task::TaskType type = Task::Unknown;
- QString description = match.captured(8);
- if (match.captured(7) == QLatin1String("warning"))
- type = Task::Warning;
- else if (match.captured(7) == QLatin1String("error") ||
- description.startsWith(QLatin1String("undefined reference to")) ||
- description.startsWith(QLatin1String("multiple definition of")))
- type = Task::Error;
- // Prepend "#warning" or "#error" if that triggered the match on (warning|error)
- // We want those to show how the warning was triggered
- if (match.captured(5).startsWith(QLatin1Char('#')))
- description = match.captured(5) + description;
-
- const FilePath filePath = absoluteFilePath(FilePath::fromUserInput(match.captured(1)));
- LinkSpecs linkSpecs;
- addLinkSpecForAbsoluteFilePath(linkSpecs, filePath, lineno, match, 1);
- createOrAmendTask(type, description, lne, false, filePath, lineno, column, linkSpecs);
- return {Status::InProgress, linkSpecs};
- }
-
- match = m_regExpScope.match(lne);
- if (match.hasMatch()) {
- const int lineno = match.captured(3).toInt();
- const int column = match.captured(4).toInt();
- const QString description = match.captured(5);
- const FilePath filePath = absoluteFilePath(FilePath::fromUserInput(match.captured(1)));
+ if (const auto data = MainRegEx::parse(lne)) {
+ const FilePath filePath = absoluteFilePath(FilePath::fromUserInput(data->rawFilePath));
LinkSpecs linkSpecs;
- addLinkSpecForAbsoluteFilePath(linkSpecs, filePath, lineno, match, 1);
- createOrAmendTask(Task::Unknown, description, lne, false, filePath, lineno, column, linkSpecs);
+ addLinkSpecForAbsoluteFilePath(
+ linkSpecs,
+ filePath,
+ data->line,
+ data->column,
+ data->fileOffset,
+ data->rawFilePath.size());
+ gccCreateOrAmendTask(
+ data->type, data->description, lne, false, filePath, data->line, data->column, linkSpecs);
return {Status::InProgress, linkSpecs};
}
- if ((lne.startsWith(' ') && !m_currentTask.isNull()) || isContinuation(lne)) {
- createOrAmendTask(Task::Unknown, lne, lne, true);
+ if ((lne.startsWith(' ') && !currentTask().isNull()) || isContinuation(lne)) {
+ gccCreateOrAmendTask(Task::Unknown, lne, lne, true);
return Status::InProgress;
}
+ qCDebug(gccParserLog) << "no match";
flush();
return Status::NotHandled;
}
bool GccParser::isContinuation(const QString &newLine) const
{
- return !m_currentTask.isNull()
- && (m_currentTask.details.last().endsWith(':')
- || m_currentTask.details.last().endsWith(',')
- || m_currentTask.details.last().contains(" required from ")
- || newLine.contains("within this context")
- || newLine.contains("note:"));
+ return !currentTask().isNull()
+ && (currentTask().details.last().endsWith(':')
+ || currentTask().details.last().endsWith(',')
+ || currentTask().details.last().contains(" required from ")
+ || newLine.contains("within this context")
+ || newLine.contains("note:"));
}
} // ProjectExplorer
@@ -322,9 +348,9 @@ void ProjectExplorerTest::testGccOutputParsers_data()
9, 0,
QVector<QTextLayout::FormatRange>()
<< formatRange(46, 0)
- << formatRange(46, 29, "olpfile:///temp/test/untitled8/main.cpp::0::-1")
+ << formatRange(46, 29, "olpfile:///temp/test/untitled8/main.cpp::0::0")
<< formatRange(75, 39)
- << formatRange(114, 29, "olpfile:///temp/test/untitled8/main.cpp::9::-1")
+ << formatRange(114, 29, "olpfile:///temp/test/untitled8/main.cpp::9::0")
<< formatRange(143, 56))
<< CompileTask(Task::Error,
"(Each undeclared identifier is reported only once for each function it appears in.)",
@@ -410,8 +436,7 @@ void ProjectExplorerTest::testGccOutputParsers_data()
FilePath::fromUserInput("C:\\temp\\test\\untitled8/main.cpp"),
8, 0,
formatRanges)
- << CompileTask(Task::Error,
- "collect2: ld returned 1 exit status"))
+ << CompileTask(Task::Error, "collect2: ld returned 1 exit status"))
<< QString();
formatRanges.clear();
@@ -436,8 +461,7 @@ void ProjectExplorerTest::testGccOutputParsers_data()
FilePath::fromUserInput("C:\\temp\\test\\untitled8/main.cpp"),
-1, 0,
formatRanges)
- << CompileTask(Task::Error,
- "collect2: ld returned 1 exit status"))
+ << CompileTask(Task::Error, "collect2: ld returned 1 exit status"))
<< QString();
QTest::newRow("linker: dll format not recognized")
@@ -495,9 +519,9 @@ void ProjectExplorerTest::testGccOutputParsers_data()
264, 0,
QVector<QTextLayout::FormatRange>()
<< formatRange(45, 0)
- << formatRange(45, 68, "olpfile:///home/code/src/creator/src/plugins/projectexplorer/gnumakeparser.cpp::0::-1")
+ << formatRange(45, 68, "olpfile:///home/code/src/creator/src/plugins/projectexplorer/gnumakeparser.cpp::0::0")
<< formatRange(113, 106)
- << formatRange(219, 68, "olpfile:///home/code/src/creator/src/plugins/projectexplorer/gnumakeparser.cpp::264::-1")
+ << formatRange(219, 68, "olpfile:///home/code/src/creator/src/plugins/projectexplorer/gnumakeparser.cpp::264::0")
<< formatRange(287, 57))
<< CompileTask(Task::Error,
"expected ';' before ':' token",
@@ -564,9 +588,9 @@ void ProjectExplorerTest::testGccOutputParsers_data()
194, 0,
QVector<QTextLayout::FormatRange>()
<< formatRange(50, 0)
- << formatRange(50, 67, "olpfile:///Qt/4.6.2-Symbian/s60sdk/epoc32/include/stdapis/stlport/stl/_tree.c::0::-1")
+ << formatRange(50, 67, "olpfile:///Qt/4.6.2-Symbian/s60sdk/epoc32/include/stdapis/stlport/stl/_tree.c::0::0")
<< formatRange(117, 216)
- << formatRange(333, 67, "olpfile:///Qt/4.6.2-Symbian/s60sdk/epoc32/include/stdapis/stlport/stl/_tree.c::194::-1")
+ << formatRange(333, 67, "olpfile:///Qt/4.6.2-Symbian/s60sdk/epoc32/include/stdapis/stlport/stl/_tree.c::194::0")
<< formatRange(400, 64)))
<< QString();
@@ -803,9 +827,9 @@ void ProjectExplorerTest::testGccOutputParsers_data()
1134, 26,
QVector<QTextLayout::FormatRange>()
<< formatRange(26, 22)
- << formatRange(48, 39, "olpfile:///Symbian/SDK/EPOC32/INCLUDE/GCCE/GCCE.h::15::-1")
+ << formatRange(48, 39, "olpfile:///Symbian/SDK/EPOC32/INCLUDE/GCCE/GCCE.h::15::0")
<< formatRange(87, 46)
- << formatRange(133, 50, "olpfile:///Symbian/SDK/epoc32/include/variant/Symbian_OS.hrh::1134::-1")
+ << formatRange(133, 50, "olpfile:///Symbian/SDK/epoc32/include/variant/Symbian_OS.hrh::1134::26")
<< formatRange(183, 44))}
<< QString();
@@ -910,7 +934,7 @@ void ProjectExplorerTest::testGccOutputParsers_data()
14, 25,
QVector<QTextLayout::FormatRange>()
<< formatRange(41, 22)
- << formatRange(63, 67, "olpfile:///home/code/src/creator/src/libs/extensionsystem/pluginerrorview.cpp::31::-1")
+ << formatRange(63, 67, "olpfile:///home/code/src/creator/src/libs/extensionsystem/pluginerrorview.cpp::31::0")
<< formatRange(130, 146))}
<< QString();
@@ -934,11 +958,11 @@ void ProjectExplorerTest::testGccOutputParsers_data()
597, 5,
QVector<QTextLayout::FormatRange>()
<< formatRange(43, 22)
- << formatRange(65, 31, "olpfile:///usr/include/qt4/QtCore/QString::1::-1")
+ << formatRange(65, 31, "olpfile:///usr/include/qt4/QtCore/QString::1::0")
<< formatRange(96, 40)
- << formatRange(136, 33, "olpfile:///usr/include/qt4/QtCore/qstring.h::0::-1")
+ << formatRange(136, 33, "olpfile:///usr/include/qt4/QtCore/qstring.h::0::0")
<< formatRange(169, 28)
- << formatRange(197, 33, "olpfile:///usr/include/qt4/QtCore/qstring.h::597::-1")
+ << formatRange(197, 33, "olpfile:///usr/include/qt4/QtCore/qstring.h::597::5")
<< formatRange(230, 99))}
<< QString();
@@ -960,7 +984,7 @@ void ProjectExplorerTest::testGccOutputParsers_data()
<< formatRange(31, 28)
<< formatRange(59, 23, "olpfile:///home/user/test/foo.cpp::2::-1")
<< formatRange(82, 34))
- << CompileTask(Task::Unknown,
+ << CompileTask(Task::Error,
"first defined here",
FilePath::fromUserInput("/home/user/test/bar.cpp"),
4)
@@ -978,7 +1002,7 @@ void ProjectExplorerTest::testGccOutputParsers_data()
<< CompileTask(Task::Error,
"multiple definition of `foo'",
FilePath::fromUserInput("foo.o"), -1)
- << CompileTask(Task::Unknown,
+ << CompileTask(Task::Error,
"first defined here",
FilePath::fromUserInput("bar.o"), -1)
<< CompileTask(Task::Error,
@@ -993,10 +1017,11 @@ void ProjectExplorerTest::testGccOutputParsers_data()
<< OutputParserTester::STDERR
<< QString() << QString()
<< Tasks({CompileTask(Task::Error,
- "Undefined symbols for architecture x86_64:\n"
- " \"SvgLayoutTest()\", referenced from:\n"
- " _main in main.cpp.o",
- "main.cpp.o")})
+ "Undefined symbols for architecture x86_64:\n"
+ "Undefined symbols for architecture x86_64:\n"
+ " \"SvgLayoutTest()\", referenced from:\n"
+ " _main in main.cpp.o",
+ "main.cpp.o")})
<< QString();
QTest::newRow("ld: undefined member function reference")
@@ -1213,17 +1238,17 @@ void ProjectExplorerTest::testGccOutputParsers_data()
273, 25,
QVector<QTextLayout::FormatRange>()
<< formatRange(140, 22)
- << formatRange(162, 32, "olpfile:///usr/include/qt/QtCore/qlocale.h::43::-1")
+ << formatRange(162, 32, "olpfile:///usr/include/qt/QtCore/qlocale.h::43::0")
<< formatRange(194, 27)
- << formatRange(221, 36, "olpfile:///usr/include/qt/QtCore/qtextstream.h::46::-1")
+ << formatRange(221, 36, "olpfile:///usr/include/qt/QtCore/qtextstream.h::46::0")
<< formatRange(257, 27)
- << formatRange(284, 38, "olpfile:///qtc/src/shared/proparser/proitems.cpp::31::-1")
+ << formatRange(284, 38, "olpfile:///qtc/src/shared/proparser/proitems.cpp::31::0")
<< formatRange(322, 5)
- << formatRange(327, 33, "olpfile:///usr/include/qt/QtCore/qvariant.h::0::-1")
+ << formatRange(327, 33, "olpfile:///usr/include/qt/QtCore/qvariant.h::0::0")
<< formatRange(360, 51)
- << formatRange(411, 33, "olpfile:///usr/include/qt/QtCore/qvariant.h::273::-1")
+ << formatRange(411, 33, "olpfile:///usr/include/qt/QtCore/qvariant.h::273::25")
<< formatRange(444, 229)
- << formatRange(673, 33, "olpfile:///usr/include/qt/QtCore/qvariant.h::399::-1")
+ << formatRange(673, 33, "olpfile:///usr/include/qt/QtCore/qvariant.h::399::16")
<< formatRange(706, 221)),
compileTask(Task::Error,
"no match for ‘operator+’ (operand types are ‘boxed_value<double>’ and ‘boxed_value<double>’)\n"
@@ -1435,6 +1460,37 @@ void ProjectExplorerTest::testGccOutputParsers_data()
<< Tasks{CompileTask(Task::Error, "blubb", "/home/tim/path/to/sources/and/more.h",
15, 22)}
<< QString();
+
+ QTest::newRow("no line number")
+ << QString::fromUtf8("In file included from /data/dev/creator/src/libs/utils/aspects.cpp:12:\n"
+ "/data/dev/creator/src/libs/utils/layoutbuilder.h: In instantiation of ‘Layouting::BuilderItem<X, XInterface>::I::I(const Inner&) [with Inner = Utils::BaseAspect; X = Layouting::Row; XInterface = Layouting::LayoutInterface]’:\n"
+ "/data/dev/creator/src/libs/utils/aspects.cpp:3454:13: required from here\n"
+ "/data/dev/creator/src/libs/utils/layoutbuilder.h:79:51: error: use of deleted function ‘Utils::BaseAspect::BaseAspect(const Utils::BaseAspect&)’\n"
+ " 79 | apply = [p](XInterface *x) { doit_nest(x, p); };\n"
+ " | ~~~~~~~~^~~~~")
+ << OutputParserTester::STDERR
+ << QString() << QString()
+ << (Tasks{compileTask(
+ Task::Error,
+ "use of deleted function ‘Utils::BaseAspect::BaseAspect(const Utils::BaseAspect&)’\n"
+ "In file included from /data/dev/creator/src/libs/utils/aspects.cpp:12:\n"
+ "/data/dev/creator/src/libs/utils/layoutbuilder.h: In instantiation of ‘Layouting::BuilderItem<X, XInterface>::I::I(const Inner&) [with Inner = Utils::BaseAspect; X = Layouting::Row; XInterface = Layouting::LayoutInterface]’:\n"
+ "/data/dev/creator/src/libs/utils/aspects.cpp:3454:13: required from here\n"
+ "/data/dev/creator/src/libs/utils/layoutbuilder.h:79:51: error: use of deleted function ‘Utils::BaseAspect::BaseAspect(const Utils::BaseAspect&)’\n"
+ " 79 | apply = [p](XInterface *x) { doit_nest(x, p); };\n"
+ " | ~~~~~~~~^~~~~",
+ FilePath::fromUserInput("/data/dev/creator/src/libs/utils/aspects.cpp"), 3454, 13,
+ QVector<QTextLayout::FormatRange>{
+ formatRange(82, 22),
+ formatRange(104, 44, "olpfile:///data/dev/creator/src/libs/utils/aspects.cpp::12::0"),
+ formatRange(148, 5),
+ formatRange(153, 48, "olpfile:///data/dev/creator/src/libs/utils/layoutbuilder.h::0::0"),
+ formatRange(201, 177),
+ formatRange(378, 44, "olpfile:///data/dev/creator/src/libs/utils/aspects.cpp::3454::13"),
+ formatRange(422, 31),
+ formatRange(453, 48, "olpfile:///data/dev/creator/src/libs/utils/layoutbuilder.h::79::51"),
+ formatRange(501, 228)})})
+ << QString();
}
void ProjectExplorerTest::testGccOutputParsers()
diff --git a/src/plugins/projectexplorer/gccparser.h b/src/plugins/projectexplorer/gccparser.h
index f4fe67f1f3..1c8431c412 100644
--- a/src/plugins/projectexplorer/gccparser.h
+++ b/src/plugins/projectexplorer/gccparser.h
@@ -11,7 +11,7 @@
namespace ProjectExplorer {
-class PROJECTEXPLORER_EXPORT GccParser : public ProjectExplorer::OutputTaskParser
+class PROJECTEXPLORER_EXPORT GccParser : public OutputTaskParser
{
Q_OBJECT
@@ -23,34 +23,24 @@ public:
static QList<OutputLineParser *> gccParserSuite();
protected:
- void createOrAmendTask(
- Task::TaskType type,
- const QString &description,
- const QString &originalLine,
- bool forceAmend = false,
- const Utils::FilePath &file = {},
- int line = -1,
- int column = 0,
- const LinkSpecs &linkSpecs = {}
- );
- void flush() override;
+ void gccCreateOrAmendTask(
+ Task::TaskType type,
+ const QString &description,
+ const QString &originalLine,
+ bool forceAmend = false,
+ const Utils::FilePath &file = {},
+ int line = -1,
+ int column = 0,
+ const LinkSpecs &linkSpecs = {});
private:
Result handleLine(const QString &line, Utils::OutputFormat type) override;
- bool isContinuation(const QString &newLine) const;
+ bool isContinuation(const QString &newLine) const override;
- QRegularExpression m_regExp;
- QRegularExpression m_regExpScope;
QRegularExpression m_regExpIncluded;
- QRegularExpression m_regExpInlined;
QRegularExpression m_regExpGccNames;
QRegularExpression m_regExpCc1plus;
-
- Task m_currentTask;
- LinkSpecs m_linkSpecs;
- int m_lines = 0;
- bool m_requiredFromHereFound = false;
};
} // namespace ProjectExplorer
diff --git a/src/plugins/projectexplorer/gnumakeparser.cpp b/src/plugins/projectexplorer/gnumakeparser.cpp
index d31ce49970..015d030d18 100644
--- a/src/plugins/projectexplorer/gnumakeparser.cpp
+++ b/src/plugins/projectexplorer/gnumakeparser.cpp
@@ -103,7 +103,7 @@ OutputLineParser::Result GnuMakeParser::handleLine(const QString &line, OutputFo
if (!m_suppressIssues) {
const FilePath file = absoluteFilePath(FilePath::fromUserInput(match.captured(1)));
const int lineNo = match.captured(4).toInt();
- addLinkSpecForAbsoluteFilePath(linkSpecs, file, lineNo, match, 1);
+ addLinkSpecForAbsoluteFilePath(linkSpecs, file, lineNo, -1, match, 1);
emitTask(BuildSystemTask(res.type, res.description, file, lineNo));
}
return {Status::Done, linkSpecs};
diff --git a/src/plugins/projectexplorer/ioutputparser.cpp b/src/plugins/projectexplorer/ioutputparser.cpp
index 5c646d0bcc..65fd3bea2b 100644
--- a/src/plugins/projectexplorer/ioutputparser.cpp
+++ b/src/plugins/projectexplorer/ioutputparser.cpp
@@ -14,6 +14,7 @@
#include <QPlainTextEdit>
+#include <numeric>
/*!
\class ProjectExplorer::OutputTaskParser
@@ -57,6 +58,10 @@ class OutputTaskParser::Private
{
public:
QList<TaskInfo> scheduledTasks;
+ Task currentTask;
+ LinkSpecs linkSpecs;
+ int lineCount = 0;
+ bool targetLinkFixed = false;
};
OutputTaskParser::OutputTaskParser() : d(new Private) { }
@@ -95,6 +100,11 @@ void OutputTaskParser::setDetailsFormat(Task &task, const LinkSpecs &linkSpecs)
}
}
+void OutputTaskParser::fixTargetLink()
+{
+ d->targetLinkFixed = true;
+}
+
void OutputTaskParser::runPostPrintActions(QPlainTextEdit *edit)
{
int offset = 0;
@@ -110,4 +120,91 @@ void OutputTaskParser::runPostPrintActions(QPlainTextEdit *edit)
d->scheduledTasks.clear();
}
+void OutputTaskParser::createOrAmendTask(
+ Task::TaskType type,
+ const QString &description,
+ const QString &originalLine,
+ bool forceAmend,
+ const Utils::FilePath &file,
+ int line,
+ int column,
+ const LinkSpecs &linkSpecs)
+{
+ const bool amend = !d->currentTask.isNull() && (forceAmend || isContinuation(originalLine));
+ if (!amend) {
+ flush();
+ d->currentTask = CompileTask(type, description, file, line, column);
+ d->currentTask.details.append(originalLine);
+ d->linkSpecs = linkSpecs;
+ d->lineCount = 1;
+ return;
+ }
+
+ LinkSpecs adaptedLinkSpecs = linkSpecs;
+ const int offset = std::accumulate(
+ d->currentTask.details.cbegin(),
+ d->currentTask.details.cend(),
+ 0,
+ [](int total, const QString &line) { return total + line.length() + 1; });
+ for (LinkSpec &ls : adaptedLinkSpecs)
+ ls.startPos += offset;
+ d->linkSpecs << adaptedLinkSpecs;
+ d->currentTask.details.append(originalLine);
+
+ // Check whether the new line is more relevant than the previous ones.
+ if ((d->currentTask.type != Task::Error && type == Task::Error)
+ || (d->currentTask.type == Task::Unknown && type != Task::Unknown)) {
+ d->currentTask.type = type;
+ d->currentTask.summary = description;
+ if (!file.isEmpty() && !d->targetLinkFixed) {
+ d->currentTask.setFile(file);
+ d->currentTask.line = line;
+ d->currentTask.column = column;
+ }
+ }
+
+ ++d->lineCount;
+}
+
+void OutputTaskParser::setCurrentTask(const Task &task)
+{
+ flush();
+ d->currentTask = task;
+ d->lineCount = 1;
+}
+
+Task &OutputTaskParser::currentTask()
+{
+ return d->currentTask;
+}
+
+const Task &OutputTaskParser::currentTask() const
+{
+ return d->currentTask;
+}
+
+bool OutputTaskParser::isContinuation(const QString &line) const
+{
+ Q_UNUSED(line)
+ return false;
+}
+
+void OutputTaskParser::flush()
+{
+ if (d->currentTask.isNull())
+ return;
+
+ // If there is only one line of details, then it is the line that we generated
+ // the summary from. Remove it, because it does not add any information.
+ if (d->currentTask.details.count() == 1)
+ d->currentTask.details.clear();
+
+ setDetailsFormat(d->currentTask, d->linkSpecs);
+ Task t = d->currentTask;
+ d->currentTask.clear();
+ d->linkSpecs.clear();
+ scheduleTask(t, d->lineCount, 1);
+ d->lineCount = 0;
+ d->targetLinkFixed = false;
+}
} // namespace ProjectExplorer
diff --git a/src/plugins/projectexplorer/ioutputparser.h b/src/plugins/projectexplorer/ioutputparser.h
index d15257bf6f..de2ee9ebfc 100644
--- a/src/plugins/projectexplorer/ioutputparser.h
+++ b/src/plugins/projectexplorer/ioutputparser.h
@@ -8,8 +8,6 @@
#include <utils/outputformatter.h>
-#include <functional>
-
namespace ProjectExplorer {
class Task;
@@ -31,10 +29,28 @@ public:
const QList<TaskInfo> taskInfo() const;
protected:
+ void flush() override;
+
void scheduleTask(const Task &task, int outputLines, int skippedLines = 0);
void setDetailsFormat(Task &task, const LinkSpecs &linkSpecs = {});
+ void fixTargetLink();
+ void createOrAmendTask(
+ Task::TaskType type,
+ const QString &description,
+ const QString &originalLine,
+ bool forceAmend = false,
+ const Utils::FilePath &file = {},
+ int line = -1,
+ int column = 0,
+ const LinkSpecs &linkSpecs = {});
+ void setCurrentTask(const Task &task);
+
+ Task &currentTask();
+ const Task &currentTask() const;
private:
+ virtual bool isContinuation(const QString &line) const;
+
void runPostPrintActions(QPlainTextEdit *edit) override;
class Private;
diff --git a/src/plugins/projectexplorer/jsonwizard/jsonfieldpage.cpp b/src/plugins/projectexplorer/jsonwizard/jsonfieldpage.cpp
index 49a0ed8957..9c55d5345c 100644
--- a/src/plugins/projectexplorer/jsonwizard/jsonfieldpage.cpp
+++ b/src/plugins/projectexplorer/jsonwizard/jsonfieldpage.cpp
@@ -166,7 +166,7 @@ QVariant JsonFieldPage::Field::toSettings() const
JsonFieldPage::Field *JsonFieldPage::Field::parse(const QVariant &input, QString *errorMessage)
{
- if (input.typeId() != QVariant::Map) {
+ if (input.typeId() != QMetaType::QVariantMap) {
*errorMessage = Tr::tr("Field is not an object.");
return nullptr;
}
@@ -409,7 +409,7 @@ QDebug &operator<<(QDebug &debug, const JsonFieldPage::Field &field)
bool LabelField::parseData(const QVariant &data, QString *errorMessage)
{
- if (data.typeId() != QVariant::Map) {
+ if (data.typeId() != QMetaType::QVariantMap) {
*errorMessage = Tr::tr("Label (\"%1\") data is not an object.").arg(name());
return false;
}
@@ -447,7 +447,7 @@ bool SpacerField::parseData(const QVariant &data, QString *errorMessage)
if (data.isNull())
return true;
- if (data.typeId() != QVariant::Map) {
+ if (data.typeId() != QMetaType::QVariantMap) {
*errorMessage = Tr::tr("Spacer (\"%1\") data is not an object.").arg(name());
return false;
}
@@ -492,7 +492,7 @@ bool LineEditField::parseData(const QVariant &data, QString *errorMessage)
if (data.isNull())
return true;
- if (data.typeId() != QVariant::Map) {
+ if (data.typeId() != QMetaType::QVariantMap) {
*errorMessage = Tr::tr("LineEdit (\"%1\") data is not an object.").arg(name());
return false;
}
@@ -689,7 +689,7 @@ bool TextEditField::parseData(const QVariant &data, QString *errorMessage)
if (data.isNull())
return true;
- if (data.typeId() != QVariant::Map) {
+ if (data.typeId() != QMetaType::QVariantMap) {
*errorMessage = Tr::tr("TextEdit (\"%1\") data is not an object.")
.arg(name());
return false;
@@ -772,7 +772,7 @@ bool PathChooserField::parseData(const QVariant &data, QString *errorMessage)
if (data.isNull())
return true;
- if (data.typeId() != QVariant::Map) {
+ if (data.typeId() != QMetaType::QVariantMap) {
*errorMessage = Tr::tr("PathChooser data is not an object.");
return false;
}
@@ -877,7 +877,7 @@ bool CheckBoxField::parseData(const QVariant &data, QString *errorMessage)
if (data.isNull())
return true;
- if (data.typeId() != QVariant::Map) {
+ if (data.typeId() != QMetaType::QVariantMap) {
*errorMessage = Tr::tr("CheckBox (\"%1\") data is not an object.").arg(name());
return false;
}
@@ -966,12 +966,12 @@ QVariant CheckBoxField::toSettings() const
std::unique_ptr<QStandardItem> createStandardItemFromListItem(const QVariant &item, QString *errorMessage)
{
- if (item.typeId() == QVariant::List) {
+ if (item.typeId() == QMetaType::QVariantList) {
*errorMessage = Tr::tr("No JSON lists allowed inside List items.");
return {};
}
auto standardItem = std::make_unique<QStandardItem>();
- if (item.typeId() == QVariant::Map) {
+ if (item.typeId() == QMetaType::QVariantMap) {
QVariantMap tmp = item.toMap();
const QString key = JsonWizardFactory::localizedString(consumeValue(tmp, "trKey", QString()).toString());
const QVariant value = consumeValue(tmp, "value", key);
@@ -1001,7 +1001,7 @@ ListField::~ListField() = default;
bool ListField::parseData(const QVariant &data, QString *errorMessage)
{
- if (data.typeId() != QVariant::Map) {
+ if (data.typeId() != QMetaType::QVariantMap) {
*errorMessage = Tr::tr("%1 (\"%2\") data is not an object.").arg(type(), name());
return false;
}
@@ -1027,7 +1027,7 @@ bool ListField::parseData(const QVariant &data, QString *errorMessage)
*errorMessage = Tr::tr("%1 (\"%2\") \"items\" missing.").arg(type(), name());
return false;
}
- if (value.typeId() != QVariant::List) {
+ if (value.typeId() != QMetaType::QVariantList) {
*errorMessage = Tr::tr("%1 (\"%2\") \"items\" is not a JSON list.").arg(type(), name());
return false;
}
@@ -1315,7 +1315,7 @@ JsonFieldPage::JsonFieldPage(MacroExpander *expander, QWidget *parent) :
vLayout->addLayout(m_formLayout);
m_errorLabel->setVisible(false);
QPalette palette = m_errorLabel->palette();
- palette.setColor(QPalette::WindowText, creatorTheme()->color(Theme::TextColorError));
+ palette.setColor(QPalette::WindowText, creatorColor(Theme::TextColorError));
m_errorLabel->setPalette(palette);
vLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::MinimumExpanding));
vLayout->addWidget(m_errorLabel);
diff --git a/src/plugins/projectexplorer/jsonwizard/jsonkitspage.cpp b/src/plugins/projectexplorer/jsonwizard/jsonkitspage.cpp
index 930d8d22f4..6e76633daf 100644
--- a/src/plugins/projectexplorer/jsonwizard/jsonkitspage.cpp
+++ b/src/plugins/projectexplorer/jsonwizard/jsonkitspage.cpp
@@ -124,7 +124,7 @@ QVector<JsonKitsPage::ConditionalFeature> JsonKitsPage::parseFeatures(const QVar
if (data.isNull())
return result;
- if (data.type() != QVariant::List) {
+ if (data.typeId() != QMetaType::QVariantList) {
if (errorMessage)
*errorMessage = Tr::tr("Feature list is set and not of type list.");
return result;
@@ -132,9 +132,9 @@ QVector<JsonKitsPage::ConditionalFeature> JsonKitsPage::parseFeatures(const QVar
const QList<QVariant> elements = data.toList();
for (const QVariant &element : elements) {
- if (element.type() == QVariant::String) {
+ if (element.typeId() == QMetaType::QString) {
result.append({ element.toString(), QVariant(true) });
- } else if (element.type() == QVariant::Map) {
+ } else if (element.typeId() == QMetaType::QVariantMap) {
const QVariantMap obj = element.toMap();
const QString feature = obj.value(QLatin1String(KEY_FEATURE)).toString();
if (feature.isEmpty()) {
diff --git a/src/plugins/projectexplorer/jsonwizard/jsonwizard.cpp b/src/plugins/projectexplorer/jsonwizard/jsonwizard.cpp
index 83b8b81bef..3d846604ba 100644
--- a/src/plugins/projectexplorer/jsonwizard/jsonwizard.cpp
+++ b/src/plugins/projectexplorer/jsonwizard/jsonwizard.cpp
@@ -219,14 +219,14 @@ QString JsonWizard::stringValue(const QString &n) const
if (!v.isValid())
return {};
- if (v.typeId() == QVariant::String) {
+ if (v.typeId() == QMetaType::QString) {
QString tmp = m_expander.expand(v.toString());
if (tmp.isEmpty())
tmp = QString::fromLatin1(""); // Make sure isNull() is *not* true.
return tmp;
}
- if (v.typeId() == QVariant::StringList)
+ if (v.typeId() == QMetaType::QStringList)
return stringListToArrayString(v.toStringList(), &m_expander);
return v.toString();
@@ -277,7 +277,7 @@ QVariant JsonWizard::value(const QString &n) const
bool JsonWizard::boolFromVariant(const QVariant &v, MacroExpander *expander)
{
- if (v.typeId() == QVariant::String) {
+ if (v.typeId() == QMetaType::QString) {
const QString tmp = expander->expand(v.toString());
return !(tmp.isEmpty() || tmp == QLatin1String("false"));
}
@@ -419,7 +419,7 @@ void JsonWizard::handleError(const QString &message)
QString JsonWizard::stringify(const QVariant &v) const
{
- if (v.typeId() == QVariant::StringList)
+ if (v.typeId() == QMetaType::QStringList)
return stringListToArrayString(v.toStringList(), &m_expander);
return Wizard::stringify(v);
}
diff --git a/src/plugins/projectexplorer/jsonwizard/jsonwizardfactory.cpp b/src/plugins/projectexplorer/jsonwizard/jsonwizardfactory.cpp
index e84ef955b8..4e8d496416 100644
--- a/src/plugins/projectexplorer/jsonwizard/jsonwizardfactory.cpp
+++ b/src/plugins/projectexplorer/jsonwizard/jsonwizardfactory.cpp
@@ -147,7 +147,7 @@ static JsonWizardFactory::Generator parseGenerator(const QVariant &value, QStrin
{
JsonWizardFactory::Generator gen;
- if (value.typeId() != QVariant::Map) {
+ if (value.typeId() != QMetaType::QVariantMap) {
*errorMessage = Tr::tr("Generator is not a object.");
return gen;
}
@@ -308,8 +308,8 @@ QVariant JsonWizardFactory::getDataValue(const QLatin1String &key, const QVarian
{
QVariant retVal = {};
- if ((valueSet.contains(key) && valueSet.value(key).typeId() == QVariant::Map) ||
- (defaultValueSet.contains(key) && defaultValueSet.value(key).typeId() == QVariant::Map)) {
+ if ((valueSet.contains(key) && valueSet.value(key).typeId() == QMetaType::QVariantMap) ||
+ (defaultValueSet.contains(key) && defaultValueSet.value(key).typeId() == QMetaType::QVariantMap)) {
retVal = mergeDataValueMaps(valueSet.value(key), defaultValueSet.value(key));
} else {
QVariant defaultValue = defaultValueSet.value(key, notExistValue);
@@ -335,7 +335,7 @@ std::pair<int, QStringList> JsonWizardFactory::screenSizeInfoFromPage(const QStr
return {};
const QVariant data = it->data;
- if (data.typeId() != QVariant::List)
+ if (data.typeId() != QMetaType::QVariantList)
return {};
const QVariant screenFactorField = Utils::findOrDefault(data.toList(),
@@ -344,11 +344,11 @@ std::pair<int, QStringList> JsonWizardFactory::screenSizeInfoFromPage(const QStr
return "ScreenFactor" == m["name"];
});
- if (screenFactorField.typeId() != QVariant::Map)
+ if (screenFactorField.typeId() != QMetaType::QVariantMap)
return {};
const QVariant screenFactorData = screenFactorField.toMap()["data"];
- if (screenFactorData.typeId() != QVariant::Map)
+ if (screenFactorData.typeId() != QMetaType::QVariantMap)
return {};
const QVariantMap screenFactorDataMap = screenFactorData.toMap();
@@ -376,7 +376,7 @@ JsonWizardFactory::Page JsonWizardFactory::parsePage(const QVariant &value, QStr
{
JsonWizardFactory::Page p;
- if (value.typeId() != QVariant::Map) {
+ if (value.typeId() != QMetaType::QVariantMap) {
*errorMessage = Tr::tr("Page is not an object.");
return p;
}
@@ -423,9 +423,9 @@ JsonWizardFactory::Page JsonWizardFactory::parsePage(const QVariant &value, QStr
if (specifiedSubData.isNull())
subData = defaultSubData;
- else if (specifiedSubData.typeId() == QVariant::Map)
+ else if (specifiedSubData.typeId() == QMetaType::QVariantMap)
subData = mergeDataValueMaps(specifiedSubData.toMap(), defaultSubData.toMap());
- else if (specifiedSubData.typeId() == QVariant::List)
+ else if (specifiedSubData.typeId() == QMetaType::QVariantList)
subData = specifiedSubData;
if (!factory->validateData(typeId, subData, errorMessage))
@@ -584,7 +584,7 @@ FilePaths &JsonWizardFactory::searchPaths()
// add paths from enabled plugin meta data
for (PluginSpec *plugin : PluginManager::plugins()) {
if (plugin->state() == PluginSpec::Running) {
- const auto base = FilePath::fromString(plugin->filePath()).parentDir();
+ const auto base = plugin->location();
const auto values = plugin->metaData().value("JsonWizardPaths").toArray();
for (const QJsonValue &v : values) {
const auto path = FilePath::fromString(v.toString());
@@ -655,6 +655,7 @@ Wizard *JsonWizardFactory::runWizardImpl(const FilePath &path, QWidget *parent,
wizard->setValue(QStringLiteral("Features"), Id::toStringList(availableFeatures(platform)));
wizard->setValue(QStringLiteral("Plugins"), Id::toStringList(pluginFeatures()));
+ wizard->setValue(QStringLiteral("SupportedProjectTypes"), Id::toStringList(supportedProjectTypes()));
// Add data to wizard:
for (auto i = variables.constBegin(); i != variables.constEnd(); ++i)
@@ -741,9 +742,9 @@ QList<QVariant> JsonWizardFactory::objectOrList(const QVariant &data, QString *e
QList<QVariant> result;
if (data.isNull())
*errorMessage = Tr::tr("key not found.");
- else if (data.typeId() == QVariant::Map)
+ else if (data.typeId() == QMetaType::QVariantMap)
result.append(data);
- else if (data.typeId() == QVariant::List)
+ else if (data.typeId() == QMetaType::QVariantList)
result = data.toList();
else
*errorMessage = Tr::tr("Expected an object or a list.");
@@ -754,7 +755,7 @@ QString JsonWizardFactory::localizedString(const QVariant &value)
{
if (value.isNull())
return {};
- if (value.typeId() == QVariant::Map) {
+ if (value.typeId() == QMetaType::QVariantMap) {
QVariantMap tmp = value.toMap();
const QString locale = languageSetting().toLower();
QStringList locales;
diff --git a/src/plugins/projectexplorer/jsonwizard/jsonwizardfilegenerator.cpp b/src/plugins/projectexplorer/jsonwizard/jsonwizardfilegenerator.cpp
index c7409fbc46..c6f6900d04 100644
--- a/src/plugins/projectexplorer/jsonwizard/jsonwizardfilegenerator.cpp
+++ b/src/plugins/projectexplorer/jsonwizard/jsonwizardfilegenerator.cpp
@@ -79,7 +79,7 @@ bool JsonWizardFileGenerator::setup(const QVariant &data, QString *errorMessage)
return false;
for (const QVariant &d : list) {
- if (d.type() != QVariant::Map) {
+ if (d.typeId() != QMetaType::QVariantMap) {
*errorMessage = Tr::tr("Files data list entry is not an object.");
return false;
}
diff --git a/src/plugins/projectexplorer/jsonwizard/jsonwizardpagefactory_p.cpp b/src/plugins/projectexplorer/jsonwizard/jsonwizardpagefactory_p.cpp
index 02f26d8401..45b2a4aa78 100644
--- a/src/plugins/projectexplorer/jsonwizard/jsonwizardpagefactory_p.cpp
+++ b/src/plugins/projectexplorer/jsonwizard/jsonwizardpagefactory_p.cpp
@@ -114,7 +114,7 @@ WizardPage *FilePageFactory::create(JsonWizard *wizard, Id typeId, const QVarian
bool FilePageFactory::validateData(Id typeId, const QVariant &data, QString *errorMessage)
{
QTC_ASSERT(canCreate(typeId), return false);
- if (!data.isNull() && (data.typeId() != QVariant::Map || !data.toMap().isEmpty())) {
+ if (!data.isNull() && (data.typeId() != QMetaType::QVariantMap || !data.toMap().isEmpty())) {
*errorMessage = Tr::tr("\"data\" for a \"File\" page needs to be unset or an empty object.");
return false;
}
@@ -174,7 +174,7 @@ bool KitsPageFactory::validateData(Id typeId, const QVariant &data, QString *err
{
QTC_ASSERT(canCreate(typeId), return false);
- if (data.isNull() || data.typeId() != QVariant::Map) {
+ if (data.isNull() || data.typeId() != QMetaType::QVariantMap) {
*errorMessage = Tr::tr("\"data\" must be a JSON object for \"Kits\" pages.");
return false;
}
@@ -242,7 +242,7 @@ bool ProjectPageFactory::validateData(Id typeId, const QVariant &data, QString *
Q_UNUSED(errorMessage)
QTC_ASSERT(canCreate(typeId), return false);
- if (!data.isNull() && data.typeId() != QVariant::Map) {
+ if (!data.isNull() && data.typeId() != QMetaType::QVariantMap) {
*errorMessage = Tr::tr("\"data\" must be empty or a JSON object for \"Project\" pages.");
return false;
}
@@ -297,7 +297,7 @@ WizardPage *SummaryPageFactory::create(JsonWizard *wizard, Id typeId, const QVar
bool SummaryPageFactory::validateData(Id typeId, const QVariant &data, QString *errorMessage)
{
QTC_ASSERT(canCreate(typeId), return false);
- if (!data.isNull() && (data.typeId() != QVariant::Map)) {
+ if (!data.isNull() && (data.typeId() != QMetaType::QVariantMap)) {
*errorMessage = Tr::tr("\"data\" for a \"Summary\" page can be unset or needs to be an object.");
return false;
}
diff --git a/src/plugins/projectexplorer/jsonwizard/jsonwizardscannergenerator.cpp b/src/plugins/projectexplorer/jsonwizard/jsonwizardscannergenerator.cpp
index a7b2b9b6ee..9122ccc5ee 100644
--- a/src/plugins/projectexplorer/jsonwizard/jsonwizardscannergenerator.cpp
+++ b/src/plugins/projectexplorer/jsonwizard/jsonwizardscannergenerator.cpp
@@ -46,7 +46,7 @@ bool JsonWizardScannerGenerator::setup(const QVariant &data, QString *errorMessa
if (data.isNull())
return true;
- if (data.type() != QVariant::Map) {
+ if (data.typeId() != QMetaType::QVariantMap) {
*errorMessage = Tr::tr("Key is not an object.");
return false;
}
diff --git a/src/plugins/projectexplorer/jsonwizard/wizarddebug.h b/src/plugins/projectexplorer/jsonwizard/wizarddebug.h
index 63f8891670..6878407d9a 100644
--- a/src/plugins/projectexplorer/jsonwizard/wizarddebug.h
+++ b/src/plugins/projectexplorer/jsonwizard/wizarddebug.h
@@ -26,10 +26,10 @@ inline QDebug &operator<<(QDebug &dbg, const QVariant &var)
case QMetaType::QStringList:
dbg << var.toList();
break;
- case QVariant::List:
+ case QMetaType::QVariantList:
dbg << var.toList();
break;
- case QVariant::Map:
+ case QMetaType::QVariantMap:
dbg << var.toMap();
break;
default: {
diff --git a/src/plugins/projectexplorer/kitaspects.cpp b/src/plugins/projectexplorer/kitaspects.cpp
index 0eef454d28..db3300f327 100644
--- a/src/plugins/projectexplorer/kitaspects.cpp
+++ b/src/plugins/projectexplorer/kitaspects.cpp
@@ -61,7 +61,7 @@ public:
private:
void makeReadOnly() override { m_chooser->setReadOnly(true); }
- void addToLayoutImpl(Layouting::LayoutItem &builder) override
+ void addToLayoutImpl(Layouting::Layout &builder) override
{
addMutableAction(m_chooser);
builder.addItem(Layouting::Span(2, m_chooser));
@@ -238,7 +238,7 @@ public:
}
private:
- void addToLayoutImpl(Layouting::LayoutItem &builder) override
+ void addToLayoutImpl(Layouting::Layout &builder) override
{
addMutableAction(m_mainWidget);
builder.addItem(m_mainWidget);
@@ -739,7 +739,7 @@ public:
~DeviceTypeKitAspectImpl() override { delete m_comboBox; }
private:
- void addToLayoutImpl(Layouting::LayoutItem &builder) override
+ void addToLayoutImpl(Layouting::Layout &builder) override
{
addMutableAction(m_comboBox);
builder.addItem(m_comboBox);
@@ -889,7 +889,7 @@ public:
}
private:
- void addToLayoutImpl(Layouting::LayoutItem &builder) override
+ void addToLayoutImpl(Layouting::Layout &builder) override
{
addMutableAction(m_comboBox);
builder.addItem(m_comboBox);
@@ -1170,7 +1170,7 @@ public:
}
private:
- void addToLayoutImpl(Layouting::LayoutItem &builder) override
+ void addToLayoutImpl(Layouting::Layout &builder) override
{
addMutableAction(m_comboBox);
builder.addItem(m_comboBox);
@@ -1433,7 +1433,7 @@ public:
}
private:
- void addToLayoutImpl(Layouting::LayoutItem &builder) override
+ void addToLayoutImpl(Layouting::Layout &builder) override
{
addMutableAction(m_mainWidget);
builder.addItem(m_mainWidget);
@@ -1539,7 +1539,7 @@ Tasks EnvironmentKitAspectFactory::validate(const Kit *k) const
QTC_ASSERT(k, return result);
const QVariant variant = k->value(EnvironmentKitAspect::id());
- if (!variant.isNull() && !variant.canConvert(QVariant::List))
+ if (!variant.isNull() && !variant.canConvert(QMetaType::QVariantList))
result << BuildSystemTask(Task::Error, Tr::tr("The environment setting value is invalid."));
return result;
@@ -1550,7 +1550,7 @@ void EnvironmentKitAspectFactory::fix(Kit *k)
QTC_ASSERT(k, return);
const QVariant variant = k->value(EnvironmentKitAspect::id());
- if (!variant.isNull() && !variant.canConvert(QVariant::List)) {
+ if (!variant.isNull() && !variant.canConvert(QMetaType::QVariantList)) {
qWarning("Kit \"%s\" has a wrong environment value set.", qPrintable(k->displayName()));
EnvironmentKitAspect::setEnvironmentChanges(k, EnvironmentItems());
}
diff --git a/src/plugins/projectexplorer/kitmanager.cpp b/src/plugins/projectexplorer/kitmanager.cpp
index 9406d746d9..154ce46f4b 100644
--- a/src/plugins/projectexplorer/kitmanager.cpp
+++ b/src/plugins/projectexplorer/kitmanager.cpp
@@ -798,7 +798,7 @@ void KitAspect::makeStickySubWidgetsReadOnly()
makeReadOnly();
}
-void KitAspect::addToLayout(Layouting::LayoutItem &parentItem)
+void KitAspect::addToLayout(Layouting::Layout &parentItem)
{
auto label = createSubWidget<QLabel>(m_factory->displayName() + ':');
label->setToolTip(m_factory->description());
diff --git a/src/plugins/projectexplorer/kitmanager.h b/src/plugins/projectexplorer/kitmanager.h
index 44c6223d79..4068aea15d 100644
--- a/src/plugins/projectexplorer/kitmanager.h
+++ b/src/plugins/projectexplorer/kitmanager.h
@@ -110,7 +110,7 @@ public:
virtual void refresh() = 0;
- void addToLayout(Layouting::LayoutItem &parentItem) override;
+ void addToLayout(Layouting::Layout &parentItem) override;
static QString msgManage();
@@ -124,7 +124,7 @@ public:
protected:
virtual void makeReadOnly() {}
- virtual void addToLayoutImpl(Layouting::LayoutItem &parentItem) = 0;
+ virtual void addToLayoutImpl(Layouting::Layout &parentItem) = 0;
virtual Utils::Id settingsPageItemToPreselect() const { return {}; }
Kit *m_kit;
diff --git a/src/plugins/projectexplorer/kitmanagerconfigwidget.cpp b/src/plugins/projectexplorer/kitmanagerconfigwidget.cpp
index 2c142dec88..ab884a9d7f 100644
--- a/src/plugins/projectexplorer/kitmanagerconfigwidget.cpp
+++ b/src/plugins/projectexplorer/kitmanagerconfigwidget.cpp
@@ -200,7 +200,7 @@ QString KitManagerConfigWidget::validityMessage() const
return m_modifiedKit->toHtml(tmp);
}
-void KitManagerConfigWidget::addAspectToWorkingCopy(Layouting::LayoutItem &parent, KitAspectFactory *factory)
+void KitManagerConfigWidget::addAspectToWorkingCopy(Layouting::Layout &parent, KitAspectFactory *factory)
{
QTC_ASSERT(factory, return);
KitAspect *aspect = factory->createKitAspect(workingCopy());
diff --git a/src/plugins/projectexplorer/kitmanagerconfigwidget.h b/src/plugins/projectexplorer/kitmanagerconfigwidget.h
index 1a499f7dab..8564ccbcc9 100644
--- a/src/plugins/projectexplorer/kitmanagerconfigwidget.h
+++ b/src/plugins/projectexplorer/kitmanagerconfigwidget.h
@@ -36,7 +36,7 @@ public:
void discard();
bool isDirty() const;
QString validityMessage() const;
- void addAspectToWorkingCopy(Layouting::LayoutItem &parent, KitAspectFactory *factory);
+ void addAspectToWorkingCopy(Layouting::Layout &parent, KitAspectFactory *factory);
void makeStickySubWidgetsReadOnly();
Kit *workingCopy() const;
diff --git a/src/plugins/projectexplorer/ldparser.cpp b/src/plugins/projectexplorer/ldparser.cpp
index 82b3c3d335..95bb4e8495 100644
--- a/src/plugins/projectexplorer/ldparser.cpp
+++ b/src/plugins/projectexplorer/ldparser.cpp
@@ -39,45 +39,49 @@ Utils::OutputLineParser::Result LdParser::handleLine(const QString &line, Utils:
return Status::NotHandled;
QString lne = rightTrimmed(line);
- if (!lne.isEmpty() && !lne.at(0).isSpace() && !m_incompleteTask.isNull()) {
- flush();
- return Status::NotHandled;
- }
-
if (lne.startsWith(QLatin1String("TeamBuilder "))
|| lne.startsWith(QLatin1String("distcc["))
|| lne.contains(QLatin1String("ar: creating "))) {
return Status::NotHandled;
}
+ const auto getStatus = [&lne] { return lne.endsWith(':') ? Status::InProgress : Status::Done; };
+
// ld on macOS
- if (lne.startsWith("Undefined symbols for architecture") && lne.endsWith(":")) {
- m_incompleteTask = CompileTask(Task::Error, lne);
+ if (lne.startsWith("Undefined symbols for architecture") && getStatus() == Status::InProgress) {
+ createOrAmendTask(Task::Error, lne, line);
return Status::InProgress;
}
- if (!m_incompleteTask.isNull() && lne.startsWith(" ")) {
- m_incompleteTask.details.append(lne);
+
+ if (!currentTask().isNull() && isContinuation(line)) {
static const QRegularExpression locRegExp(" (?<symbol>\\S+) in (?<file>\\S+)");
const QRegularExpressionMatch match = locRegExp.match(lne);
LinkSpecs linkSpecs;
+ Utils::FilePath filePath;
+ bool handle = false;
if (match.hasMatch()) {
- m_incompleteTask.setFile(absoluteFilePath(Utils::FilePath::fromString(
- match.captured("file"))));
- addLinkSpecForAbsoluteFilePath(linkSpecs, m_incompleteTask.file, 0, match, "file");
+ handle = true;
+ filePath = absoluteFilePath(Utils::FilePath::fromString(match.captured("file")));
+ addLinkSpecForAbsoluteFilePath(linkSpecs, filePath, -1, -1, match, "file");
+ currentTask().setFile(filePath);
+ } else {
+ handle = !lne.isEmpty() && lne.at(0).isSpace();
+ }
+ if (handle) {
+ createOrAmendTask(Task::Unknown, {}, line, true, filePath);
+ return {Status::InProgress, linkSpecs};
}
- return {Status::InProgress, linkSpecs};
}
if (lne.startsWith("collect2:") || lne.startsWith("collect2.exe:")) {
- scheduleTask(CompileTask(Task::Error, lne /* description */), 1);
- return Status::Done;
+ createOrAmendTask(Task::Error, lne, line);
+ return getStatus();
}
QRegularExpressionMatch match = m_ranlib.match(lne);
if (match.hasMatch()) {
- QString description = match.captured(2);
- scheduleTask(CompileTask(Task::Warning, description), 1);
- return Status::Done;
+ createOrAmendTask(Task::Warning, match.captured(2), line);
+ return getStatus();
}
match = m_regExpGccNames.match(lne);
@@ -90,8 +94,8 @@ Utils::OutputLineParser::Result LdParser::handleLine(const QString &line, Utils:
} else if (description.startsWith(QLatin1String("fatal: "))) {
description = description.mid(7);
}
- scheduleTask(CompileTask(type, description), 1);
- return Status::Done;
+ createOrAmendTask(type, description, line);
+ return getStatus();
}
match = m_regExpLinker.match(lne);
@@ -100,50 +104,47 @@ Utils::OutputLineParser::Result LdParser::handleLine(const QString &line, Utils:
int lineno = match.captured(7).toInt(&ok);
if (!ok)
lineno = -1;
- Utils::FilePath filename
+ Utils::FilePath filePath
= absoluteFilePath(Utils::FilePath::fromUserInput(match.captured(1)));
int capIndex = 1;
const QString sourceFileName = match.captured(4);
if (!sourceFileName.isEmpty()
&& !sourceFileName.startsWith(QLatin1String("(.text"))
&& !sourceFileName.startsWith(QLatin1String("(.data"))) {
- filename = absoluteFilePath(Utils::FilePath::fromUserInput(sourceFileName));
+ filePath = absoluteFilePath(Utils::FilePath::fromUserInput(sourceFileName));
capIndex = 4;
}
QString description = match.captured(8).trimmed();
- Task::TaskType type = Task::Error;
- if (description.startsWith(QLatin1String("first defined here")) ||
- description.startsWith(QLatin1String("note:"), Qt::CaseInsensitive)) {
- type = Task::Unknown;
- } else if (description.startsWith(QLatin1String("warning: "), Qt::CaseInsensitive)) {
- type = Task::Warning;
- description = description.mid(9);
- }
static const QStringList keywords{
"File format not recognized",
"undefined reference",
+ "multiple definition",
"first defined here",
"feupdateenv is not implemented and will always fail", // yes, this is quite special ...
};
const auto descriptionContainsKeyword = [&description](const QString &keyword) {
return description.contains(keyword);
};
- if (Utils::anyOf(keywords, descriptionContainsKeyword)) {
+ Task::TaskType type = Task::Unknown;
+ const bool hasKeyword = Utils::anyOf(keywords, descriptionContainsKeyword);
+ if (description.startsWith(QLatin1String("warning: "), Qt::CaseInsensitive)) {
+ type = Task::Warning;
+ description = description.mid(9);
+ } else if (hasKeyword) {
+ type = Task::Error;
+ }
+ if (hasKeyword || filePath.fileName().endsWith(".o")) {
LinkSpecs linkSpecs;
- addLinkSpecForAbsoluteFilePath(linkSpecs, filename, lineno, match, capIndex);
- scheduleTask(CompileTask(type, description, filename, lineno), 1);
- return {Status::Done, linkSpecs};
+ addLinkSpecForAbsoluteFilePath(linkSpecs, filePath, lineno, -1, match, capIndex);
+ createOrAmendTask(type, description, line, false, filePath, lineno, 0, linkSpecs);
+ return {getStatus(), linkSpecs};
}
}
return Status::NotHandled;
}
-void LdParser::flush()
+bool LdParser::isContinuation(const QString &line) const
{
- if (m_incompleteTask.isNull())
- return;
- const Task t = m_incompleteTask;
- m_incompleteTask.clear();
- scheduleTask(t, 1);
+ return currentTask().details.last().endsWith(':') || (!line.isEmpty() && line.at(0).isSpace());
}
diff --git a/src/plugins/projectexplorer/ldparser.h b/src/plugins/projectexplorer/ldparser.h
index 918f50b36c..d8c2374789 100644
--- a/src/plugins/projectexplorer/ldparser.h
+++ b/src/plugins/projectexplorer/ldparser.h
@@ -4,7 +4,6 @@
#pragma once
#include "ioutputparser.h"
-#include "task.h"
#include <QRegularExpression>
@@ -18,13 +17,11 @@ public:
LdParser();
private:
Result handleLine(const QString &line, Utils::OutputFormat type) override;
- void flush() override;
+ bool isContinuation(const QString &line) const override;
QRegularExpression m_ranlib;
QRegularExpression m_regExpLinker;
QRegularExpression m_regExpGccNames;
-
- Task m_incompleteTask;
};
} // namespace ProjectExplorer
diff --git a/src/plugins/projectexplorer/linuxiccparser.cpp b/src/plugins/projectexplorer/linuxiccparser.cpp
index 62e151846e..6e625b240a 100644
--- a/src/plugins/projectexplorer/linuxiccparser.cpp
+++ b/src/plugins/projectexplorer/linuxiccparser.cpp
@@ -12,8 +12,7 @@ using namespace Utils;
namespace ProjectExplorer {
-LinuxIccParser::LinuxIccParser() :
- m_temporary(Task())
+LinuxIccParser::LinuxIccParser()
{
setObjectName(QLatin1String("LinuxIccParser"));
// main.cpp(53): error #308: function \"AClass::privatefunc\" (declared at line 4 of \"main.h\") is inaccessible
@@ -51,7 +50,6 @@ OutputLineParser::Result LinuxIccParser::handleLine(const QString &line, OutputF
if (m_expectFirstLine) {
const QRegularExpressionMatch match = m_firstLine.match(line);
if (match.hasMatch()) {
- // Clear out old task
Task::TaskType type = Task::Unknown;
QString category = match.captured(4);
if (category == QLatin1String("error"))
@@ -61,10 +59,8 @@ OutputLineParser::Result LinuxIccParser::handleLine(const QString &line, OutputF
const FilePath filePath = absoluteFilePath(FilePath::fromUserInput(match.captured(1)));
const int lineNo = match.captured(2).toInt();
LinkSpecs linkSpecs;
- addLinkSpecForAbsoluteFilePath(linkSpecs, filePath, lineNo, match, 1);
- m_temporary = CompileTask(type, match.captured(6).trimmed(), filePath, lineNo);
-
- m_lines = 1;
+ addLinkSpecForAbsoluteFilePath(linkSpecs, filePath, lineNo, -1, match, 1);
+ createOrAmendTask(type, match.captured(6).trimmed(), line, false, filePath, lineNo);
m_expectFirstLine = false;
return Status::InProgress;
}
@@ -75,17 +71,14 @@ OutputLineParser::Result LinuxIccParser::handleLine(const QString &line, OutputF
}
if (!m_expectFirstLine && line.trimmed().isEmpty()) { // last Line
m_expectFirstLine = true;
- scheduleTask(m_temporary, m_lines);
- m_temporary = Task();
+ flush();
return Status::Done;
}
const QRegularExpressionMatch match = m_continuationLines.match(line);
if (!m_expectFirstLine && match.hasMatch()) {
- m_temporary.details.append(match.captured(1).trimmed());
- ++m_lines;
+ createOrAmendTask(Task::Unknown, {}, line, true);
return Status::InProgress;
}
- QTC_CHECK(m_temporary.isNull());
return Status::NotHandled;
}
@@ -99,17 +92,6 @@ QList<OutputLineParser *> LinuxIccParser::iccParserSuite()
return {new LinuxIccParser, new Internal::LldParser, new LdParser};
}
-void LinuxIccParser::flush()
-{
- if (m_temporary.isNull())
- return;
-
- setDetailsFormat(m_temporary);
- Task t = m_temporary;
- m_temporary.clear();
- scheduleTask(t, m_lines, 1);
-}
-
} // ProjectExplorer
#ifdef WITH_TESTS
@@ -155,8 +137,10 @@ void ProjectExplorerTest::testLinuxIccOutputParsers_data()
<< QString() << QString::fromLatin1("\n")
<< (Tasks()
<< CompileTask(Task::Error,
- "identifier \"f\" is undefined\nf(0);",
- FilePath::fromUserInput(QLatin1String("main.cpp")), 13))
+ "identifier \"f\" is undefined\n"
+ "main.cpp(13): error: identifier \"f\" is undefined\n"
+ " f(0);",
+ FilePath::fromUserInput(QLatin1String("main.cpp")), 13))
<< QString();
// same, with PCH remark
@@ -170,8 +154,10 @@ void ProjectExplorerTest::testLinuxIccOutputParsers_data()
<< QString() << QString::fromLatin1("\n")
<< (Tasks()
<< CompileTask(Task::Error,
- "identifier \"f\" is undefined\nf(0);",
- FilePath::fromUserInput("main.cpp"), 13))
+ "identifier \"f\" is undefined\n"
+ "main.cpp(13): error: identifier \"f\" is undefined\n"
+ " f(0);",
+ FilePath::fromUserInput("main.cpp"), 13))
<< QString();
@@ -184,8 +170,10 @@ void ProjectExplorerTest::testLinuxIccOutputParsers_data()
<< QString() << QString::fromLatin1("\n")
<< (Tasks()
<< CompileTask(Task::Error,
- "function \"AClass::privatefunc\" (declared at line 4 of \"main.h\") is inaccessible\nb.privatefunc();",
- FilePath::fromUserInput("main.cpp"), 53))
+ "function \"AClass::privatefunc\" (declared at line 4 of \"main.h\") is inaccessible\n"
+ "main.cpp(53): error #308: function \"AClass::privatefunc\" (declared at line 4 of \"main.h\") is inaccessible\n"
+ " b.privatefunc();",
+ FilePath::fromUserInput("main.cpp"), 53))
<< QString();
QTest::newRow("simple warning")
@@ -197,8 +185,10 @@ void ProjectExplorerTest::testLinuxIccOutputParsers_data()
<< QString() << QString::fromLatin1("\n")
<< (Tasks()
<< CompileTask(Task::Warning,
- "use of \"=\" where \"==\" may have been intended\nwhile (a = true)",
- FilePath::fromUserInput("main.cpp"), 41))
+ "use of \"=\" where \"==\" may have been intended\n"
+ "main.cpp(41): warning #187: use of \"=\" where \"==\" may have been intended\n"
+ " while (a = true)",
+ FilePath::fromUserInput("main.cpp"), 41))
<< QString();
}
diff --git a/src/plugins/projectexplorer/linuxiccparser.h b/src/plugins/projectexplorer/linuxiccparser.h
index 24b40f9c06..a17ffef84c 100644
--- a/src/plugins/projectexplorer/linuxiccparser.h
+++ b/src/plugins/projectexplorer/linuxiccparser.h
@@ -4,7 +4,6 @@
#pragma once
#include "ioutputparser.h"
-#include "task.h"
#include <QRegularExpression>
@@ -23,7 +22,6 @@ public:
private:
Result handleLine(const QString &line, Utils::OutputFormat type) override;
- void flush() override;
QRegularExpression m_firstLine;
QRegularExpression m_continuationLines;
@@ -31,8 +29,6 @@ private:
QRegularExpression m_pchInfoLine;
bool m_expectFirstLine = true;
- Task m_temporary;
- int m_lines = 0;
};
} // namespace ProjectExplorer
diff --git a/src/plugins/projectexplorer/lldparser.cpp b/src/plugins/projectexplorer/lldparser.cpp
index 3bb4079b76..0bc36ff845 100644
--- a/src/plugins/projectexplorer/lldparser.cpp
+++ b/src/plugins/projectexplorer/lldparser.cpp
@@ -46,7 +46,7 @@ Utils::OutputLineParser::Result LldParser::handleLine(const QString &line, Utils
const auto file = absoluteFilePath(Utils::FilePath::fromUserInput(
trimmedLine.mid(filePathOffset, filePathLen).trimmed()));
LinkSpecs linkSpecs;
- addLinkSpecForAbsoluteFilePath(linkSpecs, file, lineNo, filePathOffset, filePathLen);
+ addLinkSpecForAbsoluteFilePath(linkSpecs, file, lineNo, -1, filePathOffset, filePathLen);
scheduleTask(CompileTask(Task::Unknown, trimmedLine.mid(4).trimmed(),
file, lineNo), 1);
return {Status::Done, linkSpecs};
diff --git a/src/plugins/projectexplorer/makestep.cpp b/src/plugins/projectexplorer/makestep.cpp
index 730215d81c..7bf7a1f6df 100644
--- a/src/plugins/projectexplorer/makestep.cpp
+++ b/src/plugins/projectexplorer/makestep.cpp
@@ -313,7 +313,7 @@ QWidget *MakeStep::createConfigWidget()
if (m_disablingForSubDirsSupported)
builder.addRow({m_disabledForSubdirsAspect});
builder.addRow({m_buildTargetsAspect});
- builder.addItem(Layouting::noMargin);
+ builder.setNoMargins();
auto widget = builder.emerge();
diff --git a/src/plugins/projectexplorer/miniprojecttargetselector.cpp b/src/plugins/projectexplorer/miniprojecttargetselector.cpp
index bbf2b8be1d..a33cd3e247 100644
--- a/src/plugins/projectexplorer/miniprojecttargetselector.cpp
+++ b/src/plugins/projectexplorer/miniprojecttargetselector.cpp
@@ -434,7 +434,7 @@ void TargetSelectorDelegate::paint(QPainter *painter,
painter->save();
painter->setClipping(false);
- QColor textColor = creatorTheme()->color(Theme::MiniProjectTargetSelectorTextColor);
+ QColor textColor = creatorColor(Theme::MiniProjectTargetSelectorTextColor);
if (option.state & QStyle::State_Selected) {
QColor color;
if (m_view->hasFocus()) {
@@ -494,7 +494,7 @@ SelectorView::SelectorView(QWidget *parent) : TreeView(parent)
setSelectionBehavior(SelectRows);
setAttribute(Qt::WA_MacShowFocusRect, false);
setHeaderHidden(true);
- const QColor bgColor = creatorTheme()->color(Theme::MiniProjectTargetSelectorBackgroundColor);
+ const QColor bgColor = creatorColor(Theme::MiniProjectTargetSelectorBackgroundColor);
const QString bgColorName = creatorTheme()->flag(Theme::FlatToolBars)
? bgColor.lighter(120).name() : bgColor.name();
setStyleSheet(QString::fromLatin1("QAbstractItemView { background: %1; border-style: none; }").arg(bgColorName));
@@ -552,7 +552,7 @@ int SelectorView::padding()
/////////
// KitAreaWidget
/////////
-void doLayout(KitAspect *aspect, Layouting::LayoutItem &builder)
+void doLayout(KitAspect *aspect, Layouting::Layout &builder)
{
aspect->addToLayout(builder);
}
@@ -586,7 +586,8 @@ public:
if (k && k->isMutable(factory->id())) {
KitAspect *aspect = factory->createKitAspect(k);
m_kitAspects << aspect;
- grid.addItems({aspect, Layouting::br});
+ grid.addItem(aspect);
+ grid.flush();
}
}
m_gridWidget = grid.emerge();
@@ -1537,7 +1538,7 @@ void MiniProjectTargetSelector::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.fillRect(rect(), StyleHelper::baseColor());
- painter.setPen(creatorTheme()->color(Theme::MiniProjectTargetSelectorBorderColor));
+ painter.setPen(creatorColor(Theme::MiniProjectTargetSelectorBorderColor));
// draw border on top and right
QRectF borderRect = QRectF(rect()).adjusted(0.5, 0.5, -0.5, -0.5);
painter.drawLine(borderRect.topLeft(), borderRect.topRight());
diff --git a/src/plugins/projectexplorer/msvcparser.cpp b/src/plugins/projectexplorer/msvcparser.cpp
index 4a53ce958f..17710cb13c 100644
--- a/src/plugins/projectexplorer/msvcparser.cpp
+++ b/src/plugins/projectexplorer/msvcparser.cpp
@@ -94,36 +94,28 @@ OutputLineParser::Result MsvcParser::handleLine(const QString &line, OutputForma
if (type == OutputFormat::StdOutFormat) {
QRegularExpressionMatch match = m_additionalInfoRegExp.match(line);
if (line.startsWith(" ") && !match.hasMatch()) {
- if (m_lastTask.isNull())
+ if (currentTask().isNull())
return Status::NotHandled;
-
- m_lastTask.details.append(rightTrimmed(line));
- ++m_lines;
+ createOrAmendTask(Task::Unknown, {}, line, true);
return Status::InProgress;
}
const Result res = processCompileLine(line);
if (res.status != Status::NotHandled)
return res;
- const Task t = handleNmakeJomMessage(line);
- if (!t.isNull()) {
- flush();
- m_lastTask = t;
- m_lines = 1;
+ if (const Task t = handleNmakeJomMessage(line); !t.isNull()) {
+ setCurrentTask(t);
return Status::InProgress;
}
if (match.hasMatch()) {
- QString description = match.captured(1)
- + match.captured(4).trimmed();
+ QString description = match.captured(1) + match.captured(4).trimmed();
if (!match.captured(1).isEmpty())
description.chop(1); // Remove trailing quote
const FilePath filePath = absoluteFilePath(FilePath::fromUserInput(match.captured(2)));
const int lineNo = match.captured(3).toInt();
LinkSpecs linkSpecs;
- addLinkSpecForAbsoluteFilePath(linkSpecs, filePath, lineNo, match, 2);
- m_lastTask = CompileTask(Task::Unknown, description, filePath, lineNo);
- m_lastTask.details << line;
- m_lines = 1;
+ addLinkSpecForAbsoluteFilePath(linkSpecs, filePath, lineNo, -1, match, 2);
+ createOrAmendTask(Task::Unknown, description, line, false, filePath, lineNo, 0, linkSpecs);
return {Status::InProgress, linkSpecs};
}
return Status::NotHandled;
@@ -132,63 +124,46 @@ OutputLineParser::Result MsvcParser::handleLine(const QString &line, OutputForma
const Result res = processCompileLine(line);
if (res.status != Status::NotHandled)
return res;
+
// Jom outputs errors to stderr
- const Task t = handleNmakeJomMessage(line);
- if (!t.isNull()) {
- flush();
- m_lastTask = t;
- m_lines = 1;
+ if (const Task t = handleNmakeJomMessage(line); !t.isNull()) {
+ setCurrentTask(t);
return Status::InProgress;
}
+
return Status::NotHandled;
}
+bool MsvcParser::isContinuation(const QString &line) const
+{
+ return line.contains("note: ");
+}
+
MsvcParser::Result MsvcParser::processCompileLine(const QString &line)
{
QRegularExpressionMatch match = m_compileRegExp.match(line);
if (match.hasMatch()) {
QPair<FilePath, int> position = parseFileName(match.captured(1));
const FilePath filePath = absoluteFilePath(position.first);
- LinkSpecs lineLinkSpecs;
- addLinkSpecForAbsoluteFilePath(lineLinkSpecs, filePath, position.second, match, 1);
- LinkSpecs detailsLinkSpecs = lineLinkSpecs;
- if (!m_lastTask.isNull() && line.contains("note: ")) {
- const int offset = std::accumulate(m_lastTask.details.cbegin(),
- m_lastTask.details.cend(), 0,
- [](int total, const QString &line) { return total + line.length() + 1;});
- for (LinkSpec &ls : detailsLinkSpecs)
- ls.startPos += offset;
- ++m_lines;
- } else {
- flush();
- m_lastTask = CompileTask(taskType(match.captured(2)),
- match.captured(3) + match.captured(4).trimmed(), // description
- filePath, position.second);
- m_lines = 1;
- }
- m_linkSpecs << detailsLinkSpecs;
- m_lastTask.details.append(line);
- return {Status::InProgress, lineLinkSpecs};
+ LinkSpecs linkSpecs;
+ addLinkSpecForAbsoluteFilePath(linkSpecs, filePath, position.second, -1, match, 1);
+ const QString &description = match.captured(3) + match.captured(4).trimmed();
+ createOrAmendTask(
+ taskType(match.captured(2)),
+ description,
+ line,
+ false,
+ filePath,
+ position.second,
+ 0,
+ linkSpecs);
+ return {Status::InProgress, linkSpecs};
}
flush();
return Status::NotHandled;
}
-void MsvcParser::flush()
-{
- if (m_lastTask.isNull())
- return;
-
- if (m_lastTask.details.count() == 1)
- m_lastTask.details.clear();
- setDetailsFormat(m_lastTask, m_linkSpecs);
- Task t = m_lastTask;
- m_lastTask.clear();
- m_linkSpecs.clear();
- scheduleTask(t, m_lines, 1);
-}
-
// --------------------------------------------------------------------------
// ClangClParser: The compiler errors look similar to MSVC, except that the
// column number is also given and there are no 4digit CXXXX error numbers.
@@ -219,11 +194,8 @@ static inline bool isClangCodeMarker(const QString &trimmedLine)
OutputLineParser::Result ClangClParser::handleLine(const QString &line, OutputFormat type)
{
if (type == StdOutFormat) {
- const Task t = handleNmakeJomMessage(line);
- if (!t.isNull()) {
- flush();
- m_lastTask = t;
- m_linkedLines = 1;
+ if (const Task t = handleNmakeJomMessage(line); !t.isNull()) {
+ setCurrentTask(t);
flush();
return Status::Done;
}
@@ -231,11 +203,8 @@ OutputLineParser::Result ClangClParser::handleLine(const QString &line, OutputFo
}
const QString lne = rightTrimmed(line); // Strip \n.
- const Task t = handleNmakeJomMessage(lne);
- if (!t.isNull()) {
- flush();
- m_lastTask = t;
- m_linkedLines = 1;
+ if (const Task t = handleNmakeJomMessage(lne); !t.isNull()) {
+ setCurrentTask(t);
flush();
return Status::Done;
}
@@ -256,36 +225,28 @@ OutputLineParser::Result ClangClParser::handleLine(const QString &line, OutputFo
if (match.hasMatch()) {
flush();
const QPair<FilePath, int> position = parseFileName(match.captured(1));
- m_lastTask = CompileTask(taskType(match.captured(2)), match.captured(3).trimmed(),
- absoluteFilePath(position.first), position.second);
- m_linkedLines = 1;
+ const FilePath file = absoluteFilePath(position.first);
+ const int lineNo = position.second;
LinkSpecs linkSpecs;
- addLinkSpecForAbsoluteFilePath(linkSpecs, m_lastTask.file, m_lastTask.line, match, 1);
+ addLinkSpecForAbsoluteFilePath(linkSpecs, file, lineNo, -1, match, 1);
+ createOrAmendTask(
+ taskType(match.captured(2)), match.captured(3).trimmed(), line, false, file, lineNo);
return {Status::InProgress, linkSpecs};
}
- if (!m_lastTask.isNull()) {
+ if (!currentTask().isNull()) {
const QString trimmed = lne.trimmed();
if (isClangCodeMarker(trimmed)) {
flush();
return Status::Done;
}
- m_lastTask.details.append(trimmed);
- ++m_linkedLines;
+ createOrAmendTask(Task::Unknown, {}, line, true);
return Status::InProgress;
}
return Status::NotHandled;
}
-void ClangClParser::flush()
-{
- if (!m_lastTask.isNull()) {
- scheduleTask(m_lastTask, m_linkedLines, 1);
- m_lastTask.clear();
- }
-}
-
// Unit tests:
#ifdef WITH_TESTS
@@ -619,32 +580,23 @@ void ProjectExplorerTest::testClangClOutputParsers_data()
QTest::addColumn<Tasks >("tasks");
QTest::addColumn<QString>("outputLines");
- const QString warning1 = "private field 'm_version' is not used [-Wunused-private-field]\n"
- "const int m_version; //! majorVersion<<8 + minorVersion\n";
- const QString warning2 = "unused variable 'formatTextPlainC' [-Wunused-const-variable]\n"
- "static const char formatTextPlainC[] = \"text/plain\";\n";
- const QString warning3 = "unused variable 'formatTextHtmlC' [-Wunused-const-variable]\n"
- "static const char formatTextHtmlC[] = \"text/html\";\n";
- const QString error1 = "unknown type name 'errr'\n"
- " errr\n";
- const QString expectedError1 = "unknown type name 'errr'\n"
- "errr"; // Line 2 trimmed.
- const QString error2 =
- "expected unqualified-id\n"
- "void *QWindowsGdiNativeInterface::nativeResourceForBackingStore(const QByteArray &resource, QBackingStore *bs)\n";
-
const QString clangClCompilerLog =
"In file included from .\\qwindowseglcontext.cpp:40:\n"
- "./qwindowseglcontext.h(282,15) : warning: " + warning1 +
+ "./qwindowseglcontext.h(282,15) : warning: private field 'm_version' is not used [-Wunused-private-field]\n"
+ "const int m_version; //! majorVersion<<8 + minorVersion\n"
"5 warnings generated.\n"
- ".\\qwindowsclipboard.cpp(60,19) : warning: " + warning2 +
+ ".\\qwindowsclipboard.cpp(60,19) : warning: unused variable 'formatTextPlainC' [-Wunused-const-variable]\n"
+ "static const char formatTextPlainC[] = \"text/plain\";\n"
" ^\n"
- ".\\qwindowsclipboard.cpp(61,19) : warning: " + warning3 +
+ ".\\qwindowsclipboard.cpp(61,19) : warning: unused variable 'formatTextHtmlC' [-Wunused-const-variable]\n"
+ "static const char formatTextHtmlC[] = \"text/html\";\n"
" ^\n"
"2 warnings generated.\n"
- ".\\qwindowsgdinativeinterface.cpp(48,3) : error: " + error1 +
+ ".\\qwindowsgdinativeinterface.cpp(48,3) : error: unknown type name 'errr'\n"
+ " errr\n"
" ^\n"
- ".\\qwindowsgdinativeinterface.cpp(51,1) : error: " + error2 +
+ ".\\qwindowsgdinativeinterface.cpp(51,1) : error: expected unqualified-id\n"
+ "void *QWindowsGdiNativeInterface::nativeResourceForBackingStore(const QByteArray &resource, QBackingStore *bs)\n"
"^\n"
"2 errors generated.\n";
@@ -660,16 +612,31 @@ void ProjectExplorerTest::testClangClOutputParsers_data()
<< OutputParserTester::STDERR
<< "" << expectedStderr
<< (Tasks()
- << CompileTask(Task::Warning, warning1.trimmed(),
- FilePath::fromUserInput("./qwindowseglcontext.h"), 282)
- << CompileTask(Task::Warning, warning2.trimmed(),
- FilePath::fromUserInput(".\\qwindowsclipboard.cpp"), 60)
- << CompileTask(Task::Warning, warning3.trimmed(),
- FilePath::fromUserInput(".\\qwindowsclipboard.cpp"), 61)
- << CompileTask(Task::Error, expectedError1,
- FilePath::fromUserInput(".\\qwindowsgdinativeinterface.cpp"), 48)
- << CompileTask(Task::Error, error2.trimmed(),
- FilePath::fromUserInput(".\\qwindowsgdinativeinterface.cpp"), 51))
+ << CompileTask(Task::Warning,
+ "private field 'm_version' is not used [-Wunused-private-field]\n"
+ "./qwindowseglcontext.h(282,15) : warning: private field 'm_version' is not used [-Wunused-private-field]\n"
+ "const int m_version; //! majorVersion<<8 + minorVersion",
+ FilePath::fromUserInput("./qwindowseglcontext.h"), 282)
+ << CompileTask(Task::Warning,
+ "unused variable 'formatTextPlainC' [-Wunused-const-variable]\n"
+ ".\\qwindowsclipboard.cpp(60,19) : warning: unused variable 'formatTextPlainC' [-Wunused-const-variable]\n"
+ "static const char formatTextPlainC[] = \"text/plain\";",
+ FilePath::fromUserInput(".\\qwindowsclipboard.cpp"), 60)
+ << CompileTask(Task::Warning,
+ "unused variable 'formatTextHtmlC' [-Wunused-const-variable]\n"
+ ".\\qwindowsclipboard.cpp(61,19) : warning: unused variable 'formatTextHtmlC' [-Wunused-const-variable]\n"
+ "static const char formatTextHtmlC[] = \"text/html\";",
+ FilePath::fromUserInput(".\\qwindowsclipboard.cpp"), 61)
+ << CompileTask(Task::Error,
+ "unknown type name 'errr'\n"
+ ".\\qwindowsgdinativeinterface.cpp(48,3) : error: unknown type name 'errr'\n"
+ " errr",
+ FilePath::fromUserInput(".\\qwindowsgdinativeinterface.cpp"), 48)
+ << CompileTask(Task::Error,
+ "expected unqualified-id\n"
+ ".\\qwindowsgdinativeinterface.cpp(51,1) : error: expected unqualified-id\n"
+ "void *QWindowsGdiNativeInterface::nativeResourceForBackingStore(const QByteArray &resource, QBackingStore *bs)",
+ FilePath::fromUserInput(".\\qwindowsgdinativeinterface.cpp"), 51))
<< "";
QTest::newRow("other error")
@@ -688,9 +655,12 @@ void ProjectExplorerTest::testClangClOutputParsers_data()
"/FoC:\\MyData\\Project_home\\cpp\build-TestForError-msvc_2017_clang-Debug\\Debug_msvc_201_47eca974c876c8b3\\TestForError.b6dd39ae\\3a52ce780950d4d9\\main.cpp.obj "
"C:\\MyData\\Project_home\\cpp\\TestForError\\main.cpp /TP\n"
" ;\n"
- << Tasks{CompileTask(Task::Error, "expected ';' after return statement\nreturn 0",
- FilePath::fromUserInput("C:\\MyData\\Project_home\\cpp\\TestForError\\main.cpp"),
- 3)}
+ << Tasks{CompileTask(Task::Error,
+ "expected ';' after return statement\n"
+ "C:\\MyData\\Project_home\\cpp\\TestForError\\main.cpp(3,10): error: expected ';' after return statement\n"
+ "return 0",
+ FilePath::fromUserInput("C:\\MyData\\Project_home\\cpp\\TestForError\\main.cpp"),
+ 3)}
<< "";
}
diff --git a/src/plugins/projectexplorer/msvcparser.h b/src/plugins/projectexplorer/msvcparser.h
index 8655ca929c..eef1d5603b 100644
--- a/src/plugins/projectexplorer/msvcparser.h
+++ b/src/plugins/projectexplorer/msvcparser.h
@@ -4,7 +4,6 @@
#pragma once
#include "ioutputparser.h"
-#include "task.h"
#include <QRegularExpression>
#include <QString>
@@ -22,16 +21,12 @@ public:
private:
Result handleLine(const QString &line, Utils::OutputFormat type) override;
- void flush() override;
+ bool isContinuation(const QString &line) const override;
Result processCompileLine(const QString &line);
QRegularExpression m_compileRegExp;
QRegularExpression m_additionalInfoRegExp;
-
- Task m_lastTask;
- LinkSpecs m_linkSpecs;
- int m_lines = 0;
};
class PROJECTEXPLORER_EXPORT ClangClParser : public ProjectExplorer::OutputTaskParser
@@ -43,11 +38,8 @@ public:
private:
Result handleLine(const QString &line, Utils::OutputFormat type) override;
- void flush() override;
const QRegularExpression m_compileRegExp;
- Task m_lastTask;
- int m_linkedLines = 0;
};
} // namespace ProjectExplorer
diff --git a/src/plugins/projectexplorer/msvctoolchain.cpp b/src/plugins/projectexplorer/msvctoolchain.cpp
index 310e170e5a..9dad5e8eb6 100644
--- a/src/plugins/projectexplorer/msvctoolchain.cpp
+++ b/src/plugins/projectexplorer/msvctoolchain.cpp
@@ -1793,11 +1793,7 @@ Macros ClangClToolchain::msvcPredefinedMacros(const QStringList &cxxflags,
Process cpp;
cpp.setEnvironment(env);
cpp.setWorkingDirectory(Utils::TemporaryDirectory::masterDirectoryFilePath());
-
- QStringList arguments = cxxflags;
- arguments.append(gccPredefinedMacrosOptions(language()));
- arguments.append("-");
- cpp.setCommand({compilerCommand(), arguments});
+ cpp.setCommand({compilerCommand(), {cxxflags, gccPredefinedMacrosOptions(language()), "-"}});
cpp.runBlocking();
if (cpp.result() != ProcessResult::FinishedWithSuccess) {
// Show the warning but still parse the output.
diff --git a/src/plugins/projectexplorer/processparameters.cpp b/src/plugins/projectexplorer/processparameters.cpp
index a60f105947..82d391fc58 100644
--- a/src/plugins/projectexplorer/processparameters.cpp
+++ b/src/plugins/projectexplorer/processparameters.cpp
@@ -158,7 +158,7 @@ static QString invalidCommandMessage(const QString &displayName)
return QString("<b>%1:</b> <font color='%3'>%2</font>")
.arg(displayName,
::Utils::Tr::tr("Invalid command"),
- creatorTheme()->color(Theme::TextColorError).name());
+ creatorColor(Theme::TextColorError).name());
}
QString ProcessParameters::summary(const QString &displayName) const
diff --git a/src/plugins/projectexplorer/project.cpp b/src/plugins/projectexplorer/project.cpp
index 7d0ed75c6c..a46f5d5eef 100644
--- a/src/plugins/projectexplorer/project.cpp
+++ b/src/plugins/projectexplorer/project.cpp
@@ -1252,6 +1252,14 @@ void Project::addVariablesToMacroExpander(const QByteArray &prefix,
return project->projectFilePath();
return {};
});
+ expander->registerVariable(fullPrefix + "ProjectDirectory",
+ //: %1 is something like "Active project"
+ ::PE::Tr::tr("%1: Full path to Project Directory.").arg(descriptor),
+ [projectGetter]() -> QString {
+ if (const Project *const project = projectGetter())
+ return project->projectDirectory().toUserOutput();
+ return {};
+ });
expander->registerVariable(fullPrefix + "Kit:Name",
//: %1 is something like "Active project"
::PE::Tr::tr("%1: The name of the active kit.").arg(descriptor),
diff --git a/src/plugins/projectexplorer/project.h b/src/plugins/projectexplorer/project.h
index 9906047bc2..ec6c341bb2 100644
--- a/src/plugins/projectexplorer/project.h
+++ b/src/plugins/projectexplorer/project.h
@@ -68,8 +68,8 @@ public:
BuildSystem *createBuildSystem(Target *target) const;
- Utils::FilePath projectFilePath() const;
- Utils::FilePath projectDirectory() const;
+ virtual Utils::FilePath projectFilePath() const;
+ virtual Utils::FilePath projectDirectory() const;
// This does not affect nodes, only the root path.
void changeRootProjectDirectory();
diff --git a/src/plugins/projectexplorer/projectexplorer.cpp b/src/plugins/projectexplorer/projectexplorer.cpp
index 7ec216c138..b9da6f33a8 100644
--- a/src/plugins/projectexplorer/projectexplorer.cpp
+++ b/src/plugins/projectexplorer/projectexplorer.cpp
@@ -80,6 +80,7 @@
#include "toolchainmanager.h"
#include "toolchainoptionspage.h"
#include "vcsannotatetaskhandler.h"
+#include "workspaceproject.h"
#ifdef Q_OS_WIN
#include "windebuginterface.h"
@@ -197,6 +198,7 @@ const int P_MODE_SESSION = 85;
// Actions
const char LOAD[] = "ProjectExplorer.Load";
+const char LOADWORKSPACE[] = "ProjectExplorer.LoadWorkspace";
const char UNLOAD[] = "ProjectExplorer.Unload";
const char UNLOADCM[] = "ProjectExplorer.UnloadCM";
const char UNLOADOTHERSCM[] = "ProjectExplorer.UnloadOthersCM";
@@ -479,6 +481,7 @@ public:
void buildQueueFinished(bool success);
void loadAction();
+ void openWorkspaceAction();
void handleUnloadProject();
void unloadProjectContextMenu();
void unloadOtherProjectsContextMenu();
@@ -543,6 +546,7 @@ public:
QAction *m_newAction;
QAction *m_loadAction;
+ QAction *m_loadWorkspaceAction;
Action *m_unloadAction;
Action *m_unloadActionContextMenu;
Action *m_unloadOthersActionContextMenu;
@@ -792,6 +796,7 @@ static void restoreRecentProjects(QtcSettings *s)
{FilePath::fromUserInput(filePaths.at(i)), displayNames.at(i), exists});
}
}
+ dd->updateRecentProjectMenu();
dd->checkRecentProjectsAsync();
}
@@ -1099,6 +1104,12 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er
cmd->setDefaultKeySequence(QKeySequence(Tr::tr("Ctrl+Shift+O")));
msessionContextMenu->addAction(cmd, Constants::G_SESSION_FILES);
+ // load workspace action
+ dd->m_loadWorkspaceAction = new QAction(Tr::tr("Open Workspace..."), this);
+ cmd = ActionManager::registerAction(dd->m_loadWorkspaceAction, Constants::LOADWORKSPACE);
+ mfile->addAction(cmd, Core::Constants::G_FILE_OPEN);
+ msessionContextMenu->addAction(cmd, Constants::G_SESSION_FILES);
+
// Default open action
dd->m_openFileAction = new QAction(Tr::tr("Open File"), this);
cmd = ActionManager::registerAction(dd->m_openFileAction, Constants::OPENFILE,
@@ -1659,6 +1670,8 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er
dd, &ProjectExplorerPlugin::openNewProjectDialog);
connect(dd->m_loadAction, &QAction::triggered,
dd, &ProjectExplorerPluginPrivate::loadAction);
+ connect(dd->m_loadWorkspaceAction, &QAction::triggered,
+ dd, &ProjectExplorerPluginPrivate::openWorkspaceAction);
connect(dd->m_buildProjectOnlyAction, &QAction::triggered, dd, [] {
BuildManager::buildProjectWithoutDependencies(ProjectManager::startupProject());
});
@@ -1887,10 +1900,11 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er
DeviceManager::instance()->addDevice(IDevice::Ptr(new DesktopDevice));
+ setupWorkspaceProject(this);
+
#ifdef WITH_TESTS
addTestCreator(&createSanitizerOutputParserTest);
#endif
-
return true;
}
@@ -1920,6 +1934,30 @@ void ProjectExplorerPluginPrivate::loadAction()
updateActions();
}
+void ProjectExplorerPluginPrivate::openWorkspaceAction()
+{
+ FilePath dir = dd->m_lastOpenDirectory;
+
+ // for your special convenience, we preselect a pro file if it is
+ // the current file
+ if (const IDocument *document = EditorManager::currentDocument()) {
+ const FilePath fn = document->filePath();
+ const bool isProject = dd->m_profileMimeTypes.contains(document->mimeType());
+ dir = isProject ? fn : fn.absolutePath();
+ }
+
+ FilePath filePath = Utils::FileUtils::getExistingDirectory(
+ ICore::dialogParent(), Tr::tr("Open Workspace"), dir);
+ if (filePath.isEmpty())
+ return;
+
+ OpenProjectResult result = ProjectExplorerPlugin::openProject(filePath);
+ if (!result)
+ ProjectExplorerPlugin::showOpenProjectError(result);
+
+ updateActions();
+}
+
void ProjectExplorerPluginPrivate::unloadProjectContextMenu()
{
if (Project *p = ProjectTree::currentProject())
@@ -2073,7 +2111,7 @@ void ProjectExplorerPlugin::extensionsInitialized()
bool ProjectExplorerPlugin::delayedInitialize()
{
- NANOTRACE_SCOPE("ProjectExplorer", "ProjectExplorerPlugin::restoreKits");
+ NANOTRACE_SCOPE("ProjectExplorer", "ProjectExplorerPlugin::delayedInitialize");
ExtraAbi::load(); // Load this before Toolchains!
ToolchainManager::restoreToolchains();
KitManager::restoreKits();
@@ -2147,10 +2185,10 @@ void ProjectExplorerPluginPrivate::checkRecentProjectsAsync()
m_recentProjectsFuture
= QtConcurrent::mapped(&m_recentProjectsPool, m_recentProjects, [](RecentProjectsEntry p) {
// check if project is available, but avoid querying devices
- p.exists = p.filePath.needsDevice() || p.filePath.isFile();
+ p.exists = p.filePath.needsDevice() || p.filePath.exists();
return p;
});
- PluginManager::futureSynchronizer()->addFuture(m_recentProjectsFuture);
+ Utils::futureSynchronizer()->addFuture(m_recentProjectsFuture);
onResultReady(m_recentProjectsFuture, this, [this](const RecentProjectsEntry &p) {
auto it = std::find_if(
@@ -2283,10 +2321,7 @@ OpenProjectResult ProjectExplorerPlugin::openProjects(const FilePaths &filePaths
MimeType mt = Utils::mimeTypeForFile(filePath);
if (ProjectManager::canOpenProjectForMimeType(mt)) {
- if (!filePath.isFile()) {
- appendError(errorString,
- Tr::tr("Failed opening project \"%1\": Project is not a file.").arg(filePath.toUserOutput()));
- } else if (Project *pro = ProjectManager::openProject(mt, filePath)) {
+ if (Project *pro = ProjectManager::openProject(mt, filePath)) {
QString restoreError;
Project::RestoreResult restoreResult = pro->restoreSettings(&restoreError);
if (restoreResult == Project::RestoreResult::Ok) {
@@ -2456,6 +2491,12 @@ void ProjectExplorerPluginPrivate::startRunControl(RunControl *runControl)
++m_activeRunControlCount;
runControl->initiateStart();
doUpdateRunActions();
+ connect(runControl, &RunControl::started, m_instance, [runControl] {
+ emit m_instance->runControlStarted(runControl);
+ });
+ connect(runControl, &RunControl::stopped, m_instance, [runControl] {
+ emit m_instance->runControlStoped(runControl);
+ });
}
void ProjectExplorerPluginPrivate::showOutputPaneForRunControl(RunControl *runControl)
@@ -3611,7 +3652,7 @@ void ProjectExplorerPluginPrivate::openTerminalHere(const EnvironmentGetter &env
}
if (buildDevice->rootPath().needsDevice())
- Terminal::Hooks::instance().openTerminal({CommandLine{*shell, {}}, workingDir, environment});
+ Terminal::Hooks::instance().openTerminal({CommandLine{*shell}, workingDir, environment});
else
Terminal::Hooks::instance().openTerminal({workingDir, environment});
}
@@ -3651,8 +3692,8 @@ void ProjectExplorerPluginPrivate::openTerminalHereWithRunEnv()
}
if (device->rootPath().needsDevice()) {
- Terminal::Hooks::instance().openTerminal(
- {CommandLine{*shell, {}}, workingDir, runnable.environment});
+ Terminal::Hooks::instance().openTerminal({CommandLine{*shell}, workingDir,
+ runnable.environment});
} else {
Terminal::Hooks::instance().openTerminal({workingDir, runnable.environment});
}
diff --git a/src/plugins/projectexplorer/projectexplorer.h b/src/plugins/projectexplorer/projectexplorer.h
index 0c737aa9ab..3c932f759e 100644
--- a/src/plugins/projectexplorer/projectexplorer.h
+++ b/src/plugins/projectexplorer/projectexplorer.h
@@ -182,6 +182,8 @@ signals:
void customParsersChanged();
void runActionsUpdated();
+ void runControlStarted(ProjectExplorer::RunControl *runControl);
+ void runControlStoped(ProjectExplorer::RunControl *runControl);
void filesRenamed(const QList<std::pair<Utils::FilePath, Utils::FilePath>> &oldAndNewPaths);
diff --git a/src/plugins/projectexplorer/projectexplorer.qbs b/src/plugins/projectexplorer/projectexplorer.qbs
index feab87da1b..19b9221de2 100644
--- a/src/plugins/projectexplorer/projectexplorer.qbs
+++ b/src/plugins/projectexplorer/projectexplorer.qbs
@@ -115,6 +115,7 @@ QtcPlugin {
"projectmanager.cpp", "projectmanager.h",
"projectmodels.cpp", "projectmodels.h",
"projectnodes.cpp", "projectnodes.h",
+ "projectnodeshelper.h",
"projectpanelfactory.cpp", "projectpanelfactory.h",
"projectsettingswidget.cpp", "projectsettingswidget.h",
"projecttree.cpp",
@@ -154,6 +155,7 @@ QtcPlugin {
"vcsannotatetaskhandler.cpp", "vcsannotatetaskhandler.h",
"waitforstopdialog.cpp", "waitforstopdialog.h",
"windebuginterface.cpp", "windebuginterface.h",
+ "workspaceproject.cpp", "workspaceproject.h",
"xcodebuildparser.cpp", "xcodebuildparser.h"
]
}
diff --git a/src/plugins/projectexplorer/projectexplorerconstants.h b/src/plugins/projectexplorer/projectexplorerconstants.h
index ca0fe5c9e0..7638d323c9 100644
--- a/src/plugins/projectexplorer/projectexplorerconstants.h
+++ b/src/plugins/projectexplorer/projectexplorerconstants.h
@@ -88,6 +88,7 @@ const char BUILD_AND_RUN_SETTINGS_CATEGORY[] = "K.BuildAndRun";
// Build and Run page
const char BUILD_AND_RUN_SETTINGS_PAGE_ID[] = "A.ProjectExplorer.BuildAndRunOptions";
+const char SHOW_ALL_KITS_SETTINGS_KEY[] = "ProjectExplorer/Settings/ShowAllKits";
// Device settings page
const char DEVICE_SETTINGS_CATEGORY[] = "AM.Devices";
diff --git a/src/plugins/projectexplorer/projectexplorersettings.cpp b/src/plugins/projectexplorer/projectexplorersettings.cpp
index 5d4df2aecd..1780a53eff 100644
--- a/src/plugins/projectexplorer/projectexplorersettings.cpp
+++ b/src/plugins/projectexplorer/projectexplorersettings.cpp
@@ -78,7 +78,9 @@ static bool operator==(const ProjectExplorerSettings &p1, const ProjectExplorerS
&& p1.clearIssuesOnRebuild == p2.clearIssuesOnRebuild
&& p1.abortBuildAllOnError == p2.abortBuildAllOnError
&& p1.appEnvChanges == p2.appEnvChanges
- && p1.lowBuildPriority == p2.lowBuildPriority;
+ && p1.lowBuildPriority == p2.lowBuildPriority
+ && p1.warnAgainstNonAsciiBuildDir == p2.warnAgainstNonAsciiBuildDir
+ && p1.showAllKits == p2.showAllKits;
}
ProjectExplorerSettings &mutableProjectExplorerSettings()
@@ -140,8 +142,10 @@ static void loadProjectExplorerSettings()
.toBool();
settings.environmentId =
QUuid(s->value(Constants::ENVIRONMENT_ID_SETTINGS_KEY).toByteArray());
- if (settings.environmentId.isNull())
+ if (settings.environmentId.isNull()) {
settings.environmentId = QUuid::createUuid();
+ s->setValue(Constants::ENVIRONMENT_ID_SETTINGS_KEY, settings.environmentId.toByteArray());
+ }
int tmp = s->value(Constants::STOP_BEFORE_BUILD_SETTINGS_KEY,
int(defaultSettings.stopBeforeBuild))
.toInt();
@@ -171,6 +175,9 @@ static void loadProjectExplorerSettings()
.toBool();
settings.appEnvChanges = EnvironmentItem::fromStringList(
s->value(Constants::APP_ENV_CHANGES_SETTINGS_KEY).toStringList());
+ settings.showAllKits
+ = s->value(ProjectExplorer::Constants::SHOW_ALL_KITS_SETTINGS_KEY, defaultSettings.showAllKits)
+ .toBool();
}
void saveProjectExplorerSettings()
@@ -225,6 +232,10 @@ void saveProjectExplorerSettings()
int(defaultSettings.stopBeforeBuild));
s->setValueWithDefault(Constants::APP_ENV_CHANGES_SETTINGS_KEY,
EnvironmentItem::toStringList(settings.appEnvChanges));
+ s->setValueWithDefault(
+ ProjectExplorer::Constants::SHOW_ALL_KITS_SETTINGS_KEY,
+ settings.showAllKits,
+ defaultSettings.showAllKits);
}
class ProjectExplorerSettingsWidget : public IOptionsPageWidget
@@ -278,6 +289,7 @@ private:
QComboBox *m_stopBeforeBuildComboBox;
QComboBox *m_terminalModeComboBox;
QCheckBox *m_jomCheckbox;
+ QCheckBox *m_showAllKitsCheckBox;
Utils::ElidingLabel *m_appEnvLabel;
QButtonGroup *m_directoryButtonGroup;
@@ -337,6 +349,11 @@ ProjectExplorerSettingsWidget::ProjectExplorerSettingsWidget()
"Disable it if you experience problems with your builds.");
jomLabel->setWordWrap(true);
+ m_showAllKitsCheckBox = new QCheckBox(
+ Tr::tr("Show all kits in \"Build & Run\" in \"Projects\" mode"));
+ m_showAllKitsCheckBox->setToolTip(
+ Tr::tr("Show also inactive kits in \"Build & Run\" in \"Projects\" mode."));
+
const QString appEnvToolTip = Tr::tr("Environment changes to apply to run configurations, "
"but not build configurations.");
const auto appEnvDescriptionLabel = new QLabel(Tr::tr("Application environment:"));
@@ -383,6 +400,7 @@ ProjectExplorerSettingsWidget::ProjectExplorerSettingsWidget()
m_abortBuildAllOnErrorCheckBox,
m_lowBuildPriorityCheckBox,
m_warnAgainstNonAsciiBuildDirCheckBox,
+ m_showAllKitsCheckBox,
Form {
appEnvDescriptionLabel, Row{m_appEnvLabel, appEnvButton, st}, br,
Tr::tr("Build before deploying:"), m_buildBeforeDeployComboBox, br,
@@ -433,6 +451,7 @@ ProjectExplorerSettings ProjectExplorerSettingsWidget::settings() const
s.lowBuildPriority = m_lowBuildPriorityCheckBox->isChecked();
s.warnAgainstNonAsciiBuildDir = m_warnAgainstNonAsciiBuildDirCheckBox->isChecked();
s.appEnvChanges = m_appEnvChanges;
+ s.showAllKits = m_showAllKitsCheckBox->isChecked();
s.environmentId = projectExplorerSettings().environmentId;
return s;
}
@@ -456,6 +475,7 @@ void ProjectExplorerSettingsWidget::setSettings(const ProjectExplorerSettings &
m_abortBuildAllOnErrorCheckBox->setChecked(s.abortBuildAllOnError);
m_lowBuildPriorityCheckBox->setChecked(s.lowBuildPriority);
m_warnAgainstNonAsciiBuildDirCheckBox->setChecked(s.warnAgainstNonAsciiBuildDir);
+ m_showAllKitsCheckBox->setChecked(s.showAllKits);
}
FilePath ProjectExplorerSettingsWidget::projectsDirectory() const
@@ -525,5 +545,10 @@ const ProjectExplorerSettings &projectExplorerSettings()
return Internal::mutableProjectExplorerSettings();
}
+ProjectExplorerSettings &mutableProjectExplorerSettings()
+{
+ return Internal::mutableProjectExplorerSettings();
+}
+
} // ProjectExplorer
diff --git a/src/plugins/projectexplorer/projectexplorersettings.h b/src/plugins/projectexplorer/projectexplorersettings.h
index 51970eb290..0a99984dc2 100644
--- a/src/plugins/projectexplorer/projectexplorersettings.h
+++ b/src/plugins/projectexplorer/projectexplorersettings.h
@@ -33,6 +33,7 @@ public:
bool abortBuildAllOnError = true;
bool lowBuildPriority = false;
bool warnAgainstNonAsciiBuildDir = true;
+ bool showAllKits = true;
StopBeforeBuild stopBeforeBuild = Utils::HostOsInfo::isWindowsHost()
? StopBeforeBuild::SameProject
: StopBeforeBuild::None;
@@ -46,6 +47,7 @@ public:
};
PROJECTEXPLORER_EXPORT const ProjectExplorerSettings &projectExplorerSettings();
+ProjectExplorerSettings &mutableProjectExplorerSettings();
namespace Internal {
diff --git a/src/plugins/projectexplorer/projectmodels.cpp b/src/plugins/projectexplorer/projectmodels.cpp
index c8b7392bf9..484bc5a019 100644
--- a/src/plugins/projectexplorer/projectmodels.cpp
+++ b/src/plugins/projectexplorer/projectmodels.cpp
@@ -242,7 +242,7 @@ QVariant FlatModel::data(const QModelIndex &index, int role) const
}
case Qt::ForegroundRole:
return node->isEnabled() ? QVariant()
- : Utils::creatorTheme()->color(Utils::Theme::TextColorDisabled);
+ : Utils::creatorColor(Utils::Theme::TextColorDisabled);
case Project::FilePathRole:
return node->filePath().toString();
case Project::isParsingRole:
diff --git a/src/plugins/projectexplorer/projectnodeshelper.h b/src/plugins/projectexplorer/projectnodeshelper.h
index bcb5454fd8..b9e3cc27aa 100644
--- a/src/plugins/projectexplorer/projectnodeshelper.h
+++ b/src/plugins/projectexplorer/projectnodeshelper.h
@@ -14,11 +14,6 @@
#include <QPromise>
namespace ProjectExplorer {
-
-template<typename Result>
-QList<FileNode *> scanForFiles(QPromise<Result> &promise, const Utils::FilePath &directory,
- const std::function<FileNode *(const Utils::FilePath &)> factory);
-
namespace Internal {
template<typename Result>
@@ -27,43 +22,40 @@ QList<FileNode *> scanForFilesRecursively(
double progressStart,
double progressRange,
const Utils::FilePath &directory,
+ const QDir::Filters &filter,
const std::function<FileNode *(const Utils::FilePath &)> factory,
- QSet<QString> &visited,
+ QSet<Utils::FilePath> &visited,
const QList<Core::IVersionControl *> &versionControls)
{
QList<FileNode *> result;
- const QDir baseDir = QDir(directory.toString());
-
// Do not follow directory loops:
- const int visitedCount = visited.count();
- visited.insert(baseDir.canonicalPath());
- if (visitedCount == visited.count())
+ if (!Utils::insert(visited, directory.canonicalPath()))
return result;
- const QFileInfoList entries = baseDir.entryInfoList(QStringList(),
- QDir::AllEntries | QDir::NoDotAndDotDot);
+ const Utils::FilePaths entries = directory.dirEntries(filter);
double progress = 0;
const double progressIncrement = progressRange / static_cast<double>(entries.count());
int lastIntProgress = 0;
- for (const QFileInfo &entry : entries) {
+ for (const Utils::FilePath &entry : entries) {
if (promise.isCanceled())
return result;
- const Utils::FilePath entryName = Utils::FilePath::fromString(entry.absoluteFilePath());
- if (!Utils::contains(versionControls, [&entryName](const Core::IVersionControl *vc) {
- return vc->isVcsFileOrDirectory(entryName);
+ if (!Utils::contains(versionControls, [entry](const Core::IVersionControl *vc) {
+ return vc->isVcsFileOrDirectory(entry);
})) {
- if (entry.isDir())
+ if (entry.isDir()) {
result.append(scanForFilesRecursively(promise,
progress,
progressIncrement,
- entryName,
+ entry,
+ filter,
factory,
visited,
versionControls));
- else if (FileNode *node = factory(entryName))
+ } else if (FileNode *node = factory(entry)) {
result.append(node);
+ }
}
progress += progressIncrement;
const int intProgress = std::min(static_cast<int>(progressStart + progress),
@@ -80,15 +72,19 @@ QList<FileNode *> scanForFilesRecursively(
} // namespace Internal
template<typename Result>
-QList<FileNode *> scanForFiles(QPromise<Result> &promise, const Utils::FilePath &directory,
- const std::function<FileNode *(const Utils::FilePath &)> factory)
+QList<FileNode *> scanForFiles(
+ QPromise<Result> &promise,
+ const Utils::FilePath &directory,
+ const QDir::Filters &filter,
+ const std::function<FileNode *(const Utils::FilePath &)> factory)
{
- QSet<QString> visited;
+ QSet<Utils::FilePath> visited;
promise.setProgressRange(0, 1000000);
return Internal::scanForFilesRecursively(promise,
0.0,
1000000.0,
directory,
+ filter,
factory,
visited,
Core::VcsManager::versionControls());
diff --git a/src/plugins/projectexplorer/projecttreewidget.cpp b/src/plugins/projectexplorer/projecttreewidget.cpp
index 774a8bb160..8858d2252c 100644
--- a/src/plugins/projectexplorer/projecttreewidget.cpp
+++ b/src/plugins/projectexplorer/projecttreewidget.cpp
@@ -76,7 +76,7 @@ public:
const bool useUnavailableMarker = index.data(Project::UseUnavailableMarkerRole).toBool();
if (useUnavailableMarker) {
QStyleOptionViewItem opt = option;
- opt.palette.setColor(QPalette::Text, creatorTheme()->color(Theme::TextColorDisabled));
+ opt.palette.setColor(QPalette::Text, creatorColor(Theme::TextColorDisabled));
QStyledItemDelegate::paint(painter, opt, index);
static const QPixmap pixmap
= QApplication::style()->standardIcon(QStyle::SP_BrowserStop).pixmap(10);
@@ -308,11 +308,11 @@ ProjectTreeWidget::~ProjectTreeWidget()
int ProjectTreeWidget::expandedCount(Node *node)
{
if (m_projectTreeWidgets.isEmpty())
- return 0;
+ return INT_MAX;
FlatModel *model = m_projectTreeWidgets.first()->m_model;
QModelIndex index = model->indexForNode(node);
if (!index.isValid())
- return 0;
+ return INT_MAX;
int count = 0;
for (ProjectTreeWidget *tree : std::as_const(m_projectTreeWidgets)) {
diff --git a/src/plugins/projectexplorer/projectwelcomepage.cpp b/src/plugins/projectexplorer/projectwelcomepage.cpp
index 8d97d1ca8a..34ffab0b18 100644
--- a/src/plugins/projectexplorer/projectwelcomepage.cpp
+++ b/src/plugins/projectexplorer/projectwelcomepage.cpp
@@ -219,11 +219,6 @@ void ProjectWelcomePage::createActions()
///////////////////
-static QColor themeColor(Theme::Color role)
-{
- return Utils::creatorTheme()->color(role);
-}
-
static QPixmap pixmap(const QString &id, const Theme::Color color)
{
const QString fileName = QString(":/welcome/images/%1.png").arg(id);
@@ -232,8 +227,8 @@ static QPixmap pixmap(const QString &id, const Theme::Color color)
static void drawBackgroundRect(QPainter *painter, const QRectF &rect, bool hovered)
{
- const QColor fill(themeColor(hovered ? cardHoverBackground : cardDefaultBackground));
- const QPen pen(themeColor(hovered ? cardHoverStroke : cardDefaultStroke));
+ const QColor fill(creatorColor(hovered ? cardHoverBackground : cardDefaultBackground));
+ const QPen pen(creatorColor(hovered ? cardHoverStroke : cardDefaultStroke));
const qreal rounding = s(defaultCardBackgroundRounding * 1000) / 1000.0;
const qreal saneRounding = rounding <= 2 ? 0 : rounding;
@@ -514,7 +509,7 @@ public:
.contains(mousePos) && !isDisabled;
if (isActive) {
WelcomePageHelpers::drawCardBackground(painter, actionR, Qt::transparent,
- themeColor(Theme::Token_Text_Muted));
+ creatorColor(Theme::Token_Text_Muted));
m_activeActionRects[i] = actionR;
}
painter->setFont(actionFont);
@@ -524,7 +519,7 @@ public:
xx += actionR.width();
if (i < actions.count() - 1) {
const QRect dividerR(xx + s(HGapXs), yy, actionSepWidth, buttonHeight);
- painter->fillRect(dividerR, themeColor(Theme::Token_Text_Muted));
+ painter->fillRect(dividerR, creatorColor(Theme::Token_Text_Muted));
}
xx += gapWidth;
}
@@ -789,7 +784,7 @@ public:
auto sessions = new QWidget;
{
- auto sessionsLabel = new Label(Tr::tr("Sessions"), Label::Primary);
+ auto sessionsLabel = new Core::Label(Tr::tr("Sessions"), Core::Label::Primary);
auto manageSessionsButton = new Button(Tr::tr("Manage..."), Button::MediumSecondary);
auto sessionsList = new TreeView(this, "Sessions");
sessionsList->setModel(projectWelcomePage->m_sessionModel);
@@ -804,11 +799,11 @@ public:
sessionsLabel,
st,
manageSessionsButton,
- customMargin({HPaddingS, 0, sessionScrollBarGap, 0}),
+ customMargins(HPaddingS, 0, sessionScrollBarGap, 0),
},
sessionsList,
spacing(ExPaddingGapL),
- customMargin({ExVPaddingGapXl, ExVPaddingGapXl, 0, 0}),
+ customMargins(ExVPaddingGapXl, ExVPaddingGapXl, 0, 0),
}.attachTo(sessions);
connect(manageSessionsButton, &Button::clicked,
this, &SessionManager::showSessionManager);
@@ -816,7 +811,7 @@ public:
auto projects = new QWidget;
{
- auto projectsLabel = new Label(Tr::tr("Projects"), Label::Primary);
+ auto projectsLabel = new Core::Label(Tr::tr("Projects"), Core::Label::Primary);
auto projectsList = new TreeView(this, "Recent Projects");
projectsList->setUniformRowHeights(true);
projectsList->setModel(projectWelcomePage->m_projectModel);
@@ -828,11 +823,11 @@ public:
Column {
Row {
projectsLabel,
- customMargin({HPaddingS, 0, 0, 0}),
+ customMargins(HPaddingS, 0, 0, 0),
},
projectsList,
spacing(ExPaddingGapL),
- customMargin({ExVPaddingGapXl - sessionScrollBarGap, ExVPaddingGapXl, 0, 0}),
+ customMargins(ExVPaddingGapXl - sessionScrollBarGap, ExVPaddingGapXl, 0, 0),
}.attachTo(projects);
}
@@ -840,7 +835,7 @@ public:
sessions,
projects,
spacing(0),
- noMargin(),
+ noMargin,
}.attachTo(this);
}
diff --git a/src/plugins/projectexplorer/projectwindow.cpp b/src/plugins/projectexplorer/projectwindow.cpp
index 1c2f04a04c..c3214b6498 100644
--- a/src/plugins/projectexplorer/projectwindow.cpp
+++ b/src/plugins/projectexplorer/projectwindow.cpp
@@ -1061,7 +1061,7 @@ void SelectorDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti
if (TreeItem *item = model->itemForIndex(index)) {
switch (item->level()) {
case 2: {
- QColor col = creatorTheme()->color(Theme::TextColorNormal);
+ QColor col = creatorColor(Theme::TextColorNormal);
opt.palette.setColor(QPalette::Text, col);
opt.font = StyleHelper::uiFont(StyleHelper::UiElementH4);
break;
diff --git a/src/plugins/projectexplorer/projectwizardpage.cpp b/src/plugins/projectexplorer/projectwizardpage.cpp
index b3c0bd37d9..c71d762aaa 100644
--- a/src/plugins/projectexplorer/projectwizardpage.cpp
+++ b/src/plugins/projectexplorer/projectwizardpage.cpp
@@ -377,7 +377,7 @@ void ProjectWizardPage::initializeVersionControls()
m_addToVersionControlComboBox->disconnect();
QList<IVersionControl *> versionControls = VcsManager::versionControls();
if (versionControls.isEmpty())
- hideVersionControlUiElements();
+ setVersionControlUiElementsVisible(false);
IVersionControl *currentSelection = nullptr;
int currentIdx = versionControlIndex() - 1;
@@ -589,11 +589,11 @@ void ProjectWizardPage::manageVcs()
ICore::showOptionsDialog(VcsBase::Constants::VCS_COMMON_SETTINGS_ID, this);
}
-void ProjectWizardPage::hideVersionControlUiElements()
+void ProjectWizardPage::setVersionControlUiElementsVisible(bool visible)
{
- m_addToVersionControlLabel->hide();
- m_vcsManageButton->hide();
- m_addToVersionControlComboBox->hide();
+ m_addToVersionControlLabel->setVisible(visible);
+ m_vcsManageButton->setVisible(visible);
+ m_addToVersionControlComboBox->setVisible(visible);
}
void ProjectWizardPage::setProjectUiVisible(bool visible)
diff --git a/src/plugins/projectexplorer/projectwizardpage.h b/src/plugins/projectexplorer/projectwizardpage.h
index cf5cf42788..835547e261 100644
--- a/src/plugins/projectexplorer/projectwizardpage.h
+++ b/src/plugins/projectexplorer/projectwizardpage.h
@@ -61,10 +61,12 @@ signals:
void projectNodeChanged();
void versionControlChanged(int);
+protected:
+ void setVersionControlUiElementsVisible(bool visible);
+
private:
void projectChanged(int);
void manageVcs();
- void hideVersionControlUiElements();
void setAdditionalInfo(const QString &text);
void setAddingSubProject(bool addingSubProject);
diff --git a/src/plugins/projectexplorer/runconfiguration.cpp b/src/plugins/projectexplorer/runconfiguration.cpp
index be59ad0eaa..97d467fbc7 100644
--- a/src/plugins/projectexplorer/runconfiguration.cpp
+++ b/src/plugins/projectexplorer/runconfiguration.cpp
@@ -201,13 +201,13 @@ bool RunConfiguration::isEnabled(Utils::Id) const
QWidget *RunConfiguration::createConfigurationWidget()
{
Layouting::Form form;
+ form.setNoMargins();
for (BaseAspect *aspect : std::as_const(*this)) {
if (aspect->isVisible()) {
form.addItem(aspect);
- form.addItem(Layouting::br);
+ form.flush();
}
}
- form.addItem(Layouting::noMargin);
auto widget = form.emerge();
VariableChooser::addSupportForChildWidgets(widget, &m_expander);
@@ -425,6 +425,13 @@ ProcessRunData RunConfiguration::runnable() const
r.environment = environmentAspect->environment();
if (m_runnableModifier)
m_runnableModifier(r);
+
+ // TODO: Do expansion in commandLine()?
+ if (!r.command.isEmpty()) {
+ const FilePath expanded = macroExpander()->expand(r.command.executable());
+ r.command.setExecutable(expanded);
+ }
+
return r;
}
diff --git a/src/plugins/projectexplorer/runconfigurationaspects.cpp b/src/plugins/projectexplorer/runconfigurationaspects.cpp
index c042f081f1..a383438c8b 100644
--- a/src/plugins/projectexplorer/runconfigurationaspects.cpp
+++ b/src/plugins/projectexplorer/runconfigurationaspects.cpp
@@ -63,13 +63,13 @@ TerminalAspect::TerminalAspect(AspectContainer *container)
/*!
\reimp
*/
-void TerminalAspect::addToLayout(LayoutItem &parent)
+void TerminalAspect::addToLayout(Layout &parent)
{
QTC_CHECK(!m_checkBox);
m_checkBox = createSubWidget<QCheckBox>(Tr::tr("Run in terminal"));
m_checkBox->setChecked(m_useTerminal);
m_checkBox->setEnabled(isEnabled());
- parent.addItems({empty(), m_checkBox.data()});
+ parent.addItems({empty, m_checkBox.data()});
connect(m_checkBox.data(), &QAbstractButton::clicked, this, [this] {
m_userSet = true;
m_useTerminal = m_checkBox->isChecked();
@@ -174,7 +174,7 @@ void WorkingDirectoryAspect::setEnvironment(EnvironmentAspect *envAspect)
/*!
\reimp
*/
-void WorkingDirectoryAspect::addToLayout(LayoutItem &builder)
+void WorkingDirectoryAspect::addToLayout(Layout &builder)
{
QTC_CHECK(!m_chooser);
m_chooser = new PathChooser;
@@ -203,6 +203,9 @@ void WorkingDirectoryAspect::addToLayout(LayoutItem &builder)
m_chooser->setEnvironment(m_envAspect->environment());
}
+ m_chooser->setReadOnly(isReadOnly());
+ m_resetButton->setEnabled(!isReadOnly());
+
builder.addItems({Tr::tr("Working directory:"), m_chooser.data(), m_resetButton.data()});
}
@@ -401,7 +404,7 @@ void ArgumentsAspect::fromMap(const Store &map)
{
QVariant args = map.value(settingsKey());
// Until 3.7 a QStringList was stored for Remote Linux
- if (args.typeId() == QVariant::StringList)
+ if (args.typeId() == QMetaType::QStringList)
m_arguments = ProcessArgs::joinArgs(args.toStringList(), OsTypeLinux);
else
m_arguments = args.toString();
@@ -437,6 +440,7 @@ QWidget *ArgumentsAspect::setupChooser()
this, [this] { setArguments(m_multiLineChooser->toPlainText()); });
}
m_multiLineChooser->setPlainText(m_arguments);
+ m_multiLineChooser->setReadOnly(isReadOnly());
return m_multiLineChooser.data();
}
if (!m_chooser) {
@@ -445,13 +449,15 @@ QWidget *ArgumentsAspect::setupChooser()
connect(m_chooser.data(), &QLineEdit::textChanged, this, &ArgumentsAspect::setArguments);
}
m_chooser->setText(m_arguments);
+ m_chooser->setReadOnly(isReadOnly());
+
return m_chooser.data();
}
/*!
\reimp
*/
-void ArgumentsAspect::addToLayout(LayoutItem &builder)
+void ArgumentsAspect::addToLayout(Layout &builder)
{
QTC_CHECK(!m_chooser && !m_multiLineChooser && !m_multiLineButton);
@@ -597,6 +603,7 @@ void ExecutableAspect::setEnvironment(const Environment &env)
void ExecutableAspect::setReadOnly(bool readOnly)
{
+ BaseAspect::setReadOnly(readOnly);
m_executable.setReadOnly(readOnly);
}
@@ -643,11 +650,13 @@ FilePath ExecutableAspect::executable() const
/*!
\reimp
*/
-void ExecutableAspect::addToLayout(LayoutItem &builder)
+void ExecutableAspect::addToLayout(Layout &builder)
{
builder.addItem(m_executable);
- if (m_alternativeExecutable)
- builder.addItems({br, m_alternativeExecutable});
+ if (m_alternativeExecutable) {
+ builder.flush();
+ builder.addItem(m_alternativeExecutable);
+ }
}
/*!
diff --git a/src/plugins/projectexplorer/runconfigurationaspects.h b/src/plugins/projectexplorer/runconfigurationaspects.h
index d02236a659..d891132ea7 100644
--- a/src/plugins/projectexplorer/runconfigurationaspects.h
+++ b/src/plugins/projectexplorer/runconfigurationaspects.h
@@ -29,7 +29,7 @@ class PROJECTEXPLORER_EXPORT TerminalAspect : public Utils::BaseAspect
public:
explicit TerminalAspect(Utils::AspectContainer *container = nullptr);
- void addToLayout(Layouting::LayoutItem &parent) override;
+ void addToLayout(Layouting::Layout &parent) override;
bool useTerminal() const;
void setUseTerminalHint(bool useTerminal);
@@ -61,7 +61,7 @@ class PROJECTEXPLORER_EXPORT WorkingDirectoryAspect : public Utils::BaseAspect
public:
explicit WorkingDirectoryAspect(Utils::AspectContainer *container = nullptr);
- void addToLayout(Layouting::LayoutItem &parent) override;
+ void addToLayout(Layouting::Layout &parent) override;
Utils::FilePath operator()() const { return workingDirectory(); }
Utils::FilePath workingDirectory() const;
@@ -93,7 +93,7 @@ class PROJECTEXPLORER_EXPORT ArgumentsAspect : public Utils::BaseAspect
public:
explicit ArgumentsAspect(Utils::AspectContainer *container = nullptr);
- void addToLayout(Layouting::LayoutItem &parent) override;
+ void addToLayout(Layouting::Layout &parent) override;
QString operator()() const { return arguments(); }
QString arguments() const;
@@ -171,7 +171,7 @@ public:
void setDeviceSelector(Target *target, ExecutionDeviceSelector selector);
void setSettingsKey(const Utils::Key &key);
void makeOverridable(const Utils::Key &overridingKey, const Utils::Key &useOverridableKey);
- void addToLayout(Layouting::LayoutItem &parent) override;
+ void addToLayout(Layouting::Layout &parent) override;
void setLabelText(const QString &labelText);
void setPlaceHolderText(const QString &placeHolderText);
void setHistoryCompleter(const Utils::Key &historyCompleterKey);
diff --git a/src/plugins/projectexplorer/runcontrol.cpp b/src/plugins/projectexplorer/runcontrol.cpp
index 3b601d45cb..02df4037af 100644
--- a/src/plugins/projectexplorer/runcontrol.cpp
+++ b/src/plugins/projectexplorer/runcontrol.cpp
@@ -418,7 +418,7 @@ void RunControl::setDevice(const IDevice::ConstPtr &device)
QTC_CHECK(!d->device);
d->device = device;
#ifdef WITH_JOURNALD
- if (!device.isNull() && device->type() == ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE) {
+ if (device && device->type() == ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE) {
JournaldWatcher::instance()->subscribe(this, [this](const JournaldWatcher::LogEntry &entry) {
if (entry.value("_MACHINE_ID") != JournaldWatcher::instance()->machineId())
@@ -1264,6 +1264,7 @@ public:
bool m_stopReported = false;
bool m_stopForced = false;
+ bool m_suppressDefaultStdOutHandling = false;
void forwardStarted();
void forwardDone();
@@ -1372,6 +1373,9 @@ void SimpleTargetRunnerPrivate::handleDone()
void SimpleTargetRunnerPrivate::handleStandardOutput()
{
+ if (m_suppressDefaultStdOutHandling)
+ return;
+
const QByteArray data = m_process.readAllRawStandardOutput();
const QString msg = m_outputCodec->toUnicode(
data.constData(), data.length(), &m_outputCodecState);
@@ -1380,6 +1384,9 @@ void SimpleTargetRunnerPrivate::handleStandardOutput()
void SimpleTargetRunnerPrivate::handleStandardError()
{
+ if (m_suppressDefaultStdOutHandling)
+ return;
+
const QByteArray data = m_process.readAllRawStandardError();
const QString msg = m_outputCodec->toUnicode(
data.constData(), data.length(), &m_errorCodecState);
@@ -1571,6 +1578,16 @@ void SimpleTargetRunner::setProcessMode(Utils::ProcessMode processMode)
d->m_process.setProcessMode(processMode);
}
+Process *SimpleTargetRunner::process() const
+{
+ return &d->m_process;
+}
+
+void SimpleTargetRunner::suppressDefaultStdOutHandling()
+{
+ d->m_suppressDefaultStdOutHandling = true;
+}
+
void SimpleTargetRunner::forceRunOnHost()
{
const FilePath executable = d->m_command.executable();
diff --git a/src/plugins/projectexplorer/runcontrol.h b/src/plugins/projectexplorer/runcontrol.h
index 0c47ff18b6..9428866436 100644
--- a/src/plugins/projectexplorer/runcontrol.h
+++ b/src/plugins/projectexplorer/runcontrol.h
@@ -27,6 +27,7 @@ class Icon;
class MacroExpander;
class OutputLineParser;
class ProcessRunData;
+class Process;
} // Utils
namespace ProjectExplorer {
@@ -271,7 +272,9 @@ protected:
void setEnvironment(const Utils::Environment &environment);
void setWorkingDirectory(const Utils::FilePath &workingDirectory);
void setProcessMode(Utils::ProcessMode processMode);
+ Utils::Process *process() const;
+ void suppressDefaultStdOutHandling();
void forceRunOnHost();
void addExtraData(const QString &key, const QVariant &value);
diff --git a/src/plugins/projectexplorer/sanitizerparser.cpp b/src/plugins/projectexplorer/sanitizerparser.cpp
index 8da13d5d37..546de84448 100644
--- a/src/plugins/projectexplorer/sanitizerparser.cpp
+++ b/src/plugins/projectexplorer/sanitizerparser.cpp
@@ -93,7 +93,8 @@ OutputLineParser::Result SanitizerParser::handleContinuation(const QString &line
m_task.file = file;
m_task.line = summaryMatch.captured("line").toInt();
m_task.column = summaryMatch.captured("column").toInt();
- addLinkSpecForAbsoluteFilePath(linkSpecs, file, m_task.line, summaryMatch, "file");
+ addLinkSpecForAbsoluteFilePath(
+ linkSpecs, file, m_task.line, m_task.column, summaryMatch, "file");
addLinkSpecs(linkSpecs);
}
} else {
@@ -107,7 +108,7 @@ OutputLineParser::Result SanitizerParser::handleContinuation(const QString &line
const FilePath file = absoluteFilePath(FilePath::fromUserInput(fileMatch.captured("file")));
if (fileExists(file)) {
addLinkSpecForAbsoluteFilePath(linkSpecs, file, fileMatch.captured("line").toInt(),
- fileMatch, "file");
+ fileMatch.captured("column").toInt(), fileMatch, "file");
addLinkSpecs(linkSpecs);
}
}
diff --git a/src/plugins/projectexplorer/targetsettingspanel.cpp b/src/plugins/projectexplorer/targetsettingspanel.cpp
index 5e593a5e65..aba55e7a6d 100644
--- a/src/plugins/projectexplorer/targetsettingspanel.cpp
+++ b/src/plugins/projectexplorer/targetsettingspanel.cpp
@@ -10,7 +10,10 @@
#include "kitmanager.h"
#include "panelswidget.h"
#include "project.h"
+#include "projectexplorer.h"
+#include "projectexplorerconstants.h"
#include "projectexplorericons.h"
+#include "projectexplorersettings.h"
#include "projectexplorertr.h"
#include "projectimporter.h"
#include "projectmanager.h"
@@ -168,6 +171,18 @@ public:
void ensureWidget();
void rebuildContents();
+ void setShowAllKits(bool showAllKits)
+ {
+ QtcSettings *settings = Core::ICore::settings();
+ settings->setValue(ProjectExplorer::Constants::SHOW_ALL_KITS_SETTINGS_KEY, showAllKits);
+ mutableProjectExplorerSettings().showAllKits = showAllKits;
+ rebuildContents();
+ }
+ bool showAllKits() const
+ {
+ return projectExplorerSettings().showAllKits;
+ }
+
TargetGroupItem *q;
QString m_displayName;
Project *m_project;
@@ -178,6 +193,42 @@ public:
TargetSetupPageWrapper *m_targetSetupPageWrapper = nullptr;
};
+class ShowMoreItem : public TreeItem
+{
+public:
+ ShowMoreItem(TargetGroupItemPrivate *p)
+ : m_p(p)
+ {}
+
+ QVariant data(int column, int role) const override
+ {
+ Q_UNUSED(column)
+ if (role == Qt::DisplayRole) {
+ return !m_p->showAllKits() ? Tr::tr("Show All Kits") : Tr::tr("Hide Inactive Kits");
+ }
+ return {};
+ }
+
+ bool setData(int column, const QVariant &data, int role) override
+ {
+ Q_UNUSED(column)
+ Q_UNUSED(data)
+ if (role == ItemActivatedDirectlyRole) {
+ m_p->setShowAllKits(!m_p->showAllKits());
+ return true;
+ }
+ return false;
+ }
+
+ Qt::ItemFlags flags(int) const override
+ {
+ return Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable;
+ }
+
+private:
+ TargetGroupItemPrivate *m_p;
+};
+
void TargetGroupItemPrivate::ensureWidget()
{
if (!m_noKitLabel) {
@@ -270,7 +321,7 @@ public:
case Qt::ForegroundRole: {
if (!isEnabled())
- return Utils::creatorTheme()->color(Theme::TextColorDisabled);
+ return Utils::creatorColor(Theme::TextColorDisabled);
break;
}
@@ -665,6 +716,10 @@ TargetGroupItemPrivate::TargetGroupItemPrivate(TargetGroupItem *q, Project *proj
this, &TargetGroupItemPrivate::handleRemovedKit);
connect(KitManager::instance(), &KitManager::kitUpdated,
this, &TargetGroupItemPrivate::handleUpdatedKit);
+ connect(KitManager::instance(), &KitManager::kitsChanged,
+ this, &TargetGroupItemPrivate::rebuildContents);
+ connect(ProjectExplorerPlugin::instance(), &ProjectExplorerPlugin::settingsChanged,
+ this, &TargetGroupItemPrivate::rebuildContents);
rebuildContents();
}
@@ -761,14 +816,27 @@ void TargetItem::updateSubItems()
void TargetGroupItemPrivate::rebuildContents()
{
+ QGuiApplication::setOverrideCursor(Qt::WaitCursor);
+ const auto sortedKits = KitManager::sortedKits();
+ bool isAnyKitNotEnabled = std::any_of(sortedKits.begin(), sortedKits.end(), [this](Kit *kit) {
+ return kit && m_project->target(kit->id()) != nullptr;
+ });
q->removeChildren();
- for (Kit *kit : KitManager::sortedKits())
- q->appendChild(new TargetItem(m_project, kit->id(), m_project->projectIssues(kit)));
+ for (Kit *kit : sortedKits) {
+ if (!isAnyKitNotEnabled || showAllKits() || m_project->target(kit->id()) != nullptr)
+ q->appendChild(new TargetItem(m_project, kit->id(), m_project->projectIssues(kit)));
+ }
+
+ if (isAnyKitNotEnabled)
+ q->appendChild(new ShowMoreItem(this));
+
+ if (q->parent()) {
+ q->parent()
+ ->setData(0, QVariant::fromValue(static_cast<TreeItem *>(q)), ItemUpdatedFromBelowRole);
+ }
- if (q->parent())
- q->parent()->setData(0, QVariant::fromValue(static_cast<TreeItem *>(q)),
- ItemUpdatedFromBelowRole);
+ QGuiApplication::restoreOverrideCursor();
}
void TargetGroupItemPrivate::handleTargetAdded(Target *target)
diff --git a/src/plugins/projectexplorer/treescanner.cpp b/src/plugins/projectexplorer/treescanner.cpp
index 1eefc3baf4..49680621ec 100644
--- a/src/plugins/projectexplorer/treescanner.cpp
+++ b/src/plugins/projectexplorer/treescanner.cpp
@@ -43,9 +43,10 @@ bool TreeScanner::asyncScanForFiles(const Utils::FilePath &directory)
return false;
m_scanFuture = Utils::asyncRun(
- [directory, filter = m_filter, factory = m_factory] (Promise &promise) {
- TreeScanner::scanForFiles(promise, directory, filter, factory);
- });
+ [directory, filter = m_filter, dirFilter = m_dirFilter, factory = m_factory](
+ Promise &promise) {
+ TreeScanner::scanForFiles(promise, directory, filter, dirFilter, factory);
+ });
m_futureWatcher.setFuture(m_scanFuture);
return true;
@@ -57,6 +58,12 @@ void TreeScanner::setFilter(TreeScanner::FileFilter filter)
m_filter = filter;
}
+void TreeScanner::setDirFilter(QDir::Filters dirFilter)
+{
+ if (isFinished())
+ m_dirFilter = dirFilter;
+}
+
void TreeScanner::setTypeFactory(TreeScanner::FileTypeFactory factory)
{
if (isFinished())
@@ -139,24 +146,28 @@ static std::unique_ptr<FolderNode> createFolderNode(const Utils::FilePath &direc
return fileSystemNode;
}
-void TreeScanner::scanForFiles(Promise &promise, const Utils::FilePath& directory,
- const FileFilter &filter, const FileTypeFactory &factory)
+void TreeScanner::scanForFiles(
+ Promise &promise,
+ const Utils::FilePath &directory,
+ const FileFilter &filter,
+ const QDir::Filters &dirFilter,
+ const FileTypeFactory &factory)
{
- QList<FileNode *> nodes = ProjectExplorer::scanForFiles(promise, directory,
- [&filter, &factory](const Utils::FilePath &fn) -> FileNode * {
- const Utils::MimeType mimeType = Utils::mimeTypeForFile(fn);
+ QList<FileNode *> nodes = ProjectExplorer::scanForFiles(
+ promise, directory, dirFilter, [&filter, &factory](const Utils::FilePath &fn) -> FileNode * {
+ const Utils::MimeType mimeType = Utils::mimeTypesForFileName(fn.path()).value(0);
- // Skip some files during scan.
- if (filter && filter(mimeType, fn))
- return nullptr;
+ // Skip some files during scan.
+ if (filter && filter(mimeType, fn))
+ return nullptr;
- // Type detection
- FileType type = FileType::Unknown;
- if (factory)
- type = factory(mimeType, fn);
+ // Type detection
+ FileType type = FileType::Unknown;
+ if (factory)
+ type = factory(mimeType, fn);
- return new FileNode(fn, type);
- });
+ return new FileNode(fn, type);
+ });
Utils::sort(nodes, ProjectExplorer::Node::sortByPath);
diff --git a/src/plugins/projectexplorer/treescanner.h b/src/plugins/projectexplorer/treescanner.h
index f8d019121b..6de26e399a 100644
--- a/src/plugins/projectexplorer/treescanner.h
+++ b/src/plugins/projectexplorer/treescanner.h
@@ -45,6 +45,9 @@ public:
// Setup filter for ignored files
void setFilter(FileFilter filter);
+ // Setup dir filters for scanned folders
+ void setDirFilter(QDir::Filters dirFilter);
+
// Setup factory for file types
void setTypeFactory(FileTypeFactory factory);
@@ -69,11 +72,15 @@ signals:
void finished();
private:
- static void scanForFiles(Promise &fi, const Utils::FilePath &directory,
- const FileFilter &filter, const FileTypeFactory &factory);
+ static void scanForFiles(Promise &fi,
+ const Utils::FilePath &directory,
+ const FileFilter &filter,
+ const QDir::Filters &dirFilter,
+ const FileTypeFactory &factory);
private:
FileFilter m_filter;
+ QDir::Filters m_dirFilter = QDir::AllEntries | QDir::NoDotAndDotDot;
FileTypeFactory m_factory;
FutureWatcher m_futureWatcher;
diff --git a/src/plugins/projectexplorer/userfileaccessor.cpp b/src/plugins/projectexplorer/userfileaccessor.cpp
index 82e4d0edfb..d592935377 100644
--- a/src/plugins/projectexplorer/userfileaccessor.cpp
+++ b/src/plugins/projectexplorer/userfileaccessor.cpp
@@ -453,7 +453,7 @@ Store UserFileVersion14Upgrader::upgrade(const Store &map)
{
Store result;
for (auto it = map.cbegin(), end = map.cend(); it != end; ++it) {
- if (it.value().typeId() == QVariant::Map)
+ if (it.value().typeId() == QMetaType::QVariantMap)
result.insert(it.key(), variantFromStore(upgrade(storeFromVariant(it.value()))));
else if (it.key() == "AutotoolsProjectManager.AutotoolsBuildConfiguration.BuildDirectory"
|| it.key() == "CMakeProjectManager.CMakeBuildConfiguration.BuildDirectory"
@@ -709,13 +709,13 @@ Store UserFileVersion17Upgrader::upgrade(const Store &map)
QVariant UserFileVersion17Upgrader::process(const QVariant &entry)
{
switch (entry.typeId()) {
- case QVariant::List: {
+ case QMetaType::QVariantList: {
QVariantList result;
for (const QVariant &item : entry.toList())
result.append(process(item));
return result;
}
- case QVariant::Map: {
+ case QMetaType::QVariantMap: {
Store result = storeFromVariant(entry);
for (Store::iterator i = result.begin(), end = result.end(); i != end; ++i) {
QVariant &v = i.value();
@@ -737,9 +737,9 @@ Store UserFileVersion18Upgrader::upgrade(const Store &map)
QVariant UserFileVersion18Upgrader::process(const QVariant &entry)
{
switch (entry.typeId()) {
- case QVariant::List:
+ case QMetaType::QVariantList:
return Utils::transform(entry.toList(), &UserFileVersion18Upgrader::process);
- case QVariant::Map: {
+ case QMetaType::QVariantMap: {
Store map = storeFromVariant(entry);
Store result;
for (auto it = map.cbegin(), end = map.cend(); it != end; ++it) {
@@ -793,10 +793,10 @@ QVariant UserFileVersion19Upgrader::process(const QVariant &entry, const KeyList
static const KeyList dyldKeys = {"Qbs.RunConfiguration.UseDyldImageSuffix",
"QmakeProjectManager.QmakeRunConfiguration.UseDyldImageSuffix"};
switch (entry.typeId()) {
- case QVariant::List:
+ case QMetaType::QVariantList:
return Utils::transform(entry.toList(),
std::bind(&UserFileVersion19Upgrader::process, std::placeholders::_1, path));
- case QVariant::Map: {
+ case QMetaType::QVariantMap: {
Store map = storeFromVariant(entry);
Store result;
for (auto it = map.cbegin(), end = map.cend(); it != end; ++it) {
@@ -836,9 +836,9 @@ Store UserFileVersion20Upgrader::upgrade(const Store &map)
QVariant UserFileVersion20Upgrader::process(const QVariant &entry)
{
switch (entry.typeId()) {
- case QVariant::List:
+ case QMetaType::QVariantList:
return Utils::transform(entry.toList(), &UserFileVersion20Upgrader::process);
- case QVariant::Map: {
+ case QMetaType::QVariantMap: {
Store map = storeFromVariant(entry);
Store result;
for (auto it = map.cbegin(), end = map.cend(); it != end; ++it) {
@@ -865,9 +865,9 @@ Store UserFileVersion21Upgrader::upgrade(const Store &map)
QVariant UserFileVersion21Upgrader::process(const QVariant &entry)
{
switch (entry.typeId()) {
- case QVariant::List:
+ case QMetaType::QVariantList:
return Utils::transform(entry.toList(), &UserFileVersion21Upgrader::process);
- case QVariant::Map: {
+ case QMetaType::QVariantMap: {
Store entryMap = storeFromVariant(entry);
if (entryMap.value("ProjectExplorer.ProjectConfiguration.Id").toString()
== "DeployToGenericLinux") {
diff --git a/src/plugins/projectexplorer/workspaceproject.cpp b/src/plugins/projectexplorer/workspaceproject.cpp
new file mode 100644
index 0000000000..2a22c65c4b
--- /dev/null
+++ b/src/plugins/projectexplorer/workspaceproject.cpp
@@ -0,0 +1,337 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "workspaceproject.h"
+
+#include "buildsystem.h"
+#include "projectexplorer.h"
+#include "projectexplorerconstants.h"
+#include "projectexplorertr.h"
+#include "projectmanager.h"
+#include "projecttree.h"
+#include "runconfiguration.h"
+#include "runconfigurationaspects.h"
+#include "runcontrol.h"
+#include "target.h"
+#include "treescanner.h"
+
+#include <coreplugin/actionmanager/actionmanager.h>
+
+#include <utils/algorithm.h>
+#include <utils/stringutils.h>
+
+#include <QJsonArray>
+#include <QJsonDocument>
+#include <QJsonObject>
+
+const QLatin1StringView FOLDER_MIMETYPE{"inode/directory"};
+const QLatin1StringView WORKSPACE_MIMETYPE{"text/x-workspace-project"};
+const QLatin1StringView WORKSPACE_PROJECT_ID{"ProjectExplorer.WorkspaceProject"};
+const QLatin1StringView WORKSPACE_PROJECT_RUNCONFIG_ID{"WorkspaceProject.RunConfiguration:"};
+
+const QLatin1StringView PROJECT_NAME_KEY{"project.name"};
+const QLatin1StringView FILES_EXCLUDE_KEY{"files.exclude"};
+const QLatin1StringView EXCLUDE_ACTION_ID{"ProjectExplorer.ExcludeFromWorkspace"};
+
+
+using namespace Utils;
+using namespace Core;
+
+namespace ProjectExplorer {
+
+const expected_str<QJsonObject> projectDefinition(const Project *project)
+{
+ if (auto fileContents = project->projectFilePath().fileContents())
+ return QJsonDocument::fromJson(*fileContents).object();
+ return {};
+}
+
+static bool checkEnabled(FolderNode *fn)
+{
+ if (fn->findChildFileNode([](FileNode *fn) { return fn->isEnabled(); }))
+ return true;
+
+ if (fn->findChildFolderNode([](FolderNode *fn) { return checkEnabled(fn); }))
+ return true;
+
+ fn->setEnabled(false);
+ return false;
+}
+
+class WorkspaceBuildSystem : public BuildSystem
+{
+public:
+ WorkspaceBuildSystem(Target *t)
+ :BuildSystem(t)
+ {
+ connect(&m_scanner, &TreeScanner::finished, this, [this] {
+ auto root = std::make_unique<ProjectNode>(projectDirectory());
+ root->setDisplayName(target()->project()->displayName());
+ std::vector<std::unique_ptr<FileNode>> nodePtrs
+ = Utils::transform<std::vector>(m_scanner.release().allFiles, [this](FileNode *fn) {
+ fn->setEnabled(!Utils::anyOf(
+ m_filters, [path = fn->path().path()](const QRegularExpression &filter) {
+ return filter.match(path).hasMatch();
+ }));
+ return std::unique_ptr<FileNode>(fn);
+ });
+ root->addNestedNodes(std::move(nodePtrs));
+ root->forEachFolderNode(&checkEnabled);
+ setRootProjectNode(std::move(root));
+
+ m_parseGuard.markAsSuccess();
+ m_parseGuard = {};
+
+ emitBuildSystemUpdated();
+ });
+ m_scanner.setDirFilter(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::Hidden);
+
+ connect(target()->project(),
+ &Project::projectFileIsDirty,
+ this,
+ &BuildSystem::requestDelayedParse);
+
+ requestDelayedParse();
+ }
+
+ void triggerParsing() final
+ {
+ m_filters.clear();
+ FilePath projectPath = project()->projectDirectory();
+
+ const QJsonObject json = projectDefinition(project()).value_or(QJsonObject());
+ const QJsonValue projectNameValue = json.value(PROJECT_NAME_KEY);
+ if (projectNameValue.isString())
+ project()->setDisplayName(projectNameValue.toString());
+ const QJsonArray excludesJson = json.value(FILES_EXCLUDE_KEY).toArray();
+ for (const QJsonValue &excludeJson : excludesJson) {
+ if (excludeJson.isString()) {
+ FilePath absolute = projectPath.pathAppended(excludeJson.toString());
+ if (absolute.isDir())
+ absolute = absolute.pathAppended("*");
+ m_filters << QRegularExpression(
+ Utils::wildcardToRegularExpression(absolute.path()),
+ QRegularExpression::CaseInsensitiveOption);
+ }
+ }
+
+ QList<BuildTargetInfo> targetInfos;
+
+ const QJsonArray targets = json.value("targets").toArray();
+ int i = 0;
+ for (const QJsonValue &target : targets) {
+ i++;
+ QTC_ASSERT(target.isObject(), continue);
+ const QJsonObject targetObject = target.toObject();
+
+ QJsonArray args = targetObject["arguments"].toArray();
+ QStringList arguments = Utils::transform<QStringList>(args, [](const QJsonValue &arg) {
+ return arg.toString();
+ });
+ FilePath workingDirectory = FilePath::fromUserInput(
+ targetObject["workingDirectory"].toString());
+
+ if (!workingDirectory.isDir())
+ workingDirectory = FilePath::currentWorkingPath();
+
+ const QString name = targetObject["name"].toString();
+ const FilePath executable = FilePath::fromUserInput(
+ targetObject["executable"].toString());
+
+ if (name.isEmpty() || executable.isEmpty())
+ continue;
+
+ BuildTargetInfo bti;
+ bti.buildKey = name + QString::number(i);
+ bti.displayName = name;
+ bti.displayNameUniquifier = QString(" (%1)").arg(i);
+ bti.targetFilePath = executable;
+ bti.projectFilePath = projectPath;
+ bti.workingDirectory = workingDirectory;
+ bti.additionalData = QVariantMap{{"arguments", arguments}};
+
+ targetInfos << bti;
+ }
+
+ setApplicationTargets(targetInfos);
+
+ m_parseGuard = guardParsingRun();
+ m_scanner.asyncScanForFiles(target()->project()->projectDirectory());
+ }
+
+ QString name() const final { return QLatin1String("Workspace"); }
+
+private:
+ QList<QRegularExpression> m_filters;
+ ParseGuard m_parseGuard;
+ TreeScanner m_scanner;
+};
+
+class WorkspaceRunConfiguration : public RunConfiguration
+{
+public:
+ WorkspaceRunConfiguration(Target *target, Id id)
+ : RunConfiguration(target, id)
+ {
+ hint.setText(
+ Tr::tr("You can edit this configuration inside the .qtcreator/project.json file."));
+
+ const BuildTargetInfo bti = buildTargetInfo();
+ executable.setLabelText(Tr::tr("Executable:"));
+ executable.setReadOnly(true);
+ executable.setValue(bti.targetFilePath);
+ executable.setMacroExpanderProvider(
+ [this]() -> MacroExpander * { return const_cast<MacroExpander *>(macroExpander()); });
+
+ auto argumentsAsString = [this]() {
+ return CommandLine{
+ "", buildTargetInfo().additionalData.toMap()["arguments"].toStringList()}
+ .arguments();
+ };
+
+ arguments.setLabelText(Tr::tr("Arguments:"));
+ arguments.setReadOnly(true);
+ arguments.setMacroExpander(macroExpander());
+ arguments.setArguments(argumentsAsString());
+
+ workingDirectory.setLabelText(Tr::tr("Working directory:"));
+ workingDirectory.setReadOnly(true);
+ workingDirectory.setDefaultWorkingDirectory(bti.workingDirectory);
+
+ setCommandLineGetter([this] {
+ const BuildTargetInfo bti = buildTargetInfo();
+ CommandLine cmdLine{
+ macroExpander()->expand(bti.targetFilePath),
+ Utils::transform(
+ bti.additionalData.toMap()["arguments"].toStringList(),
+ [this](const QString &arg) { return macroExpander()->expand(arg); })};
+
+ return cmdLine;
+ });
+
+ setUpdater([this, argumentsAsString] {
+ const BuildTargetInfo bti = buildTargetInfo();
+ executable.setValue(bti.targetFilePath);
+ arguments.setArguments(argumentsAsString());
+ workingDirectory.setDefaultWorkingDirectory(bti.workingDirectory);
+ });
+
+ connect(target, &Target::buildSystemUpdated, this, &RunConfiguration::update);
+ }
+
+ TextDisplay hint{this};
+ FilePathAspect executable{this};
+ ArgumentsAspect arguments{this};
+ WorkingDirectoryAspect workingDirectory{this};
+};
+
+class WorkspaceProjectRunConfigurationFactory : public RunConfigurationFactory
+{
+public:
+ WorkspaceProjectRunConfigurationFactory()
+ {
+ registerRunConfiguration<WorkspaceRunConfiguration>(
+ Id::fromString(WORKSPACE_PROJECT_RUNCONFIG_ID));
+ addSupportedProjectType(Id::fromString(WORKSPACE_PROJECT_ID));
+ }
+};
+
+class WorkspaceProjectRunWorkerFactory : public RunWorkerFactory
+{
+public:
+ WorkspaceProjectRunWorkerFactory()
+ {
+ setProduct<SimpleTargetRunner>();
+ addSupportedRunMode(Constants::NORMAL_RUN_MODE);
+ addSupportedRunConfig(Id::fromString(WORKSPACE_PROJECT_RUNCONFIG_ID));
+ }
+};
+
+class WorkspaceProject : public Project
+{
+ Q_OBJECT
+public:
+ WorkspaceProject(const FilePath file)
+ : Project(FOLDER_MIMETYPE, file.isDir() ? file / ".qtcreator" / "project.json" : file)
+ {
+ QTC_CHECK(projectFilePath().absolutePath().ensureWritableDir());
+ QTC_CHECK(projectFilePath().ensureExistingFile());
+
+ setId(Id::fromString(WORKSPACE_PROJECT_ID));
+ setDisplayName(projectDirectory().fileName());
+ setBuildSystemCreator([](Target *t) { return new WorkspaceBuildSystem(t); });
+ }
+
+ FilePath projectDirectory() const override
+ {
+ return Project::projectDirectory().parentDir();
+ }
+
+ void saveProjectDefinition(const QJsonObject &json)
+ {
+ Utils::FileSaver saver(projectFilePath());
+ saver.write(QJsonDocument(json).toJson());
+ saver.finalize();
+ }
+
+ void excludePath(const FilePath &path)
+ {
+ QTC_ASSERT(projectFilePath().exists(), return);
+ if (expected_str<QJsonObject> json = projectDefinition(this)) {
+ QJsonArray excludes = (*json)[FILES_EXCLUDE_KEY].toArray();
+ const QString relative = path.relativePathFrom(projectDirectory()).path();
+ if (excludes.contains(relative))
+ return;
+ excludes << relative;
+ json->insert(FILES_EXCLUDE_KEY, excludes);
+ saveProjectDefinition(*json);
+ }
+ }
+
+ void excludeNode(Node *node)
+ {
+ node->setEnabled(false);
+ if (auto fileNode = node->asFileNode()) {
+ excludePath(fileNode->path());
+ } else if (auto folderNode = node->asFolderNode()) {
+ folderNode->forEachNode([](Node *node) { node->setEnabled(false); });
+ excludePath(folderNode->path());
+ }
+ }
+};
+
+void setupWorkspaceProject(QObject *guard)
+{
+ ProjectManager::registerProjectType<WorkspaceProject>(FOLDER_MIMETYPE);
+ ProjectManager::registerProjectType<WorkspaceProject>(WORKSPACE_MIMETYPE);
+
+ QObject::connect(
+ ProjectTree::instance(),
+ &ProjectTree::aboutToShowContextMenu,
+ ProjectExplorerPlugin::instance(),
+ [](Node *node) {
+ const bool enabled = node && node->isEnabled()
+ && qobject_cast<WorkspaceProject *>(node->getProject());
+ ActionManager::command(Id::fromString(EXCLUDE_ACTION_ID))->action()->setEnabled(enabled);
+ });
+
+ ActionBuilder excludeAction(guard, Id::fromString(EXCLUDE_ACTION_ID));
+ excludeAction.setContext(Id::fromString(WORKSPACE_PROJECT_ID));
+ excludeAction.setText(Tr::tr("Exclude from Project"));
+ excludeAction.addToContainer(Constants::M_FOLDERCONTEXT, Constants::G_FOLDER_OTHER);
+ excludeAction.addToContainer(Constants::M_FILECONTEXT, Constants::G_FILE_OTHER);
+ excludeAction.addOnTriggered([] {
+ Node *node = ProjectTree::currentNode();
+ QTC_ASSERT(node, return);
+ const auto project = qobject_cast<WorkspaceProject *>(node->getProject());
+ QTC_ASSERT(project, return);
+ project->excludeNode(node);
+ });
+
+ static WorkspaceProjectRunConfigurationFactory theRunConfigurationFactory;
+ static WorkspaceProjectRunWorkerFactory theRunWorkerFactory;
+}
+
+} // namespace ProjectExplorer
+
+#include "workspaceproject.moc"
diff --git a/src/plugins/projectexplorer/workspaceproject.h b/src/plugins/projectexplorer/workspaceproject.h
new file mode 100644
index 0000000000..3fe26a8c8e
--- /dev/null
+++ b/src/plugins/projectexplorer/workspaceproject.h
@@ -0,0 +1,16 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <qglobal.h>
+
+QT_BEGIN_NAMESPACE
+class QObject;
+QT_END_NAMESPACE
+
+namespace ProjectExplorer {
+
+void setupWorkspaceProject(QObject *guard);
+
+} // namespace ProjectExplorer
diff --git a/src/plugins/projectexplorer/xcodebuildparser.cpp b/src/plugins/projectexplorer/xcodebuildparser.cpp
index 8a2d3e2962..2037614e04 100644
--- a/src/plugins/projectexplorer/xcodebuildparser.cpp
+++ b/src/plugins/projectexplorer/xcodebuildparser.cpp
@@ -57,7 +57,8 @@ OutputLineParser::Result XcodebuildParser::handleLine(const QString &line, Outpu
absoluteFilePath(FilePath::fromString(
lne.left(filePathEndPos))));
LinkSpecs linkSpecs;
- addLinkSpecForAbsoluteFilePath(linkSpecs, task.file, task.line, 0, filePathEndPos);
+ addLinkSpecForAbsoluteFilePath(linkSpecs, task.file, task.line, task.column, 0,
+ filePathEndPos);
scheduleTask(task, 1);
return {Status::Done, linkSpecs};
}
diff --git a/src/plugins/python/Python.json.in b/src/plugins/python/Python.json.in
index 5dfb4c6d49..19c4694864 100644
--- a/src/plugins/python/Python.json.in
+++ b/src/plugins/python/Python.json.in
@@ -13,8 +13,13 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Other Languages",
- "Description" : "Plugin for supporting the Python language.",
- "Url" : "http://www.qt.io",
+ "Description" : "Develop applications using the Qt bindings for the Python programming language",
+ "LongDescription" : [
+ "You also need:",
+ "- Qt for Python",
+ "- Tools for Python development"
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES},
"Mimetypes" : [
diff --git a/src/plugins/python/pythonbuildconfiguration.cpp b/src/plugins/python/pythonbuildconfiguration.cpp
index 1255af1442..a64020083a 100644
--- a/src/plugins/python/pythonbuildconfiguration.cpp
+++ b/src/plugins/python/pythonbuildconfiguration.cpp
@@ -30,8 +30,6 @@
#include <projectexplorer/runconfiguration.h>
#include <projectexplorer/target.h>
-#include <extensionsystem/pluginmanager.h>
-
#include <utils/algorithm.h>
#include <utils/commandline.h>
#include <utils/detailswidget.h>
@@ -110,7 +108,7 @@ void PySideBuildStep::checkForPySide(const FilePath &python, const QString &pySi
});
const auto future = Pip::instance(python)->info(package);
m_watcher->setFuture(future);
- ExtensionSystem::PluginManager::futureSynchronizer()->addFuture(future);
+ Utils::futureSynchronizer()->addFuture(future);
}
void PySideBuildStep::handlePySidePackageInfo(const PipPackageInfo &pySideInfo,
diff --git a/src/plugins/python/pythonkitaspect.cpp b/src/plugins/python/pythonkitaspect.cpp
index 0be12cead0..1b0004fd78 100644
--- a/src/plugins/python/pythonkitaspect.cpp
+++ b/src/plugins/python/pythonkitaspect.cpp
@@ -73,7 +73,7 @@ public:
}
protected:
- void addToLayoutImpl(Layouting::LayoutItem &parent) override
+ void addToLayoutImpl(Layouting::Layout &parent) override
{
addMutableAction(m_comboBox);
parent.addItem(m_comboBox);
diff --git a/src/plugins/python/pythonlanguageclient.cpp b/src/plugins/python/pythonlanguageclient.cpp
index 2379ffb1a1..3c05b8f87e 100644
--- a/src/plugins/python/pythonlanguageclient.cpp
+++ b/src/plugins/python/pythonlanguageclient.cpp
@@ -151,7 +151,7 @@ PyLSClient *clientForPython(const FilePath &python)
if (auto client = pythonClients()[python])
return client;
auto interface = new PyLSInterface;
- interface->setCommandLine(CommandLine(python, {"-m", "pylsp"}));
+ interface->setCommandLine({python, {"-m", "pylsp"}});
auto client = new PyLSClient(interface);
client->setName(Tr::tr("Python Language Server (%1)").arg(python.toUserOutput()));
client->setActivateDocumentAutomatically(true);
diff --git a/src/plugins/python/pythonrunconfiguration.cpp b/src/plugins/python/pythonrunconfiguration.cpp
index d01f4bc9bb..e4ec5a9e84 100644
--- a/src/plugins/python/pythonrunconfiguration.cpp
+++ b/src/plugins/python/pythonrunconfiguration.cpp
@@ -3,14 +3,8 @@
#include "pythonrunconfiguration.h"
-#include "pyside.h"
-#include "pythonbuildconfiguration.h"
#include "pythonconstants.h"
-#include "pythoneditor.h"
-#include "pythonkitaspect.h"
-#include "pythonlanguageclient.h"
#include "pythonproject.h"
-#include "pythonsettings.h"
#include "pythontr.h"
#include <coreplugin/editormanager/editormanager.h>
@@ -18,31 +12,16 @@
#include <debugger/debuggerruncontrol.h>
-#include <extensionsystem/pluginmanager.h>
-
-#include <projectexplorer/buildsteplist.h>
#include <projectexplorer/buildsystem.h>
-#include <projectexplorer/devicesupport/idevice.h>
-#include <projectexplorer/kitaspects.h>
#include <projectexplorer/runconfiguration.h>
#include <projectexplorer/runconfigurationaspects.h>
#include <projectexplorer/runcontrol.h>
#include <projectexplorer/target.h>
#include <projectexplorer/taskhub.h>
-#include <texteditor/textdocument.h>
-
#include <utils/aspects.h>
#include <utils/fileutils.h>
-#include <utils/futuresynchronizer.h>
-#include <utils/layoutbuilder.h>
#include <utils/outputformatter.h>
-#include <utils/qtcassert.h>
-#include <utils/theme/theme.h>
-
-#include <QComboBox>
-#include <QPlainTextEdit>
-#include <QPushButton>
using namespace ProjectExplorer;
using namespace Utils;
diff --git a/src/plugins/python/pythonsettings.cpp b/src/plugins/python/pythonsettings.cpp
index cad296ae5d..bfc411c6c2 100644
--- a/src/plugins/python/pythonsettings.cpp
+++ b/src/plugins/python/pythonsettings.cpp
@@ -751,11 +751,9 @@ PythonSettings::PythonSettings()
initFromSettings(Core::ICore::settings());
const auto onRegistrySetup = [](Async<QList<Interpreter>> &task) {
- task.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
task.setConcurrentCallData(pythonsFromRegistry);
};
const auto onPathSetup = [](Async<QList<Interpreter>> &task) {
- task.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
task.setConcurrentCallData(pythonsFromPath);
};
const auto onTaskDone = [](const Async<QList<Interpreter>> &task) {
diff --git a/src/plugins/python/pythonutils.cpp b/src/plugins/python/pythonutils.cpp
index 2e4cd4ed58..87156e3b9c 100644
--- a/src/plugins/python/pythonutils.cpp
+++ b/src/plugins/python/pythonutils.cpp
@@ -121,11 +121,9 @@ void openPythonRepl(QObject *parent, const FilePath &file, ReplType type)
return file.absolutePath();
};
- const auto args = QStringList{"-i"} + replImportArgs(file, type);
const FilePath pythonCommand = detectPython(file);
-
Process process;
- process.setCommand({pythonCommand, args});
+ process.setCommand({pythonCommand, {"-i", replImportArgs(file, type)}});
process.setWorkingDirectory(workingDir(file));
process.setTerminalMode(TerminalMode::Detached);
process.start();
@@ -201,7 +199,7 @@ static bool isUsableHelper(QHash<FilePath, bool> *cache, const QString &keyStrin
if (it == cache->end()) {
const Key key = keyFromString(keyString);
Process process;
- process.setCommand({python, QStringList{"-m", commandArg, "-h"}});
+ process.setCommand({python, {"-m", commandArg, "-h"}});
process.runBlocking();
const bool usable = process.result() == ProcessResult::FinishedWithSuccess;
it = cache->insert(python, usable);
diff --git a/src/plugins/qbsprojectmanager/QbsProjectManager.json.in b/src/plugins/qbsprojectmanager/QbsProjectManager.json.in
index 263722d28d..522593366d 100644
--- a/src/plugins/qbsprojectmanager/QbsProjectManager.json.in
+++ b/src/plugins/qbsprojectmanager/QbsProjectManager.json.in
@@ -13,7 +13,10 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Build Systems",
- "Description" : "QBS support.",
- "Url" : "http://www.qt.io",
+ "Description" : "Build applications with Qbs",
+ "LongDescription" : [
+ "Generate a build graph from a high-level project description (like with qmake or CMake) and execute the commands in the low-level build graph (like with make)."
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/qbsprojectmanager/customqbspropertiesdialog.cpp b/src/plugins/qbsprojectmanager/customqbspropertiesdialog.cpp
index 78e0634bc5..ad8c5d7995 100644
--- a/src/plugins/qbsprojectmanager/customqbspropertiesdialog.cpp
+++ b/src/plugins/qbsprojectmanager/customqbspropertiesdialog.cpp
@@ -50,7 +50,7 @@ CustomQbsPropertiesDialog::CustomQbsPropertiesDialog(const QVariantMap &properti
Column {
PushButton {
text(Tr::tr("&Add")),
- onClicked([this] { addProperty(); } ),
+ onClicked([this] { addProperty(); }, nullptr),
},
m_removeButton,
st
diff --git a/src/plugins/qbsprojectmanager/qbsbuildstep.cpp b/src/plugins/qbsprojectmanager/qbsbuildstep.cpp
index 71cc48aaf8..9d0a9f642c 100644
--- a/src/plugins/qbsprojectmanager/qbsbuildstep.cpp
+++ b/src/plugins/qbsprojectmanager/qbsbuildstep.cpp
@@ -60,7 +60,7 @@ ArchitecturesAspect::ArchitecturesAspect(AspectContainer *container)
setAllValues(m_abisToArchMap.keys());
}
-void ArchitecturesAspect::addToLayout(Layouting::LayoutItem &parent)
+void ArchitecturesAspect::addToLayout(Layouting::Layout &parent)
{
MultiSelectionAspect::addToLayout(parent);
const auto changeHandler = [this] {
diff --git a/src/plugins/qbsprojectmanager/qbsbuildstep.h b/src/plugins/qbsprojectmanager/qbsbuildstep.h
index 4815a96a85..37ed218187 100644
--- a/src/plugins/qbsprojectmanager/qbsbuildstep.h
+++ b/src/plugins/qbsprojectmanager/qbsbuildstep.h
@@ -20,7 +20,7 @@ public:
ArchitecturesAspect(Utils::AspectContainer *container = nullptr);
void setKit(const ProjectExplorer::Kit *kit) { m_kit = kit; }
- void addToLayout(Layouting::LayoutItem &parent) override;
+ void addToLayout(Layouting::Layout &parent) override;
QStringList selectedArchitectures() const;
void setSelectedArchitectures(const QStringList& architectures);
bool isManagedByTarget() const { return m_isManagedByTarget; }
diff --git a/src/plugins/qbsprojectmanager/qbskitaspect.cpp b/src/plugins/qbsprojectmanager/qbskitaspect.cpp
index 647f44f02c..adc35c2f3f 100644
--- a/src/plugins/qbsprojectmanager/qbskitaspect.cpp
+++ b/src/plugins/qbsprojectmanager/qbskitaspect.cpp
@@ -34,7 +34,7 @@ private:
void makeReadOnly() override { m_changeButton->setEnabled(false); }
void refresh() override { m_contentLabel->setText(QbsKitAspect::representation(kit())); }
- void addToLayoutImpl(Layouting::LayoutItem &parent) override
+ void addToLayoutImpl(Layouting::Layout &parent) override
{
addMutableAction(m_contentLabel);
parent.addItem(m_contentLabel);
diff --git a/src/plugins/qbsprojectmanager/qbsprofilemanager.cpp b/src/plugins/qbsprojectmanager/qbsprofilemanager.cpp
index 3cfd68d5ce..01dd5efb4f 100644
--- a/src/plugins/qbsprojectmanager/qbsprofilemanager.cpp
+++ b/src/plugins/qbsprojectmanager/qbsprofilemanager.cpp
@@ -60,7 +60,7 @@ QString toJSLiteral(const QVariant &val)
{
if (!val.isValid())
return QString("undefined");
- if (val.typeId() == QVariant::List || val.typeId() == QVariant::StringList) {
+ if (val.typeId() == QMetaType::QVariantList || val.typeId() == QMetaType::QStringList) {
QString res;
const auto list = val.toList();
for (const QVariant &child : list) {
@@ -71,7 +71,7 @@ QString toJSLiteral(const QVariant &val)
res.append(']');
return res;
}
- if (val.typeId() == QVariant::Map) {
+ if (val.typeId() == QMetaType::QVariantMap) {
const QVariantMap &vm = val.toMap();
QString str("{");
for (auto it = vm.begin(); it != vm.end(); ++it) {
@@ -84,7 +84,7 @@ QString toJSLiteral(const QVariant &val)
}
if (val.typeId() == QVariant::Bool)
return toJSLiteral(val.toBool());
- if (val.canConvert(QVariant::String))
+ if (val.canConvert(QMetaType::QString))
return toJSLiteral(val.toString());
return QString::fromLatin1("Unconvertible type %1").arg(QLatin1String(val.typeName()));
}
diff --git a/src/plugins/qbsprojectmanager/qbsprofilessettingspage.cpp b/src/plugins/qbsprojectmanager/qbsprofilessettingspage.cpp
index fb048f9475..9b25c17d34 100644
--- a/src/plugins/qbsprojectmanager/qbsprofilessettingspage.cpp
+++ b/src/plugins/qbsprojectmanager/qbsprofilessettingspage.cpp
@@ -133,11 +133,11 @@ QbsProfilesSettingsWidget::QbsProfilesSettingsWidget()
Column {
PushButton {
text(Tr::tr("E&xpand All")),
- onClicked([this] { m_propertiesView->expandAll(); }),
+ onClicked([this] { m_propertiesView->expandAll(); }, nullptr),
},
PushButton {
text(Tr::tr("&Collapse All")),
- onClicked([this] { m_propertiesView->collapseAll(); }),
+ onClicked([this] { m_propertiesView->collapseAll(); }, nullptr),
},
st,
},
diff --git a/src/plugins/qbsprojectmanager/qbssession.cpp b/src/plugins/qbsprojectmanager/qbssession.cpp
index b2801acfa2..193d039605 100644
--- a/src/plugins/qbsprojectmanager/qbssession.cpp
+++ b/src/plugins/qbsprojectmanager/qbssession.cpp
@@ -135,12 +135,14 @@ public:
QbsLanguageClient *languageClient = nullptr;
PacketReader *packetReader = nullptr;
QJsonObject currentRequest;
+ QList<QJsonObject> queuedFileUpdateRequests;
QJsonObject projectData;
QEventLoop eventLoop;
QJsonObject reply;
QHash<QString, QStringList> generatedFilesForSources;
std::optional<Error> lastError;
State state = State::Inactive;
+ bool fileUpdatePossible = true;
};
QbsSession::QbsSession(QbsBuildSystem *buildSystem) : QObject(buildSystem), d(new Private)
@@ -465,6 +467,8 @@ void QbsSession::handlePacket(const QJsonObject &packet)
} else if (type == "project-resolved") {
setProjectDataFromReply(packet, true);
emit projectResolved(getErrorInfo(packet));
+ d->fileUpdatePossible = true;
+ sendNextPendingFileUpdateRequest();
} else if (type == "project-built") {
setProjectDataFromReply(packet, false);
emit projectBuilt(getErrorInfo(packet));
@@ -586,17 +590,21 @@ FileChangeResult QbsSession::updateFileList(const char *action, const QStringLis
{
if (d->state != State::Active)
return FileChangeResult(files, Tr::tr("The qbs session is not in a valid state."));
- sendRequestNow(QJsonObject{
+ const QJsonObject fileUpdateRequest{
{"type", QLatin1String(action)},
{"files", QJsonArray::fromStringList(files)},
{"product", product},
- {"group", group}
- });
+ {"group", group}};
+ if (d->fileUpdatePossible)
+ sendFileUpdateRequest(fileUpdateRequest);
+ else
+ d->queuedFileUpdateRequests << fileUpdateRequest;
return FileChangeResult(QStringList());
}
void QbsSession::handleFileListUpdated(const QJsonObject &reply)
{
+ QTC_CHECK(!d->fileUpdatePossible);
setProjectDataFromReply(reply, false);
const QStringList failedFiles = arrayToStringList(reply.value("failed-files"));
if (!failedFiles.isEmpty()) {
@@ -604,10 +612,24 @@ void QbsSession::handleFileListUpdated(const QJsonObject &reply)
Tr::tr("Failed to update files in Qbs project: %1.\n"
"The affected files are: \n\t%2")
.arg(getErrorInfo(reply).toString(), failedFiles.join("\n\t")));
+ d->fileUpdatePossible = true;
+ sendNextPendingFileUpdateRequest();
}
emit fileListUpdated();
}
+void QbsSession::sendNextPendingFileUpdateRequest()
+{
+ if (!d->queuedFileUpdateRequests.isEmpty())
+ sendFileUpdateRequest(d->queuedFileUpdateRequests.takeFirst());
+}
+
+void QbsSession::sendFileUpdateRequest(const QJsonObject &request)
+{
+ d->fileUpdatePossible = false;
+ sendRequestNow(request);
+}
+
ErrorInfoItem::ErrorInfoItem(const QJsonObject &data)
{
description = data.value("description").toString();
diff --git a/src/plugins/qbsprojectmanager/qbssession.h b/src/plugins/qbsprojectmanager/qbssession.h
index 670388a5bb..88c008dc16 100644
--- a/src/plugins/qbsprojectmanager/qbssession.h
+++ b/src/plugins/qbsprojectmanager/qbssession.h
@@ -173,6 +173,8 @@ private:
FileChangeResult updateFileList(const char *action, const QStringList &files,
const QString &product, const QString &group);
void handleFileListUpdated(const QJsonObject &reply);
+ void sendNextPendingFileUpdateRequest();
+ void sendFileUpdateRequest(const QJsonObject &request);
class Private;
Private * const d;
diff --git a/src/plugins/qmakeprojectmanager/QmakeProjectManager.json.in b/src/plugins/qmakeprojectmanager/QmakeProjectManager.json.in
index a080d69dec..8b2cc0cb38 100644
--- a/src/plugins/qmakeprojectmanager/QmakeProjectManager.json.in
+++ b/src/plugins/qmakeprojectmanager/QmakeProjectManager.json.in
@@ -13,8 +13,11 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Build Systems",
- "Description" : "Provides project type for Qt/QMake .pro files and tools.",
- "Url" : "http://www.qt.io",
+ "Description" : "Build applications with qmake",
+ "LongDescription" : [
+ "Use .pro project configuration files and tools that automate the generation of Makefiles."
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES},
"Mimetypes" : [
diff --git a/src/plugins/qmakeprojectmanager/customwidgetwizard/customwidgetwizarddialog.cpp b/src/plugins/qmakeprojectmanager/customwidgetwizard/customwidgetwizarddialog.cpp
index d8d742267c..49ff339f45 100644
--- a/src/plugins/qmakeprojectmanager/customwidgetwizard/customwidgetwizarddialog.cpp
+++ b/src/plugins/qmakeprojectmanager/customwidgetwizard/customwidgetwizarddialog.cpp
@@ -28,8 +28,8 @@ CustomWidgetWizardDialog::CustomWidgetWizardDialog(const Core::BaseFileWizardFac
setWindowIcon(icon);
setWindowTitle(templateName);
- setIntroDescription(Tr::tr("This wizard generates a Qt Designer Custom Widget "
- "or a Qt Designer Custom Widget Collection project."));
+ setIntroDescription(Tr::tr("This wizard generates a Qt Widgets Designer Custom Widget "
+ "or a Qt Widgets Designer Custom Widget Collection project."));
if (!parameters.extraValues().contains(QLatin1String(ProjectExplorer::Constants::PROJECT_KIT_IDS)))
addTargetSetupPage();
diff --git a/src/plugins/qmakeprojectmanager/qmakekitaspect.cpp b/src/plugins/qmakeprojectmanager/qmakekitaspect.cpp
index d8e1e41648..c29c8da43b 100644
--- a/src/plugins/qmakeprojectmanager/qmakekitaspect.cpp
+++ b/src/plugins/qmakeprojectmanager/qmakekitaspect.cpp
@@ -39,7 +39,7 @@ public:
~QmakeKitAspectImpl() override { delete m_lineEdit; }
private:
- void addToLayoutImpl(Layouting::LayoutItem &parent) override
+ void addToLayoutImpl(Layouting::Layout &parent) override
{
addMutableAction(m_lineEdit);
parent.addItem(m_lineEdit);
diff --git a/src/plugins/qmakeprojectmanager/qmakeparser.cpp b/src/plugins/qmakeprojectmanager/qmakeparser.cpp
index 57051ba5a2..0b0bdc5af0 100644
--- a/src/plugins/qmakeprojectmanager/qmakeparser.cpp
+++ b/src/plugins/qmakeprojectmanager/qmakeparser.cpp
@@ -46,7 +46,7 @@ OutputLineParser::Result QMakeParser::handleLine(const QString &line, OutputForm
BuildSystemTask t(type, description, absoluteFilePath(FilePath::fromUserInput(fileName)),
match.captured(2).toInt() /* line */);
LinkSpecs linkSpecs;
- addLinkSpecForAbsoluteFilePath(linkSpecs, t.file, t.line, fileNameOffset,
+ addLinkSpecForAbsoluteFilePath(linkSpecs, t.file, t.line, t.column, fileNameOffset,
fileName.length());
scheduleTask(t, 1);
return {Status::Done, linkSpecs};
diff --git a/src/plugins/qmakeprojectmanager/qmakestep.cpp b/src/plugins/qmakeprojectmanager/qmakestep.cpp
index e012499b54..65b79a7238 100644
--- a/src/plugins/qmakeprojectmanager/qmakestep.cpp
+++ b/src/plugins/qmakeprojectmanager/qmakestep.cpp
@@ -188,7 +188,7 @@ bool QMakeStep::init()
else
workingDirectory = qmakeBc->buildDirectory();
- m_qmakeCommand = CommandLine{qtVersion->qmakeFilePath(), allArguments(qtVersion), CommandLine::Raw};
+ m_qmakeCommand = {qtVersion->qmakeFilePath(), allArguments(qtVersion), CommandLine::Raw};
m_runMakeQmake = (qtVersion->qtVersion() >= QVersionNumber(5, 0 ,0));
// The Makefile is used by qmake and make on the build device, from that
@@ -216,7 +216,7 @@ bool QMakeStep::init()
OutputFormat::ErrorMessage);
return false;
}
- m_makeCommand = CommandLine{make, makeArguments(makeFile.path()), CommandLine::Raw};
+ m_makeCommand = {make, makeArguments(makeFile.path()), CommandLine::Raw};
} else {
m_makeCommand = {};
}
@@ -428,7 +428,7 @@ QWidget *QMakeStep::createConfigWidget()
builder.addRow({userArguments});
builder.addRow({effectiveCall});
builder.addRow({abisLabel, abisListWidget});
- builder.addItem(Layouting::noMargin);
+ builder.setNoMargins();
auto widget = builder.emerge();
qmakeBuildConfigChanged();
diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt
index 0b0075915f..c29017523d 100644
--- a/src/plugins/qmldesigner/CMakeLists.txt
+++ b/src/plugins/qmldesigner/CMakeLists.txt
@@ -27,6 +27,10 @@ env_with_default("QTC_ENABLE_MODEL_TRACING" ENV_QTC_ENABLE_MODEL_TRACING OFF)
option(ENABLE_MODEL_TRACING "Enable model tracing" ${ENV_QTC_ENABLE_MODEL_TRACING})
add_feature_info("Model tracing" ${ENABLE_MODEL_TRACING} "")
+env_with_default("QTC_ENABLE_METAINFO_TRACING" ENV_QTC_ENABLE_METAINFO_TRACING OFF)
+option(ENABLE_METAINFO_TRACING "Enable meta info tracing" ${ENV_QTC_ENABLE_METAINFO_TRACING})
+add_feature_info("Meta info tracing" ${ENABLE_METAINFO_TRACING} "")
+
add_qtc_library(QmlDesignerUtils STATIC
DEPENDS
Qt::Gui Utils Qt::QmlPrivate
@@ -93,14 +97,13 @@ add_qtc_library(QmlDesignerCore STATIC
${CMAKE_CURRENT_LIST_DIR}/designercore/include
SOURCES_PREFIX ${CMAKE_CURRENT_LIST_DIR}/designercore
SOURCES
- rewritertransaction.cpp
- rewritertransaction.h
- generatedcomponentutils.cpp
- generatedcomponentutils.h
+ rewritertransaction.cpp rewritertransaction.h
+ generatedcomponentutils.cpp generatedcomponentutils.h
+ uniquename.cpp uniquename.h
)
extend_qtc_library(QmlDesignerCore
- CONDITION ENABLE_PROJECT_STORAGE_TRACING OR ENABLE_IMAGE_CACHE_TRACING OR ENABLE_MODEL_TRACING
+ CONDITION ENABLE_PROJECT_STORAGE_TRACING OR ENABLE_IMAGE_CACHE_TRACING OR ENABLE_MODEL_TRACING OR ENABLE_METAINFO_TRACING
PUBLIC_DEPENDS Nanotrace
PUBLIC_DEFINES
ENABLE_QMLDESIGNER_TRACING
@@ -108,6 +111,7 @@ extend_qtc_library(QmlDesignerCore
$<$<BOOL:${ENABLE_PROJECT_STORAGE_TRACING}>:ENABLE_PROJECT_STORAGE_TRACING>
$<$<BOOL:${ENABLE_IMAGE_CACHE_TRACING}>:ENABLE_IMAGE_CACHE_TRACING>
$<$<BOOL:${ENABLE_MODEL_TRACING}>:ENABLE_MODEL_TRACING>
+ $<$<BOOL:${ENABLE_METAINFO_TRACING}>:ENABLE_METAINFO_TRACING>
)
extend_qtc_library(QmlDesignerCore
@@ -463,6 +467,8 @@ extend_qtc_library(QmlDesignerCore
projectstoragetypes.h
projectstorageupdater.cpp projectstorageupdater.h
projectstorage.cpp projectstorage.h
+ projectstorageerrornotifierinterface.h
+ projectstorageerrornotifier.cpp projectstorageerrornotifier.h
sourcepath.h
sourcepathcache.h
sourcepathcacheinterface.h
@@ -498,7 +504,6 @@ add_qtc_plugin(QmlDesigner
INCLUDES
${CMAKE_CURRENT_LIST_DIR}/components
${CMAKE_CURRENT_LIST_DIR}/components/assetslibrary
- ${CMAKE_CURRENT_LIST_DIR}/components/collectioneditor
${CMAKE_CURRENT_LIST_DIR}/components/debugview
${CMAKE_CURRENT_LIST_DIR}/components/edit3d
${CMAKE_CURRENT_LIST_DIR}/components/formeditor
@@ -739,6 +744,8 @@ extend_qtc_plugin(QmlDesigner
assetimportupdatetreeitemdelegate.cpp assetimportupdatetreeitemdelegate.h
assetimportupdatetreemodel.cpp assetimportupdatetreemodel.h
assetimportupdatetreeview.cpp assetimportupdatetreeview.h
+ import3dcanvas.cpp import3dcanvas.h
+ import3dconnectionmanager.cpp import3dconnectionmanager.h
itemlibrary.qrc
itemlibraryconstants.h
itemlibraryimageprovider.cpp itemlibraryimageprovider.h
@@ -826,7 +833,7 @@ extend_qtc_plugin(QmlDesigner
contentlibrarymaterialscategory.cpp contentlibrarymaterialscategory.h
contentlibrarymaterial.cpp contentlibrarymaterial.h
contentlibraryiconprovider.cpp contentlibraryiconprovider.h
- contentlibraryeffect.cpp contentlibraryeffect.h
+ contentlibraryitem.cpp contentlibraryitem.h
contentlibraryeffectscategory.cpp contentlibraryeffectscategory.h
contentlibraryeffectsmodel.cpp contentlibraryeffectsmodel.h
contentlibraryusermodel.cpp contentlibraryusermodel.h
@@ -844,21 +851,6 @@ extend_qtc_plugin(QmlDesigner
)
extend_qtc_plugin(QmlDesigner
- SOURCES_PREFIX components/collectioneditor
- SOURCES
- collectiondatatypemodel.cpp collectiondatatypemodel.h
- collectiondetails.cpp collectiondetails.h
- collectiondetailsmodel.cpp collectiondetailsmodel.h
- collectiondetailssortfiltermodel.cpp collectiondetailssortfiltermodel.h
- collectioneditorconstants.h
- collectioneditorutils.cpp collectioneditorutils.h
- collectionlistmodel.cpp collectionlistmodel.h
- collectionview.cpp collectionview.h
- collectionwidget.cpp collectionwidget.h
- datastoremodelnode.cpp datastoremodelnode.h
-)
-
-extend_qtc_plugin(QmlDesigner
SOURCES_PREFIX components/textureeditor
SOURCES
textureeditorcontextobject.cpp textureeditorcontextobject.h
diff --git a/src/plugins/qmldesigner/QmlDesigner.json.in b/src/plugins/qmldesigner/QmlDesigner.json.in
index 4b5682f485..e96f2f8513 100644
--- a/src/plugins/qmldesigner/QmlDesigner.json.in
+++ b/src/plugins/qmldesigner/QmlDesigner.json.in
@@ -15,7 +15,7 @@
"Category" : "Qt Quick",
"Description" : "Visual Designer for QML files.",
"Deprecated" : true,
- "Url" : "http://www.qt.io",
+ "Url" : "https://www.qt.io",
"Arguments" : [
{
"Name" : "-capture-puppet-stream",
diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.cpp b/src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.cpp
index a4509c38a6..cee25240cf 100644
--- a/src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.cpp
+++ b/src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.cpp
@@ -143,7 +143,7 @@ AssetExportDialog::AssetExportDialog(const FilePath &exportPath,
m_exportAssetsCheck,
m_perComponentExportCheck,
st,
- noMargin(),
+ noMargin,
}.attachTo(optionsWidget);
Column {
diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp
index 9d09f52d8f..7f488bd615 100644
--- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp
+++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp
@@ -3,12 +3,15 @@
#include "assetslibrarymodel.h"
-#include <asset.h>
#include <modelnodeoperations.h>
#include <qmldesignerplugin.h>
+#include <uniquename.h>
#include <coreplugin/icore.h>
+
#include <utils/algorithm.h>
+#include <utils/asset.h>
+#include <utils/filepath.h>
#include <utils/filesystemwatcher.h>
#include <QFileInfo>
@@ -151,16 +154,15 @@ bool AssetsLibraryModel::renameFolder(const QString &folderPath, const QString &
QString AssetsLibraryModel::addNewFolder(const QString &folderPath)
{
- QString iterPath = folderPath;
- QDir dir{folderPath};
+ Utils::FilePath uniqueDirPath = Utils::FilePath::fromString(UniqueName::generatePath(folderPath));
- while (dir.exists()) {
- iterPath = getUniqueName(iterPath);
-
- dir.setPath(iterPath);
+ auto res = uniqueDirPath.ensureWritableDir();
+ if (!res.has_value()) {
+ qWarning() << __FUNCTION__ << res.error();
+ return {};
}
- return dir.mkpath(iterPath) ? iterPath : "";
+ return uniqueDirPath.path();
}
bool AssetsLibraryModel::urlPathExistsInModel(const QUrl &url) const
@@ -242,36 +244,6 @@ void AssetsLibraryModel::syncHasFiles()
setHasFiles(checkHasFiles());
}
-QString AssetsLibraryModel::getUniqueName(const QString &oldName) {
- static QRegularExpression rgx("\\d+$"); // matches a number at the end of a string
-
- QString uniqueName = oldName;
- // if the folder name ends with a number, increment it
- QRegularExpressionMatch match = rgx.match(uniqueName);
- if (match.hasMatch()) { // ends with a number
- QString numStr = match.captured(0);
- int num = match.captured(0).toInt();
-
- // get number of padding zeros, ex: for "005" = 2
- int nPaddingZeros = 0;
- for (; nPaddingZeros < numStr.size() && numStr[nPaddingZeros] == '0'; ++nPaddingZeros);
-
- ++num;
-
- // if the incremented number's digits increased, decrease the padding zeros
- if (std::fmod(std::log10(num), 1.0) == 0)
- --nPaddingZeros;
-
- uniqueName = oldName.mid(0, match.capturedStart())
- + QString('0').repeated(nPaddingZeros)
- + QString::number(num);
- } else {
- uniqueName = oldName + '1';
- }
-
- return uniqueName;
-}
-
void AssetsLibraryModel::setRootPath(const QString &newPath)
{
beginResetModel();
diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h
index 2516be787f..f08578651a 100644
--- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h
+++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h
@@ -58,8 +58,6 @@ public:
bool hasFiles() const { return m_hasFiles; }
- QString getUniqueName(const QString &oldName);
-
signals:
void directoryLoaded(const QString &path);
void rootPathChanged();
diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp
index 4b270c8902..5e2211ce0d 100644
--- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp
+++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp
@@ -7,7 +7,6 @@
#include "assetslibrarymodel.h"
#include "assetslibraryview.h"
-#include <asset.h>
#include <designeractionmanager.h>
#include <designerpaths.h>
#include <hdrimage.h>
@@ -18,6 +17,7 @@
#include <qmldesignerplugin.h>
#include <studioquickwidget.h>
#include <theme.h>
+#include <uniquename.h>
#include <utils3d.h>
#include <coreplugin/fileutils.h>
@@ -25,6 +25,7 @@
#include <coreplugin/messagebox.h>
#include <utils/algorithm.h>
+#include <utils/asset.h>
#include <utils/environment.h>
#include <utils/filepath.h>
#include <utils/qtcassert.h>
@@ -94,7 +95,7 @@ AssetsLibraryWidget::AssetsLibraryWidget(AsynchronousImageCache &asynchronousFon
, m_assetsModel{new AssetsLibraryModel(this)}
, m_assetsView{view}
, m_createTextures{view}
- , m_assetsWidget{new StudioQuickWidget(this)}
+ , m_assetsWidget{Utils::makeUniqueObjectPtr<StudioQuickWidget>(this)}
{
setWindowTitle(tr("Assets Library", "Title of assets library widget"));
setMinimumWidth(250);
@@ -130,7 +131,7 @@ AssetsLibraryWidget::AssetsLibraryWidget(AsynchronousImageCache &asynchronousFon
auto layout = new QVBoxLayout(this);
layout->setContentsMargins({});
layout->setSpacing(0);
- layout->addWidget(m_assetsWidget.data());
+ layout->addWidget(m_assetsWidget.get());
updateSearch();
@@ -174,23 +175,10 @@ void AssetsLibraryWidget::deleteSelectedAssets()
QString AssetsLibraryWidget::getUniqueEffectPath(const QString &parentFolder, const QString &effectName)
{
- auto genEffectPath = [&parentFolder](const QString &name) {
- QString effectsDir = ModelNodeOperations::getEffectsDefaultDirectory(parentFolder);
- return QLatin1String("%1/%2.qep").arg(effectsDir, name);
- };
+ QString effectsDir = ModelNodeOperations::getEffectsDefaultDirectory(parentFolder);
+ QString effectPath = QLatin1String("%1/%2.qep").arg(effectsDir, effectName);
- QString uniqueName = effectName;
- QString path = genEffectPath(uniqueName);
- QFileInfo file{path};
-
- while (file.exists()) {
- uniqueName = m_assetsModel->getUniqueName(uniqueName);
-
- path = genEffectPath(uniqueName);
- file.setFile(path);
- }
-
- return path;
+ return UniqueName::generatePath(effectPath);
}
bool AssetsLibraryWidget::createNewEffect(const QString &effectPath, bool openInEffectComposer)
@@ -647,12 +635,6 @@ void AssetsLibraryWidget::addResources(const QStringList &files, bool showDialog
}
}
-bool AssetsLibraryWidget::userBundleEnabled() const
-{
- // TODO: this method is to be removed after user bundle implementation is complete
- return Core::ICore::settings()->value("QML/Designer/UseExperimentalFeatures45", false).toBool();
-}
-
void AssetsLibraryWidget::addAssetsToContentLibrary(const QStringList &assetPaths)
{
m_assetsView->emitCustomNotification("add_assets_to_content_lib", {}, {assetPaths});
diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h
index 8b59ae0785..f2d476c842 100644
--- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h
+++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h
@@ -8,6 +8,8 @@
#include <coreplugin/icontext.h>
+#include <utils/uniqueobjectptr.h>
+
#include <QFrame>
#include <QQmlPropertyMap>
#include <QQuickWidget>
@@ -98,7 +100,6 @@ public:
Q_INVOKABLE void showInGraphicalShell(const QString &path);
Q_INVOKABLE QString showInGraphicalShellMsg() const;
- Q_INVOKABLE bool userBundleEnabled() const;
Q_INVOKABLE void addAssetsToContentLibrary(const QStringList &assetPaths);
signals:
@@ -137,7 +138,7 @@ private:
AssetsLibraryView *m_assetsView = nullptr;
CreateTextures m_createTextures = nullptr;
- QScopedPointer<StudioQuickWidget> m_assetsWidget;
+ Utils::UniqueObjectPtr<StudioQuickWidget> m_assetsWidget;
std::unique_ptr<PreviewTooltipBackend> m_fontPreviewTooltipBackend;
QShortcut *m_qmlSourceUpdateShortcut = nullptr;
diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectiondatatypemodel.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectiondatatypemodel.cpp
deleted file mode 100644
index 9a534ec88e..0000000000
--- a/src/plugins/qmldesigner/components/collectioneditor/collectiondatatypemodel.cpp
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright (C) 2024 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#include "collectiondatatypemodel.h"
-
-#include <QHash>
-#include <QtQml/QmlTypeAndRevisionsRegistration>
-
-namespace QmlDesigner {
-
-struct CollectionDataTypeModel::Details
-{
- CollectionDetails::DataType type;
- QString name;
- QString description;
-};
-
-const QList<CollectionDataTypeModel::Details> CollectionDataTypeModel::m_orderedDetails{
- {DataType::String, "String", "Text"},
- {DataType::Integer, "Integer", "Whole number that can be positive, negative, or zero"},
- {DataType::Real, "Real", "Number with a decimal"},
- {DataType::Image, "Image", "Image resource"},
- {DataType::Color, "Color", "HEX value"},
- {DataType::Url, "Url", "Resource locator"},
- {DataType::Boolean, "Boolean", "True/false"},
-};
-
-CollectionDataTypeModel::CollectionDataTypeModel(QObject *parent)
- : QAbstractListModel(parent)
-{
-}
-
-int CollectionDataTypeModel::rowCount([[maybe_unused]] const QModelIndex &parent) const
-{
- return m_orderedDetails.size();
-}
-
-QVariant CollectionDataTypeModel::data(const QModelIndex &index, int role) const
-{
- if (!index.isValid())
- return {};
-
- if (role == Qt::DisplayRole)
- return m_orderedDetails.at(index.row()).name;
- if (role == Qt::ToolTipRole)
- return m_orderedDetails.at(index.row()).description;
-
- return {};
-}
-
-QString CollectionDataTypeModel::dataTypeToString(DataType dataType)
-{
- static const QHash<DataType, QString> dataTypeHash = []() -> QHash<DataType, QString> {
- QHash<DataType, QString> result;
- for (const Details &details : m_orderedDetails)
- result.insert(details.type, details.name);
- return result;
- }();
-
- if (dataTypeHash.contains(dataType))
- return dataTypeHash.value(dataType);
-
- return "Unknown";
-}
-
-CollectionDetails::DataType CollectionDataTypeModel::dataTypeFromString(const QString &dataType)
-{
- static const QHash<QString, DataType> stringTypeHash = []() -> QHash<QString, DataType> {
- QHash<QString, DataType> result;
- for (const Details &details : m_orderedDetails)
- result.insert(details.name, details.type);
- return result;
- }();
-
- if (stringTypeHash.contains(dataType))
- return stringTypeHash.value(dataType);
-
- return DataType::String;
-}
-
-void CollectionDataTypeModel::registerDeclarativeType()
-{
- qmlRegisterType<CollectionDataTypeModel>("CollectionDetails", 1, 0, "CollectionDataTypeModel");
-}
-
-} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectiondatatypemodel.h b/src/plugins/qmldesigner/components/collectioneditor/collectiondatatypemodel.h
deleted file mode 100644
index 1f91aecbff..0000000000
--- a/src/plugins/qmldesigner/components/collectioneditor/collectiondatatypemodel.h
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright (C) 2024 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#pragma once
-
-#include "collectiondetails.h"
-
-#include <QAbstractListModel>
-#include <QList>
-
-namespace QmlDesigner {
-
-class CollectionDataTypeModel : public QAbstractListModel
-{
- Q_OBJECT
-
-public:
- using DataType = CollectionDetails::DataType;
- CollectionDataTypeModel(QObject *parent = nullptr);
-
- int rowCount(const QModelIndex &parent = QModelIndex()) const override;
- QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
-
- static Q_INVOKABLE QString dataTypeToString(DataType dataType);
- static Q_INVOKABLE DataType dataTypeFromString(const QString &dataType);
-
- static void registerDeclarativeType();
-
-private:
- struct Details;
- static const QList<Details> m_orderedDetails;
-};
-
-} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectiondetails.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectiondetails.cpp
deleted file mode 100644
index a391d9bb1f..0000000000
--- a/src/plugins/qmldesigner/components/collectioneditor/collectiondetails.cpp
+++ /dev/null
@@ -1,965 +0,0 @@
-// Copyright (C) 2023 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#include "collectiondetails.h"
-
-#include "collectiondatatypemodel.h"
-#include "collectioneditorutils.h"
-
-#include <utils/span.h>
-#include <qmljs/parser/qmljsast_p.h>
-#include <qmljs/parser/qmljsastvisitor_p.h>
-#include <qmljs/qmljsdocument.h>
-#include <qqml.h>
-
-#include <QJsonArray>
-#include <QJsonDocument>
-#include <QJsonObject>
-#include <QRegularExpression>
-#include <QTextStream>
-#include <QUrl>
-#include <QVariant>
-
-namespace QmlDesigner {
-#define COLLERR_OK QT_TRANSLATE_NOOP("CollectioParseError", "no error occurred")
-#define COLLERR_MAINOBJECT QT_TRANSLATE_NOOP("CollectioParseError", "Document object not found")
-#define COLLERR_COLLECTIONNAME QT_TRANSLATE_NOOP("CollectioParseError", "Model name not found")
-#define COLLERR_COLLECTIONOBJ QT_TRANSLATE_NOOP("CollectioParseError", "Model is not an object")
-#define COLLERR_COLUMNARRAY QT_TRANSLATE_NOOP("CollectioParseError", "Column is not an array")
-#define COLLERR_UNKNOWN QT_TRANSLATE_NOOP("CollectioParseError", "Unknown error")
-
-struct CollectionProperty
-{
- using DataType = CollectionDetails::DataType;
-
- QString name;
- DataType type;
-};
-
-const QMap<DataTypeWarning::Warning, QString> DataTypeWarning::dataTypeWarnings = {
- {DataTypeWarning::CellDataTypeMismatch, "Cell and column data types do not match."}
-};
-
-class CollectionDetails::Private
-{
-public:
- QList<CollectionProperty> properties;
- QList<QJsonArray> dataRecords;
- CollectionReference reference;
- bool isChanged = false;
-
- bool isValidColumnId(int column) const { return column > -1 && column < properties.size(); }
-
- bool isValidRowId(int row) const { return row > -1 && row < dataRecords.size(); }
-};
-
-inline static bool isValidColorName(const QString &colorName)
-{
- return QColor::isValidColorName(colorName);
-}
-
-/**
- * @brief getCustomUrl
- * Address = <Url|LocalFile>
- *
- * @param value The input value to be evaluated
- * @param dataType if the value is a valid url, the data type
- * will be stored to this parameter, otherwise, it will be String
- * @param urlResult if the value is a valid url, the address
- * will be stored in this parameter, otherwise it will be empty.
- * @return true if the result is url
- */
-static bool getCustomUrl(const QString &value,
- CollectionDetails::DataType &dataType,
- QUrl *urlResult = nullptr)
-{
- static const QRegularExpression urlRegex{
- "^(?<Address>"
- "(?<Url>https?:\\/\\/"
- "(?:www\\.|(?!www))[A-z0-9][A-z0-9-]+[A-z0-9]\\.[^\\s]{2,}|www\\.[A-z0-9][A-z0-9-]+"
- "[A-z0-9]\\.[^\\s]{2,}|https?:\\/\\/"
- "(?:www\\.|(?!www))[A-z0-9]+\\.[^\\s]{2,}|www\\.[A-z0-9]+\\.[^\\s]{2,})|" // end of Url
- "(?<LocalFile>("
- "?:(?:[A-z]:)|(?:(?:\\\\|\\/){1,2}\\w+)\\$?)(?:(?:\\\\|\\/)(?:\\w[\\w ]*.*))+)" // end of LocalFile
- "){1}$" // end of Address
- };
-
- const QRegularExpressionMatch match = urlRegex.match(value.trimmed());
- if (match.hasCaptured("Address")) {
- dataType = CollectionDetails::DataType::Url;
-
- if (urlResult)
- urlResult->setUrl(match.captured("Address"));
-
- return true;
- }
-
- if (urlResult)
- urlResult->clear();
-
- dataType = CollectionDetails::DataType::String;
- return false;
-}
-
-/**
- * @brief dataTypeFromString
- * @param value The string value to be evaluated
- * @return Bool, Color, Integer, Real, Url,
- * Image if these types are detected within the non-empty string,
- * Otherwise it returns String.
- * If the value is integer, but it's out of the int range, it will be
- * considered as a Real.
- */
-static CollectionDetails::DataType dataTypeFromString(const QString &value)
-{
- using DataType = CollectionDetails::DataType;
- static const QRegularExpression validator{
- "(?<boolean>^(?:true|false)$)|"
- "(?<color>^(?:#(?:(?:[0-9a-fA-F]{2}){3,4}|(?:[0-9a-fA-F]){3,4}))$)|"
- "(?<integer>^\\d+$)|"
- "(?<real>^(?:-?(?:0|[1-9]\\d*)?(?:\\.\\d*)?(?<=\\d|\\.)"
- "(?:e-?(?:0|[1-9]\\d*))?|0x[0-9a-f]+)$)"};
- static const int boolIndex = validator.namedCaptureGroups().indexOf("boolean");
- static const int colorIndex = validator.namedCaptureGroups().indexOf("color");
- static const int integerIndex = validator.namedCaptureGroups().indexOf("integer");
- static const int realIndex = validator.namedCaptureGroups().indexOf("real");
-
- [[maybe_unused]] static const bool allIndexesFound =
- [](const std::initializer_list<int> &captureIndexes) {
- QTC_ASSERT(Utils::allOf(captureIndexes, [](int val) { return val > -1; }), return false);
- return true;
- }({boolIndex, colorIndex, integerIndex, realIndex});
-
- if (value.isEmpty())
- return DataType::String;
-
- const QString trimmedValue = value.trimmed();
- QRegularExpressionMatch match = validator.match(trimmedValue);
-
- if (match.hasCaptured(boolIndex))
- return DataType::Boolean;
- if (match.hasCaptured(colorIndex))
- return DataType::Color;
- if (match.hasCaptured(integerIndex)) {
- bool isInt = false;
- trimmedValue.toInt(&isInt);
- return isInt ? DataType::Integer : DataType::Real;
- }
- if (match.hasCaptured(realIndex))
- return DataType::Real;
-
- DataType urlType;
- if (getCustomUrl(trimmedValue, urlType))
- return urlType;
-
- return DataType::String;
-}
-
-static CollectionProperty::DataType dataTypeFromJsonValue(const QJsonValue &value)
-{
- using DataType = CollectionDetails::DataType;
- using JsonType = QJsonValue::Type;
-
- switch (value.type()) {
- case JsonType::Null:
- case JsonType::Undefined:
- return DataType::String;
- case JsonType::Bool:
- return DataType::Boolean;
- case JsonType::Double: {
- if (qFuzzyIsNull(std::remainder(value.toDouble(), 1)))
- return DataType::Integer;
- return DataType::Real;
- }
- case JsonType::String:
- return dataTypeFromString(value.toString());
- default:
- return DataType::String;
- }
-}
-
-static QList<CollectionProperty> getColumnsFromImportedJsonArray(const QJsonArray &importedArray)
-{
- using DataType = CollectionDetails::DataType;
-
- QHash<QString, int> resultSet;
- QList<CollectionProperty> result;
-
- for (const QJsonValue &value : importedArray) {
- if (value.isObject()) {
- const QJsonObject object = value.toObject();
- QJsonObject::ConstIterator element = object.constBegin();
- const QJsonObject::ConstIterator stopItem = object.constEnd();
-
- while (element != stopItem) {
- const QString propertyName = element.key();
- if (resultSet.contains(propertyName)) {
- CollectionProperty &property = result[resultSet.value(propertyName)];
- if (property.type == DataType::Integer) {
- const DataType currentCellDataType = dataTypeFromJsonValue(element.value());
- if (currentCellDataType == DataType::Real)
- property.type = currentCellDataType;
- }
- } else {
- result.append({propertyName, dataTypeFromJsonValue(element.value())});
- resultSet.insert(propertyName, resultSet.size());
- }
- ++element;
- }
- }
- }
-
- return result;
-}
-
-static QVariant valueToVariant(const QJsonValue &value, CollectionDetails::DataType type)
-{
- using DataType = CollectionDetails::DataType;
- QVariant variantValue = value.toVariant();
-
- switch (type) {
- case DataType::String:
- return variantValue.toString();
- case DataType::Integer:
- return variantValue.toInt();
- case DataType::Real:
- return variantValue.toDouble();
- case DataType::Boolean:
- return variantValue.toBool();
- case DataType::Color:
- return variantValue.value<QColor>();
- case DataType::Url:
- case DataType::Image:
- return variantValue.value<QUrl>();
- default:
- return variantValue;
- }
-}
-
-static QJsonValue variantToJsonValue(
- const QVariant &variant, CollectionDetails::DataType type = CollectionDetails::DataType::String)
-{
- using DataType = CollectionDetails::DataType;
-
- switch (type) {
- case DataType::Boolean:
- return variant.toBool();
- case DataType::Real:
- return variant.toDouble();
- case DataType::Integer:
- return variant.toInt();
- case DataType::Image:
- case DataType::String:
- case DataType::Color:
- case DataType::Url:
- default:
- return variant.toString();
- }
-}
-
-inline static bool isEmptyJsonValue(const QJsonValue &value)
-{
- return value.isNull() || value.isUndefined() || (value.isString() && value.toString().isEmpty());
-}
-
-QStringList csvReadLine(const QString &line)
-{
- static const QRegularExpression lineRegex{
- "(?:,\\\"|^\\\")(?<value>\\\"\\\"|[\\w\\W]*?)(?=\\\",|\\\"$)"
- "|(?:,(?!\\\")|^(?!\\\"))(?<quote>[^,]*?)(?=$|,)|(\\\\r\\\\n|\\\\n)"};
- static const int valueIndex = lineRegex.namedCaptureGroups().indexOf("value");
- static const int quoteIndex = lineRegex.namedCaptureGroups().indexOf("quote");
- Q_ASSERT(valueIndex > 0 && quoteIndex > 0);
-
- QStringList result;
- QRegularExpressionMatchIterator iterator = lineRegex.globalMatch(line, 0);
- while (iterator.hasNext()) {
- const QRegularExpressionMatch match = iterator.next();
-
- if (match.hasCaptured(valueIndex))
- result.append(match.captured(valueIndex));
- else if (match.hasCaptured(quoteIndex))
- result.append(match.captured(quoteIndex));
- }
- return result;
-}
-
-class PropertyOrderFinder : public QmlJS::AST::Visitor
-{
-public:
- static QStringList parse(const QString &jsonContent)
- {
- PropertyOrderFinder finder;
- QmlJS::Document::MutablePtr jsonDoc = QmlJS::Document::create(Utils::FilePath::fromString(
- "<expression>"),
- QmlJS::Dialect::Json);
-
- jsonDoc->setSource(jsonContent);
- jsonDoc->parseJavaScript();
-
- if (!jsonDoc->isParsedCorrectly())
- return {};
-
- jsonDoc->ast()->accept(&finder);
- return finder.m_orderedList;
- }
-
-protected:
- bool visit(QmlJS::AST::PatternProperty *patternProperty) override
- {
- const QString propertyName = patternProperty->name->asString();
- if (!m_propertySet.contains(propertyName)) {
- m_propertySet.insert(propertyName);
- m_orderedList.append(propertyName);
- }
- return true;
- }
-
- void throwRecursionDepthError() override
- {
- qWarning() << Q_FUNC_INFO << __LINE__ << "Recursion depth error";
- };
-
-private:
- QSet<QString> m_propertySet;
- QStringList m_orderedList;
-};
-
-QString CollectionParseError::errorString() const
-{
- switch (errorNo) {
- case NoError:
- return COLLERR_OK;
- case MainObjectMissing:
- return COLLERR_MAINOBJECT;
- case CollectionNameNotFound:
- return COLLERR_COLLECTIONNAME;
- case CollectionIsNotObject:
- return COLLERR_COLLECTIONOBJ;
- case ColumnsBlockIsNotArray:
- return COLLERR_COLUMNARRAY;
- case UnknownError:
- default:
- return COLLERR_UNKNOWN;
- }
-}
-
-CollectionDetails::CollectionDetails()
- : d(new Private())
-{}
-
-CollectionDetails::CollectionDetails(const CollectionReference &reference)
- : CollectionDetails()
-{
- d->reference = reference;
-}
-
-void CollectionDetails::resetData(const QJsonDocument &localDocument,
- const QString &collectionToImport,
- CollectionParseError *error)
-{
- CollectionDetails importedCollection = fromLocalJson(localDocument, collectionToImport, error);
- d->properties.swap(importedCollection.d->properties);
- d->dataRecords.swap(importedCollection.d->dataRecords);
-}
-
-CollectionDetails::CollectionDetails(const CollectionDetails &other) = default;
-
-CollectionDetails::~CollectionDetails() = default;
-
-void CollectionDetails::insertColumn(const QString &propertyName,
- int colIdx,
- const QVariant &defaultValue,
- DataType type)
-{
- if (containsPropertyName(propertyName))
- return;
-
- CollectionProperty property = {propertyName, type};
- if (d->isValidColumnId(colIdx)) {
- d->properties.insert(colIdx, property);
- } else {
- colIdx = d->properties.size();
- d->properties.append(property);
- }
-
- const QJsonValue defaultJsonValue = QJsonValue::fromVariant(defaultValue);
- for (QJsonArray &record : d->dataRecords)
- record.insert(colIdx, defaultJsonValue);
-
- markChanged();
-}
-
-bool CollectionDetails::removeColumns(int colIdx, int count)
-{
- if (!d->isValidColumnId(colIdx))
- return false;
-
- int maxCount = d->properties.count() - colIdx;
- count = std::min(maxCount, count);
-
- if (count < 1)
- return false;
-
- d->properties.remove(colIdx, count);
-
- for (QJsonArray &record : d->dataRecords) {
- QJsonArray newElement;
-
- auto elementItr = record.constBegin();
- auto elementEnd = elementItr + colIdx;
- while (elementItr != elementEnd)
- newElement.append(*(elementItr++));
-
- elementItr += count;
- elementEnd = record.constEnd();
-
- while (elementItr != elementEnd)
- newElement.append(*(elementItr++));
-
- record = newElement;
- }
-
- markChanged();
-
- return true;
-}
-
-void CollectionDetails::insertEmptyRows(int row, int count)
-{
- if (count < 1)
- return;
-
- row = qBound(0, row, rows());
-
- insertRecords({}, row, count);
-
- markChanged();
-}
-
-bool CollectionDetails::removeRows(int row, int count)
-{
- if (!d->isValidRowId(row))
- return false;
-
- int maxCount = d->dataRecords.count() - row;
- count = std::min(maxCount, count);
-
- if (count < 1)
- return false;
-
- d->dataRecords.remove(row, count);
- markChanged();
- return true;
-}
-
-bool CollectionDetails::setPropertyValue(int row, int column, const QVariant &value)
-{
- if (!d->isValidRowId(row) || !d->isValidColumnId(column))
- return false;
-
- QVariant currentValue = data(row, column);
- if (value == currentValue)
- return false;
-
- QJsonArray &record = d->dataRecords[row];
- record.replace(column, variantToJsonValue(value, typeAt(column)));
- markChanged();
- return true;
-}
-
-bool CollectionDetails::setPropertyName(int column, const QString &value)
-{
- if (!d->isValidColumnId(column))
- return false;
-
- const CollectionProperty &oldProperty = d->properties.at(column);
- if (oldProperty.name == value)
- return false;
-
- d->properties.replace(column, {value, oldProperty.type});
-
- markChanged();
- return true;
-}
-
-bool CollectionDetails::setPropertyType(int column, DataType type)
-{
- if (!d->isValidColumnId(column))
- return false;
-
- bool changed = false;
- CollectionProperty &property = d->properties[column];
- if (property.type != type)
- changed = true;
-
- const DataType formerType = property.type;
- property.type = type;
-
- for (QJsonArray &rowData : d->dataRecords) {
- if (column < rowData.size()) {
- const QJsonValue value = rowData.at(column);
- const QVariant properTypedValue = valueToVariant(value, formerType);
- const QJsonValue properTypedJsonValue = variantToJsonValue(properTypedValue, type);
- rowData.replace(column, properTypedJsonValue);
- changed = true;
- }
- }
-
- if (changed)
- markChanged();
-
- return changed;
-}
-
-CollectionReference CollectionDetails::reference() const
-{
- return d->reference;
-}
-
-QVariant CollectionDetails::data(int row, int column) const
-{
- if (!d->isValidRowId(row))
- return {};
-
- if (!d->isValidColumnId(column))
- return {};
-
- const QJsonValue cellValue = d->dataRecords.at(row).at(column);
-
- return cellValue.toVariant();
-}
-
-QString CollectionDetails::propertyAt(int column) const
-{
- if (!d->isValidColumnId(column))
- return {};
-
- return d->properties.at(column).name;
-}
-
-CollectionDetails::DataType CollectionDetails::typeAt(int column) const
-{
- if (!d->isValidColumnId(column))
- return {};
-
- return d->properties.at(column).type;
-}
-
-CollectionDetails::DataType CollectionDetails::typeAt(int row, int column) const
-{
- if (!d->isValidRowId(row) || !d->isValidColumnId(column))
- return {};
-
- const QJsonValue cellData = d->dataRecords.at(row).at(column);
- return dataTypeFromJsonValue(cellData);
-}
-
-DataTypeWarning::Warning CollectionDetails::cellWarningCheck(int row, int column) const
-{
- const QJsonValue cellValue = d->dataRecords.at(row).at(column);
-
- const DataType columnType = typeAt(column);
- const DataType cellType = typeAt(row, column);
-
- if (isEmptyJsonValue(cellValue))
- return DataTypeWarning::Warning::None;
-
- if ((columnType == DataType::String || columnType == DataType::Real) && cellType == DataType::Integer)
- return DataTypeWarning::Warning::None;
-
- if ((columnType == DataType::Url || columnType == DataType::Image) && cellType == DataType::String)
- return DataTypeWarning::Warning::None;
-
- if (columnType != cellType)
- return DataTypeWarning::Warning::CellDataTypeMismatch;
-
- return DataTypeWarning::Warning::None;
-}
-
-bool CollectionDetails::containsPropertyName(const QString &propertyName) const
-{
- return Utils::anyOf(d->properties, [&propertyName](const CollectionProperty &property) {
- return property.name == propertyName;
- });
-}
-
-bool CollectionDetails::hasValidReference() const
-{
- return d->reference.node.isValid() && d->reference.name.size();
-}
-
-bool CollectionDetails::isChanged() const
-{
- return d->isChanged;
-}
-
-int CollectionDetails::columns() const
-{
- return d->properties.size();
-}
-
-int CollectionDetails::rows() const
-{
- return d->dataRecords.size();
-}
-
-bool CollectionDetails::markSaved()
-{
- if (d->isChanged) {
- d->isChanged = false;
- return true;
- }
- return false;
-}
-
-void CollectionDetails::swap(CollectionDetails &other)
-{
- d.swap(other.d);
-}
-
-void CollectionDetails::resetReference(const CollectionReference &reference)
-{
- if (d->reference != reference) {
- d->reference = reference;
- markChanged();
- }
-}
-
-QString CollectionDetails::toJson() const
-{
- QJsonArray exportedArray;
- const int propertyCount = d->properties.count();
-
- for (const QJsonArray &record : std::as_const(d->dataRecords)) {
- const int valueCount = std::min(int(record.count()), propertyCount);
-
- QJsonObject exportedElement;
- for (int i = 0; i < valueCount; ++i) {
- const QJsonValue &value = record.at(i);
- if (isEmptyJsonValue(value))
- exportedElement.insert(d->properties.at(i).name, QJsonValue::Null);
- else
- exportedElement.insert(d->properties.at(i).name, value);
- }
-
- exportedArray.append(exportedElement);
- }
-
- return QString::fromUtf8(QJsonDocument(exportedArray).toJson());
-}
-
-QString CollectionDetails::toCsv() const
-{
- QString content;
-
- auto gotoNextLine = [&content]() {
- if (content.size() && content.back() == ',')
- content.back() = '\n';
- else
- content += "\n";
- };
-
- const int propertyCount = d->properties.count();
- if (propertyCount <= 0)
- return "";
-
- for (const CollectionProperty &property : std::as_const(d->properties))
- content += property.name + ',';
-
- gotoNextLine();
-
- for (const QJsonArray &record : std::as_const(d->dataRecords)) {
- const int valueCount = std::min(int(record.count()), propertyCount);
- int i = 0;
- for (; i < valueCount; ++i) {
- const QJsonValue &value = record.at(i);
-
- if (value.isDouble())
- content += QString::number(value.toDouble()) + ',';
- else if (value.isBool())
- content += value.toBool() ? QString("true,") : QString("false,");
- else
- content += value.toString() + ',';
- }
-
- for (; i < propertyCount; ++i)
- content += ',';
-
- gotoNextLine();
- }
-
- return content;
-}
-
-QJsonObject CollectionDetails::toLocalJson() const
-{
- QJsonObject collectionObject;
- QJsonArray columnsArray;
- QJsonArray dataArray;
-
- for (const CollectionProperty &property : std::as_const(d->properties)) {
- QJsonObject columnObject;
- columnObject.insert("name", property.name);
- columnObject.insert("type", CollectionDataTypeModel::dataTypeToString(property.type));
- columnsArray.append(columnObject);
- }
-
- for (const QJsonArray &record : std::as_const(d->dataRecords))
- dataArray.append(record);
-
- collectionObject.insert("columns", columnsArray);
- collectionObject.insert("data", dataArray);
-
- return collectionObject;
-}
-
-void CollectionDetails::registerDeclarativeType()
-{
- typedef CollectionDetails::DataType DataType;
- qRegisterMetaType<DataType>("DataType");
- qmlRegisterUncreatableType<CollectionDetails>("CollectionDetails", 1, 0, "DataType", "Enum type");
-
- qRegisterMetaType<DataTypeWarning::Warning>("Warning");
- qmlRegisterUncreatableType<DataTypeWarning>("CollectionDetails", 1, 0, "Warning", "Enum type");
-}
-
-CollectionDetails CollectionDetails::fromImportedCsv(const QByteArray &document,
- const bool &firstRowIsHeader)
-{
- QStringList headers;
- QJsonArray importedArray;
-
- QTextStream stream(document);
- stream.setEncoding(QStringConverter::Latin1);
-
- if (firstRowIsHeader && !stream.atEnd()) {
- headers = Utils::transform(csvReadLine(stream.readLine()),
- [](const QString &value) -> QString { return value.trimmed(); });
- }
-
- while (!stream.atEnd()) {
- const QStringList recordDataList = csvReadLine(stream.readLine());
- int column = -1;
- QJsonObject recordData;
- for (const QString &cellData : recordDataList) {
- if (++column == headers.size()) {
- QString proposalName;
- int proposalId = column;
- do
- proposalName = QString("Column %1").arg(++proposalId);
- while (headers.contains(proposalName));
- headers.append(proposalName);
- }
- recordData.insert(headers.at(column), cellData);
- }
- importedArray.append(recordData);
- }
-
- return fromImportedJson(importedArray, headers);
-}
-
-CollectionDetails CollectionDetails::fromImportedJson(const QByteArray &json, QJsonParseError *error)
-{
- QJsonArray importedCollection;
- auto refineJsonArray = [](const QJsonArray &array) -> QJsonArray {
- QJsonArray resultArray;
- for (const QJsonValue &collectionData : array) {
- if (collectionData.isObject()) {
- QJsonObject rowObject = collectionData.toObject();
- const QStringList rowKeys = rowObject.keys();
- for (const QString &key : rowKeys) {
- const QJsonValue cellValue = rowObject.value(key);
- if (cellValue.isArray())
- rowObject.remove(key);
- }
- resultArray.push_back(rowObject);
- }
- }
- return resultArray;
- };
-
- QJsonParseError parseError;
- QJsonDocument document = QJsonDocument::fromJson(json, &parseError);
- if (error)
- *error = parseError;
-
- if (parseError.error != QJsonParseError::NoError)
- return CollectionDetails{};
-
- if (document.isArray()) {
- importedCollection = refineJsonArray(document.array());
- } else if (document.isObject()) {
- QJsonObject documentObject = document.object();
- const QStringList mainKeys = documentObject.keys();
-
- bool arrayFound = false;
- for (const QString &key : mainKeys) {
- const QJsonValue value = documentObject.value(key);
- if (value.isArray()) {
- arrayFound = true;
- importedCollection = refineJsonArray(value.toArray());
- break;
- }
- }
-
- if (!arrayFound) {
- QJsonObject singleObject;
- for (const QString &key : mainKeys) {
- const QJsonValue value = documentObject.value(key);
-
- if (!value.isObject())
- singleObject.insert(key, value);
- }
- importedCollection.push_back(singleObject);
- }
- }
-
- return fromImportedJson(importedCollection, PropertyOrderFinder::parse(QLatin1String(json)));
-}
-
-CollectionDetails CollectionDetails::fromLocalJson(const QJsonDocument &document,
- const QString &collectionName,
- CollectionParseError *error)
-{
- auto setError = [&error](CollectionParseError::ParseError parseError) {
- if (error)
- error->errorNo = parseError;
- };
-
- setError(CollectionParseError::NoError);
-
- if (document.isObject()) {
- QJsonObject collectionMap = document.object();
- if (collectionMap.contains(collectionName)) {
- QJsonValue collectionValue = collectionMap.value(collectionName);
- if (collectionValue.isObject())
- return fromLocalCollection(collectionValue.toObject());
- else
- setError(CollectionParseError::CollectionIsNotObject);
- } else {
- setError(CollectionParseError::CollectionNameNotFound);
- }
- } else {
- setError(CollectionParseError::MainObjectMissing);
- }
-
- return CollectionDetails{};
-}
-
-CollectionDetails &CollectionDetails::operator=(const CollectionDetails &other)
-{
- CollectionDetails value(other);
- swap(value);
- return *this;
-}
-
-void CollectionDetails::markChanged()
-{
- d->isChanged = true;
-}
-
-void CollectionDetails::insertRecords(const QJsonArray &record, int idx, int count)
-{
- if (count < 1)
- return;
-
- QJsonArray localRecord;
- const int columnsCount = columns();
- for (int i = 0; i < columnsCount; i++) {
- const QJsonValue originalCellData = record.at(i);
- if (originalCellData.isArray())
- localRecord.append({});
- else
- localRecord.append(originalCellData);
- }
-
- if (idx > d->dataRecords.size() || idx < 0)
- idx = d->dataRecords.size();
-
- d->dataRecords.insert(idx, count, localRecord);
-}
-
-CollectionDetails CollectionDetails::fromImportedJson(const QJsonArray &importedArray,
- const QStringList &propertyPriority)
-{
- QList<CollectionProperty> columnData = getColumnsFromImportedJsonArray(importedArray);
- if (!propertyPriority.isEmpty()) {
- QMap<QString, int> priorityMap;
- for (const QString &propertyName : propertyPriority) {
- if (!priorityMap.contains(propertyName))
- priorityMap.insert(propertyName, priorityMap.size());
- }
- const int lowestPriority = priorityMap.size();
-
- Utils::sort(columnData, [&](const CollectionProperty &a, const CollectionProperty &b) {
- return priorityMap.value(a.name, lowestPriority)
- < priorityMap.value(b.name, lowestPriority);
- });
- }
-
- QList<QJsonArray> localJsonArray;
- for (const QJsonValue &importedRowValue : importedArray) {
- QJsonObject importedRowObject = importedRowValue.toObject();
- QJsonArray localRow;
- for (const CollectionProperty &property : columnData)
- localRow.append(importedRowObject.value(property.name));
- localJsonArray.append(localRow);
- }
- CollectionDetails result;
- result.d->properties = columnData;
- result.d->dataRecords = localJsonArray;
- result.markSaved();
-
- return result;
-}
-
-CollectionDetails CollectionDetails::fromLocalCollection(const QJsonObject &localCollection,
- CollectionParseError *error)
-{
- auto setError = [&error](CollectionParseError::ParseError parseError) {
- if (error)
- error->errorNo = parseError;
- };
-
- CollectionDetails result;
- setError(CollectionParseError::NoError);
-
- if (localCollection.contains("columns")) {
- const QJsonValue columnsValue = localCollection.value("columns");
- if (columnsValue.isArray()) {
- const QJsonArray columns = columnsValue.toArray();
- for (const QJsonValue &columnValue : columns) {
- if (columnValue.isObject()) {
- const QJsonObject column = columnValue.toObject();
- const QString columnName = column.value("name").toString();
- if (!columnName.isEmpty()) {
- result.insertColumn(columnName,
- -1,
- {},
- CollectionDataTypeModel::dataTypeFromString(
- column.value("type").toString()));
- }
- }
- }
-
- if (int columnsCount = result.columns()) {
- const QJsonArray dataRecords = localCollection.value("data").toArray();
- for (const QJsonValue &dataRecordValue : dataRecords) {
- QJsonArray dataRecord = dataRecordValue.toArray();
- while (dataRecord.count() > columnsCount)
- dataRecord.removeLast();
-
- result.insertRecords(dataRecord);
- }
- }
- } else {
- setError(CollectionParseError::ColumnsBlockIsNotArray);
- return result;
- }
- }
-
- return result;
-}
-
-} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectiondetails.h b/src/plugins/qmldesigner/components/collectioneditor/collectiondetails.h
deleted file mode 100644
index 7243c585c6..0000000000
--- a/src/plugins/qmldesigner/components/collectioneditor/collectiondetails.h
+++ /dev/null
@@ -1,152 +0,0 @@
-// Copyright (C) 2023 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#pragma once
-
-#include "modelnode.h"
-
-#include <QSharedPointer>
-
-QT_BEGIN_NAMESPACE
-class QJsonObject;
-struct QJsonParseError;
-class QVariant;
-QT_END_NAMESPACE
-
-namespace QmlDesigner {
-
-struct CollectionReference
-{
- ModelNode node;
- QString name;
-
- friend auto qHash(const CollectionReference &collection)
- {
- return qHash(collection.node) ^ ::qHash(collection.name);
- }
-
- bool operator==(const CollectionReference &other) const
- {
- return node == other.node && name == other.name;
- }
-
- bool operator!=(const CollectionReference &other) const { return !(*this == other); }
-};
-
-struct CollectionProperty;
-
-struct DataTypeWarning {
-public:
- enum Warning { None, CellDataTypeMismatch };
- Q_ENUM(Warning)
-
- Warning warning = None;
- DataTypeWarning(Warning warning)
- : warning(warning)
- {}
-
- static QString getDataTypeWarningString(Warning warning)
- {
- return dataTypeWarnings.value(warning);
- }
-
-private:
- Q_GADGET
- static const QMap<Warning, QString> dataTypeWarnings;
-};
-
-struct CollectionParseError
-{
- enum ParseError {
- NoError,
- MainObjectMissing,
- CollectionNameNotFound,
- CollectionIsNotObject,
- ColumnsBlockIsNotArray,
- UnknownError
- };
-
- ParseError errorNo = ParseError::NoError;
- QString errorString() const;
-};
-
-class CollectionDetails
-{
- Q_GADGET
-
-public:
- enum class DataType { Unknown, String, Url, Integer, Real, Boolean, Image, Color };
- Q_ENUM(DataType)
-
- explicit CollectionDetails();
- CollectionDetails(const CollectionReference &reference);
- CollectionDetails(const CollectionDetails &other);
- ~CollectionDetails();
-
- void resetData(const QJsonDocument &localDocument,
- const QString &collectionToImport,
- CollectionParseError *error = nullptr);
-
- void insertColumn(const QString &propertyName,
- int colIdx = -1,
- const QVariant &defaultValue = {},
- DataType type = DataType::String);
- bool removeColumns(int colIdx, int count = 1);
-
- void insertEmptyRows(int row = 0, int count = 1);
- bool removeRows(int row, int count = 1);
- bool setPropertyValue(int row, int column, const QVariant &value);
-
- bool setPropertyName(int column, const QString &value);
- bool setPropertyType(int column, DataType type);
-
- CollectionReference reference() const;
- QVariant data(int row, int column) const;
- QString propertyAt(int column) const;
- DataType typeAt(int column) const;
- DataType typeAt(int row, int column) const;
- DataTypeWarning::Warning cellWarningCheck(int row, int column) const;
- bool containsPropertyName(const QString &propertyName) const;
-
- bool hasValidReference() const;
- bool isChanged() const;
-
- int columns() const;
- int rows() const;
-
- bool markSaved();
-
- void swap(CollectionDetails &other);
- void resetReference(const CollectionReference &reference);
-
- QString toJson() const;
- QString toCsv() const;
- QJsonObject toLocalJson() const;
-
- static void registerDeclarativeType();
-
- static CollectionDetails fromImportedCsv(const QByteArray &document,
- const bool &firstRowIsHeader = true);
- static CollectionDetails fromImportedJson(const QByteArray &json,
- QJsonParseError *error = nullptr);
- static CollectionDetails fromLocalJson(const QJsonDocument &document,
- const QString &collectionName,
- CollectionParseError *error = nullptr);
-
- CollectionDetails &operator=(const CollectionDetails &other);
-
-private:
- void markChanged();
- void insertRecords(const QJsonArray &record, int idx = -1, int count = 1);
-
- static CollectionDetails fromImportedJson(const QJsonArray &importedArray,
- const QStringList &propertyPriority = {});
- static CollectionDetails fromLocalCollection(const QJsonObject &localCollection,
- CollectionParseError *error = nullptr);
-
- // The private data is supposed to be shared between the copies
- class Private;
- QSharedPointer<Private> d;
-};
-
-} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.cpp
deleted file mode 100644
index fcd6d686ef..0000000000
--- a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.cpp
+++ /dev/null
@@ -1,627 +0,0 @@
-// Copyright (C) 2023 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#include "collectiondetailsmodel.h"
-
-#include "collectiondatatypemodel.h"
-#include "collectioneditorutils.h"
-#include "modelnode.h"
-
-#include <coreplugin/editormanager/editormanager.h>
-
-#include <utils/fileutils.h>
-#include <utils/qtcassert.h>
-#include <utils/textfileformat.h>
-
-#include <QJsonArray>
-#include <QJsonDocument>
-#include <QJsonObject>
-#include <QJsonParseError>
-
-namespace QmlDesigner {
-
-CollectionDetailsModel::CollectionDetailsModel(QObject *parent)
- : QAbstractTableModel(parent)
-{
- connect(this, &CollectionDetailsModel::modelReset, this, &CollectionDetailsModel::updateEmpty);
- connect(this, &CollectionDetailsModel::rowsInserted, this, &CollectionDetailsModel::updateEmpty);
- connect(this, &CollectionDetailsModel::rowsRemoved, this, &CollectionDetailsModel::updateEmpty);
-}
-
-QHash<int, QByteArray> CollectionDetailsModel::roleNames() const
-{
- static QHash<int, QByteArray> roles;
- if (roles.isEmpty()) {
- roles.insert(QAbstractTableModel::roleNames());
- roles.insert(SelectedRole, "itemSelected");
- roles.insert(DataTypeRole, "dataType");
- roles.insert(ColumnDataTypeRole, "columnType");
- roles.insert(DataTypeWarningRole, "dataTypeWarning");
- }
- return roles;
-}
-
-int CollectionDetailsModel::rowCount([[maybe_unused]] const QModelIndex &parent) const
-{
- return m_currentCollection.rows();
-}
-
-int CollectionDetailsModel::columnCount([[maybe_unused]] const QModelIndex &parent) const
-{
- return m_currentCollection.columns();
-}
-
-QVariant CollectionDetailsModel::data(const QModelIndex &index, int role) const
-{
- if (!index.isValid())
- return {};
-
- QTC_ASSERT(m_currentCollection.hasValidReference(), return {});
-
- if (role == SelectedRole)
- return (index.column() == m_selectedColumn || index.row() == m_selectedRow);
-
- if (role == DataTypeRole)
- return QVariant::fromValue(m_currentCollection.typeAt(index.row(), index.column()));
-
- if (role == ColumnDataTypeRole)
- return QVariant::fromValue(m_currentCollection.typeAt(index.column()));
-
- if (role == Qt::EditRole)
- return m_currentCollection.data(index.row(), index.column());
-
- if (role == DataTypeWarningRole )
- return QVariant::fromValue(m_currentCollection.cellWarningCheck(index.row(), index.column()));
-
- return m_currentCollection.data(index.row(), index.column()).toString();
-}
-
-bool CollectionDetailsModel::setData(const QModelIndex &index, const QVariant &value, int role)
-{
- QTC_ASSERT(m_currentCollection.hasValidReference(), return false);
-
- if (!index.isValid())
- return {};
-
- if (role == Qt::EditRole) {
- DataTypeWarning::Warning prevWarning = m_currentCollection.cellWarningCheck(index.row(), index.column());
- bool changed = m_currentCollection.setPropertyValue(index.row(), index.column(), value);
-
- if (changed) {
- QList<int> roles = {Qt::DisplayRole, Qt::EditRole};
-
- if (prevWarning != m_currentCollection.cellWarningCheck(index.row(), index.column()))
- roles << DataTypeWarningRole;
-
- setHasUnsavedChanges(true);
- emit dataChanged(index, index, roles);
- }
-
- return true;
- }
-
- return false;
-}
-
-bool CollectionDetailsModel::setHeaderData(int section,
- Qt::Orientation orientation,
- const QVariant &value,
- [[maybe_unused]] int role)
-{
- QTC_ASSERT(m_currentCollection.hasValidReference(), return false);
-
- if (orientation == Qt::Vertical)
- return false;
-
- bool headerChanged = m_currentCollection.setPropertyName(section, value.toString());
- if (headerChanged)
- emit this->headerDataChanged(orientation, section, section);
-
- return headerChanged;
-}
-
-bool CollectionDetailsModel::insertRows(int row, int count, [[maybe_unused]] const QModelIndex &parent)
-{
- QTC_ASSERT(m_currentCollection.hasValidReference(), return false);
-
- if (count < 1)
- return false;
-
- row = qBound(0, row, rowCount());
-
- beginInsertRows({}, row, row + count - 1);
- m_currentCollection.insertEmptyRows(row, count);
- endInsertRows();
- setHasUnsavedChanges(true);
-
- return true;
-}
-
-bool CollectionDetailsModel::removeColumns(int column, int count, const QModelIndex &parent)
-{
- QTC_ASSERT(m_currentCollection.hasValidReference(), return false);
-
- if (column < 0 || column >= columnCount(parent) || count < 1)
- return false;
-
- count = std::min(count, columnCount(parent) - column);
- beginRemoveColumns(parent, column, column + count - 1);
- bool columnsRemoved = m_currentCollection.removeColumns(column, count);
- endRemoveColumns();
-
- if (!columnCount(parent))
- removeRows(0, rowCount(parent), parent);
-
- ensureSingleCell();
- return columnsRemoved;
-}
-
-bool CollectionDetailsModel::removeRows(int row, int count, const QModelIndex &parent)
-{
- QTC_ASSERT(m_currentCollection.hasValidReference(), return false);
-
- if (row < 0 || row >= rowCount(parent) || count < 1)
- return false;
-
- count = std::min(count, rowCount(parent) - row);
- beginRemoveRows(parent, row, row + count - 1);
- bool rowsRemoved = m_currentCollection.removeRows(row, count);
- endRemoveRows();
-
- ensureSingleCell();
- return rowsRemoved;
-}
-
-Qt::ItemFlags CollectionDetailsModel::flags(const QModelIndex &index) const
-{
- if (!index.isValid())
- return {};
-
- return {Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable};
-}
-
-QVariant CollectionDetailsModel::headerData(int section, Qt::Orientation orientation, int role) const
-{
- if (orientation == Qt::Horizontal) {
- if (role == DataTypeRole)
- return CollectionDataTypeModel::dataTypeToString(m_currentCollection.typeAt(section));
- else
- return m_currentCollection.propertyAt(section);
- }
-
- if (orientation == Qt::Vertical)
- return section + 1;
-
- return {};
-}
-
-CollectionDetails::DataType CollectionDetailsModel::propertyDataType(int column) const
-{
- QTC_ASSERT(m_currentCollection.hasValidReference(), return CollectionDetails::DataType::String);
-
- return m_currentCollection.typeAt(column);
-}
-
-int CollectionDetailsModel::selectedColumn() const
-{
- return m_selectedColumn;
-}
-
-int CollectionDetailsModel::selectedRow() const
-{
- return m_selectedRow;
-}
-
-QString CollectionDetailsModel::propertyName(int column) const
-{
- QTC_ASSERT(m_currentCollection.hasValidReference(), return {});
-
- return m_currentCollection.propertyAt(column);
-}
-
-QString CollectionDetailsModel::propertyType(int column) const
-{
- QTC_ASSERT(m_currentCollection.hasValidReference(), return {});
-
- return CollectionDataTypeModel::dataTypeToString(m_currentCollection.typeAt(column));
-}
-
-bool CollectionDetailsModel::isPropertyAvailable(const QString &name)
-{
- QTC_ASSERT(m_currentCollection.hasValidReference(), return false);
-
- return m_currentCollection.containsPropertyName(name);
-}
-
-bool CollectionDetailsModel::addColumn(int column, const QString &name, const QString &propertyType)
-{
- QTC_ASSERT(m_currentCollection.hasValidReference(), return false);
-
- if (m_currentCollection.containsPropertyName(name))
- return false;
-
- if (column < 0 || column > columnCount())
- column = columnCount();
-
- beginInsertColumns({}, column, column);
- m_currentCollection.insertColumn(name,
- column,
- {},
- CollectionDataTypeModel::dataTypeFromString(propertyType));
- endInsertColumns();
- setHasUnsavedChanges(true);
- return m_currentCollection.containsPropertyName(name);
-}
-
-bool CollectionDetailsModel::selectColumn(int section)
-{
- if (m_selectedColumn == section)
- return false;
-
- const int columns = columnCount();
-
- if (section >= columns)
- section = columns - 1;
-
- selectRow(-1);
-
- const int rows = rowCount();
- const int previousColumn = m_selectedColumn;
-
- m_selectedColumn = section;
- emit this->selectedColumnChanged(m_selectedColumn);
-
- auto notifySelectedDataChanged = [this, columns, rows](int notifyingColumn) {
- if (notifyingColumn > -1 && notifyingColumn < columns && rows) {
- emit dataChanged(index(0, notifyingColumn),
- index(rows - 1, notifyingColumn),
- {SelectedRole});
- }
- };
-
- notifySelectedDataChanged(previousColumn);
- notifySelectedDataChanged(m_selectedColumn);
-
- return true;
-}
-
-bool CollectionDetailsModel::renameColumn(int section, const QString &newValue)
-{
- return setHeaderData(section, Qt::Horizontal, newValue);
-}
-
-bool CollectionDetailsModel::setPropertyType(int column, const QString &newValue)
-{
- QTC_ASSERT(m_currentCollection.hasValidReference(), return false);
-
- bool changed = m_currentCollection.setPropertyType(column,
- CollectionDataTypeModel::dataTypeFromString(
- newValue));
- if (changed) {
- emit headerDataChanged(Qt::Horizontal, column, column);
- emit dataChanged(
- index(0, column),
- index(rowCount() - 1, column),
- {Qt::DisplayRole, Qt::EditRole, DataTypeRole, DataTypeWarningRole, ColumnDataTypeRole});
- }
-
- setHasUnsavedChanges(true);
- return changed;
-}
-
-bool CollectionDetailsModel::selectRow(int row)
-{
- if (m_selectedRow == row)
- return false;
-
- const int rows = rowCount();
-
- if (row >= rows)
- row = rows - 1;
-
- selectColumn(-1);
-
- const int columns = columnCount();
- const int previousRow = m_selectedRow;
-
- m_selectedRow = row;
- emit this->selectedRowChanged(m_selectedRow);
-
- auto notifySelectedDataChanged = [this, rows, columns](int notifyingRow) {
- if (notifyingRow > -1 && notifyingRow < rows && columns)
- emit dataChanged(index(notifyingRow, 0), index(notifyingRow, columns - 1), {SelectedRole});
- };
-
- notifySelectedDataChanged(previousRow);
- notifySelectedDataChanged(m_selectedRow);
-
- return true;
-}
-
-void CollectionDetailsModel::deselectAll()
-{
- selectColumn(-1);
- selectRow(-1);
-}
-
-void CollectionDetailsModel::loadCollection(const ModelNode &sourceNode, const QString &collection)
-{
- QString fileName = CollectionEditorUtils::getSourceCollectionPath(sourceNode);
-
- CollectionReference newReference{sourceNode, collection};
- bool alreadyOpen = m_openedCollections.contains(newReference);
-
- if (alreadyOpen) {
- if (m_currentCollection.reference() != newReference) {
- deselectAll();
- beginResetModel();
- switchToCollection(newReference);
- ensureSingleCell();
- endResetModel();
- }
- } else {
- deselectAll();
- switchToCollection(newReference);
- loadJsonCollection(fileName, collection);
- }
-}
-
-void CollectionDetailsModel::removeCollection(const ModelNode &sourceNode, const QString &collection)
-{
- CollectionReference collectionRef{sourceNode, collection};
- if (!m_openedCollections.contains(collectionRef))
- return;
-
- if (m_currentCollection.reference() == collectionRef)
- loadCollection({}, {});
-
- m_openedCollections.remove(collectionRef);
-}
-
-void CollectionDetailsModel::removeAllCollections()
-{
- loadCollection({}, {});
- m_openedCollections.clear();
-}
-
-void CollectionDetailsModel::renameCollection(const ModelNode &sourceNode,
- const QString &oldName,
- const QString &newName)
-{
- CollectionReference oldRef{sourceNode, oldName};
- if (!m_openedCollections.contains(oldRef))
- return;
-
- CollectionReference newReference{sourceNode, newName};
- bool collectionIsSelected = m_currentCollection.reference() == oldRef;
- CollectionDetails collection = m_openedCollections.take(oldRef);
- collection.resetReference(newReference);
- m_openedCollections.insert(newReference, collection);
-
- if (collectionIsSelected)
- setCollectionName(newName);
-}
-
-bool CollectionDetailsModel::saveDataStoreCollections()
-{
- const ModelNode node = m_currentCollection.reference().node;
- const Utils::FilePath path = CollectionEditorUtils::dataStoreJsonFilePath();
- Utils::FileReader fileData;
-
- if (!fileData.fetch(path)) {
- qWarning() << Q_FUNC_INFO << "Cannot read the json file:" << fileData.errorString();
- return false;
- }
-
- QJsonParseError jpe;
- QJsonDocument document = QJsonDocument::fromJson(fileData.data(), &jpe);
-
- if (jpe.error == QJsonParseError::NoError) {
- QJsonObject obj = document.object();
-
- QList<CollectionDetails> collectionsToBeSaved;
- for (CollectionDetails &openedCollection : m_openedCollections) {
- const CollectionReference reference = openedCollection.reference();
- if (reference.node == node) {
- obj.insert(reference.name, openedCollection.toLocalJson());
- collectionsToBeSaved << openedCollection;
- }
- }
-
- document.setObject(obj);
-
- if (CollectionEditorUtils::writeToJsonDocument(path, document)) {
- const CollectionReference currentReference = m_currentCollection.reference();
- for (CollectionDetails &collection : collectionsToBeSaved) {
- collection.markSaved();
- const CollectionReference reference = collection.reference();
- if (reference != currentReference)
- closeCollectionIfSaved(reference);
- }
- setHasUnsavedChanges(false);
- return true;
- }
- }
- return false;
-}
-
-bool CollectionDetailsModel::exportCollection(const QUrl &url)
-{
- using Core::EditorManager;
- using Utils::FilePath;
- using Utils::TextFileFormat;
-
- QTC_ASSERT(m_currentCollection.hasValidReference(), return false);
-
- bool saved = false;
- const FilePath filePath = FilePath::fromUserInput(url.isLocalFile() ? url.toLocalFile()
- : url.toString());
- const QString saveFormat = filePath.toFileInfo().suffix().toLower();
- const QString content = saveFormat == "csv" ? m_currentCollection.toCsv()
- : m_currentCollection.toJson();
-
- TextFileFormat textFileFormat;
- textFileFormat.codec = EditorManager::defaultTextCodec();
- textFileFormat.lineTerminationMode = EditorManager::defaultLineEnding();
- QString errorMessage;
- saved = textFileFormat.writeFile(filePath, content, &errorMessage);
-
- if (!saved)
- qWarning() << Q_FUNC_INFO << "Unable to write file" << errorMessage;
-
- return saved;
-}
-
-const CollectionDetails CollectionDetailsModel::upToDateConstCollection(
- const CollectionReference &reference) const
-{
- using Utils::FilePath;
- using Utils::FileReader;
- CollectionDetails collection;
-
- if (m_openedCollections.contains(reference)) {
- collection = m_openedCollections.value(reference);
- } else {
- QUrl url = CollectionEditorUtils::getSourceCollectionPath(reference.node);
- FilePath path = FilePath::fromUserInput(url.isLocalFile() ? url.toLocalFile()
- : url.toString());
- FileReader file;
-
- if (!file.fetch(path))
- return collection;
-
- QJsonParseError jpe;
- QJsonDocument document = QJsonDocument::fromJson(file.data(), &jpe);
-
- if (jpe.error != QJsonParseError::NoError)
- return collection;
-
- collection = CollectionDetails::fromLocalJson(document, reference.name);
- collection.resetReference(reference);
- }
- return collection;
-}
-
-bool CollectionDetailsModel::collectionHasColumn(const CollectionReference &reference,
- const QString &columnName) const
-{
- const CollectionDetails collection = upToDateConstCollection(reference);
- return collection.containsPropertyName(columnName);
-}
-
-QString CollectionDetailsModel::getFirstColumnName(const CollectionReference &reference) const
-{
- const CollectionDetails collection = upToDateConstCollection(reference);
- return collection.propertyAt(0);
-}
-
-void CollectionDetailsModel::updateEmpty()
-{
- bool isEmptyNow = rowCount() == 0;
- if (m_isEmpty != isEmptyNow) {
- m_isEmpty = isEmptyNow;
- emit isEmptyChanged(m_isEmpty);
- }
-}
-
-void CollectionDetailsModel::switchToCollection(const CollectionReference &collection)
-{
- if (m_currentCollection.reference() == collection)
- return;
-
- closeCurrentCollectionIfSaved();
-
- if (!m_openedCollections.contains(collection))
- m_openedCollections.insert(collection, CollectionDetails(collection));
-
- m_currentCollection = m_openedCollections.value(collection);
-
- setCollectionName(collection.name);
-}
-
-void CollectionDetailsModel::closeCollectionIfSaved(const CollectionReference &collection)
-{
- if (!m_openedCollections.contains(collection))
- return;
-
- const CollectionDetails &collectionDetails = m_openedCollections.value(collection);
-
- if (!collectionDetails.isChanged())
- m_openedCollections.remove(collection);
-}
-
-void CollectionDetailsModel::closeCurrentCollectionIfSaved()
-{
- if (m_currentCollection.hasValidReference()) {
- closeCollectionIfSaved(m_currentCollection.reference());
- m_currentCollection = CollectionDetails{};
- }
-}
-
-void CollectionDetailsModel::loadJsonCollection(const QString &filePath, const QString &collection)
-{
- QJsonDocument document = readJsonFile(filePath);
-
- beginResetModel();
- m_currentCollection.resetData(document, collection);
- ensureSingleCell();
- endResetModel();
-}
-
-void CollectionDetailsModel::ensureSingleCell()
-{
- if (!m_currentCollection.hasValidReference())
- return;
-
- if (!columnCount())
- addColumn(0, "Column 1", "String");
-
- if (!rowCount())
- insertRow(0);
-
- updateEmpty();
-}
-
-QJsonDocument CollectionDetailsModel::readJsonFile(const QUrl &url)
-{
- using Utils::FilePath;
- using Utils::FileReader;
- FilePath path = FilePath::fromUserInput(url.isLocalFile() ? url.toLocalFile() : url.toString());
- FileReader file;
-
- if (!file.fetch(path)) {
- emit warning(tr("File reading problem"), file.errorString());
- return {};
- }
-
- QJsonParseError jpe;
- QJsonDocument document = QJsonDocument::fromJson(file.data(), &jpe);
-
- if (jpe.error != QJsonParseError::NoError)
- emit warning(tr("Json parse error"), jpe.errorString());
-
- return document;
-}
-
-void CollectionDetailsModel::setCollectionName(const QString &newCollectionName)
-{
- if (m_collectionName != newCollectionName) {
- m_collectionName = newCollectionName;
- emit this->collectionNameChanged(m_collectionName);
- }
-}
-
-QString CollectionDetailsModel::warningToString(DataTypeWarning::Warning warning) const
-{
- return DataTypeWarning::getDataTypeWarningString(warning);
-}
-
-void CollectionDetailsModel::setHasUnsavedChanges(bool val)
-{
- if (m_hasUnsavedChanges == val)
- return;
- m_hasUnsavedChanges = val;
- emit hasUnsavedChangesChanged();
-}
-
-} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.h b/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.h
deleted file mode 100644
index 8844ff4a3e..0000000000
--- a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.h
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright (C) 2023 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#pragma once
-
-#include "collectiondetails.h"
-
-#include <QAbstractTableModel>
-#include <QHash>
-
-namespace QmlDesigner {
-
-class ModelNode;
-
-class CollectionDetailsModel : public QAbstractTableModel
-{
- Q_OBJECT
-
- Q_PROPERTY(QString collectionName MEMBER m_collectionName NOTIFY collectionNameChanged)
- Q_PROPERTY(int selectedColumn READ selectedColumn WRITE selectColumn NOTIFY selectedColumnChanged)
- Q_PROPERTY(int selectedRow READ selectedRow WRITE selectRow NOTIFY selectedRowChanged)
- Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged)
- Q_PROPERTY(bool hasUnsavedChanges MEMBER m_hasUnsavedChanges WRITE setHasUnsavedChanges NOTIFY hasUnsavedChangesChanged)
-
-public:
- enum DataRoles { SelectedRole = Qt::UserRole + 1, DataTypeRole, ColumnDataTypeRole, DataTypeWarningRole };
- explicit CollectionDetailsModel(QObject *parent = nullptr);
-
- QHash<int, QByteArray> roleNames() const override;
- int rowCount(const QModelIndex &parent = {}) const override;
- int columnCount(const QModelIndex &parent = {}) const override;
- QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
- bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
- bool setHeaderData(int section,
- Qt::Orientation orientation,
- const QVariant &value,
- int role = Qt::EditRole) override;
- bool insertRows(int row, int count, const QModelIndex &parent = {}) override;
- bool removeColumns(int column, int count, const QModelIndex &parent = {}) override;
- bool removeRows(int row, int count, const QModelIndex &parent = {}) override;
-
- Qt::ItemFlags flags(const QModelIndex &index) const override;
- QVariant headerData(int section,
- Qt::Orientation orientation,
- int role = Qt::DisplayRole) const override;
-
- CollectionDetails::DataType propertyDataType(int column) const;
-
- int selectedColumn() const;
- int selectedRow() const;
- Q_INVOKABLE QString propertyName(int column) const;
- Q_INVOKABLE QString propertyType(int column) const;
-
- Q_INVOKABLE bool isPropertyAvailable(const QString &name);
- Q_INVOKABLE bool addColumn(int column, const QString &name, const QString &propertyType = {});
- Q_INVOKABLE bool selectColumn(int section);
- Q_INVOKABLE bool renameColumn(int section, const QString &newValue);
- Q_INVOKABLE bool setPropertyType(int column, const QString &newValue);
- Q_INVOKABLE bool selectRow(int row);
- Q_INVOKABLE void deselectAll();
- Q_INVOKABLE QString warningToString(DataTypeWarning::Warning warning) const;
-
- void loadCollection(const ModelNode &sourceNode, const QString &collection);
- void removeCollection(const ModelNode &sourceNode, const QString &collection);
- void removeAllCollections();
- void renameCollection(const ModelNode &sourceNode, const QString &oldName, const QString &newName);
-
- Q_INVOKABLE bool saveDataStoreCollections();
- Q_INVOKABLE bool exportCollection(const QUrl &url);
-
- const CollectionDetails upToDateConstCollection(const CollectionReference &reference) const;
- bool collectionHasColumn(const CollectionReference &reference, const QString &columnName) const;
- QString getFirstColumnName(const CollectionReference &reference) const;
- void setHasUnsavedChanges(bool val);
-
-signals:
- void collectionNameChanged(const QString &collectionName);
- void selectedColumnChanged(int);
- void selectedRowChanged(int);
- void isEmptyChanged(bool);
- void hasUnsavedChangesChanged();
- void warning(const QString &title, const QString &body);
-
-private slots:
- void updateEmpty();
-
-private:
- void switchToCollection(const CollectionReference &collection);
- void closeCollectionIfSaved(const CollectionReference &collection);
- void closeCurrentCollectionIfSaved();
- void setCollectionName(const QString &newCollectionName);
- void loadJsonCollection(const QString &filePath, const QString &collection);
- void ensureSingleCell();
- QJsonDocument readJsonFile(const QUrl &url);
-
- QHash<CollectionReference, CollectionDetails> m_openedCollections;
- CollectionDetails m_currentCollection;
- bool m_isEmpty = true;
- bool m_hasUnsavedChanges = false;
- int m_selectedColumn = -1;
- int m_selectedRow = -1;
-
- QString m_collectionName;
-};
-
-} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailssortfiltermodel.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectiondetailssortfiltermodel.cpp
deleted file mode 100644
index 2cc6ac05a6..0000000000
--- a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailssortfiltermodel.cpp
+++ /dev/null
@@ -1,163 +0,0 @@
-// Copyright (C) 2023 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#include "collectiondetailssortfiltermodel.h"
-
-#include "collectiondetailsmodel.h"
-#include "collectioneditorutils.h"
-
-#include <utils/qtcassert.h>
-
-namespace QmlDesigner {
-
-CollectionDetailsSortFilterModel::CollectionDetailsSortFilterModel(QObject *parent)
- : QSortFilterProxyModel(parent)
-{
- connect(this, &CollectionDetailsSortFilterModel::rowsInserted,
- this, &CollectionDetailsSortFilterModel::updateRowCountChanges);
- connect(this, &CollectionDetailsSortFilterModel::rowsRemoved,
- this, &CollectionDetailsSortFilterModel::updateRowCountChanges);
- connect(this, &CollectionDetailsSortFilterModel::modelReset,
- this, &CollectionDetailsSortFilterModel::updateRowCountChanges);
-
- setDynamicSortFilter(true);
-}
-
-void CollectionDetailsSortFilterModel::setSourceModel(CollectionDetailsModel *model)
-{
- m_source = model;
- Super::setSourceModel(model);
- connect(m_source, &CollectionDetailsModel::selectedColumnChanged,
- this, &CollectionDetailsSortFilterModel::updateSelectedColumn);
-
- connect(m_source, &CollectionDetailsModel::selectedRowChanged,
- this, &CollectionDetailsSortFilterModel::updateSelectedRow);
-}
-
-int CollectionDetailsSortFilterModel::selectedRow() const
-{
- QTC_ASSERT(m_source, return -1);
-
- return mapFromSource(m_source->index(m_source->selectedRow(), 0)).row();
-}
-
-int CollectionDetailsSortFilterModel::selectedColumn() const
-{
- QTC_ASSERT(m_source, return -1);
-
- return mapFromSource(m_source->index(0, m_source->selectedColumn())).column();
-}
-
-bool CollectionDetailsSortFilterModel::selectRow(int row)
-{
- QTC_ASSERT(m_source, return false);
-
- return m_source->selectRow(mapToSource(index(row, 0)).row());
-}
-
-bool CollectionDetailsSortFilterModel::selectColumn(int column)
-{
- QTC_ASSERT(m_source, return false);
-
- return m_source->selectColumn(mapToSource(index(0, column)).column());
-}
-
-void CollectionDetailsSortFilterModel::deselectAll()
-{
- QTC_ASSERT(m_source, return);
- m_source->deselectAll();
-}
-
-CollectionDetailsSortFilterModel::~CollectionDetailsSortFilterModel() = default;
-
-bool CollectionDetailsSortFilterModel::filterAcceptsRow(int sourceRow,
- const QModelIndex &sourceParent) const
-{
- QTC_ASSERT(m_source, return false);
- QModelIndex sourceIndex(m_source->index(sourceRow, 0, sourceParent));
- return sourceIndex.isValid();
-}
-
-bool CollectionDetailsSortFilterModel::lessThan(const QModelIndex &sourceleft,
- const QModelIndex &sourceRight) const
-{
- QTC_ASSERT(m_source, return false);
-
- if (sourceleft.column() == sourceRight.column()) {
- int column = sourceleft.column();
- CollectionDetails::DataType columnType = m_source->propertyDataType(column);
- return CollectionEditorUtils::variantIslessThan(sourceleft.data(),
- sourceRight.data(),
- columnType);
- }
-
- return false;
-}
-
-void CollectionDetailsSortFilterModel::updateEmpty()
-{
- bool newValue = rowCount() == 0;
- if (m_isEmpty != newValue) {
- m_isEmpty = newValue;
- emit isEmptyChanged(m_isEmpty);
- }
-}
-
-void CollectionDetailsSortFilterModel::updateSelectedRow()
-{
- const int upToDateSelectedRow = selectedRow();
- if (m_selectedRow == upToDateSelectedRow)
- return;
-
- const int rows = rowCount();
- const int columns = columnCount();
- const int previousRow = m_selectedRow;
-
- m_selectedRow = upToDateSelectedRow;
- emit this->selectedRowChanged(m_selectedRow);
-
- auto notifySelectedDataChanged = [this, rows, columns](int notifyingRow) {
- if (notifyingRow > -1 && notifyingRow < rows && columns) {
- emit dataChanged(index(notifyingRow, 0),
- index(notifyingRow, columns - 1),
- {CollectionDetailsModel::SelectedRole});
- }
- };
-
- notifySelectedDataChanged(previousRow);
- notifySelectedDataChanged(m_selectedRow);
-}
-
-void CollectionDetailsSortFilterModel::updateSelectedColumn()
-{
- const int upToDateSelectedColumn = selectedColumn();
- if (m_selectedColumn == upToDateSelectedColumn)
- return;
-
- const int rows = rowCount();
- const int columns = columnCount();
- const int previousColumn = m_selectedColumn;
-
- m_selectedColumn = upToDateSelectedColumn;
- emit this->selectedColumnChanged(m_selectedColumn);
-
- auto notifySelectedDataChanged = [this, rows, columns](int notifyingCol) {
- if (notifyingCol > -1 && notifyingCol < columns && rows) {
- emit dataChanged(index(0, notifyingCol),
- index(rows - 1, notifyingCol),
- {CollectionDetailsModel::SelectedRole});
- }
- };
-
- notifySelectedDataChanged(previousColumn);
- notifySelectedDataChanged(m_selectedColumn);
-}
-
-void CollectionDetailsSortFilterModel::updateRowCountChanges()
-{
- updateEmpty();
- updateSelectedRow();
- invalidate();
-}
-
-} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailssortfiltermodel.h b/src/plugins/qmldesigner/components/collectioneditor/collectiondetailssortfiltermodel.h
deleted file mode 100644
index 10f6e09b05..0000000000
--- a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailssortfiltermodel.h
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright (C) 2023 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#pragma once
-
-#include <QPointer>
-#include <QSortFilterProxyModel>
-
-namespace QmlDesigner {
-
-class CollectionDetailsModel;
-
-class CollectionDetailsSortFilterModel : public QSortFilterProxyModel
-{
- Q_OBJECT
-
- Q_PROPERTY(int selectedColumn READ selectedColumn WRITE selectColumn NOTIFY selectedColumnChanged)
- Q_PROPERTY(int selectedRow READ selectedRow WRITE selectRow NOTIFY selectedRowChanged)
- Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged)
-
- using Super = QSortFilterProxyModel;
-
-public:
- explicit CollectionDetailsSortFilterModel(QObject *parent = nullptr);
- virtual ~CollectionDetailsSortFilterModel();
-
- void setSourceModel(CollectionDetailsModel *model);
-
- int selectedRow() const;
- int selectedColumn() const;
-
- Q_INVOKABLE bool selectRow(int row);
- Q_INVOKABLE bool selectColumn(int column);
- Q_INVOKABLE void deselectAll();
-
-signals:
- void selectedColumnChanged(int);
- void selectedRowChanged(int);
- void isEmptyChanged(bool);
-
-protected:
- using Super::setSourceModel;
- bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
- bool lessThan(const QModelIndex &sourceleft, const QModelIndex &sourceRight) const override;
-
-private:
- void updateEmpty();
- void updateSelectedRow();
- void updateSelectedColumn();
- void updateRowCountChanges();
-
- QPointer<CollectionDetailsModel> m_source;
- int m_selectedColumn = -1;
- int m_selectedRow = -1;
- bool m_isEmpty = true;
-};
-
-} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectioneditorconstants.h b/src/plugins/qmldesigner/components/collectioneditor/collectioneditorconstants.h
deleted file mode 100644
index 76524762ed..0000000000
--- a/src/plugins/qmldesigner/components/collectioneditor/collectioneditorconstants.h
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright (C) 2023 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#pragma once
-
-namespace QmlDesigner::CollectionEditorConstants {
-
-enum class SourceFormat { Unknown, Json };
-
-inline constexpr char SOURCEFILE_PROPERTY[] = "source";
-inline constexpr char ALLMODELS_PROPERTY[] = "allModels";
-inline constexpr char JSONCHILDMODELNAME_PROPERTY[] = "modelName";
-
-inline constexpr char COLLECTIONMODEL_IMPORT[] = "QtQuick.Studio.Utils";
-inline constexpr char JSONCOLLECTIONMODEL_TYPENAME[] = "QtQuick.Studio.Utils.JsonListModel";
-inline constexpr char JSONCOLLECTIONCHILDMODEL_TYPENAME[] = "QtQuick.Studio.Utils.ChildListModel";
-inline constexpr char JSONBACKEND_TYPENAME[] = "JsonData";
-
-} // namespace QmlDesigner::CollectionEditorConstants
diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.cpp
deleted file mode 100644
index 29b833cc2c..0000000000
--- a/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.cpp
+++ /dev/null
@@ -1,345 +0,0 @@
-// Copyright (C) 2023 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#include "collectioneditorutils.h"
-
-#include "collectiondatatypemodel.h"
-#include "model.h"
-#include "nodemetainfo.h"
-#include "propertymetainfo.h"
-
-#include <coreplugin/documentmanager.h>
-#include <coreplugin/icore.h>
-#include <projectexplorer/project.h>
-#include <projectexplorer/projectexplorer.h>
-#include <projectexplorer/projectmanager.h>
-#include <qmljs/qmljsmodelmanagerinterface.h>
-#include <utils/qtcassert.h>
-
-#include <variant>
-
-#include <QColor>
-#include <QJsonArray>
-#include <QJsonDocument>
-#include <QJsonObject>
-#include <QJsonParseError>
-#include <QJsonValue>
-#include <QUrl>
-
-using DataType = QmlDesigner::CollectionDetails::DataType;
-
-namespace {
-
-using CollectionDataVariant = std::variant<QString, bool, double, int, QUrl, QColor>;
-
-inline bool operator<(const QColor &a, const QColor &b)
-{
- return a.name(QColor::HexArgb) < b.name(QColor::HexArgb);
-}
-
-inline CollectionDataVariant valueToVariant(const QVariant &value, DataType type)
-{
- switch (type) {
- case DataType::String:
- return value.toString();
- case DataType::Real:
- return value.toDouble();
- case DataType::Integer:
- return value.toInt();
- case DataType::Boolean:
- return value.toBool();
- case DataType::Color:
- return value.value<QColor>();
- case DataType::Image:
- case DataType::Url:
- return value.value<QUrl>();
- default:
- return false;
- }
-}
-
-struct LessThanVisitor
-{
- template<typename T1, typename T2>
- bool operator()(const T1 &a, const T2 &b) const
- {
- return CollectionDataVariant(a).index() < CollectionDataVariant(b).index();
- }
-
- template<typename T>
- bool operator()(const T &a, const T &b) const
- {
- return a < b;
- }
-};
-
-Utils::FilePath findFile(const Utils::FilePath &path, const QString &fileName)
-{
- QDirIterator it(path.toString(), QDirIterator::Subdirectories);
-
- while (it.hasNext()) {
- QFileInfo file(it.next());
- if (file.isDir())
- continue;
-
- if (file.fileName() == fileName)
- return Utils::FilePath::fromFileInfo(file);
- }
- return {};
-}
-
-Utils::FilePath dataStoreDir()
-{
- using Utils::FilePath;
- ProjectExplorer::Project *currentProject = ProjectExplorer::ProjectManager::startupProject();
-
- if (!currentProject)
- return {};
-
- FilePath oldImportDirectory = currentProject->projectDirectory().pathAppended(
- "imports/" + currentProject->displayName());
- if (oldImportDirectory.exists())
- return oldImportDirectory;
-
- return currentProject->projectDirectory().pathAppended(currentProject->displayName());
-}
-
-inline Utils::FilePath collectionPath(const QString &filePath)
-{
- return dataStoreDir().pathAppended(filePath);
-}
-
-inline Utils::FilePath qmlDirFilePath()
-{
- return collectionPath("qmldir");
-}
-
-} // namespace
-
-namespace QmlDesigner::CollectionEditorUtils {
-
-bool variantIslessThan(const QVariant &a, const QVariant &b, DataType type)
-{
- return std::visit(LessThanVisitor{}, valueToVariant(a, type), valueToVariant(b, type));
-}
-
-QString getSourceCollectionType(const ModelNode &node)
-{
- using namespace QmlDesigner;
- if (node.type() == CollectionEditorConstants::JSONCOLLECTIONMODEL_TYPENAME)
- return "json";
-
- return {};
-}
-
-Utils::FilePath dataStoreJsonFilePath()
-{
- return collectionPath("models.json");
-}
-
-Utils::FilePath dataStoreQmlFilePath()
-{
- return collectionPath("DataStore.qml");
-}
-
-bool canAcceptCollectionAsModel(const ModelNode &node)
-{
- const NodeMetaInfo nodeMetaInfo = node.metaInfo();
- if (!nodeMetaInfo.isValid())
- return false;
-
- const PropertyMetaInfo modelProperty = nodeMetaInfo.property("model");
- if (!modelProperty.isValid())
- return false;
-
- return modelProperty.isWritable() && !modelProperty.isPrivate()
- && modelProperty.propertyType().isVariant();
-}
-
-bool hasTextRoleProperty(const ModelNode &node)
-{
- const NodeMetaInfo nodeMetaInfo = node.metaInfo();
- if (!nodeMetaInfo.isValid())
- return false;
-
- const PropertyMetaInfo textRoleProperty = nodeMetaInfo.property("textRole");
- if (!textRoleProperty.isValid())
- return false;
-
- return textRoleProperty.isWritable() && !textRoleProperty.isPrivate()
- && textRoleProperty.propertyType().isString();
-}
-
-QString getSourceCollectionPath(const ModelNode &dataStoreNode)
-{
- using Utils::FilePath;
- if (!dataStoreNode.isValid())
- return {};
-
- const FilePath expectedFile = dataStoreJsonFilePath();
-
- if (expectedFile.exists())
- return expectedFile.toFSPathString();
-
- return {};
-}
-
-bool isDataStoreNode(const ModelNode &dataStoreNode)
-{
- using Utils::FilePath;
-
- if (!dataStoreNode.isValid())
- return false;
-
- const FilePath expectedFile = dataStoreQmlFilePath();
-
- if (!expectedFile.exists())
- return false;
-
- FilePath modelPath = FilePath::fromUserInput(dataStoreNode.model()->fileUrl().toLocalFile());
-
- return modelPath.isSameFile(expectedFile);
-}
-
-bool ensureDataStoreExists(bool &justCreated)
-{
- using Utils::FilePath;
- using Utils::FileReader;
- using Utils::FileSaver;
-
- FilePath qmlDestinationPath = dataStoreQmlFilePath();
- justCreated = false;
-
- auto extractDependency = [&justCreated](const FilePath &filePath) -> bool {
- if (filePath.exists())
- return true;
-
- const QString templateFileName = filePath.fileName() + u".tpl";
- const FilePath templatePath = findFile(Core::ICore::resourcePath(), templateFileName);
- if (!templatePath.exists()) {
- qWarning() << Q_FUNC_INFO << __LINE__ << templateFileName << "does not exist";
- return false;
- }
-
- if (!filePath.parentDir().ensureWritableDir()) {
- qWarning() << Q_FUNC_INFO << __LINE__ << "Cannot create directory"
- << filePath.parentDir();
- return false;
- }
-
- if (templatePath.copyFile(filePath)) {
- justCreated = true;
- return true;
- }
-
- qWarning() << Q_FUNC_INFO << __LINE__ << "Cannot copy" << templateFileName << "to" << filePath;
- return false;
- };
-
- if (!extractDependency(dataStoreJsonFilePath()))
- return false;
-
- if (!extractDependency(collectionPath("data.json")))
- return false;
-
- if (!extractDependency(collectionPath("JsonData.qml")))
- return false;
-
- if (!qmlDestinationPath.exists()) {
- if (qmlDestinationPath.ensureExistingFile()) {
- justCreated = true;
- } else {
- qWarning() << Q_FUNC_INFO << __LINE__ << "Can't create DataStore Qml File";
- return false;
- }
- }
-
- FilePath qmlDirPath = qmlDirFilePath();
- qmlDirPath.ensureExistingFile();
-
- FileReader qmlDirReader;
- if (!qmlDirReader.fetch(qmlDirPath)) {
- qWarning() << Q_FUNC_INFO << __LINE__ << "Can't read the content of the qmldir";
- return false;
- }
-
- QByteArray qmlDirContent = qmlDirReader.data();
- const QList<QByteArray> qmlDirLines = qmlDirContent.split('\n');
- for (const QByteArray &line : qmlDirLines) {
- if (line.startsWith("singleton DataStore "))
- return true;
- }
-
- if (!qmlDirContent.isEmpty() && qmlDirContent.back() != '\n')
- qmlDirContent.append("\n");
- qmlDirContent.append("singleton DataStore 1.0 DataStore.qml\n");
-
- FileSaver qmlDirSaver(qmlDirPath);
- qmlDirSaver.write(qmlDirContent);
-
- if (qmlDirSaver.finalize()) {
- justCreated = true;
- return true;
- }
-
- qWarning() << Q_FUNC_INFO << __LINE__ << "Can't write to the qmldir file";
- return false;
-}
-
-QJsonObject defaultCollection()
-{
- QJsonObject collectionObject;
-
- QJsonArray columns;
- QJsonObject defaultColumn;
- defaultColumn.insert("name", "Column 1");
- defaultColumn.insert("type", CollectionDataTypeModel::dataTypeToString(DataType::String));
- columns.append(defaultColumn);
-
- QJsonArray collectionData;
- QJsonArray cellData;
- cellData.append(QString{});
- collectionData.append(cellData);
-
- collectionObject.insert("columns", columns);
- collectionObject.insert("data", collectionData);
-
- return collectionObject;
-}
-
-QJsonObject defaultColorCollection()
-{
- using Utils::FilePath;
- using Utils::FileReader;
- const FilePath templatePath = findFile(Core::ICore::resourcePath(), "Colors.json.tpl");
-
- FileReader fileReader;
- if (!fileReader.fetch(templatePath)) {
- qWarning() << Q_FUNC_INFO << __LINE__ << "Can't read the content of the file" << templatePath;
- return {};
- }
-
- QJsonParseError parseError;
- const CollectionDetails collection = CollectionDetails::fromImportedJson(fileReader.data(),
- &parseError);
- if (parseError.error != QJsonParseError::NoError) {
- qWarning() << Q_FUNC_INFO << __LINE__ << "Error in template file" << parseError.errorString();
- return {};
- }
-
- return collection.toLocalJson();
-}
-
-bool writeToJsonDocument(const Utils::FilePath &path, const QJsonDocument &document, QString *errorString)
-{
- Core::FileChangeBlocker fileBlocker(path);
- Utils::FileSaver jsonFile(path);
- if (jsonFile.write(document.toJson()))
- jsonFile.finalize();
- if (errorString)
- *errorString = jsonFile.errorString();
-
- return !jsonFile.hasError();
-}
-
-} // namespace QmlDesigner::CollectionEditorUtils
diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.h b/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.h
deleted file mode 100644
index 355addf59b..0000000000
--- a/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.h
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright (C) 2023 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#pragma once
-
-#include "collectiondetails.h"
-#include "collectioneditorconstants.h"
-
-QT_BEGIN_NAMESPACE
-class QJsonArray;
-class QJsonObject;
-QT_END_NAMESPACE
-
-namespace Utils {
-class FilePath;
-}
-
-namespace QmlDesigner::CollectionEditorUtils {
-
-bool variantIslessThan(const QVariant &a, const QVariant &b, CollectionDetails::DataType type);
-
-QString getSourceCollectionType(const QmlDesigner::ModelNode &node);
-
-QString getSourceCollectionPath(const QmlDesigner::ModelNode &dataStoreNode);
-
-Utils::FilePath dataStoreJsonFilePath();
-
-Utils::FilePath dataStoreQmlFilePath();
-
-bool writeToJsonDocument(const Utils::FilePath &path,
- const QJsonDocument &document,
- QString *errorString = nullptr);
-
-bool isDataStoreNode(const ModelNode &dataStoreNode);
-
-bool ensureDataStoreExists(bool &justCreated);
-
-bool canAcceptCollectionAsModel(const ModelNode &node);
-
-bool hasTextRoleProperty(const ModelNode &node);
-
-QJsonObject defaultCollection();
-
-QJsonObject defaultColorCollection();
-
-} // namespace QmlDesigner::CollectionEditorUtils
diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionlistmodel.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectionlistmodel.cpp
deleted file mode 100644
index d27a077d2a..0000000000
--- a/src/plugins/qmldesigner/components/collectioneditor/collectionlistmodel.cpp
+++ /dev/null
@@ -1,521 +0,0 @@
-// Copyright (C) 2023 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#include "collectionlistmodel.h"
-
-#include "collectioneditorutils.h"
-
-#include <utils/algorithm.h>
-#include <utils/fileutils.h>
-#include <utils/qtcassert.h>
-
-#include <QJsonDocument>
-#include <QJsonObject>
-#include <QJsonParseError>
-
-namespace {
-
-template<typename ValueType>
-bool containsItem(const std::initializer_list<ValueType> &container, const ValueType &value)
-{
- auto begin = std::cbegin(container);
- auto end = std::cend(container);
-
- auto it = std::find(begin, end, value);
- return it != end;
-}
-
-bool sameCollectionNames(QStringList a, QStringList b)
-{
- if (a.size() != b.size())
- return false;
-
- a.sort(Qt::CaseSensitive);
- b.sort(Qt::CaseSensitive);
-
- return a == b;
-}
-
-} // namespace
-
-namespace QmlDesigner {
-
-CollectionListModel::CollectionListModel()
- : QAbstractListModel()
-{
- connect(this, &CollectionListModel::modelReset, this, &CollectionListModel::updateEmpty);
- connect(this, &CollectionListModel::rowsRemoved, this, &CollectionListModel::updateEmpty);
- connect(this, &CollectionListModel::rowsInserted, this, &CollectionListModel::updateEmpty);
-}
-
-QHash<int, QByteArray> CollectionListModel::roleNames() const
-{
- static QHash<int, QByteArray> roles;
- if (roles.isEmpty()) {
- roles.insert(Super::roleNames());
- roles.insert({
- {IdRole, "collectionId"},
- {NameRole, "collectionName"},
- {SelectedRole, "collectionIsSelected"},
- });
- }
- return roles;
-}
-
-int CollectionListModel::rowCount([[maybe_unused]] const QModelIndex &parent) const
-{
- return m_data.count();
-}
-
-bool CollectionListModel::setData(const QModelIndex &index, const QVariant &value, int role)
-{
- if (!index.isValid())
- return false;
-
- if (containsItem<int>({Qt::EditRole, Qt::DisplayRole, NameRole}, role)) {
- if (collectionExists(value.toString()))
- return false;
-
- QString oldName = collectionNameAt(index.row());
- bool nameChanged = value != data(index);
- if (nameChanged) {
- QString newName = value.toString();
- QString errorString;
- if (renameCollectionInDataStore(oldName, newName, errorString)) {
- m_data.replace(index.row(), newName);
- emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole, NameRole});
- emit this->collectionNameChanged(oldName, newName);
- if (m_selectedCollectionName == oldName)
- updateSelectedCollectionName();
- return true;
- } else {
- emit warning("Rename Model", errorString);
- return false;
- }
- }
- } else if (role == SelectedRole) {
- if (value.toBool() != index.data(SelectedRole).toBool()) {
- setSelectedIndex(value.toBool() ? index.row() : -1);
- return true;
- }
- }
- return false;
-}
-
-bool CollectionListModel::removeRows(int row, int count, const QModelIndex &parent)
-{
- const int rows = rowCount(parent);
- if (row >= rows)
- return false;
-
- row = qBound(0, row, rows - 1);
- count = qBound(0, count, rows - row);
-
- if (count < 1)
- return false;
-
- QString errorString;
- QStringList removedCollections = m_data.mid(row, count);
- if (removeCollectionsFromDataStore(removedCollections, errorString)) {
- beginRemoveRows(parent, row, row + count - 1);
- m_data.remove(row, count);
- endRemoveRows();
-
- emit collectionsRemoved(removedCollections);
- if (m_selectedIndex >= row) {
- int preferredIndex = m_selectedIndex - count;
- if (preferredIndex < 0) // If the selected item is deleted, reset selection
- selectCollectionIndex(-1);
- selectCollectionIndex(preferredIndex, true);
- }
-
- updateSelectedCollectionName();
- return true;
- } else {
- emit warning("Remove Model", errorString);
- return false;
- }
-}
-
-QVariant CollectionListModel::data(const QModelIndex &index, int role) const
-{
- QTC_ASSERT(index.isValid(), return {});
-
- switch (role) {
- case IdRole:
- return index.row();
- case SelectedRole:
- return index.row() == m_selectedIndex;
- case NameRole:
- default:
- return m_data.at(index.row());
- }
-}
-
-void CollectionListModel::setDataStoreNode(const ModelNode &dataStoreNode)
-{
- m_dataStoreNode = dataStoreNode;
- update();
-}
-
-int CollectionListModel::selectedIndex() const
-{
- return m_selectedIndex;
-}
-
-ModelNode CollectionListModel::sourceNode() const
-{
- return m_dataStoreNode;
-}
-
-bool CollectionListModel::collectionExists(const QString &collectionName) const
-{
- return m_data.contains(collectionName);
-}
-
-QStringList CollectionListModel::collections() const
-{
- return m_data;
-}
-
-QString CollectionListModel::getUniqueCollectionName(const QString &baseName) const
-{
- QString name = baseName.isEmpty() ? "Model" : baseName;
- QString nameTemplate = name + "%1";
-
- int num = 0;
-
- while (collectionExists(name))
- name = nameTemplate.arg(++num, 2, 10, QChar('0'));
-
- return name;
-}
-
-void CollectionListModel::selectCollectionIndex(int idx, bool selectAtLeastOne)
-{
- int collectionCount = m_data.size();
- int preferredIndex = -1;
- if (collectionCount) {
- if (selectAtLeastOne)
- preferredIndex = std::max(0, std::min(idx, collectionCount - 1));
- else if (idx > -1 && idx < collectionCount)
- preferredIndex = idx;
- }
-
- setSelectedIndex(preferredIndex);
-}
-
-void CollectionListModel::selectCollectionName(QString collectionName, bool selectAtLeastOne)
-{
- int idx = m_data.indexOf(collectionName);
- if (idx > -1)
- selectCollectionIndex(idx);
- else
- selectCollectionIndex(selectedIndex(), selectAtLeastOne);
-
- collectionName = collectionNameAt(selectedIndex());
- if (m_selectedCollectionName == collectionName)
- return;
-
- m_selectedCollectionName = collectionName;
- emit selectedCollectionNameChanged(m_selectedCollectionName);
-}
-
-QString CollectionListModel::collectionNameAt(int idx) const
-{
- return index(idx).data(NameRole).toString();
-}
-
-QString CollectionListModel::selectedCollectionName() const
-{
- return m_selectedCollectionName;
-}
-
-void CollectionListModel::update()
-{
- using Utils::FilePath;
- using Utils::FileReader;
-
- FileReader sourceFile;
- QString sourceFileAddress = CollectionEditorUtils::getSourceCollectionPath(m_dataStoreNode);
- FilePath path = FilePath::fromUserInput(sourceFileAddress);
- bool fileRead = false;
- if (path.exists()) {
- fileRead = sourceFile.fetch(path);
- if (!fileRead)
- emit this->warning(tr("Model Editor"),
- tr("Cannot read the dataStore file\n%1").arg(sourceFile.errorString()));
- }
-
- QStringList collectionNames;
- if (fileRead) {
- QJsonParseError parseError;
- QJsonDocument document = QJsonDocument::fromJson(sourceFile.data(), &parseError);
- if (parseError.error != QJsonParseError::NoError) {
- emit this->warning(tr("Model Editor"),
- tr("There is an error in the JSON file.\n%1")
- .arg(parseError.errorString()));
- } else {
- if (document.isObject())
- collectionNames = document.object().toVariantMap().keys();
- else
- emit this->warning(tr("Model Editor"), tr("The JSON document be an object."));
- }
- }
-
- if (!sameCollectionNames(m_data, collectionNames)) {
- QString prevSelectedCollection = selectedIndex() > -1 ? m_data.at(selectedIndex())
- : QString();
- beginResetModel();
- m_data = collectionNames;
- endResetModel();
- emit this->collectionNamesChanged(collections());
- selectCollectionName(prevSelectedCollection, true);
- }
-}
-
-bool CollectionListModel::addCollection(const QString &collectionName,
- const QJsonObject &localCollection)
-{
- if (collectionExists(collectionName)) {
- emit warning(tr("Add Model"), tr("Model \"%1\" already exists.").arg(collectionName));
- return false;
- }
-
- QString errorMessage;
- if (addCollectionToDataStore(collectionName, localCollection, errorMessage)) {
- int row = rowCount();
- beginInsertRows({}, row, row);
- m_data.append(collectionName);
- endInsertRows();
-
- selectCollectionName(collectionName);
- emit collectionAdded(collectionName);
- return true;
- } else {
- emit warning(tr("Add Collection"), errorMessage);
- }
- return false;
-}
-
-void CollectionListModel::setSelectedIndex(int idx)
-{
- idx = (idx > -1 && idx < rowCount()) ? idx : -1;
-
- if (m_selectedIndex != idx) {
- QModelIndex previousIndex = index(m_selectedIndex);
- QModelIndex newIndex = index(idx);
-
- m_selectedIndex = idx;
-
- if (previousIndex.isValid())
- emit dataChanged(previousIndex, previousIndex, {SelectedRole});
-
- if (newIndex.isValid())
- emit dataChanged(newIndex, newIndex, {SelectedRole});
-
- emit selectedIndexChanged(idx);
- updateSelectedCollectionName();
- }
-}
-
-bool CollectionListModel::removeCollectionsFromDataStore(const QStringList &removedCollections,
- QString &error) const
-{
- using Utils::FilePath;
- using Utils::FileReader;
- auto setErrorAndReturn = [&error](const QString &msg) -> bool {
- error = msg;
- return false;
- };
-
- if (m_dataStoreNode.type() != CollectionEditorConstants::JSONCOLLECTIONMODEL_TYPENAME)
- return setErrorAndReturn(tr("Invalid node type"));
-
- QString sourceFileAddress = CollectionEditorUtils::getSourceCollectionPath(m_dataStoreNode);
-
- QFileInfo sourceFileInfo(sourceFileAddress);
- if (!sourceFileInfo.isFile())
- return setErrorAndReturn(tr("The selected node has an invalid source address"));
-
- FilePath jsonPath = FilePath::fromUserInput(sourceFileAddress);
- FileReader jsonFile;
- if (!jsonFile.fetch(jsonPath)) {
- return setErrorAndReturn(tr("Can't read file \"%1\".\n%2")
- .arg(sourceFileInfo.absoluteFilePath(), jsonFile.errorString()));
- }
-
- QJsonParseError parseError;
- QJsonDocument document = QJsonDocument::fromJson(jsonFile.data(), &parseError);
- if (parseError.error != QJsonParseError::NoError) {
- return setErrorAndReturn(tr("\"%1\" is corrupted.\n%2")
- .arg(sourceFileInfo.absoluteFilePath(), parseError.errorString()));
- }
-
- if (document.isObject()) {
- QJsonObject rootObject = document.object();
-
- for (const QString &collectionName : removedCollections) {
- bool sourceContainsCollection = rootObject.contains(collectionName);
- if (sourceContainsCollection) {
- rootObject.remove(collectionName);
- } else {
- setErrorAndReturn(tr("The model group doesn't contain the model name (%1).")
- .arg(sourceContainsCollection));
- }
- }
-
- document.setObject(rootObject);
-
- if (CollectionEditorUtils::writeToJsonDocument(jsonPath, document)) {
- error.clear();
- return true;
- } else {
- return setErrorAndReturn(
- tr("Can't write to \"%1\".").arg(sourceFileInfo.absoluteFilePath()));
- }
- } else {
- return setErrorAndReturn(tr("Local Json Document should be an object"));
- }
-
- return false;
-}
-
-bool CollectionListModel::renameCollectionInDataStore(const QString &oldName,
- const QString &newName,
- QString &error)
-{
- using Utils::FilePath;
- using Utils::FileReader;
- using Utils::FileSaver;
-
- auto setErrorAndReturn = [&error](const QString &msg) -> bool {
- error = msg;
- return false;
- };
-
- if (m_dataStoreNode.type() != CollectionEditorConstants::JSONCOLLECTIONMODEL_TYPENAME)
- return setErrorAndReturn(tr("Invalid node type"));
-
- QString sourceFileAddress = CollectionEditorUtils::getSourceCollectionPath(m_dataStoreNode);
-
- QFileInfo sourceFileInfo(sourceFileAddress);
- if (!sourceFileInfo.isFile())
- return setErrorAndReturn(tr("Selected node must have a valid source file address"));
-
- FilePath jsonPath = FilePath::fromUserInput(sourceFileAddress);
- FileReader jsonFile;
- if (!jsonFile.fetch(jsonPath)) {
- return setErrorAndReturn(
- tr("Can't read \"%1\".\n%2").arg(sourceFileInfo.absoluteFilePath(), jsonFile.errorString()));
- }
-
- QJsonParseError parseError;
- QJsonDocument document = QJsonDocument::fromJson(jsonFile.data(), &parseError);
- if (parseError.error != QJsonParseError::NoError) {
- return setErrorAndReturn(tr("\"%1\" is corrupted.\n%2")
- .arg(sourceFileInfo.absoluteFilePath(), parseError.errorString()));
- }
-
- if (document.isObject()) {
- QJsonObject rootObject = document.object();
-
- bool collectionContainsOldName = rootObject.contains(oldName);
- bool collectionContainsNewName = rootObject.contains(newName);
-
- if (!collectionContainsOldName) {
- return setErrorAndReturn(
- tr("The model group doesn't contain the old model name (%1).").arg(oldName));
- }
-
- if (collectionContainsNewName) {
- return setErrorAndReturn(
- tr("The model name \"%1\" already exists in the model group.").arg(newName));
- }
-
- QJsonValue oldValue = rootObject.value(oldName);
- rootObject.insert(newName, oldValue);
- rootObject.remove(oldName);
-
- document.setObject(rootObject);
-
- if (CollectionEditorUtils::writeToJsonDocument(jsonPath, document)) {
- error.clear();
- return true;
- } else {
- return setErrorAndReturn(
- tr("Can't write to \"%1\".").arg(sourceFileInfo.absoluteFilePath()));
- }
- } else {
- return setErrorAndReturn(tr("Local Json Document should be an object"));
- }
- return false;
-}
-
-bool CollectionListModel::addCollectionToDataStore(const QString &collectionName,
- const QJsonObject &localCollection,
- QString &errorString) const
-{
- using Utils::FilePath;
- using Utils::FileReader;
- auto returnError = [&errorString](const QString &msg) -> bool {
- errorString = msg;
- return false;
- };
-
- if (collectionExists(collectionName))
- return returnError(tr("A model with the identical name already exists."));
-
- QString sourceFileAddress = CollectionEditorUtils::getSourceCollectionPath(m_dataStoreNode);
-
- QFileInfo sourceFileInfo(sourceFileAddress);
- if (!sourceFileInfo.isFile())
- return returnError(tr("Selected node must have a valid source file address"));
-
- FilePath jsonPath = FilePath::fromUserInput(sourceFileAddress);
- FileReader jsonFile;
- if (!jsonFile.fetch(jsonPath)) {
- return returnError(
- tr("Can't read \"%1\".\n%2").arg(sourceFileInfo.absoluteFilePath(), jsonFile.errorString()));
- }
-
- QJsonParseError parseError;
- QJsonDocument document = QJsonDocument::fromJson(jsonFile.data(), &parseError);
- if (parseError.error != QJsonParseError::NoError)
- return returnError(tr("\"%1\" is corrupted.\n%2")
- .arg(sourceFileInfo.absoluteFilePath(), parseError.errorString()));
-
- if (document.isObject()) {
- QJsonObject sourceObject = document.object();
- sourceObject.insert(collectionName, localCollection);
- document.setObject(sourceObject);
-
- if (CollectionEditorUtils::writeToJsonDocument(jsonPath, document))
- return true;
- else
- return returnError(tr("Can't write to \"%1\".").arg(sourceFileInfo.absoluteFilePath()));
- } else {
- return returnError(tr("JSON document type should be an object containing models."));
- }
-}
-
-void CollectionListModel::updateEmpty()
-{
- bool isEmptyNow = m_data.isEmpty();
- if (m_isEmpty != isEmptyNow) {
- m_isEmpty = isEmptyNow;
- emit isEmptyChanged(m_isEmpty);
-
- if (m_isEmpty)
- setSelectedIndex(-1);
- }
-}
-
-void CollectionListModel::updateSelectedCollectionName()
-{
- QString selectedCollectionByIndex = collectionNameAt(selectedIndex());
- if (selectedCollectionByIndex != selectedCollectionName())
- selectCollectionName(selectedCollectionByIndex);
-}
-
-} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionlistmodel.h b/src/plugins/qmldesigner/components/collectioneditor/collectionlistmodel.h
deleted file mode 100644
index 7902fd5909..0000000000
--- a/src/plugins/qmldesigner/components/collectioneditor/collectionlistmodel.h
+++ /dev/null
@@ -1,79 +0,0 @@
-// Copyright (C) 2023 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#pragma once
-
-#include <QAbstractListModel>
-#include <QHash>
-
-#include "modelnode.h"
-
-namespace QmlDesigner {
-
-class CollectionListModel : public QAbstractListModel
-{
- Q_OBJECT
-
- Q_PROPERTY(int selectedIndex MEMBER m_selectedIndex NOTIFY selectedIndexChanged)
- Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged)
- Q_PROPERTY(QString selectedCollectionName
- READ selectedCollectionName
- WRITE selectCollectionName
- NOTIFY selectedCollectionNameChanged)
-
-public:
- enum Roles { IdRole = Qt::UserRole + 1, NameRole, SelectedRole };
-
- explicit CollectionListModel();
- QHash<int, QByteArray> roleNames() const override;
-
- int rowCount(const QModelIndex &parent = QModelIndex()) const override;
- bool setData(const QModelIndex &index, const QVariant &value, int role) override;
- bool removeRows(int row, int count, const QModelIndex &parent = {}) override;
- QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
- void setDataStoreNode(const ModelNode &dataStoreNode = {});
-
- Q_INVOKABLE int selectedIndex() const;
- Q_INVOKABLE ModelNode sourceNode() const;
- Q_INVOKABLE bool collectionExists(const QString &collectionName) const;
- Q_INVOKABLE QStringList collections() const;
- Q_INVOKABLE QString getUniqueCollectionName(const QString &baseName = {}) const;
-
- void selectCollectionIndex(int idx, bool selectAtLeastOne = false);
- void selectCollectionName(QString collectionName, bool selectAtLeastOne = false);
- QString collectionNameAt(int idx) const;
- QString selectedCollectionName() const;
-
- void update();
- bool addCollection(const QString &collectionName, const QJsonObject &localCollection);
-
-signals:
- void selectedIndexChanged(int idx);
- void isEmptyChanged(bool);
- void collectionNameChanged(const QString &oldName, const QString &newName);
- void collectionNamesChanged(const QStringList &collectionNames);
- void collectionsRemoved(const QStringList &names);
- void collectionAdded(const QString &name);
- void selectedCollectionNameChanged(const QString &selectedCollectionName);
- void warning(const QString &title, const QString &body);
-
-private:
- void setSelectedIndex(int idx);
- bool removeCollectionsFromDataStore(const QStringList &removedCollections, QString &error) const;
- bool renameCollectionInDataStore(const QString &oldName, const QString &newName, QString &error);
- bool addCollectionToDataStore(const QString &collectionName,
- const QJsonObject &localCollection,
- QString &errorString) const;
-
- void updateEmpty();
- void updateSelectedCollectionName();
-
- using Super = QAbstractListModel;
- int m_selectedIndex = -1;
- bool m_isEmpty = false;
- ModelNode m_dataStoreNode;
- QString m_selectedCollectionName;
- QStringList m_data;
-};
-
-} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionview.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectionview.cpp
deleted file mode 100644
index 0c9a2eed94..0000000000
--- a/src/plugins/qmldesigner/components/collectioneditor/collectionview.cpp
+++ /dev/null
@@ -1,501 +0,0 @@
-// Copyright (C) 2023 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#include "collectionview.h"
-
-#include "collectiondatatypemodel.h"
-#include "collectiondetailsmodel.h"
-#include "collectioneditorconstants.h"
-#include "collectioneditorutils.h"
-#include "collectionlistmodel.h"
-#include "collectionwidget.h"
-#include "datastoremodelnode.h"
-#include "designmodecontext.h"
-#include "nodeabstractproperty.h"
-#include "nodemetainfo.h"
-#include "nodeproperty.h"
-#include "qmldesignerplugin.h"
-#include "variantproperty.h"
-
-#include <projectexplorer/project.h>
-#include <projectexplorer/projectexplorer.h>
-#include <projectexplorer/projectmanager.h>
-#include <qmljs/qmljsmodelmanagerinterface.h>
-
-#include <coreplugin/icore.h>
-#include <utils/algorithm.h>
-#include <utils/qtcassert.h>
-
-#include <QTimer>
-
-namespace {
-
-bool isStudioCollectionModel(const QmlDesigner::ModelNode &node)
-{
- return node.metaInfo().isQtQuickStudioUtilsJsonListModel();
-}
-
-inline bool isProjectImport(const QmlDesigner::Import &import)
-{
- ProjectExplorer::Project *currentProject = ProjectExplorer::ProjectManager::startupProject();
- return currentProject && import.toString() == currentProject->displayName();
-}
-
-inline void setVariantPropertyValue(const QmlDesigner::ModelNode &node,
- const QmlDesigner::PropertyName &propertyName,
- const QVariant &value)
-{
- QmlDesigner::VariantProperty property = node.variantProperty(propertyName);
- property.setValue(value);
-}
-
-inline void setBindingPropertyExpression(const QmlDesigner::ModelNode &node,
- const QmlDesigner::PropertyName &propertyName,
- const QString &expression)
-{
- QmlDesigner::BindingProperty property = node.bindingProperty(propertyName);
- property.setExpression(expression);
-}
-
-} // namespace
-
-namespace QmlDesigner {
-
-CollectionView::CollectionView(ExternalDependenciesInterface &externalDependencies)
- : AbstractView(externalDependencies)
- , m_dataStore(std::make_unique<DataStoreModelNode>())
-
-{
-}
-
-CollectionView::~CollectionView() = default;
-
-bool CollectionView::hasWidget() const
-{
- return true;
-}
-
-QmlDesigner::WidgetInfo CollectionView::widgetInfo()
-{
- if (!m_widget) {
- m_widget = Utils::makeUniqueObjectPtr<CollectionWidget>(this);
- m_widget->setMinimumSize(m_widget->minimumSizeHint());
- connect(ProjectExplorer::ProjectManager::instance(),
- &ProjectExplorer::ProjectManager::startupProjectChanged, m_widget.get(), [&] {
- resetDataStoreNode();
- m_widget->collectionDetailsModel()->removeAllCollections();
- });
-
- auto collectionEditorContext = new Internal::CollectionEditorContext(m_widget.get());
- Core::ICore::addContextObject(collectionEditorContext);
- CollectionListModel *listModel = m_widget->listModel().data();
-
- connect(listModel,
- &CollectionListModel::selectedCollectionNameChanged,
- this,
- [this](const QString &collection) {
- m_widget->collectionDetailsModel()->loadCollection(dataStoreNode(), collection);
- });
-
- connect(listModel, &CollectionListModel::isEmptyChanged, this, [this](bool isEmpty) {
- if (isEmpty)
- m_widget->collectionDetailsModel()->loadCollection({}, {});
- });
-
- connect(listModel, &CollectionListModel::modelReset, this, [this] {
- CollectionListModel *listModel = m_widget->listModel().data();
- if (listModel->sourceNode() == dataStoreNode())
- m_dataStore->setCollectionNames(listModel->collections());
- });
-
- connect(listModel,
- &CollectionListModel::collectionAdded,
- this,
- [this](const QString &collectionName) { m_dataStore->addCollection(collectionName); });
-
- connect(listModel,
- &CollectionListModel::collectionNameChanged,
- this,
- [this](const QString &oldName, const QString &newName) {
- m_dataStore->renameCollection(oldName, newName);
- m_widget->collectionDetailsModel()->renameCollection(dataStoreNode(),
- oldName,
- newName);
- });
-
- connect(listModel,
- &CollectionListModel::collectionsRemoved,
- this,
- [this](const QStringList &collectionNames) {
- m_dataStore->removeCollections(collectionNames);
- for (const QString &collectionName : collectionNames) {
- m_widget->collectionDetailsModel()->removeCollection(dataStoreNode(),
- collectionName);
- }
- });
- }
-
- return createWidgetInfo(m_widget.get(),
- "CollectionEditor",
- WidgetInfo::LeftPane,
- 0,
- tr("Model Editor [beta]"),
- tr("Model Editor view"));
-}
-
-void CollectionView::modelAttached(Model *model)
-{
- AbstractView::modelAttached(model);
- m_widget->setProjectImportExists(Utils::anyOf(model->imports(), isProjectImport));
- resetDataStoreNode();
-}
-
-void CollectionView::modelAboutToBeDetached([[maybe_unused]] Model *model)
-{
- unloadDataStore();
- m_widget->setProjectImportExists(false);
-}
-
-void CollectionView::selectedNodesChanged(const QList<ModelNode> &selectedNodeList,
- [[maybe_unused]] const QList<ModelNode> &lastSelectedNodeList)
-{
- if (!m_widget)
- return;
-
- QList<ModelNode> selectedCollectionNodes = Utils::filtered(selectedNodeList,
- &isStudioCollectionModel);
-
- bool singleNonCollectionNodeSelected = selectedNodeList.size() == 1
- && selectedCollectionNodes.isEmpty();
-
- bool singleSelectedHasModelProperty = false;
- if (singleNonCollectionNodeSelected) {
- const ModelNode selectedNode = selectedNodeList.first();
- singleSelectedHasModelProperty = CollectionEditorUtils::canAcceptCollectionAsModel(
- selectedNode);
- }
-
- m_widget->setTargetNodeSelected(singleSelectedHasModelProperty);
-}
-
-void CollectionView::importsChanged(const Imports &addedImports, const Imports &removedImports)
-{
- if (Utils::anyOf(addedImports, isProjectImport)) {
- m_widget->setProjectImportExists(true);
- resetDataStoreNode();
- } else if (Utils::anyOf(removedImports, isProjectImport)) {
- m_widget->setProjectImportExists(false);
- unloadDataStore();
- }
-}
-
-void CollectionView::customNotification(const AbstractView *,
- const QString &identifier,
- const QList<ModelNode> &nodeList,
- const QList<QVariant> &data)
-{
- if (!m_widget)
- return;
-
- if (identifier == QLatin1String("item_library_created_by_drop") && !nodeList.isEmpty())
- onItemLibraryNodeCreated(nodeList.first());
- else if (identifier == QLatin1String("open_collection_by_id") && !data.isEmpty())
- m_widget->openCollection(collectionNameFromDataStoreChildren(data.first().toByteArray()));
- else if (identifier == "delete_selected_collection")
- m_widget->deleteSelectedCollection();
-}
-
-void CollectionView::addResource(const QUrl &url, const QString &name)
-{
- executeInTransaction(Q_FUNC_INFO, [this, &url, &name]() {
- ensureStudioModelImport();
- QString sourceAddress;
- if (url.isLocalFile()) {
- Utils::FilePath fp = QmlDesignerPlugin::instance()->currentDesignDocument()->fileName().parentDir();
- sourceAddress = Utils::FilePath::calcRelativePath(url.toLocalFile(),
- fp.absoluteFilePath().toString());
- } else {
- sourceAddress = url.toString();
- }
-#ifdef QDS_USE_PROJECTSTORAGE
- ModelNode resourceNode = createModelNode("JsonListModel");
-#else
- const NodeMetaInfo resourceMetaInfo = jsonCollectionMetaInfo();
- ModelNode resourceNode = createModelNode(resourceMetaInfo.typeName(),
- resourceMetaInfo.majorVersion(),
- resourceMetaInfo.minorVersion());
-#endif
- VariantProperty sourceProperty = resourceNode.variantProperty(
- CollectionEditorConstants::SOURCEFILE_PROPERTY);
- VariantProperty nameProperty = resourceNode.variantProperty("objectName");
- sourceProperty.setValue(sourceAddress);
- nameProperty.setValue(name);
- resourceNode.setIdWithoutRefactoring(model()->generateIdFromName(name, "model"));
- rootModelNode().defaultNodeAbstractProperty().reparentHere(resourceNode);
- });
-}
-
-void CollectionView::addProjectImport()
-{
- if (!m_widget)
- return;
-
- ProjectExplorer::Project *currentProject = ProjectExplorer::ProjectManager::startupProject();
- if (!currentProject)
- return;
-
- executeInTransaction(__FUNCTION__, [&] {
- Import import = Import::createLibraryImport(currentProject->displayName());
- if (!model()->hasImport(import, true, true))
- model()->changeImports({import}, {});
- });
-}
-
-void CollectionView::assignCollectionToNode(const QString &collectionName, const ModelNode &node)
-{
- if (!m_widget)
- return;
-
- using DataType = CollectionDetails::DataType;
- executeInTransaction("CollectionView::assignCollectionToNode", [&]() {
- m_dataStore->assignCollectionToNode(
- this,
- node,
- collectionName,
- [&](const QString &collectionName, const QString &columnName) -> bool {
- const CollectionReference reference{dataStoreNode(), collectionName};
- return m_widget->collectionDetailsModel()->collectionHasColumn(reference, columnName);
- },
- [&](const QString &collectionName) -> QString {
- const CollectionReference reference{dataStoreNode(), collectionName};
- return m_widget->collectionDetailsModel()->getFirstColumnName(reference);
- });
-
- // Create and assign a delegate to the list view item
- if (node.metaInfo().isQtQuickListView()) {
- CollectionDetails collection = m_widget->collectionDetailsModel()->upToDateConstCollection(
- {dataStoreNode(), collectionName});
-
- ModelNode rowItem(createModelNode("QtQuick.Row"));
- ::setVariantPropertyValue(rowItem, "spacing", 5);
-
- const int columnsCount = collection.columns();
- for (int column = 0; column < columnsCount; ++column) {
- const DataType dataType = collection.typeAt(column);
- const QString columnName = collection.propertyAt(column);
- ModelNode cellItem;
- if (dataType == DataType::Color) {
- cellItem = createModelNode("QtQuick.Rectangle");
- ::setBindingPropertyExpression(cellItem, "color", columnName);
- ::setVariantPropertyValue(cellItem, "height", 20);
- } else {
- cellItem = createModelNode("QtQuick.Text");
- ::setBindingPropertyExpression(cellItem, "text", columnName);
- }
- ::setVariantPropertyValue(cellItem, "width", 100);
- rowItem.defaultNodeAbstractProperty().reparentHere(cellItem);
- }
-
- NodeProperty delegateProperty = node.nodeProperty("delegate");
- // Remove the old model node if is available
- if (delegateProperty.modelNode())
- delegateProperty.modelNode().destroy();
-
- delegateProperty.setModelNode(rowItem);
- }
- });
-}
-
-void CollectionView::assignCollectionToSelectedNode(const QString &collectionName)
-{
- QTC_ASSERT(dataStoreNode() && hasSingleSelectedModelNode(), return);
- assignCollectionToNode(collectionName, singleSelectedModelNode());
-}
-
-void CollectionView::addNewCollection(const QString &collectionName, const QJsonObject &localCollection)
-{
- if (!m_widget)
- return;
-
- addTask(QSharedPointer<CollectionTask>(
- new AddCollectionTask(this, m_widget->listModel(), localCollection, collectionName)));
-}
-
-void CollectionView::openCollection(const QString &collectionName)
-{
- if (!m_widget)
- return;
-
- m_widget->openCollection(collectionName);
-}
-
-void CollectionView::registerDeclarativeType()
-{
- CollectionDetails::registerDeclarativeType();
- CollectionDataTypeModel::registerDeclarativeType();
-}
-
-void CollectionView::resetDataStoreNode()
-{
- if (!m_widget)
- return;
-
- m_dataStore->reloadModel();
-
- ModelNode dataStore = dataStoreNode();
- m_widget->setDataStoreExists(dataStore.isValid());
- if (!dataStore || m_widget->listModel()->sourceNode() == dataStore)
- return;
-
- bool dataStoreSingletonFound = m_dataStoreTypeFound;
- if (!dataStoreSingletonFound && rewriterView() && rewriterView()->isAttached()) {
- const QList<QmlTypeData> types = rewriterView()->getQMLTypes();
- for (const QmlTypeData &cppTypeData : types) {
- if (cppTypeData.isSingleton && cppTypeData.typeName == "DataStore") {
- dataStoreSingletonFound = true;
- break;
- }
- }
- if (!dataStoreSingletonFound && !m_rewriterAmended) {
- rewriterView()->forceAmend();
- m_rewriterAmended = true;
- }
- }
-
- if (dataStoreSingletonFound) {
- m_widget->listModel()->setDataStoreNode(dataStore);
- m_dataStoreTypeFound = true;
-
- while (!m_delayedTasks.isEmpty())
- m_delayedTasks.takeFirst()->process();
- } else if (++m_reloadCounter < 50) {
- QTimer::singleShot(200, this, &CollectionView::resetDataStoreNode);
- } else {
- QTC_ASSERT(false, m_delayedTasks.clear());
- }
-}
-
-ModelNode CollectionView::dataStoreNode() const
-{
- return m_dataStore->modelNode();
-}
-
-void CollectionView::ensureDataStoreExists()
-{
- bool filesJustCreated = false;
- bool filesExist = CollectionEditorUtils::ensureDataStoreExists(filesJustCreated);
- if (filesExist && filesJustCreated) {
- // Force code model reset to notice changes to existing module
- if (auto modelManager = QmlJS::ModelManagerInterface::instance())
- modelManager->resetCodeModel();
- resetDataStoreNode();
- }
-}
-
-QString CollectionView::collectionNameFromDataStoreChildren(const PropertyName &childPropertyName) const
-{
- return dataStoreNode()
- .nodeProperty(childPropertyName)
- .modelNode()
- .property(CollectionEditorConstants::JSONCHILDMODELNAME_PROPERTY)
- .toVariantProperty()
- .value()
- .toString();
-}
-
-NodeMetaInfo CollectionView::jsonCollectionMetaInfo() const
-{
- return model()->metaInfo(CollectionEditorConstants::JSONCOLLECTIONMODEL_TYPENAME);
-}
-
-void CollectionView::unloadDataStore()
-{
- m_reloadCounter = 0;
- m_rewriterAmended = false;
- m_dataStoreTypeFound = false;
- QTC_ASSERT(m_delayedTasks.isEmpty(), m_delayedTasks.clear());
- if (m_widget) {
- m_widget->setDataStoreExists(dataStoreNode().isValid());
- m_widget->listModel()->setDataStoreNode();
- }
-}
-
-void CollectionView::ensureStudioModelImport()
-{
- executeInTransaction(__FUNCTION__, [&] {
- Import import = Import::createLibraryImport(CollectionEditorConstants::COLLECTIONMODEL_IMPORT);
- try {
- if (!model()->hasImport(import, true, true))
- model()->changeImports({import}, {});
- } catch (const Exception &) {
- QTC_ASSERT(false, return);
- }
- });
-}
-
-void CollectionView::onItemLibraryNodeCreated(const ModelNode &node)
-{
- if (!m_widget)
- return;
-
- if (node.metaInfo().isQtQuickListView()) {
- addTask(QSharedPointer<CollectionTask>(
- new DropListViewTask(this, m_widget->listModel(), node)));
- }
-}
-
-void CollectionView::addTask(QSharedPointer<CollectionTask> task)
-{
- ensureDataStoreExists();
- if (m_dataStoreTypeFound)
- task->process();
- else if (dataStoreNode())
- m_delayedTasks << task;
-}
-
-CollectionTask::CollectionTask(CollectionView *view, CollectionListModel *listModel)
- : m_collectionView(view)
- , m_listModel(listModel)
-{}
-
-DropListViewTask::DropListViewTask(CollectionView *view,
- CollectionListModel *listModel,
- const ModelNode &node)
- : CollectionTask(view, listModel)
- , m_node(node)
-{}
-
-void DropListViewTask::process()
-{
- AbstractView *view = m_node.view();
- if (!m_node || !m_collectionView || !m_listModel || !view)
- return;
-
- const QString newCollectionName = m_listModel->getUniqueCollectionName("ListModel");
- m_listModel->addCollection(newCollectionName, CollectionEditorUtils::defaultColorCollection());
- m_collectionView->openCollection(newCollectionName);
- m_collectionView->assignCollectionToNode(newCollectionName, m_node);
-}
-
-AddCollectionTask::AddCollectionTask(CollectionView *view,
- CollectionListModel *listModel,
- const QJsonObject &localJsonObject,
- const QString &collectionName)
- : CollectionTask(view, listModel)
- , m_localJsonObject(localJsonObject)
- , m_name(collectionName)
-{}
-
-void AddCollectionTask::process()
-{
- if (!m_listModel)
- return;
-
- const QString newCollectionName = m_listModel->collectionExists(m_name)
- ? m_listModel->getUniqueCollectionName(m_name)
- : m_name;
-
- m_listModel->addCollection(newCollectionName, m_localJsonObject);
-}
-
-} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionview.h b/src/plugins/qmldesigner/components/collectioneditor/collectionview.h
deleted file mode 100644
index 3de3bd7ae6..0000000000
--- a/src/plugins/qmldesigner/components/collectioneditor/collectionview.h
+++ /dev/null
@@ -1,124 +0,0 @@
-// Copyright (C) 2023 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#pragma once
-
-#include "datastoremodelnode.h"
-
-#include <abstractview.h>
-#include <modelnode.h>
-
-#include <utils/uniqueobjectptr.h>
-
-#include <QJsonObject>
-
-namespace QmlJS {
-class Document;
-}
-
-namespace QmlDesigner {
-
-class CollectionDetails;
-class CollectionListModel;
-class CollectionTask;
-class CollectionWidget;
-class DataStoreModelNode;
-
-class CollectionView : public AbstractView
-{
- Q_OBJECT
-
-public:
- explicit CollectionView(ExternalDependenciesInterface &externalDependencies);
- ~CollectionView();
-
- bool hasWidget() const override;
- WidgetInfo widgetInfo() override;
-
- void modelAttached(Model *model) override;
- void modelAboutToBeDetached(Model *model) override;
-
- void selectedNodesChanged(const QList<ModelNode> &selectedNodeList,
- const QList<ModelNode> &lastSelectedNodeList) override;
-
- void importsChanged(const Imports &addedImports, const Imports &removedImports) override;
-
- void customNotification(const AbstractView *view,
- const QString &identifier,
- const QList<ModelNode> &nodeList,
- const QList<QVariant> &data) override;
-
- void addResource(const QUrl &url, const QString &name);
-
- void addProjectImport();
- void assignCollectionToNode(const QString &collectionName, const ModelNode &node);
- void assignCollectionToSelectedNode(const QString &collectionName);
- void addNewCollection(const QString &collectionName, const QJsonObject &localCollection);
-
- void openCollection(const QString &collectionName);
-
- static void registerDeclarativeType();
-
- void resetDataStoreNode();
- ModelNode dataStoreNode() const;
- void ensureDataStoreExists();
- QString collectionNameFromDataStoreChildren(const PropertyName &childPropertyName) const;
-
-private:
- friend class CollectionTask;
-
- NodeMetaInfo jsonCollectionMetaInfo() const;
- void unloadDataStore();
- void ensureStudioModelImport();
- void onItemLibraryNodeCreated(const ModelNode &node);
- void addTask(QSharedPointer<CollectionTask> task);
-
- std::unique_ptr<DataStoreModelNode> m_dataStore;
- Utils::UniqueObjectPtr<CollectionWidget> m_widget;
- QList<QSharedPointer<CollectionTask>> m_delayedTasks;
- bool m_dataStoreTypeFound = false;
- bool m_rewriterAmended = false;
- int m_reloadCounter = 0;
-};
-
-class CollectionTask
-{
-public:
- CollectionTask(CollectionView *view, CollectionListModel *listModel);
- CollectionTask() = delete;
- virtual ~CollectionTask() = default;
-
- virtual void process() = 0;
-
-protected:
- QPointer<CollectionView> m_collectionView;
- QPointer<CollectionListModel> m_listModel;
-};
-
-class DropListViewTask : public CollectionTask
-{
-public:
- DropListViewTask(CollectionView *view, CollectionListModel *listModel, const ModelNode &node);
-
- void process() override;
-
-private:
- ModelNode m_node;
-};
-
-class AddCollectionTask : public CollectionTask
-{
-public:
- AddCollectionTask(CollectionView *view,
- CollectionListModel *listModel,
- const QJsonObject &localJsonObject,
- const QString &collectionName);
-
- void process() override;
-
-private:
- QJsonObject m_localJsonObject;
- QString m_name;
-};
-
-} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.cpp
deleted file mode 100644
index dd706145cf..0000000000
--- a/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.cpp
+++ /dev/null
@@ -1,320 +0,0 @@
-// Copyright (C) 2023 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#include "collectionwidget.h"
-
-#include "collectiondetails.h"
-#include "collectiondetailsmodel.h"
-#include "collectiondetailssortfiltermodel.h"
-#include "collectioneditorutils.h"
-#include "collectionlistmodel.h"
-#include "collectionview.h"
-#include "designmodewidget.h"
-#include "qmldesignerconstants.h"
-#include "qmldesignerplugin.h"
-#include "theme.h"
-
-#include <coreplugin/icore.h>
-#include <coreplugin/messagebox.h>
-#include <studioquickwidget.h>
-
-#include <QFileInfo>
-#include <QJsonArray>
-#include <QJsonDocument>
-#include <QJsonObject>
-#include <QJsonParseError>
-#include <QMetaObject>
-#include <QQmlEngine>
-#include <QQuickItem>
-#include <QShortcut>
-#include <QVBoxLayout>
-
-namespace {
-
-QString collectionViewResourcesPath()
-{
-#ifdef SHARE_QML_PATH
- if (qEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE"))
- return QLatin1String(SHARE_QML_PATH) + "/collectionEditorQmlSource";
-#endif
- return Core::ICore::resourcePath("qmldesigner/collectionEditorQmlSource").toString();
-}
-
-QString getPreferredCollectionName(const QUrl &url, const QString &collectionName)
-{
- if (collectionName.isEmpty()) {
- QFileInfo fileInfo(url.isLocalFile() ? url.toLocalFile() : url.toString());
- return fileInfo.completeBaseName();
- }
-
- return collectionName;
-}
-
-} // namespace
-
-namespace QmlDesigner {
-CollectionWidget::CollectionWidget(CollectionView *view)
- : m_view(view)
- , m_listModel(new CollectionListModel)
- , m_collectionDetailsModel(new CollectionDetailsModel)
- , m_collectionDetailsSortFilterModel(std::make_unique<CollectionDetailsSortFilterModel>())
- , m_quickWidget(new StudioQuickWidget(this))
-{
- setWindowTitle(tr("Model Editor", "Title of model editor widget"));
-
- Core::IContext *icontext = nullptr;
- Core::Context context(Constants::C_QMLCOLLECTIONEDITOR);
- icontext = new Core::IContext(this);
- icontext->setContext(context);
- icontext->setWidget(this);
-
- connect(m_listModel, &CollectionListModel::warning, this, &CollectionWidget::warn);
-
- m_collectionDetailsSortFilterModel->setSourceModel(m_collectionDetailsModel);
-
- m_quickWidget->quickWidget()->setObjectName(Constants::OBJECT_NAME_COLLECTION_EDITOR);
- m_quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
- m_quickWidget->engine()->addImportPath(collectionViewResourcesPath() + "/imports");
- m_quickWidget->setClearColor(Theme::getColor(Theme::Color::DSpanelBackground));
-
- Theme::setupTheme(m_quickWidget->engine());
- m_quickWidget->quickWidget()->installEventFilter(this);
-
- auto layout = new QVBoxLayout(this);
- layout->setContentsMargins({});
- layout->setSpacing(0);
- layout->addWidget(m_quickWidget.data());
-
- qmlRegisterAnonymousType<CollectionWidget>("CollectionEditorBackend", 1);
- auto map = m_quickWidget->registerPropertyMap("CollectionEditorBackend");
- map->setProperties({
- {"rootView", QVariant::fromValue(this)},
- {"model", QVariant::fromValue(m_listModel.data())},
- {"collectionDetailsModel", QVariant::fromValue(m_collectionDetailsModel.data())},
- {"collectionDetailsSortFilterModel",
- QVariant::fromValue(m_collectionDetailsSortFilterModel.get())},
- });
-
- auto hotReloadShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_F4), this);
- connect(hotReloadShortcut, &QShortcut::activated, this, &CollectionWidget::reloadQmlSource);
-
- reloadQmlSource();
-
- QmlDesignerPlugin::trackWidgetFocusTime(this, Constants::EVENT_MODELEDITOR_TIME);
-}
-
-CollectionWidget::~CollectionWidget() = default;
-
-void CollectionWidget::contextHelp(const Core::IContext::HelpCallback &callback) const
-{
- if (m_view)
- QmlDesignerPlugin::contextHelp(callback, m_view->contextHelpId());
- else
- callback({});
-}
-
-QPointer<CollectionListModel> CollectionWidget::listModel() const
-{
- return m_listModel;
-}
-
-QPointer<CollectionDetailsModel> CollectionWidget::collectionDetailsModel() const
-{
- return m_collectionDetailsModel;
-}
-
-void CollectionWidget::reloadQmlSource()
-{
- const QString collectionViewQmlPath = collectionViewResourcesPath() + "/CollectionView.qml";
-
- QTC_ASSERT(QFileInfo::exists(collectionViewQmlPath), return);
-
- m_quickWidget->setSource(QUrl::fromLocalFile(collectionViewQmlPath));
-
- if (!m_quickWidget->rootObject()) {
- QString errorString;
- const auto errors = m_quickWidget->errors();
- for (const QQmlError &error : errors)
- errorString.append("\n" + error.toString());
-
- Core::AsynchronousMessageBox::warning(tr("Cannot Create QtQuick View"),
- tr("StatesEditorWidget: %1 cannot be created.%2")
- .arg(collectionViewQmlPath, errorString));
- return;
- }
-}
-
-QSize CollectionWidget::minimumSizeHint() const
-{
- return {300, 300};
-}
-
-bool CollectionWidget::loadJsonFile(const QUrl &url, const QString &collectionName)
-{
- if (!isJsonFile(url))
- return false;
-
- m_view->addResource(url, getPreferredCollectionName(url, collectionName));
-
- return true;
-}
-
-bool CollectionWidget::loadCsvFile(const QUrl &url, const QString &collectionName)
-{
- m_view->addResource(url, getPreferredCollectionName(url, collectionName));
-
- return true;
-}
-
-bool CollectionWidget::isJsonFile(const QUrl &url) const
-{
- Utils::FilePath filePath = Utils::FilePath::fromUserInput(url.isLocalFile() ? url.toLocalFile()
- : url.toString());
- Utils::FileReader file;
- if (!file.fetch(filePath))
- return false;
-
- QJsonParseError error;
- QJsonDocument::fromJson(file.data(), &error);
- if (error.error)
- return false;
-
- return true;
-}
-
-bool CollectionWidget::isCsvFile(const QUrl &url) const
-{
- QString filePath = url.isLocalFile() ? url.toLocalFile() : url.toString();
- QFileInfo fileInfo(filePath);
- return fileInfo.exists() && !fileInfo.suffix().compare("csv", Qt::CaseInsensitive);
-}
-
-bool CollectionWidget::isValidUrlToImport(const QUrl &url) const
-{
- using Utils::FilePath;
- FilePath fileInfo = FilePath::fromUserInput(url.isLocalFile() ? url.toLocalFile()
- : url.toString());
- if (fileInfo.suffix() == "json")
- return isJsonFile(url);
-
- if (fileInfo.suffix() == "csv")
- return isCsvFile(url);
-
- return false;
-}
-
-bool CollectionWidget::importFile(const QString &collectionName,
- const QUrl &url,
- const bool &firstRowIsHeader)
-{
- using Utils::FilePath;
-
- FilePath fileInfo = FilePath::fromUserInput(url.isLocalFile() ? url.toLocalFile()
- : url.toString());
- CollectionDetails loadedCollection;
- QByteArray fileContent;
-
- auto loadUrlContent = [&]() -> bool {
- Utils::FileReader file;
- if (file.fetch(fileInfo)) {
- fileContent = file.data();
- return true;
- }
-
- warn(tr("Import from file"), tr("Cannot import from file \"%1\"").arg(fileInfo.fileName()));
- return false;
- };
-
- if (fileInfo.suffix() == "json") {
- if (!loadUrlContent())
- return false;
-
- QJsonParseError parseError;
- loadedCollection = CollectionDetails::fromImportedJson(fileContent, &parseError);
- if (parseError.error != QJsonParseError::NoError) {
- warn(tr("Json file Import error"),
- tr("Cannot parse json content\n%1").arg(parseError.errorString()));
- }
- } else if (fileInfo.suffix() == "csv") {
- if (!loadUrlContent())
- return false;
- loadedCollection = CollectionDetails::fromImportedCsv(fileContent, firstRowIsHeader);
- }
-
- if (loadedCollection.columns()) {
- m_view->addNewCollection(collectionName, loadedCollection.toLocalJson());
- return true;
- } else {
- warn(tr("Can not add a model to the JSON file"),
- tr("The imported model is empty or is not supported."));
- }
- return false;
-}
-
-void CollectionWidget::addProjectImport()
-{
- m_view->addProjectImport();
-}
-
-void CollectionWidget::addCollectionToDataStore(const QString &collectionName)
-{
- m_view->addNewCollection(collectionName, CollectionEditorUtils::defaultCollection());
-}
-
-void CollectionWidget::assignCollectionToSelectedNode(const QString collectionName)
-{
- m_view->assignCollectionToSelectedNode(collectionName);
-}
-
-void CollectionWidget::openCollection(const QString &collectionName)
-{
- m_listModel->selectCollectionName(collectionName);
- QmlDesignerPlugin::instance()->mainWidget()->showDockWidget("CollectionEditor", true);
-}
-
-ModelNode CollectionWidget::dataStoreNode() const
-{
- return m_view->dataStoreNode();
-}
-
-void CollectionWidget::warn(const QString &title, const QString &body)
-{
- QMetaObject::invokeMethod(m_quickWidget->rootObject(),
- "showWarning",
- Q_ARG(QVariant, title),
- Q_ARG(QVariant, body));
-}
-
-void CollectionWidget::setTargetNodeSelected(bool selected)
-{
- if (m_targetNodeSelected == selected)
- return;
-
- m_targetNodeSelected = selected;
- emit targetNodeSelectedChanged(m_targetNodeSelected);
-}
-
-void CollectionWidget::setProjectImportExists(bool exists)
-{
- if (m_projectImportExists == exists)
- return;
-
- m_projectImportExists = exists;
- emit projectImportExistsChanged(m_projectImportExists);
-}
-
-void CollectionWidget::setDataStoreExists(bool exists)
-{
- if (m_dataStoreExists == exists)
- return;
-
- m_dataStoreExists = exists;
- emit dataStoreExistsChanged(m_dataStoreExists);
-}
-
-void CollectionWidget::deleteSelectedCollection()
-{
- QMetaObject::invokeMethod(m_quickWidget->quickWidget()->rootObject(), "deleteSelectedCollection");
-}
-
-} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.h b/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.h
deleted file mode 100644
index 13c3566c78..0000000000
--- a/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.h
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright (C) 2023 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#pragma once
-
-#include <QFrame>
-
-#include <coreplugin/icontext.h>
-
-class StudioQuickWidget;
-
-namespace QmlDesigner {
-
-class CollectionDetailsModel;
-class CollectionDetailsSortFilterModel;
-class CollectionListModel;
-class CollectionView;
-class ModelNode;
-
-class CollectionWidget : public QFrame
-{
- Q_OBJECT
-
- Q_PROPERTY(bool targetNodeSelected MEMBER m_targetNodeSelected NOTIFY targetNodeSelectedChanged)
- Q_PROPERTY(bool projectImportExists MEMBER m_projectImportExists NOTIFY projectImportExistsChanged)
- Q_PROPERTY(bool dataStoreExists MEMBER m_dataStoreExists NOTIFY dataStoreExistsChanged)
-
-public:
- CollectionWidget(CollectionView *view);
- ~CollectionWidget();
- void contextHelp(const Core::IContext::HelpCallback &callback) const;
-
- QPointer<CollectionListModel> listModel() const;
- QPointer<CollectionDetailsModel> collectionDetailsModel() const;
-
- void reloadQmlSource();
-
- QSize minimumSizeHint() const override;
-
- Q_INVOKABLE bool loadJsonFile(const QUrl &url, const QString &collectionName = {});
- Q_INVOKABLE bool loadCsvFile(const QUrl &url, const QString &collectionName = {});
- Q_INVOKABLE bool isJsonFile(const QUrl &url) const;
- Q_INVOKABLE bool isCsvFile(const QUrl &url) const;
- Q_INVOKABLE bool isValidUrlToImport(const QUrl &url) const;
-
- Q_INVOKABLE bool importFile(const QString &collectionName,
- const QUrl &url,
- const bool &firstRowIsHeader = true);
-
- Q_INVOKABLE void addProjectImport();
- Q_INVOKABLE void addCollectionToDataStore(const QString &collectionName);
- Q_INVOKABLE void assignCollectionToSelectedNode(const QString collectionName);
- Q_INVOKABLE void openCollection(const QString &collectionName);
- Q_INVOKABLE ModelNode dataStoreNode() const;
-
- void warn(const QString &title, const QString &body);
- void setTargetNodeSelected(bool selected);
- void setProjectImportExists(bool exists);
- void setDataStoreExists(bool exists);
-
- void deleteSelectedCollection();
-
-signals:
- void targetNodeSelectedChanged(bool);
- void projectImportExistsChanged(bool);
- void dataStoreExistsChanged(bool);
-
-private:
- QString generateUniqueCollectionName(const ModelNode &node, const QString &name);
-
- QPointer<CollectionView> m_view;
- QPointer<CollectionListModel> m_listModel;
- QPointer<CollectionDetailsModel> m_collectionDetailsModel;
- std::unique_ptr<CollectionDetailsSortFilterModel> m_collectionDetailsSortFilterModel;
- QScopedPointer<StudioQuickWidget> m_quickWidget;
- bool m_targetNodeSelected = false;
- bool m_projectImportExists = false;
- bool m_dataStoreExists = false;
-};
-
-} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/collectioneditor/datastoremodelnode.cpp b/src/plugins/qmldesigner/components/collectioneditor/datastoremodelnode.cpp
deleted file mode 100644
index 5be9c20f9e..0000000000
--- a/src/plugins/qmldesigner/components/collectioneditor/datastoremodelnode.cpp
+++ /dev/null
@@ -1,511 +0,0 @@
-// Copyright (C) 2023 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#include "datastoremodelnode.h"
-
-#include "abstractview.h"
-#include "collectioneditorconstants.h"
-#include "collectioneditorutils.h"
-#include "model/qmltextgenerator.h"
-#include "plaintexteditmodifier.h"
-#include "qmldesignerbase/qmldesignerbaseplugin.h"
-#include "qmldesignerexternaldependencies.h"
-#include "rewriterview.h"
-
-#include <model.h>
-#include <nodemetainfo.h>
-#include <nodeproperty.h>
-#include <variantproperty.h>
-
-#include <qmljstools/qmljscodestylepreferences.h>
-#include <qmljstools/qmljstoolssettings.h>
-
-#include <coreplugin/documentmanager.h>
-#include <projectexplorer/project.h>
-#include <projectexplorer/projectexplorer.h>
-#include <projectexplorer/projectmanager.h>
-
-#include <utils/fileutils.h>
-#include <utils/qtcassert.h>
-
-#include <QPlainTextEdit>
-#include <QRegularExpression>
-#include <QRegularExpressionMatch>
-#include <QScopedPointer>
-
-namespace {
-
-inline constexpr char CHILDLISTMODEL_TYPENAME[] = "ChildListModel";
-
-QmlDesigner::PropertyNameList createNameList(const QmlDesigner::ModelNode &node)
-{
- using QmlDesigner::AbstractProperty;
- using QmlDesigner::PropertyName;
- using QmlDesigner::PropertyNameList;
- static PropertyNameList defaultsNodeProps = {
- "id",
- QmlDesigner::CollectionEditorConstants::SOURCEFILE_PROPERTY,
- QmlDesigner::CollectionEditorConstants::JSONCHILDMODELNAME_PROPERTY,
- "backend"};
- PropertyNameList dynamicPropertyNames = Utils::transform(
- node.dynamicProperties(),
- [](const AbstractProperty &property) -> PropertyName { return property.name(); });
-
- Utils::sort(dynamicPropertyNames);
-
- return defaultsNodeProps + dynamicPropertyNames;
-}
-
-bool isValidCollectionPropertyName(const QString &collectionId)
-{
- static const QmlDesigner::PropertyNameList reservedKeywords = {
- QmlDesigner::CollectionEditorConstants::SOURCEFILE_PROPERTY,
- QmlDesigner::CollectionEditorConstants::JSONBACKEND_TYPENAME,
- "backend",
- "models",
- };
-
- return QmlDesigner::ModelNode::isValidId(collectionId)
- && !reservedKeywords.contains(collectionId.toLatin1());
-}
-
-QMap<QString, QmlDesigner::PropertyName> getModelIdMap(const QmlDesigner::ModelNode &rootNode)
-{
- using namespace QmlDesigner;
- QMap<QString, PropertyName> modelNameForId;
-
- const QList<AbstractProperty> propertyNames = rootNode.dynamicProperties();
-
- for (const AbstractProperty &property : std::as_const(propertyNames)) {
- if (!property.isNodeProperty())
- continue;
-
- NodeProperty nodeProperty = property.toNodeProperty();
- if (!nodeProperty.hasDynamicTypeName(CHILDLISTMODEL_TYPENAME))
- continue;
-
- ModelNode childNode = nodeProperty.modelNode();
- if (childNode.hasProperty(CollectionEditorConstants::JSONCHILDMODELNAME_PROPERTY)) {
- QString modelName = childNode
- .property(CollectionEditorConstants::JSONCHILDMODELNAME_PROPERTY)
- .toVariantProperty()
- .value()
- .toString();
-
- if (!modelName.isEmpty())
- modelNameForId.insert(modelName, property.name());
- }
- }
- return modelNameForId;
-}
-
-void setQmlContextToModel(QmlDesigner::Model *model, const QString &qmlContext)
-{
- using namespace QmlDesigner;
- Q_ASSERT(model);
-
- QScopedPointer<QPlainTextEdit> textEdit(new QPlainTextEdit);
- QScopedPointer<NotIndentingTextEditModifier> modifier(
- new NotIndentingTextEditModifier(textEdit.data()));
- textEdit->hide();
- textEdit->setPlainText(qmlContext);
- QmlDesigner::ExternalDependencies externalDependencies{QmlDesignerBasePlugin::settings()};
- QScopedPointer<RewriterView> rewriter(
- new RewriterView(externalDependencies, QmlDesigner::RewriterView::Validate));
-
- rewriter->setParent(model);
- rewriter->setTextModifier(modifier.get());
- rewriter->setCheckSemanticErrors(false);
-
- model->attachView(rewriter.get());
- model->detachView(rewriter.get());
-}
-
-} // namespace
-
-namespace QmlDesigner {
-
-DataStoreModelNode::DataStoreModelNode()
-{
- reloadModel();
-}
-
-void DataStoreModelNode::reloadModel()
-{
- using Utils::FilePath;
- if (!ProjectExplorer::ProjectManager::startupProject()) {
- reset();
- return;
- }
- bool forceUpdate = false;
-
- const FilePath dataStoreQmlPath = CollectionEditorUtils::dataStoreQmlFilePath();
- const FilePath dataStoreJsonPath = CollectionEditorUtils::dataStoreJsonFilePath();
- QUrl dataStoreQmlUrl = dataStoreQmlPath.toUrl();
-
- if (dataStoreQmlPath.exists() && dataStoreJsonPath.exists()) {
- if (!m_model.get() || m_model->fileUrl() != dataStoreQmlUrl) {
-#ifdef QDS_USE_PROJECTSTORAGE
- m_model = model()->createModel("JsonListModel");
- forceUpdate = true;
- Import import = Import::createLibraryImport("QtQuick.Studio.Utils");
- m_model->changeImports({import}, {});
-#else
- m_model = Model::create(CollectionEditorConstants::JSONCOLLECTIONMODEL_TYPENAME, 1, 1);
- forceUpdate = true;
- Import import = Import::createLibraryImport(
- CollectionEditorConstants::COLLECTIONMODEL_IMPORT);
- try {
- if (!m_model->hasImport(import, true, true))
- m_model->changeImports({import}, {});
- } catch (const Exception &) {
- QTC_ASSERT(false, return);
- }
-#endif
- }
- } else {
- reset();
- }
-
- if (!m_model.get())
- return;
-
- if (forceUpdate) {
- m_model->setFileUrl(dataStoreQmlUrl);
- m_dataRelativePath = dataStoreJsonPath.relativePathFrom(dataStoreQmlPath).toFSPathString();
- preloadFile();
- update();
- }
-}
-
-QStringList DataStoreModelNode::collectionNames() const
-{
- return m_collectionPropertyNames.keys();
-}
-
-Model *DataStoreModelNode::model() const
-{
- return m_model.get();
-}
-
-ModelNode DataStoreModelNode::modelNode() const
-{
- if (!m_model.get())
- return {};
- return m_model->rootModelNode();
-}
-
-QString DataStoreModelNode::getModelQmlText()
-{
- ModelNode node = modelNode();
- QTC_ASSERT(node, return {});
-
- Internal::QmlTextGenerator textGen(createNameList(node),
- QmlJSTools::QmlJSToolsSettings::globalCodeStyle()->tabSettings());
-
- QString genText = textGen(node);
- return genText;
-}
-
-void DataStoreModelNode::reset()
-{
- if (m_model)
- m_model.reset();
-
- m_dataRelativePath.clear();
- setCollectionNames({});
-}
-
-void DataStoreModelNode::preloadFile()
-{
- using Utils::FilePath;
- using Utils::FileReader;
-
- if (!m_model)
- return;
-
- const FilePath dataStoreQmlPath = dataStoreQmlFilePath();
- FileReader dataStoreQmlFile;
- QString sourceQmlContext;
-
- if (dataStoreQmlFile.fetch(dataStoreQmlPath))
- sourceQmlContext = QString::fromLatin1(dataStoreQmlFile.data());
-
- setQmlContextToModel(m_model.get(), sourceQmlContext);
- m_collectionPropertyNames = getModelIdMap(m_model->rootModelNode());
-}
-
-void DataStoreModelNode::updateDataStoreProperties()
-{
- QTC_ASSERT(model(), return);
-
- ModelNode rootNode = modelNode();
- QTC_ASSERT(rootNode.isValid(), return);
-
- QSet<QString> collectionNamesToBeAdded;
- const QStringList allCollectionNames = m_collectionPropertyNames.keys();
- for (const QString &collectionName : allCollectionNames)
- collectionNamesToBeAdded << collectionName;
-
- const QList<AbstractProperty> formerPropertyNames = rootNode.dynamicProperties();
-
- // Remove invalid collection names from the properties
- for (const AbstractProperty &property : formerPropertyNames) {
- if (!property.isNodeProperty())
- continue;
-
- NodeProperty nodeProprty = property.toNodeProperty();
- if (!nodeProprty.hasDynamicTypeName(CHILDLISTMODEL_TYPENAME))
- continue;
-
- ModelNode childNode = nodeProprty.modelNode();
- if (childNode.hasProperty(CollectionEditorConstants::JSONCHILDMODELNAME_PROPERTY)) {
- QString modelName = childNode
- .property(CollectionEditorConstants::JSONCHILDMODELNAME_PROPERTY)
- .toVariantProperty()
- .value()
- .toString();
- if (collectionNamesToBeAdded.contains(modelName)) {
- m_collectionPropertyNames.insert(modelName, property.name());
- collectionNamesToBeAdded.remove(modelName);
- } else {
- rootNode.removeProperty(property.name());
- }
- } else {
- rootNode.removeProperty(property.name());
- }
- }
-
- rootNode.setIdWithoutRefactoring("models");
-
- QStringList collectionNamesLeft = collectionNamesToBeAdded.values();
- Utils::sort(collectionNamesLeft);
- for (const QString &collectionName : std::as_const(collectionNamesLeft))
- addCollectionNameToTheModel(collectionName, getUniquePropertyName(collectionName));
-
- // Backend Property
- ModelNode backendNode = model()->createModelNode(CollectionEditorConstants::JSONBACKEND_TYPENAME);
- NodeProperty backendProperty = rootNode.nodeProperty("backend");
- backendProperty.setDynamicTypeNameAndsetModelNode(CollectionEditorConstants::JSONBACKEND_TYPENAME,
- backendNode);
- // Source Property
- VariantProperty sourceProp = rootNode.variantProperty(
- CollectionEditorConstants::SOURCEFILE_PROPERTY);
- sourceProp.setValue(m_dataRelativePath);
-}
-
-void DataStoreModelNode::updateSingletonFile()
-{
- using Utils::FilePath;
- using Utils::FileSaver;
- QTC_ASSERT(m_model.get(), return);
-
- const QString pragmaSingleTone = "pragma Singleton\n";
- QString imports;
-
- for (const Import &import : m_model->imports())
- imports += QStringLiteral("import %1\n").arg(import.toString(true));
-
- QString content = pragmaSingleTone + imports + getModelQmlText();
- Core::DocumentManager::expectFileChange(dataStoreQmlFilePath());
- FileSaver file(dataStoreQmlFilePath());
- file.write(content.toLatin1());
- file.finalize();
-}
-
-void DataStoreModelNode::update()
-{
- if (!m_model.get())
- return;
-
- updateDataStoreProperties();
- updateSingletonFile();
-}
-
-void DataStoreModelNode::addCollectionNameToTheModel(const QString &collectionName,
- const PropertyName &dataStorePropertyName)
-{
- ModelNode rootNode = modelNode();
- QTC_ASSERT(rootNode.isValid(), return);
-
- if (dataStorePropertyName.isEmpty()) {
- qWarning() << __FUNCTION__ << __LINE__
- << QString("The property name cannot be generated from \"%1\"").arg(collectionName);
- return;
- }
-
- ModelNode collectionNode = model()->createModelNode(CHILDLISTMODEL_TYPENAME);
- VariantProperty modelNameProperty = collectionNode.variantProperty(
- CollectionEditorConstants::JSONCHILDMODELNAME_PROPERTY);
- modelNameProperty.setValue(collectionName);
-
- NodeProperty nodeProp = rootNode.nodeProperty(dataStorePropertyName);
- nodeProp.setDynamicTypeNameAndsetModelNode(CHILDLISTMODEL_TYPENAME, collectionNode);
-
- m_collectionPropertyNames.insert(collectionName, dataStorePropertyName);
-}
-
-Utils::FilePath DataStoreModelNode::dataStoreQmlFilePath() const
-{
- QUrl modelUrl = m_model->fileUrl();
- return Utils::FilePath::fromUserInput(modelUrl.isLocalFile() ? modelUrl.toLocalFile()
- : modelUrl.toString());
-}
-
-PropertyName DataStoreModelNode::getUniquePropertyName(const QString &collectionName)
-{
- ModelNode dataStoreNode = modelNode();
- QTC_ASSERT(!collectionName.isEmpty() && dataStoreNode.isValid(), return {});
-
- QString newProperty;
-
- // convert to camel case
- QStringList nameWords = collectionName.split(' ');
- nameWords[0] = nameWords[0].at(0).toLower() + nameWords[0].mid(1);
- for (int i = 1; i < nameWords.size(); ++i)
- nameWords[i] = nameWords[i].at(0).toUpper() + nameWords[i].mid(1);
- newProperty = nameWords.join("");
-
- // if id starts with a number prepend an underscore
- if (newProperty.at(0).isDigit())
- newProperty.prepend('_');
-
- // If the new id is not valid (e.g. qml keyword match), prepend an underscore
- if (!isValidCollectionPropertyName(newProperty))
- newProperty.prepend('_');
-
- static const QRegularExpression rgx("\\d+$"); // matches a number at the end of a string
- while (dataStoreNode.hasProperty(newProperty.toLatin1())) { // id exists
- QRegularExpressionMatch match = rgx.match(newProperty);
- if (match.hasMatch()) { // ends with a number, increment it
- QString numStr = match.captured();
- int num = numStr.toInt() + 1;
- newProperty = newProperty.mid(0, match.capturedStart()) + QString::number(num);
- } else {
- newProperty.append('1');
- }
- }
-
- return newProperty.toLatin1();
-}
-
-void DataStoreModelNode::setCollectionNames(const QStringList &newCollectionNames)
-{
- m_collectionPropertyNames.clear();
- for (const QString &collectionName : newCollectionNames)
- m_collectionPropertyNames.insert(collectionName, {});
- update();
-}
-
-void DataStoreModelNode::addCollection(const QString &collectionName)
-{
- if (!m_collectionPropertyNames.contains(collectionName)) {
- m_collectionPropertyNames.insert(collectionName, {});
- update();
- }
-}
-
-void DataStoreModelNode::renameCollection(const QString &oldName, const QString &newName)
-{
- ModelNode dataStoreNode = modelNode();
- QTC_ASSERT(dataStoreNode.isValid(), return);
-
- if (m_collectionPropertyNames.contains(oldName)) {
- const PropertyName oldPropertyName = m_collectionPropertyNames.value(oldName);
- if (!oldPropertyName.isEmpty() && dataStoreNode.hasProperty(oldPropertyName)) {
- NodeProperty collectionNode = dataStoreNode.property(oldPropertyName).toNodeProperty();
- if (collectionNode.isValid()) {
- VariantProperty modelNameProperty = collectionNode.modelNode().variantProperty(
- CollectionEditorConstants::JSONCHILDMODELNAME_PROPERTY);
- modelNameProperty.setValue(newName);
- m_collectionPropertyNames.remove(oldName);
- m_collectionPropertyNames.insert(newName, collectionNode.name());
- update();
- return;
- }
- qWarning() << __FUNCTION__ << __LINE__
- << "There is no valid node for the old collection name";
- return;
- }
- qWarning() << __FUNCTION__ << __LINE__ << QString("Invalid old property name")
- << oldPropertyName;
- return;
- }
- qWarning() << __FUNCTION__ << __LINE__
- << QString("There is no old collection name registered with this name \"%1\"").arg(oldName);
-}
-
-void DataStoreModelNode::removeCollections(const QStringList &collectionNames)
-{
- bool updateRequired = false;
- for (const QString &collectionName : collectionNames) {
- if (m_collectionPropertyNames.contains(collectionName)) {
- m_collectionPropertyNames.remove(collectionName);
- updateRequired = true;
- }
- }
-
- if (updateRequired)
- update();
-}
-
-void DataStoreModelNode::assignCollectionToNode(AbstractView *view,
- const ModelNode &targetNode,
- const QString &collectionName,
- CollectionColumnFinder collectionHasColumn,
- FirstColumnProvider firstColumnProvider)
-{
- QTC_ASSERT(targetNode.isValid(), return);
-
- if (!CollectionEditorUtils::canAcceptCollectionAsModel(targetNode))
- return;
-
- if (!m_collectionPropertyNames.contains(collectionName)) {
- qWarning() << __FUNCTION__ << __LINE__ << "Collection doesn't exist in the DataStore"
- << collectionName;
- return;
- }
-
- PropertyName propertyName = m_collectionPropertyNames.value(collectionName);
-
- const ModelNode dataStore = modelNode();
- VariantProperty sourceProperty = dataStore.variantProperty(propertyName);
- if (!sourceProperty.exists()) {
- qWarning() << __FUNCTION__ << __LINE__
- << "The source property doesn't exist in the DataStore.";
- return;
- }
-
- view->executeInTransaction("assignCollectionToNode", [&]() {
- QString identifier = QString("DataStore.%1").arg(QString::fromLatin1(sourceProperty.name()));
-
- // Remove the old model node property if exists
- NodeProperty modelNodeProperty = targetNode.nodeProperty("model");
- if (modelNodeProperty.modelNode())
- modelNodeProperty.modelNode().destroy();
-
- // Assign the collection to the node
- BindingProperty modelProperty = targetNode.bindingProperty("model");
- modelProperty.setExpression(identifier);
-
- if (CollectionEditorUtils::hasTextRoleProperty(targetNode)) {
- VariantProperty textRoleProperty = targetNode.variantProperty("textRole");
- const QVariant currentTextRoleValue = textRoleProperty.value();
-
- if (currentTextRoleValue.isValid() && !currentTextRoleValue.isNull()) {
- if (currentTextRoleValue.type() == QVariant::String) {
- const QString currentTextRole = currentTextRoleValue.toString();
- if (collectionHasColumn(collectionName, currentTextRole))
- return;
- } else {
- return;
- }
- }
-
- QString textRoleValue = firstColumnProvider(collectionName);
- textRoleProperty.setValue(textRoleValue);
- }
- });
-}
-
-} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/collectioneditor/datastoremodelnode.h b/src/plugins/qmldesigner/components/collectioneditor/datastoremodelnode.h
deleted file mode 100644
index 6cd969edbe..0000000000
--- a/src/plugins/qmldesigner/components/collectioneditor/datastoremodelnode.h
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright (C) 2023 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#pragma once
-
-#include <modelnode.h>
-
-#include <QMap>
-
-namespace Utils {
-class FilePath;
-}
-
-namespace QmlDesigner {
-
-class Model;
-
-class DataStoreModelNode
-{
-public:
- using CollectionColumnFinder = std::function<bool(const QString &collectionName,
- const QString &columnName)>;
- using FirstColumnProvider = std::function<QString(const QString &collectionName)>;
-
- DataStoreModelNode();
-
- void reloadModel();
- QStringList collectionNames() const;
-
- Model *model() const;
- ModelNode modelNode() const;
-
- void setCollectionNames(const QStringList &newCollectionNames);
- void addCollection(const QString &collectionName);
- void renameCollection(const QString &oldName, const QString &newName);
- void removeCollections(const QStringList &collectionNames);
-
- void assignCollectionToNode(AbstractView *view,
- const ModelNode &targetNode,
- const QString &collectionName,
- CollectionColumnFinder collectionHasColumn,
- FirstColumnProvider firstColumnProvider);
-
-private:
- QString getModelQmlText();
-
- void reset();
- void preloadFile();
- void updateDataStoreProperties();
- void updateSingletonFile();
- void update();
- void addCollectionNameToTheModel(const QString &collectionName,
- const PropertyName &dataStorePropertyName);
- Utils::FilePath dataStoreQmlFilePath() const;
-
- PropertyName getUniquePropertyName(const QString &collectionName);
-
- ModelPointer m_model;
- QMap<QString, PropertyName> m_collectionPropertyNames;
- QString m_dataRelativePath;
-};
-
-} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/componentcore/abstractaction.cpp b/src/plugins/qmldesigner/components/componentcore/abstractaction.cpp
index 559e8ea69c..3379d99834 100644
--- a/src/plugins/qmldesigner/components/componentcore/abstractaction.cpp
+++ b/src/plugins/qmldesigner/components/componentcore/abstractaction.cpp
@@ -8,7 +8,7 @@
namespace QmlDesigner {
AbstractAction::AbstractAction(const QString &description)
- : m_pureAction(new DefaultAction(description))
+ : m_pureAction(std::make_unique<DefaultAction>(description))
{
const Utils::Icon defaultIcon({
{":/utils/images/select.png", Utils::Theme::QmlDesigner_FormEditorForegroundColor}}, Utils::Icon::MenuTintedStyle);
@@ -56,7 +56,7 @@ void AbstractAction::setCheckable(bool checkable)
PureActionInterface *AbstractAction::pureAction() const
{
- return m_pureAction.data();
+ return m_pureAction.get();
}
SelectionContext AbstractAction::selectionContext() const
diff --git a/src/plugins/qmldesigner/components/componentcore/abstractaction.h b/src/plugins/qmldesigner/components/componentcore/abstractaction.h
index ca4cc582ce..53b540cc7a 100644
--- a/src/plugins/qmldesigner/components/componentcore/abstractaction.h
+++ b/src/plugins/qmldesigner/components/componentcore/abstractaction.h
@@ -6,7 +6,8 @@
#include "actioninterface.h"
#include <QAction>
-#include <QScopedPointer>
+
+#include <memory>
namespace QmlDesigner {
@@ -58,7 +59,7 @@ protected:
SelectionContext selectionContext() const;
private:
- QScopedPointer<PureActionInterface> m_pureAction;
+ std::unique_ptr<PureActionInterface> m_pureAction;
SelectionContext m_selectionContext;
};
diff --git a/src/plugins/qmldesigner/components/componentcore/abstractactiongroup.cpp b/src/plugins/qmldesigner/components/componentcore/abstractactiongroup.cpp
index 288b8e409d..5b340343e7 100644
--- a/src/plugins/qmldesigner/components/componentcore/abstractactiongroup.cpp
+++ b/src/plugins/qmldesigner/components/componentcore/abstractactiongroup.cpp
@@ -8,14 +8,14 @@
namespace QmlDesigner {
-AbstractActionGroup::AbstractActionGroup(const QString &displayName) :
- m_displayName(displayName),
- m_menu(new QmlEditorMenu)
+AbstractActionGroup::AbstractActionGroup(const QString &displayName)
+ : m_displayName(displayName)
+ , m_menu(Utils::makeUniqueObjectPtr<QmlEditorMenu>())
{
m_menu->setTitle(displayName);
m_action = m_menu->menuAction();
- QmlEditorMenu *qmlEditorMenu = qobject_cast<QmlEditorMenu *>(m_menu.data());
+ QmlEditorMenu *qmlEditorMenu = qobject_cast<QmlEditorMenu *>(m_menu.get());
if (qmlEditorMenu)
qmlEditorMenu->setIconsVisible(false);
}
@@ -32,7 +32,7 @@ QAction *AbstractActionGroup::action() const
QMenu *AbstractActionGroup::menu() const
{
- return m_menu.data();
+ return m_menu.get();
}
SelectionContext AbstractActionGroup::selectionContext() const
diff --git a/src/plugins/qmldesigner/components/componentcore/abstractactiongroup.h b/src/plugins/qmldesigner/components/componentcore/abstractactiongroup.h
index dd89849ecf..f239eeab3d 100644
--- a/src/plugins/qmldesigner/components/componentcore/abstractactiongroup.h
+++ b/src/plugins/qmldesigner/components/componentcore/abstractactiongroup.h
@@ -5,9 +5,10 @@
#include "actioninterface.h"
+#include <utils/uniqueobjectptr.h>
+
#include <QAction>
#include <QMenu>
-#include <QScopedPointer>
namespace QmlDesigner {
@@ -29,7 +30,7 @@ public:
private:
const QString m_displayName;
SelectionContext m_selectionContext;
- QScopedPointer<QMenu> m_menu;
+ Utils::UniqueObjectPtr<QMenu> m_menu;
QAction *m_action;
};
diff --git a/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h b/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h
index d992a6a5bf..da7c5bf72e 100644
--- a/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h
+++ b/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h
@@ -69,7 +69,6 @@ const char mergeTemplateCommandId[] = "MergeTemplate";
const char goToImplementationCommandId[] = "GoToImplementation";
const char makeComponentCommandId[] = "MakeComponent";
const char editMaterialCommandId[] = "EditMaterial";
-const char editCollectionCommandId[] = "EditCollection";
const char addItemToStackedContainerCommandId[] = "AddItemToStackedContainer";
const char addTabBarToStackedContainerCommandId[] = "AddTabBarToStackedContainer";
const char increaseIndexOfStackedContainerCommandId[] = "IncreaseIndexOfStackedContainer";
@@ -128,7 +127,6 @@ const char mergeTemplateDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMen
const char goToImplementationDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Go to Implementation");
const char makeComponentDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Create Component");
const char editMaterialDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Edit Material");
-const char editCollectionDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Edit Model");
const char editAnnotationsDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Edit Annotations");
const char addMouseAreaFillDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Add Mouse Area");
const char editIn3dViewDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Edit in 3D View");
@@ -214,7 +212,6 @@ enum PrioritiesEnum : int {
ArrangeCategory,
EditCategory,
EditListModel,
- EditCollection,
/******** Section *****************************/
PositionSection = 2000,
SnappingCategory,
diff --git a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp
index 8d2b2c43c2..bbe64935f6 100644
--- a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp
+++ b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp
@@ -113,7 +113,6 @@ void DesignerActionManager::polishActions() const
Core::Context qmlDesignerNavigatorContext(Constants::C_QMLNAVIGATOR);
Core::Context qmlDesignerMaterialBrowserContext(Constants::C_QMLMATERIALBROWSER);
Core::Context qmlDesignerAssetsLibraryContext(Constants::C_QMLASSETSLIBRARY);
- Core::Context qmlDesignerCollectionEditorContext(Constants::C_QMLCOLLECTIONEDITOR);
Core::Context qmlDesignerUIContext;
qmlDesignerUIContext.add(qmlDesignerFormEditorContext);
@@ -121,7 +120,6 @@ void DesignerActionManager::polishActions() const
qmlDesignerUIContext.add(qmlDesignerNavigatorContext);
qmlDesignerUIContext.add(qmlDesignerMaterialBrowserContext);
qmlDesignerUIContext.add(qmlDesignerAssetsLibraryContext);
- qmlDesignerUIContext.add(qmlDesignerCollectionEditorContext);
for (auto *action : actions) {
if (!action->menuId().isEmpty()) {
@@ -1988,8 +1986,8 @@ void DesignerActionManager::createDefaultDesignerActions()
QKeySequence(),
44,
&editMaterial,
- &modelHasMaterial,
- &isModel));
+ &hasEditableMaterial,
+ &isModelOrMaterial));
addDesignerAction(new ModelNodeContextMenuAction(
mergeTemplateCommandId,
@@ -2011,16 +2009,6 @@ void DesignerActionManager::createDefaultDesignerActions()
addDesignerAction(new EditListModelAction);
- addDesignerAction(new ModelNodeContextMenuAction(editCollectionCommandId,
- editCollectionDisplayName,
- contextIcon(DesignerIcons::EditIcon),
- rootCategory,
- QKeySequence("Alt+e"),
- ComponentCoreConstants::Priorities::EditCollection,
- &editCollection,
- &hasCollectionAsModel,
- &hasCollectionAsModel));
-
addDesignerAction(new ModelNodeContextMenuAction(openSignalDialogCommandId,
openSignalDialogDisplayName,
{},
@@ -2193,7 +2181,8 @@ void DesignerActionManager::addCustomTransitionEffectAction()
void DesignerActionManager::setupIcons()
{
- m_designerIcons.reset(new DesignerIcons("qtds_propertyIconFont.ttf", designerIconResourcesPath()));
+ m_designerIcons = std::make_unique<DesignerIcons>("qtds_propertyIconFont.ttf",
+ designerIconResourcesPath());
}
QString DesignerActionManager::designerIconResourcesPath() const
diff --git a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.h b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.h
index 16d6219cd6..89505fcbe8 100644
--- a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.h
+++ b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.h
@@ -138,7 +138,7 @@ private:
QList<AddResourceHandler> m_addResourceHandler;
QList<ModelNodePreviewImageHandler> m_modelNodePreviewImageHandlers;
ExternalDependenciesInterface &m_externalDependencies;
- QScopedPointer<DesignerIcons> m_designerIcons;
+ std::unique_ptr<DesignerIcons> m_designerIcons;
QList<ActionAddedInterface> m_callBacks;
};
diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu_helper.h b/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu_helper.h
index aec14e9d04..6734bac568 100644
--- a/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu_helper.h
+++ b/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu_helper.h
@@ -64,33 +64,24 @@ inline bool addMouseAreaFillCheck(const SelectionContext &selectionContext)
return false;
}
-inline bool isModel(const SelectionContext &selectionState)
+inline bool isModelOrMaterial(const SelectionContext &selectionState)
{
ModelNode node = selectionState.currentSingleSelectedNode();
- return node.metaInfo().isQtQuick3DModel();
+ return node.metaInfo().isQtQuick3DModel() || node.metaInfo().isQtQuick3DMaterial();
}
-inline bool modelHasMaterial(const SelectionContext &selectionState)
+inline bool hasEditableMaterial(const SelectionContext &selectionState)
{
ModelNode node = selectionState.currentSingleSelectedNode();
+ if (node.metaInfo().isQtQuick3DMaterial())
+ return true;
+
BindingProperty prop = node.bindingProperty("materials");
return prop.exists() && (!prop.expression().isEmpty() || !prop.resolveToModelNodeList().empty());
}
-inline bool hasCollectionAsModel(const SelectionContext &selectionState)
-{
- if (!selectionState.isInBaseState() || !selectionState.singleNodeIsSelected())
- return false;
-
- const ModelNode singleSelectedNode = selectionState.currentSingleSelectedNode();
-
- return singleSelectedNode.metaInfo().isQtQuickListView()
- && singleSelectedNode.property("model").toBindingProperty().expression().startsWith(
- "DataStore.");
-}
-
inline bool selectionEnabled(const SelectionContext &selectionState)
{
return selectionState.showSelectionTools();
diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp
index c24ec9aa3e..bf8e78a2c7 100644
--- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp
+++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp
@@ -817,21 +817,25 @@ void editMaterial(const SelectionContext &selectionContext)
QTC_ASSERT(modelNode.isValid(), return);
- BindingProperty prop = modelNode.bindingProperty("materials");
- if (!prop.exists())
- return;
-
AbstractView *view = selectionContext.view();
ModelNode material;
- if (view->hasId(prop.expression())) {
- material = view->modelNodeForId(prop.expression());
+ if (modelNode.metaInfo().isQtQuick3DMaterial()) {
+ material = modelNode;
} else {
- QList<ModelNode> materials = prop.resolveToModelNodeList();
+ BindingProperty prop = modelNode.bindingProperty("materials");
+ if (!prop.exists())
+ return;
+
+ if (view->hasId(prop.expression())) {
+ material = view->modelNodeForId(prop.expression());
+ } else {
+ QList<ModelNode> materials = prop.resolveToModelNodeList();
- if (materials.size() > 0)
- material = materials.first();
+ if (materials.size() > 0)
+ material = materials.first();
+ }
}
if (material.isValid()) {
@@ -842,30 +846,6 @@ void editMaterial(const SelectionContext &selectionContext)
}
}
-// Open a collection in the collection editor
-void editCollection(const SelectionContext &selectionContext)
-{
- ModelNode modelNode = selectionContext.targetNode();
-
- if (!modelNode)
- modelNode = selectionContext.currentSingleSelectedNode();
-
- if (!modelNode)
- return;
-
- const QString dataStoreExpression = "DataStore.";
-
- BindingProperty prop = modelNode.bindingProperty("model");
- if (!prop.exists() || !prop.expression().startsWith(dataStoreExpression))
- return;
-
- AbstractView *view = selectionContext.view();
- const QString collectionId = prop.expression().mid(dataStoreExpression.size());
-
- // to CollectionEditor...
- view->emitCustomNotification("open_collection_by_id", {}, {collectionId});
-}
-
void addItemToStackedContainer(const SelectionContext &selectionContext)
{
AbstractView *view = selectionContext.view();
@@ -1722,7 +1702,7 @@ void editIn3dView(const SelectionContext &selectionContext)
bool isEffectComposerActivated()
{
- const QVector<ExtensionSystem::PluginSpec *> specs = ExtensionSystem::PluginManager::plugins();
+ const ExtensionSystem::PluginSpecs specs = ExtensionSystem::PluginManager::plugins();
return std::find_if(specs.begin(), specs.end(),
[](ExtensionSystem::PluginSpec *spec) {
return spec->name() == "EffectComposer" && spec->isEffectivelyEnabled();
diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h
index a67cef4942..26562f429a 100644
--- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h
+++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h
@@ -92,7 +92,6 @@ void layoutGridLayout(const SelectionContext &selectionState);
void goImplementation(const SelectionContext &selectionState);
void addNewSignalHandler(const SelectionContext &selectionState);
void editMaterial(const SelectionContext &selectionContext);
-void editCollection(const SelectionContext &selectionContext);
void addSignalHandlerOrGotoImplementation(const SelectionContext &selectionState, bool addAlwaysNewSlot);
void removeLayout(const SelectionContext &selectionContext);
void removePositioner(const SelectionContext &selectionContext);
diff --git a/src/plugins/qmldesigner/components/componentcore/propertycomponentgenerator.cpp b/src/plugins/qmldesigner/components/componentcore/propertycomponentgenerator.cpp
index 8cc84058d2..58326dc77a 100644
--- a/src/plugins/qmldesigner/components/componentcore/propertycomponentgenerator.cpp
+++ b/src/plugins/qmldesigner/components/componentcore/propertycomponentgenerator.cpp
@@ -37,7 +37,7 @@ Type getProperty(const QmlJS::SimpleReaderNode *node, const QString &name)
{
if (auto property = node->property(name)) {
const auto &value = property.value;
- if (value.type() == QVariant::List) {
+ if (value.typeId() == QMetaType::QVariantList) {
auto list = value.toList();
if (list.size())
return list.front().value<Type>();
@@ -179,7 +179,7 @@ std::optional<PropertyComponentGenerator::Entry> createEntry(QmlJS::SimpleReader
if (moduleName.isEmpty())
return {};
- auto module = model->module(moduleName);
+ auto module = model->module(moduleName, Storage::ModuleKind::QmlLibrary);
auto typeName = getProperty<QByteArray>(node, "typeNames");
diff --git a/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp b/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp
index b011d9fbbf..a56735862f 100644
--- a/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp
+++ b/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp
@@ -7,7 +7,6 @@
#include <abstractview.h>
#include <assetslibraryview.h>
#include <capturingconnectionmanager.h>
-#include <collectionview.h>
#include <componentaction.h>
#include <componentview.h>
#include <contentlibraryview.h>
@@ -42,14 +41,6 @@
namespace QmlDesigner {
-static bool enableModelEditor()
-{
- Utils::QtcSettings *settings = Core::ICore::settings();
- const Utils::Key enableModelManagerKey = "QML/Designer/UseExperimentalFeatures44";
-
- return settings->value(enableModelManagerKey, false).toBool();
-}
-
static Q_LOGGING_CATEGORY(viewBenchmark, "qtc.viewmanager.attach", QtWarningMsg)
class ViewManagerData
@@ -64,8 +55,7 @@ public:
: connectionManager,
externalDependencies,
true)
- , collectionView{externalDependencies}
- , contentLibraryView{externalDependencies}
+ , contentLibraryView{imageCache, externalDependencies}
, componentView{externalDependencies}
#ifndef QTC_USE_QML_DESIGNER_LITE
, edit3DView{externalDependencies}
@@ -90,7 +80,6 @@ public:
Internal::DebugView debugView;
DesignerActionManagerView designerActionManagerView;
NodeInstanceView nodeInstanceView;
- CollectionView collectionView;
ContentLibraryView contentLibraryView;
ComponentView componentView;
#ifndef QTC_USE_QML_DESIGNER_LITE
@@ -235,9 +224,6 @@ QList<AbstractView *> ViewManager::standardViews() const
&d->designerActionManagerView};
#endif
- if (enableModelEditor())
- list.append(&d->collectionView);
-
if (QmlDesignerPlugin::instance()
->settings()
.value(DesignerSettingsKey::ENABLE_DEBUGVIEW)
@@ -418,8 +404,6 @@ QList<WidgetInfo> ViewManager::widgetInfos() const
widgetInfoList.append(d->textureEditorView.widgetInfo());
#endif
widgetInfoList.append(d->statesEditorView.widgetInfo());
- if (enableModelEditor())
- widgetInfoList.append(d->collectionView.widgetInfo());
if (checkEnterpriseLicense())
widgetInfoList.append(d->contentLibraryView.widgetInfo());
diff --git a/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp
index f871bae84b..2cee7b0f97 100644
--- a/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp
+++ b/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp
@@ -22,6 +22,7 @@ namespace QmlDesigner {
BindingModel::BindingModel(ConnectionView *view)
: m_connectionView(view)
+ , m_delegate(*this)
{
setHorizontalHeaderLabels(BindingModelItem::headerLabels());
}
@@ -246,11 +247,8 @@ void BindingModel::addModelNode(const ModelNode &node)
appendRow(new BindingModelItem(property));
}
-BindingModelBackendDelegate::BindingModelBackendDelegate()
- : m_targetNode()
- , m_property()
- , m_sourceNode()
- , m_sourceNodeProperty()
+BindingModelBackendDelegate::BindingModelBackendDelegate(BindingModel &model)
+ : m_model{model}
{
connect(&m_sourceNode, &StudioQmlComboBoxBackend::activated, this, [this] {
sourceNodeChanged();
@@ -322,17 +320,14 @@ StudioQmlComboBoxBackend *BindingModelBackendDelegate::sourceProperty()
void BindingModelBackendDelegate::sourceNodeChanged()
{
- BindingModel *model = qobject_cast<BindingModel *>(parent());
- QTC_ASSERT(model, return);
-
- ConnectionView *view = model->connectionView();
+ ConnectionView *view = m_model.connectionView();
QTC_ASSERT(view, return);
QTC_ASSERT(view->isAttached(), return );
const QString sourceNode = m_sourceNode.currentText();
const QString sourceProperty = m_sourceNodeProperty.currentText();
- BindingProperty targetProperty = model->currentProperty();
+ BindingProperty targetProperty = m_model.currentProperty();
QStringList properties = availableSourceProperties(sourceNode, targetProperty, view);
if (!properties.contains(sourceProperty)) {
@@ -351,9 +346,6 @@ void BindingModelBackendDelegate::sourcePropertyNameChanged() const
return;
auto commit = [this, sourceProperty]() {
- BindingModel *model = qobject_cast<BindingModel *>(parent());
- QTC_ASSERT(model, return);
-
const QString sourceNode = m_sourceNode.currentText();
QString expression;
if (sourceProperty.isEmpty())
@@ -361,8 +353,8 @@ void BindingModelBackendDelegate::sourcePropertyNameChanged() const
else
expression = sourceNode + QLatin1String(".") + sourceProperty;
- int row = model->currentIndex();
- model->commitExpression(row, expression);
+ int row = m_model.currentIndex();
+ m_model.commitExpression(row, expression);
};
callLater(commit);
@@ -371,11 +363,9 @@ void BindingModelBackendDelegate::sourcePropertyNameChanged() const
void BindingModelBackendDelegate::targetPropertyNameChanged() const
{
auto commit = [this] {
- BindingModel *model = qobject_cast<BindingModel *>(parent());
- QTC_ASSERT(model, return);
const PropertyName propertyName = m_property.currentText().toUtf8();
- int row = model->currentIndex();
- model->commitPropertyName(row, propertyName);
+ int row = m_model.currentIndex();
+ m_model.commitPropertyName(row, propertyName);
};
callLater(commit);
diff --git a/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.h b/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.h
index b57cc5c958..69b137e78a 100644
--- a/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.h
+++ b/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.h
@@ -30,7 +30,7 @@ signals:
void targetNodeChanged();
public:
- BindingModelBackendDelegate();
+ BindingModelBackendDelegate(class BindingModel &model);
void update(const BindingProperty &property, AbstractView *view);
@@ -44,6 +44,7 @@ private:
StudioQmlComboBoxBackend *sourceNode();
StudioQmlComboBoxBackend *sourceProperty();
+ BindingModel &m_model;
QString m_targetNode;
StudioQmlComboBoxBackend m_property;
StudioQmlComboBoxBackend m_sourceNode;
diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorutils.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectioneditorutils.cpp
index 57ca619a70..3cbfb8c038 100644
--- a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorutils.cpp
+++ b/src/plugins/qmldesigner/components/connectioneditor/connectioneditorutils.cpp
@@ -212,7 +212,8 @@ bool isDynamicVariantPropertyType(const TypeName &type)
{
// "variant" is considered value type as it is initialized as one.
// This may need to change if we provide any kind of proper editor for it.
- static const QSet<TypeName> valueTypes{"int", "real", "color", "string", "bool", "url", "var", "variant"};
+ static const QSet<TypeName> valueTypes{
+ "int", "real", "double", "color", "string", "bool", "url", "var", "variant"};
return valueTypes.contains(type);
}
diff --git a/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp
index 9fdd3daec3..fa29c6c8a1 100644
--- a/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp
+++ b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp
@@ -25,7 +25,7 @@ namespace QmlDesigner {
DynamicPropertiesModel::DynamicPropertiesModel(bool exSelection, AbstractView *view)
: m_view(view)
- , m_delegate(std::make_unique<DynamicPropertiesModelBackendDelegate>())
+ , m_delegate(std::make_unique<DynamicPropertiesModelBackendDelegate>(*this))
, m_explicitSelection(exSelection)
{
setHorizontalHeaderLabels(DynamicPropertiesItem::headerLabels());
@@ -382,8 +382,8 @@ void DynamicPropertiesModel::setSelectedNode(const ModelNode &node)
reset();
}
-DynamicPropertiesModelBackendDelegate::DynamicPropertiesModelBackendDelegate()
- : m_internalNodeId(std::nullopt)
+DynamicPropertiesModelBackendDelegate::DynamicPropertiesModelBackendDelegate(DynamicPropertiesModel &model)
+ : m_model(model)
{
m_type.setModel({"int", "bool", "var", "real", "string", "url", "color"});
connect(&m_type, &StudioQmlComboBoxBackend::activated, this, [this] { handleTypeChanged(); });
@@ -411,32 +411,26 @@ void DynamicPropertiesModelBackendDelegate::update(const AbstractProperty &prope
void DynamicPropertiesModelBackendDelegate::handleTypeChanged()
{
- DynamicPropertiesModel *model = qobject_cast<DynamicPropertiesModel *>(parent());
- QTC_ASSERT(model, return);
-
const PropertyName name = m_name.text().toUtf8();
- int current = model->currentIndex();
+ int current = m_model.currentIndex();
const TypeName type = m_type.currentText().toUtf8();
- model->commitPropertyType(current, type);
+ m_model.commitPropertyType(current, type);
// The order might have changed!
- model->setCurrent(m_internalNodeId.value_or(-1), name);
+ m_model.setCurrent(m_internalNodeId.value_or(-1), name);
}
void DynamicPropertiesModelBackendDelegate::handleNameChanged()
{
- DynamicPropertiesModel *model = qobject_cast<DynamicPropertiesModel *>(parent());
- QTC_ASSERT(model, return);
-
const PropertyName name = m_name.text().toUtf8();
QTC_ASSERT(!name.isEmpty(), return);
- int current = model->currentIndex();
- model->commitPropertyName(current, name);
+ int current = m_model.currentIndex();
+ m_model.commitPropertyName(current, name);
// The order might have changed!
- model->setCurrent(m_internalNodeId.value_or(-1), name);
+ m_model.setCurrent(m_internalNodeId.value_or(-1), name);
}
// TODO: Maybe replace with utils typeConvertVariant?
@@ -456,12 +450,9 @@ QVariant valueFromText(const QString &value, const QString &type)
void DynamicPropertiesModelBackendDelegate::handleValueChanged()
{
- DynamicPropertiesModel *model = qobject_cast<DynamicPropertiesModel *>(parent());
- QTC_ASSERT(model, return);
-
- int current = model->currentIndex();
+ int current = m_model.currentIndex();
QVariant value = valueFromText(m_value.text(), m_type.currentText());
- model->commitPropertyValue(current, value);
+ m_model.commitPropertyValue(current, value);
}
QString DynamicPropertiesModelBackendDelegate::targetNode() const
diff --git a/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.h b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.h
index c2beed8730..071c72bef8 100644
--- a/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.h
+++ b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.h
@@ -97,7 +97,7 @@ class DynamicPropertiesModelBackendDelegate : public QObject
Q_PROPERTY(StudioQmlTextBackend *value READ value CONSTANT)
public:
- DynamicPropertiesModelBackendDelegate();
+ DynamicPropertiesModelBackendDelegate(DynamicPropertiesModel &model);
void update(const AbstractProperty &property);
@@ -116,6 +116,7 @@ private:
StudioQmlTextBackend *value();
QString targetNode() const;
+ DynamicPropertiesModel &m_model;
std::optional<int> m_internalNodeId;
StudioQmlComboBoxBackend m_type;
StudioQmlTextBackend m_name;
diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarybundleimporter.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarybundleimporter.cpp
index 5c8d42a306..6388c93fa9 100644
--- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarybundleimporter.cpp
+++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarybundleimporter.cpp
@@ -3,32 +3,26 @@
#include "contentlibrarybundleimporter.h"
-#include "documentmanager.h"
-#include "import.h"
-#include "model.h"
-#include "qmldesignerconstants.h"
-#include "qmldesignerplugin.h"
-#include "rewritingexception.h"
+#include <documentmanager.h>
+#include <import.h>
+#include <model.h>
+#include <nodemetainfo.h>
+#include <qmldesignerconstants.h>
+#include <qmldesignerplugin.h>
+#include <rewritingexception.h>
#include <qmljs/qmljsmodelmanagerinterface.h>
-#include <QFileInfo>
#include <QJsonDocument>
#include <QJsonObject>
#include <QStringList>
using namespace Utils;
-namespace QmlDesigner::Internal {
+namespace QmlDesigner {
-ContentLibraryBundleImporter::ContentLibraryBundleImporter(const QString &bundleDir,
- const QString &bundleId,
- const QStringList &sharedFiles,
- QObject *parent)
+ContentLibraryBundleImporter::ContentLibraryBundleImporter(QObject *parent)
: QObject(parent)
- , m_bundleDir(FilePath::fromString(bundleDir))
- , m_bundleId(bundleId)
- , m_sharedFiles(sharedFiles)
{
m_importTimer.setInterval(200);
connect(&m_importTimer, &QTimer::timeout, this, &ContentLibraryBundleImporter::handleImportTimer);
@@ -38,47 +32,38 @@ ContentLibraryBundleImporter::ContentLibraryBundleImporter(const QString &bundle
// Note that there is also an asynchronous portion to the import, which will only
// be done if this method returns success. Once the asynchronous portion of the
// import is completed, importFinished signal will be emitted.
-QString ContentLibraryBundleImporter::importComponent(const QString &qmlFile,
+QString ContentLibraryBundleImporter::importComponent(const QString &bundleDir,
+ const TypeName &type,
+ const QString &qmlFile,
const QStringList &files)
{
- FilePath bundleImportPath = resolveBundleImportPath();
+ QString module = QString::fromLatin1(type.left(type.lastIndexOf('.')));
+ m_bundleId = module.mid(module.lastIndexOf('.') + 1);
+
+ FilePath bundleDirPath = FilePath::fromString(bundleDir); // source dir
+ FilePath bundleImportPath = resolveBundleImportPath(m_bundleId); // target dir
+
if (bundleImportPath.isEmpty())
return "Failed to resolve bundle import folder";
- bool bundleImportPathExists = bundleImportPath.exists();
-
- if (!bundleImportPathExists && !bundleImportPath.createDir())
+ if (!bundleImportPath.exists() && !bundleImportPath.createDir())
return QStringLiteral("Failed to create bundle import folder: '%1'").arg(bundleImportPath.toString());
- for (const QString &file : std::as_const(m_sharedFiles)) {
- FilePath target = bundleImportPath.resolvePath(file);
- if (!target.exists()) {
- FilePath parentDir = target.parentDir();
- if (!parentDir.exists() && !parentDir.createDir())
- return QStringLiteral("Failed to create folder for: '%1'").arg(target.toString());
- FilePath source = m_bundleDir.resolvePath(file);
- if (!source.copyFile(target))
- return QStringLiteral("Failed to copy shared file: '%1'").arg(source.toString());
- }
- }
-
- FilePath qmldirPath = bundleImportPath.resolvePath(QStringLiteral("qmldir"));
+ FilePath qmldirPath = bundleImportPath.pathAppended("qmldir");
QString qmldirContent = QString::fromUtf8(qmldirPath.fileContents().value_or(QByteArray()));
if (qmldirContent.isEmpty()) {
qmldirContent.append("module ");
- qmldirContent.append(moduleName());
+ qmldirContent.append(module);
qmldirContent.append('\n');
}
- FilePath qmlSourceFile = bundleImportPath.resolvePath(FilePath::fromString(qmlFile));
+ FilePath qmlSourceFile = bundleImportPath.pathAppended(qmlFile);
const bool qmlFileExists = qmlSourceFile.exists();
const QString qmlType = qmlSourceFile.baseName();
- const QString fullTypeName = QStringLiteral("%1.%2.%3")
- .arg(QmlDesignerPlugin::instance()->documentManager()
- .generatedComponentUtils().componentBundlesTypePrefix(),
- m_bundleId, qmlType);
- if (m_pendingTypes.contains(fullTypeName) && !m_pendingTypes[fullTypeName])
- return QStringLiteral("Unable to import while unimporting the same type: '%1'").arg(fullTypeName);
+
+ if (m_pendingTypes.contains(type) && !m_pendingTypes.value(type))
+ return QStringLiteral("Unable to import while unimporting the same type: '%1'").arg(QLatin1String(type));
+
if (!qmldirContent.contains(qmlFile)) {
qmldirContent.append(qmlType);
qmldirContent.append(" 1.0 ");
@@ -91,12 +76,12 @@ QString ContentLibraryBundleImporter::importComponent(const QString &qmlFile,
allFiles.append(files);
allFiles.append(qmlFile);
for (const QString &file : std::as_const(allFiles)) {
- FilePath target = bundleImportPath.resolvePath(file);
+ FilePath target = bundleImportPath.pathAppended(file);
FilePath parentDir = target.parentDir();
if (!parentDir.exists() && !parentDir.createDir())
return QStringLiteral("Failed to create folder for: '%1'").arg(target.toString());
- FilePath source = m_bundleDir.resolvePath(file);
+ FilePath source = bundleDirPath.pathAppended(file);
if (target.exists()) {
if (source.lastModified() == target.lastModified())
continue;
@@ -125,23 +110,23 @@ QString ContentLibraryBundleImporter::importComponent(const QString &qmlFile,
if (!model)
return "Model not available, cannot add import statement or update code model";
- Import import = Import::createLibraryImport(moduleName(), "1.0");
+ Import import = Import::createLibraryImport(module, "1.0");
if (!model->hasImport(import)) {
if (model->possibleImports().contains(import)) {
- m_importAddPending = false;
+ m_pendingImport.clear();
try {
model->changeImports({import}, {});
} catch (const RewritingException &) {
// No point in trying to add import asynchronously either, so just fail out
- return QStringLiteral("Failed to add import statement for: '%1'").arg(moduleName());
+ return QStringLiteral("Failed to add import statement for: '%1'").arg(module);
}
} else {
// If import is not yet possible, import statement needs to be added asynchronously to
// avoid errors, as code model update takes a while.
- m_importAddPending = true;
+ m_pendingImport = module;
}
}
- m_pendingTypes.insert(fullTypeName, true);
+ m_pendingTypes.insert(type, true);
m_importTimerCount = 0;
m_importTimer.start();
@@ -153,17 +138,19 @@ void ContentLibraryBundleImporter::handleImportTimer()
auto handleFailure = [this] {
m_importTimer.stop();
m_fullReset = false;
- m_importAddPending = false;
+ m_pendingImport.clear();
m_importTimerCount = 0;
// Emit dummy finished signals for all pending types
- const QStringList pendingTypes = m_pendingTypes.keys();
- for (const QString &pendingType : pendingTypes) {
+ const QList<TypeName> pendingTypes = m_pendingTypes.keys();
+ for (const TypeName &pendingType : pendingTypes) {
m_pendingTypes.remove(pendingType);
- if (m_pendingTypes[pendingType])
- emit importFinished({});
+ if (m_pendingTypes.value(pendingType))
+ emit importFinished({}, m_bundleId);
else
- emit unimportFinished({});
+ emit unimportFinished({}, m_bundleId);
+
+ m_bundleId.clear();
}
};
@@ -185,12 +172,12 @@ void ContentLibraryBundleImporter::handleImportTimer()
QmlDesignerPlugin::instance()->documentManager().resetPossibleImports();
- if (m_importAddPending) {
+ if (!m_pendingImport.isEmpty()) {
try {
- Import import = Import::createLibraryImport(moduleName(), "1.0");
+ Import import = Import::createLibraryImport(m_pendingImport, "1.0");
if (model->possibleImports().contains(import)) {
model->changeImports({import}, {});
- m_importAddPending = false;
+ m_pendingImport.clear();
}
} catch (const RewritingException &) {
// Import adding is unlikely to succeed later, either, so just bail out
@@ -200,21 +187,23 @@ void ContentLibraryBundleImporter::handleImportTimer()
}
// Detect when the code model has the new material(s) fully available
- const QStringList pendingTypes = m_pendingTypes.keys();
- for (const QString &pendingType : pendingTypes) {
- NodeMetaInfo metaInfo = model->metaInfo(pendingType.toUtf8());
- const bool isImport = m_pendingTypes[pendingType];
+ const QList<TypeName> pendingTypes = m_pendingTypes.keys();
+ for (const TypeName &pendingType : pendingTypes) {
+ NodeMetaInfo metaInfo = model->metaInfo(pendingType);
+ const bool isImport = m_pendingTypes.value(pendingType);
const bool typeComplete = metaInfo.isValid() && !metaInfo.prototypes().empty();
if (isImport == typeComplete) {
m_pendingTypes.remove(pendingType);
if (isImport)
#ifdef QDS_USE_PROJECTSTORAGE
- emit importFinished(pendingType.toUtf8());
+ emit importFinished(pendingType, m_bundleId);
#else
- emit importFinished(metaInfo);
+ emit importFinished(metaInfo, m_bundleId);
#endif
else
- emit unimportFinished(metaInfo);
+ emit unimportFinished(metaInfo, m_bundleId);
+
+ m_bundleId.clear();
}
}
@@ -224,10 +213,10 @@ void ContentLibraryBundleImporter::handleImportTimer()
}
}
-QVariantHash ContentLibraryBundleImporter::loadAssetRefMap(const Utils::FilePath &bundlePath)
+QVariantHash ContentLibraryBundleImporter::loadAssetRefMap(const FilePath &bundlePath)
{
FilePath assetRefPath = bundlePath.resolvePath(QLatin1String(Constants::COMPONENT_BUNDLES_ASSET_REF_FILE));
- const Utils::expected_str<QByteArray> content = assetRefPath.fileContents();
+ const expected_str<QByteArray> content = assetRefPath.fileContents();
if (content) {
QJsonParseError error;
QJsonDocument bundleDataJsonDoc = QJsonDocument::fromJson(*content, &error);
@@ -241,7 +230,7 @@ QVariantHash ContentLibraryBundleImporter::loadAssetRefMap(const Utils::FilePath
return {};
}
-void ContentLibraryBundleImporter::writeAssetRefMap(const Utils::FilePath &bundlePath,
+void ContentLibraryBundleImporter::writeAssetRefMap(const FilePath &bundlePath,
const QVariantHash &assetRefMap)
{
FilePath assetRefPath = bundlePath.resolvePath(QLatin1String(Constants::COMPONENT_BUNDLES_ASSET_REF_FILE));
@@ -252,16 +241,14 @@ void ContentLibraryBundleImporter::writeAssetRefMap(const Utils::FilePath &bundl
}
}
-QString ContentLibraryBundleImporter::moduleName()
+QString ContentLibraryBundleImporter::unimportComponent(const TypeName &type, const QString &qmlFile)
{
- return QStringLiteral("%1.%2").arg(QmlDesignerPlugin::instance()->documentManager()
- .generatedComponentUtils().componentBundlesTypePrefix(),
- m_bundleId);
-}
+ QString module = QString::fromLatin1(type.left(type.lastIndexOf('.')));
+ m_bundleId = module.mid(module.lastIndexOf('.') + 1);
-QString ContentLibraryBundleImporter::unimportComponent(const QString &qmlFile)
-{
- FilePath bundleImportPath = resolveBundleImportPath();
+ emit aboutToUnimport(type, m_bundleId);
+
+ FilePath bundleImportPath = resolveBundleImportPath(m_bundleId);
if (bundleImportPath.isEmpty())
return QStringLiteral("Failed to resolve bundle import folder for: '%1'").arg(qmlFile);
@@ -280,12 +267,10 @@ QString ContentLibraryBundleImporter::unimportComponent(const QString &qmlFile)
QByteArray newContent;
QString qmlType = qmlFilePath.baseName();
- const QString fullTypeName = QStringLiteral("%1.%2.%3")
- .arg(QmlDesignerPlugin::instance()->documentManager()
- .generatedComponentUtils().componentBundlesTypePrefix(),
- m_bundleId, qmlType);
- if (m_pendingTypes.contains(fullTypeName) && m_pendingTypes[fullTypeName])
- return QStringLiteral("Unable to unimport while importing the same type: '%1'").arg(fullTypeName);
+ if (m_pendingTypes.contains(type) && m_pendingTypes.value(type)) {
+ return QStringLiteral("Unable to unimport while importing the same type: '%1'")
+ .arg(QString::fromLatin1(type));
+ }
if (qmldirContent) {
int typeIndex = qmldirContent->indexOf(qmlType.toUtf8());
@@ -301,7 +286,7 @@ QString ContentLibraryBundleImporter::unimportComponent(const QString &qmlFile)
}
}
- m_pendingTypes.insert(fullTypeName, false);
+ m_pendingTypes.insert(type, false);
QVariantHash assetRefMap = loadAssetRefMap(bundleImportPath);
bool writeAssetRefs = false;
@@ -335,7 +320,7 @@ QString ContentLibraryBundleImporter::unimportComponent(const QString &qmlFile)
auto doc = QmlDesignerPlugin::instance()->currentDesignDocument();
Model *model = doc ? doc->currentModel() : nullptr;
if (model) {
- Import import = Import::createLibraryImport(moduleName(), "1.0");
+ Import import = Import::createLibraryImport(module, "1.0");
if (model->imports().contains(import))
model->changeImports({}, {import});
}
@@ -348,14 +333,14 @@ QString ContentLibraryBundleImporter::unimportComponent(const QString &qmlFile)
return {};
}
-FilePath ContentLibraryBundleImporter::resolveBundleImportPath()
+FilePath ContentLibraryBundleImporter::resolveBundleImportPath(const QString &bundleId)
{
FilePath bundleImportPath = QmlDesignerPlugin::instance()->documentManager()
.generatedComponentUtils().componentBundlesBasePath();
if (bundleImportPath.isEmpty())
- return bundleImportPath;
+ return {};
- return bundleImportPath.resolvePath(m_bundleId);
+ return bundleImportPath.resolvePath(bundleId);
}
-} // namespace QmlDesigner::Internal
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarybundleimporter.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarybundleimporter.h
index 7fb2a48886..8155311e3e 100644
--- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarybundleimporter.h
+++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarybundleimporter.h
@@ -3,59 +3,53 @@
#pragma once
-#include <utils/filepath.h>
+#include <modelfwd.h>
-#include "nodemetainfo.h"
+#include <utils/filepath.h>
#include <QTimer>
#include <QVariantHash>
-QT_BEGIN_NAMESPACE
-QT_END_NAMESPACE
+namespace QmlDesigner {
-namespace QmlDesigner::Internal {
+class NodeMetaInfo;
class ContentLibraryBundleImporter : public QObject
{
Q_OBJECT
public:
- ContentLibraryBundleImporter(const QString &bundleDir,
- const QString &bundleId,
- const QStringList &sharedFiles,
- QObject *parent = nullptr);
+ ContentLibraryBundleImporter(QObject *parent = nullptr);
~ContentLibraryBundleImporter() = default;
- QString importComponent(const QString &qmlFile,
+ QString importComponent(const QString &bundleDir, const TypeName &type, const QString &qmlFile,
const QStringList &files);
- QString unimportComponent(const QString &qmlFile);
- Utils::FilePath resolveBundleImportPath();
+ QString unimportComponent(const TypeName &type, const QString &qmlFile);
+ Utils::FilePath resolveBundleImportPath(const QString &bundleId);
signals:
// The metaInfo parameter will be invalid if an error was encountered during
// asynchronous part of the import. In this case all remaining pending imports have been
// terminated, and will not receive separate importFinished notifications.
#ifdef QDS_USE_PROJECTSTORAGE
- void importFinished(const QmlDesigner::TypeName &typeName);
+ void importFinished(const QmlDesigner::TypeName &typeName, const QString &bundleId);
#else
- void importFinished(const QmlDesigner::NodeMetaInfo &metaInfo);
+ void importFinished(const QmlDesigner::NodeMetaInfo &metaInfo, const QString &bundleId);
#endif
- void unimportFinished(const QmlDesigner::NodeMetaInfo &metaInfo);
+ void unimportFinished(const QmlDesigner::NodeMetaInfo &metaInfo, const QString &bundleId);
+ void aboutToUnimport(const TypeName &type, const QString &bundleId);
private:
void handleImportTimer();
QVariantHash loadAssetRefMap(const Utils::FilePath &bundlePath);
void writeAssetRefMap(const Utils::FilePath &bundlePath, const QVariantHash &assetRefMap);
- QString moduleName();
- Utils::FilePath m_bundleDir;
- QString m_bundleId;
- QStringList m_sharedFiles;
QTimer m_importTimer;
int m_importTimerCount = 0;
- bool m_importAddPending = false;
+ QString m_pendingImport;
+ QString m_bundleId;
bool m_fullReset = false;
- QHash<QString, bool> m_pendingTypes; // <type, isImport>
+ QHash<TypeName, bool> m_pendingTypes; // <type, isImport>
};
-} // namespace QmlDesigner::Internal
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffect.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffect.cpp
deleted file mode 100644
index f572fbe65f..0000000000
--- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffect.cpp
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright (C) 2023 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#include "contentlibraryeffect.h"
-
-#include <QFileInfo>
-
-namespace QmlDesigner {
-
-ContentLibraryEffect::ContentLibraryEffect(QObject *parent,
- const QString &name,
- const QString &qml,
- const TypeName &type,
- const QUrl &icon,
- const QStringList &files)
- : QObject(parent), m_name(name), m_qml(qml), m_type(type), m_icon(icon), m_files(files)
-{
- m_allFiles = m_files;
- m_allFiles.push_back(m_qml);
-}
-
-bool ContentLibraryEffect::filter(const QString &searchText)
-{
- if (m_visible != m_name.contains(searchText, Qt::CaseInsensitive)) {
- m_visible = !m_visible;
- emit itemVisibleChanged();
- }
-
- return m_visible;
-}
-
-QUrl ContentLibraryEffect::icon() const
-{
- return m_icon;
-}
-
-QString ContentLibraryEffect::qml() const
-{
- return m_qml;
-}
-
-TypeName ContentLibraryEffect::type() const
-{
- return m_type;
-}
-
-QStringList ContentLibraryEffect::files() const
-{
- return m_files;
-}
-
-bool ContentLibraryEffect::visible() const
-{
- return m_visible;
-}
-
-bool ContentLibraryEffect::setImported(bool imported)
-{
- if (m_imported != imported) {
- m_imported = imported;
- emit itemImportedChanged();
- return true;
- }
-
- return false;
-}
-
-bool ContentLibraryEffect::imported() const
-{
- return m_imported;
-}
-
-QStringList ContentLibraryEffect::allFiles() const
-{
- return m_allFiles;
-}
-
-} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectscategory.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectscategory.cpp
index 38e6eed3da..f904775d52 100644
--- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectscategory.cpp
+++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectscategory.cpp
@@ -3,14 +3,12 @@
#include "contentlibraryeffectscategory.h"
-#include "contentlibraryeffect.h"
-
namespace QmlDesigner {
ContentLibraryEffectsCategory::ContentLibraryEffectsCategory(QObject *parent, const QString &name)
: QObject(parent), m_name(name) {}
-void ContentLibraryEffectsCategory::addBundleItem(ContentLibraryEffect *bundleItem)
+void ContentLibraryEffectsCategory::addBundleItem(ContentLibraryItem *bundleItem)
{
m_categoryItems.append(bundleItem);
}
@@ -19,7 +17,7 @@ bool ContentLibraryEffectsCategory::updateImportedState(const QStringList &impor
{
bool changed = false;
- for (ContentLibraryEffect *item : std::as_const(m_categoryItems))
+ for (ContentLibraryItem *item : std::as_const(m_categoryItems))
changed |= item->setImported(importedItems.contains(item->qml().chopped(4)));
return changed;
@@ -28,7 +26,7 @@ bool ContentLibraryEffectsCategory::updateImportedState(const QStringList &impor
bool ContentLibraryEffectsCategory::filter(const QString &searchText)
{
bool visible = false;
- for (ContentLibraryEffect *item : std::as_const(m_categoryItems))
+ for (ContentLibraryItem *item : std::as_const(m_categoryItems))
visible |= item->filter(searchText);
if (visible != m_visible) {
@@ -55,7 +53,7 @@ bool ContentLibraryEffectsCategory::expanded() const
return m_expanded;
}
-QList<ContentLibraryEffect *> ContentLibraryEffectsCategory::categoryItems() const
+QList<ContentLibraryItem *> ContentLibraryEffectsCategory::categoryItems() const
{
return m_categoryItems;
}
diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectscategory.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectscategory.h
index 79737c1ec2..9f56de3c6b 100644
--- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectscategory.h
+++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectscategory.h
@@ -3,12 +3,12 @@
#pragma once
+#include "contentlibraryitem.h"
+
#include <QObject>
namespace QmlDesigner {
-class ContentLibraryEffect;
-
class ContentLibraryEffectsCategory : public QObject
{
Q_OBJECT
@@ -16,20 +16,20 @@ class ContentLibraryEffectsCategory : public QObject
Q_PROPERTY(QString bundleCategoryName MEMBER m_name CONSTANT)
Q_PROPERTY(bool bundleCategoryVisible MEMBER m_visible NOTIFY categoryVisibleChanged)
Q_PROPERTY(bool bundleCategoryExpanded MEMBER m_expanded NOTIFY categoryExpandChanged)
- Q_PROPERTY(QList<ContentLibraryEffect *> bundleCategoryItems MEMBER m_categoryItems
+ Q_PROPERTY(QList<ContentLibraryItem *> bundleCategoryItems MEMBER m_categoryItems
NOTIFY categoryItemsChanged)
public:
ContentLibraryEffectsCategory(QObject *parent, const QString &name);
- void addBundleItem(ContentLibraryEffect *bundleItem);
+ void addBundleItem(ContentLibraryItem *bundleItem);
bool updateImportedState(const QStringList &importedMats);
bool filter(const QString &searchText);
QString name() const;
bool visible() const;
bool expanded() const;
- QList<ContentLibraryEffect *> categoryItems() const;
+ QList<ContentLibraryItem *> categoryItems() const;
signals:
void categoryVisibleChanged();
@@ -41,7 +41,7 @@ private:
bool m_visible = true;
bool m_expanded = true;
- QList<ContentLibraryEffect *> m_categoryItems;
+ QList<ContentLibraryItem *> m_categoryItems;
};
} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectsmodel.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectsmodel.cpp
index 334c017116..e0fe2b6c54 100644
--- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectsmodel.cpp
+++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectsmodel.cpp
@@ -4,8 +4,8 @@
#include "contentlibraryeffectsmodel.h"
#include "contentlibrarybundleimporter.h"
-#include "contentlibraryeffect.h"
#include "contentlibraryeffectscategory.h"
+#include "contentlibraryitem.h"
#include "contentlibrarywidget.h"
#include <qmldesignerplugin.h>
@@ -63,6 +63,11 @@ bool ContentLibraryEffectsModel::isValidIndex(int idx) const
return idx > -1 && idx < rowCount();
}
+QString ContentLibraryEffectsModel::bundleId() const
+{
+ return m_bundleId;
+}
+
void ContentLibraryEffectsModel::updateIsEmpty()
{
bool anyCatVisible = Utils::anyOf(m_bundleCategories, [&](ContentLibraryEffectsCategory *cat) {
@@ -88,49 +93,23 @@ QHash<int, QByteArray> ContentLibraryEffectsModel::roleNames() const
return roles;
}
-void ContentLibraryEffectsModel::createImporter(const QString &bundlePath, const QString &bundleId,
- const QStringList &sharedFiles)
-{
- m_importer = new Internal::ContentLibraryBundleImporter(bundlePath, bundleId, sharedFiles);
-#ifdef QDS_USE_PROJECTSTORAGE
- connect(m_importer,
- &Internal::ContentLibraryBundleImporter::importFinished,
- this,
- [&](const QmlDesigner::TypeName &typeName) {
- m_importerRunning = false;
- emit importerRunningChanged();
- if (typeName.size())
- emit bundleItemImported(typeName);
- });
-#else
- connect(m_importer,
- &Internal::ContentLibraryBundleImporter::importFinished,
- this,
- [&](const QmlDesigner::NodeMetaInfo &metaInfo) {
- m_importerRunning = false;
- emit importerRunningChanged();
- if (metaInfo.isValid())
- emit bundleItemImported(metaInfo);
- });
-#endif
-
- connect(m_importer, &Internal::ContentLibraryBundleImporter::unimportFinished, this,
- [&](const QmlDesigner::NodeMetaInfo &metaInfo) {
- Q_UNUSED(metaInfo)
- m_importerRunning = false;
- emit importerRunningChanged();
- emit bundleItemUnimported(metaInfo);
- });
-
- resetModel();
- updateIsEmpty();
-}
-
void ContentLibraryEffectsModel::loadBundle()
{
- if (m_bundleExists || m_probeBundleDir)
+ auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils();
+
+ if (m_probeBundleDir || (m_bundleExists && m_bundleId == compUtils.effectsBundleId()))
return;
+ // clean up
+ qDeleteAll(m_bundleCategories);
+ m_bundleCategories.clear();
+ m_bundleExists = false;
+ m_isEmpty = true;
+ m_probeBundleDir = false;
+ m_bundleObj = {};
+ m_bundleId.clear();
+ m_bundlePath.clear();
+
QDir bundleDir;
if (!qEnvironmentVariable("EFFECT_BUNDLE_PATH").isEmpty())
@@ -145,30 +124,32 @@ void ContentLibraryEffectsModel::loadBundle()
while (!bundleDir.cd("effect_bundle") && bundleDir.cdUp())
; // do nothing
- if (bundleDir.dirName() != "effect_bundle") // bundlePathDir not found
+ if (bundleDir.dirName() != "effect_bundle") { // bundlePathDir not found
+ resetModel();
return;
+ }
}
QString bundlePath = bundleDir.filePath("effect_bundle.json");
- if (m_bundleObj.isEmpty()) {
- QFile propsFile(bundlePath);
-
- if (!propsFile.open(QIODevice::ReadOnly)) {
- qWarning("Couldn't open effect_bundle.json");
- return;
- }
+ QFile bundleFile(bundlePath);
+ if (!bundleFile.open(QIODevice::ReadOnly)) {
+ qWarning("Couldn't open effect_bundle.json");
+ resetModel();
+ return;
+ }
- QJsonDocument bundleJsonDoc = QJsonDocument::fromJson(propsFile.readAll());
- if (bundleJsonDoc.isNull()) {
- qWarning("Invalid effect_bundle.json file");
- return;
- } else {
- m_bundleObj = bundleJsonDoc.object();
- }
+ QJsonDocument bundleJsonDoc = QJsonDocument::fromJson(bundleFile.readAll());
+ if (bundleJsonDoc.isNull()) {
+ qWarning("Invalid effect_bundle.json file");
+ resetModel();
+ return;
}
- QString bundleId = m_bundleObj.value("id").toString();
+ m_bundleObj = bundleJsonDoc.object();
+
+ QString bundleType = compUtils.effectsBundleType();
+ m_bundleId = compUtils.effectsBundleId();
const QJsonObject catsObj = m_bundleObj.value("categories").toObject();
const QStringList categories = catsObj.keys();
@@ -176,39 +157,36 @@ void ContentLibraryEffectsModel::loadBundle()
auto category = new ContentLibraryEffectsCategory(this, cat);
const QJsonObject itemsObj = catsObj.value(cat).toObject();
- const QStringList items = itemsObj.keys();
- for (const QString &item : items) {
- const QJsonObject itemObj = itemsObj.value(item).toObject();
+ const QStringList itemsNames = itemsObj.keys();
+ for (const QString &itemName : itemsNames) {
+ const QJsonObject itemObj = itemsObj.value(itemName).toObject();
QStringList files;
const QJsonArray assetsArr = itemObj.value("files").toArray();
- for (const auto /*QJson{Const,}ValueRef*/ &asset : assetsArr)
+ for (const QJsonValueConstRef &asset : assetsArr)
files.append(asset.toString());
QUrl icon = QUrl::fromLocalFile(bundleDir.filePath(itemObj.value("icon").toString()));
QString qml = itemObj.value("qml").toString();
- TypeName type = QLatin1String("%1.%2.%3")
- .arg(QmlDesignerPlugin::instance()->documentManager()
- .generatedComponentUtils().componentBundlesTypePrefix(),
- bundleId,
- qml.chopped(4)).toLatin1(); // chopped(4): remove .qml
+ TypeName type = QLatin1String("%1.%2")
+ .arg(bundleType, qml.chopped(4)).toLatin1(); // chopped(4): remove .qml
- auto bundleItem = new ContentLibraryEffect(category, item, qml, type, icon, files);
+ auto bundleItem = new ContentLibraryItem(category, itemName, qml, type, icon, files);
category->addBundleItem(bundleItem);
}
m_bundleCategories.append(category);
}
- QStringList sharedFiles;
+ m_bundleSharedFiles.clear();
const QJsonArray sharedFilesArr = m_bundleObj.value("sharedFiles").toArray();
- for (const auto /*QJson{Const,}ValueRef*/ &file : sharedFilesArr)
- sharedFiles.append(file.toString());
-
- createImporter(bundleDir.path(), bundleId, sharedFiles);
+ for (const QJsonValueConstRef &file : sharedFilesArr)
+ m_bundleSharedFiles.append(file.toString());
+ m_bundlePath = bundleDir.path();
m_bundleExists = true;
- emit bundleExistsChanged();
+ updateIsEmpty();
+ resetModel();
}
bool ContentLibraryEffectsModel::hasRequiredQuick3DImport() const
@@ -221,11 +199,6 @@ bool ContentLibraryEffectsModel::bundleExists() const
return m_bundleExists;
}
-Internal::ContentLibraryBundleImporter *ContentLibraryEffectsModel::bundleImporter() const
-{
- return m_importer;
-}
-
void ContentLibraryEffectsModel::setSearchText(const QString &searchText)
{
QString lowerSearchText = searchText.toLower();
@@ -278,30 +251,26 @@ void ContentLibraryEffectsModel::resetModel()
endResetModel();
}
-void ContentLibraryEffectsModel::addInstance(ContentLibraryEffect *bundleItem)
+void ContentLibraryEffectsModel::addInstance(ContentLibraryItem *bundleItem)
{
- QString err = m_importer->importComponent(bundleItem->qml(), bundleItem->files());
+ QString err = m_widget->importer()->importComponent(m_bundlePath, bundleItem->type(),
+ bundleItem->qml(),
+ bundleItem->files() + m_bundleSharedFiles);
- if (err.isEmpty()) {
- m_importerRunning = true;
- emit importerRunningChanged();
- } else {
+ if (err.isEmpty())
+ m_widget->setImporterRunning(true);
+ else
qWarning() << __FUNCTION__ << err;
- }
}
-void ContentLibraryEffectsModel::removeFromProject(ContentLibraryEffect *bundleItem)
+void ContentLibraryEffectsModel::removeFromProject(ContentLibraryItem *bundleItem)
{
- emit bundleItemAboutToUnimport(bundleItem->type());
+ QString err = m_widget->importer()->unimportComponent(bundleItem->type(), bundleItem->qml());
- QString err = m_importer->unimportComponent(bundleItem->qml());
-
- if (err.isEmpty()) {
- m_importerRunning = true;
- emit importerRunningChanged();
- } else {
+ if (err.isEmpty())
+ m_widget->setImporterRunning(true);
+ else
qWarning() << __FUNCTION__ << err;
- }
}
} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectsmodel.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectsmodel.h
index 5d67ac3da8..5bcb857fca 100644
--- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectsmodel.h
+++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectsmodel.h
@@ -3,22 +3,15 @@
#pragma once
-#include "nodemetainfo.h"
-
#include <QAbstractListModel>
-#include <QDir>
#include <QJsonObject>
namespace QmlDesigner {
-class ContentLibraryEffect;
+class ContentLibraryItem;
class ContentLibraryEffectsCategory;
class ContentLibraryWidget;
-namespace Internal {
-class ContentLibraryBundleImporter;
-}
-
class ContentLibraryEffectsModel : public QAbstractListModel
{
Q_OBJECT
@@ -26,7 +19,6 @@ class ContentLibraryEffectsModel : public QAbstractListModel
Q_PROPERTY(bool bundleExists READ bundleExists NOTIFY bundleExistsChanged)
Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged)
Q_PROPERTY(bool hasRequiredQuick3DImport READ hasRequiredQuick3DImport NOTIFY hasRequiredQuick3DImportChanged)
- Q_PROPERTY(bool importerRunning MEMBER m_importerRunning NOTIFY importerRunningChanged)
public:
ContentLibraryEffectsModel(ContentLibraryWidget *parent = nullptr);
@@ -49,46 +41,33 @@ public:
void resetModel();
void updateIsEmpty();
- Internal::ContentLibraryBundleImporter *bundleImporter() const;
+ Q_INVOKABLE void addInstance(QmlDesigner::ContentLibraryItem *bundleItem);
+ Q_INVOKABLE void removeFromProject(QmlDesigner::ContentLibraryItem *bundleItem);
- Q_INVOKABLE void addInstance(QmlDesigner::ContentLibraryEffect *bundleItem);
- Q_INVOKABLE void removeFromProject(QmlDesigner::ContentLibraryEffect *bundleItem);
+ QString bundleId() const;
signals:
void isEmptyChanged();
void hasRequiredQuick3DImportChanged();
-#ifdef QDS_USE_PROJECTSTORAGE
- void bundleItemImported(const QmlDesigner::TypeName &typeName);
-#else
- void bundleItemImported(const QmlDesigner::NodeMetaInfo &metaInfo);
-#endif
- void bundleItemAboutToUnimport(const QmlDesigner::TypeName &type);
- void bundleItemUnimported(const QmlDesigner::NodeMetaInfo &metaInfo);
- void importerRunningChanged();
void bundleExistsChanged();
private:
bool isValidIndex(int idx) const;
- void createImporter(const QString &bundlePath, const QString &bundleId,
- const QStringList &sharedFiles);
ContentLibraryWidget *m_widget = nullptr;
QString m_searchText;
+ QString m_bundlePath;
+ QString m_bundleId;
+ QStringList m_bundleSharedFiles;
QList<ContentLibraryEffectsCategory *> m_bundleCategories;
QJsonObject m_bundleObj;
- Internal::ContentLibraryBundleImporter *m_importer = nullptr;
bool m_isEmpty = true;
bool m_bundleExists = false;
bool m_probeBundleDir = false;
- bool m_importerRunning = false;
int m_quick3dMajorVersion = -1;
int m_quick3dMinorVersion = -1;
-
- QString m_importerBundlePath;
- QString m_importerBundleId;
- QStringList m_importerSharedFiles;
};
} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryitem.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryitem.cpp
new file mode 100644
index 0000000000..38fb69abbc
--- /dev/null
+++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryitem.cpp
@@ -0,0 +1,76 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "contentlibraryitem.h"
+
+namespace QmlDesigner {
+
+ContentLibraryItem::ContentLibraryItem(QObject *parent,
+ const QString &name,
+ const QString &qml,
+ const TypeName &type,
+ const QUrl &icon,
+ const QStringList &files)
+ : QObject(parent), m_name(name), m_qml(qml), m_type(type), m_icon(icon), m_files(files)
+{
+ m_allFiles = m_files;
+ m_allFiles.push_back(m_qml);
+}
+
+bool ContentLibraryItem::filter(const QString &searchText)
+{
+ if (m_visible != m_name.contains(searchText, Qt::CaseInsensitive)) {
+ m_visible = !m_visible;
+ emit itemVisibleChanged();
+ }
+
+ return m_visible;
+}
+
+QUrl ContentLibraryItem::icon() const
+{
+ return m_icon;
+}
+
+QString ContentLibraryItem::qml() const
+{
+ return m_qml;
+}
+
+TypeName ContentLibraryItem::type() const
+{
+ return m_type;
+}
+
+QStringList ContentLibraryItem::files() const
+{
+ return m_files;
+}
+
+bool ContentLibraryItem::visible() const
+{
+ return m_visible;
+}
+
+bool ContentLibraryItem::setImported(bool imported)
+{
+ if (m_imported != imported) {
+ m_imported = imported;
+ emit itemImportedChanged();
+ return true;
+ }
+
+ return false;
+}
+
+bool ContentLibraryItem::imported() const
+{
+ return m_imported;
+}
+
+QStringList ContentLibraryItem::allFiles() const
+{
+ return m_allFiles;
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffect.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryitem.h
index fdb302b613..bdf8736728 100644
--- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffect.h
+++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryitem.h
@@ -10,7 +10,7 @@
namespace QmlDesigner {
-class ContentLibraryEffect : public QObject
+class ContentLibraryItem : public QObject
{
Q_OBJECT
@@ -19,14 +19,15 @@ class ContentLibraryEffect : public QObject
Q_PROPERTY(QStringList bundleItemFiles READ allFiles CONSTANT)
Q_PROPERTY(bool bundleItemVisible MEMBER m_visible NOTIFY itemVisibleChanged)
Q_PROPERTY(bool bundleItemImported READ imported WRITE setImported NOTIFY itemImportedChanged)
+ Q_PROPERTY(QString itemType MEMBER m_itemType CONSTANT)
public:
- ContentLibraryEffect(QObject *parent,
- const QString &name,
- const QString &qml,
- const TypeName &type,
- const QUrl &icon,
- const QStringList &files);
+ ContentLibraryItem(QObject *parent,
+ const QString &name,
+ const QString &qml,
+ const TypeName &type,
+ const QUrl &icon,
+ const QStringList &files);
bool filter(const QString &searchText);
@@ -35,11 +36,9 @@ public:
TypeName type() const;
QStringList files() const;
bool visible() const;
- QString qmlFilePath() const;
bool setImported(bool imported);
bool imported() const;
- QString parentDirPath() const;
QStringList allFiles() const;
signals:
@@ -57,6 +56,7 @@ private:
bool m_imported = false;
QStringList m_allFiles;
+ const QString m_itemType = "item";
};
} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterial.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterial.cpp
index 834bc8aa30..25d1523199 100644
--- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterial.cpp
+++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterial.cpp
@@ -32,6 +32,11 @@ bool ContentLibraryMaterial::filter(const QString &searchText)
return m_visible;
}
+QString ContentLibraryMaterial::name() const
+{
+ return m_name;
+}
+
QUrl ContentLibraryMaterial::icon() const
{
return m_icon;
@@ -84,7 +89,7 @@ QString ContentLibraryMaterial::qmlFilePath() const
return m_downloadPath + "/" + m_qml;
}
-QString ContentLibraryMaterial::parentDirPath() const
+QString ContentLibraryMaterial::dirPath() const
{
return m_downloadPath;
}
diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterial.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterial.h
index 55af2accbd..aaaf9517f9 100644
--- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterial.h
+++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterial.h
@@ -17,9 +17,9 @@ class ContentLibraryMaterial : public QObject
Q_PROPERTY(QString bundleMaterialName MEMBER m_name CONSTANT)
Q_PROPERTY(QUrl bundleMaterialIcon MEMBER m_icon CONSTANT)
Q_PROPERTY(bool bundleMaterialVisible MEMBER m_visible NOTIFY materialVisibleChanged)
- Q_PROPERTY(bool bundleMaterialImported READ imported WRITE setImported NOTIFY materialImportedChanged)
+ Q_PROPERTY(bool bundleItemImported READ imported WRITE setImported NOTIFY materialImportedChanged)
Q_PROPERTY(QString bundleMaterialBaseWebUrl MEMBER m_baseWebUrl CONSTANT)
- Q_PROPERTY(QString bundleMaterialParentPath READ parentDirPath CONSTANT)
+ Q_PROPERTY(QString bundleMaterialDirPath READ dirPath CONSTANT)
Q_PROPERTY(QStringList bundleMaterialFiles READ allFiles CONSTANT)
Q_PROPERTY(QString itemType MEMBER m_itemType CONSTANT)
@@ -37,6 +37,7 @@ public:
Q_INVOKABLE bool isDownloaded() const;
+ QString name() const;
QUrl icon() const;
QString qml() const;
TypeName type() const;
@@ -46,7 +47,7 @@ public:
bool setImported(bool imported);
bool imported() const;
- QString parentDirPath() const;
+ QString dirPath() const;
QStringList allFiles() const;
signals:
diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterialsmodel.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterialsmodel.cpp
index 26747d359c..adb47108a9 100644
--- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterialsmodel.cpp
+++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterialsmodel.cpp
@@ -20,6 +20,7 @@
#include <utils/qtcassert.h>
#include <QCoreApplication>
+#include <QDir>
#include <QJsonArray>
#include <QJsonDocument>
#include <QQmlEngine>
@@ -40,7 +41,10 @@ ContentLibraryMaterialsModel::ContentLibraryMaterialsModel(ContentLibraryWidget
qmlRegisterType<QmlDesigner::FileDownloader>("WebFetcher", 1, 0, "FileDownloader");
qmlRegisterType<QmlDesigner::MultiFileDownloader>("WebFetcher", 1, 0, "MultiFileDownloader");
+}
+void ContentLibraryMaterialsModel::loadBundle()
+{
QDir bundleDir{m_downloadPath};
if (fetchBundleMetadata(bundleDir) && fetchBundleIcons(bundleDir))
loadMaterialBundle(bundleDir);
@@ -192,11 +196,9 @@ void ContentLibraryMaterialsModel::downloadSharedFiles(const QDir &targetDir, co
extractor->setAlwaysCreateDir(false);
extractor->setClearTargetPathContents(false);
- QObject::connect(extractor, &FileExtractor::finishedChanged, this, [this, downloader, extractor]() {
+ QObject::connect(extractor, &FileExtractor::finishedChanged, this, [downloader, extractor]() {
downloader->deleteLater();
extractor->deleteLater();
-
- createImporter(m_importerBundlePath, m_importerBundleId, m_importerSharedFiles);
});
extractor->extract();
@@ -205,71 +207,48 @@ void ContentLibraryMaterialsModel::downloadSharedFiles(const QDir &targetDir, co
downloader->start();
}
-void ContentLibraryMaterialsModel::createImporter(const QString &bundlePath, const QString &bundleId,
- const QStringList &sharedFiles)
+QString ContentLibraryMaterialsModel::bundleId() const
{
- m_importer = new Internal::ContentLibraryBundleImporter(bundlePath, bundleId, sharedFiles);
-#ifdef QDS_USE_PROJECTSTORAGE
- connect(m_importer,
- &Internal::ContentLibraryBundleImporter::importFinished,
- this,
- [&](const QmlDesigner::TypeName &typeName) {
- m_importerRunning = false;
- emit importerRunningChanged();
- if (typeName.size())
- emit bundleMaterialImported(typeName);
- });
-#else
- connect(m_importer,
- &Internal::ContentLibraryBundleImporter::importFinished,
- this,
- [&](const QmlDesigner::NodeMetaInfo &metaInfo) {
- m_importerRunning = false;
- emit importerRunningChanged();
- if (metaInfo.isValid())
- emit bundleMaterialImported(metaInfo);
- });
-#endif
-
- connect(m_importer, &Internal::ContentLibraryBundleImporter::unimportFinished, this,
- [&](const QmlDesigner::NodeMetaInfo &metaInfo) {
- Q_UNUSED(metaInfo)
- m_importerRunning = false;
- emit importerRunningChanged();
- emit bundleMaterialUnimported(metaInfo);
- });
-
- resetModel();
- updateIsEmpty();
+ return m_bundleId;
}
-void ContentLibraryMaterialsModel::loadMaterialBundle(const QDir &matBundleDir)
+void ContentLibraryMaterialsModel::loadMaterialBundle(const QDir &bundleDir)
{
- if (m_matBundleExists)
+ auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils();
+
+ if (m_bundleExists && m_bundleId == compUtils.materialsBundleId())
return;
- QString matBundlePath = matBundleDir.filePath("material_bundle.json");
+ // clean up
+ qDeleteAll(m_bundleCategories);
+ m_bundleCategories.clear();
+ m_bundleExists = false;
+ m_isEmpty = true;
+ m_bundleObj = {};
+ m_bundleId.clear();
- if (m_matBundleObj.isEmpty()) {
- QFile matPropsFile(matBundlePath);
+ QString bundlePath = bundleDir.filePath("material_bundle.json");
- if (!matPropsFile.open(QIODevice::ReadOnly)) {
- qWarning("Couldn't open material_bundle.json");
- return;
- }
+ QFile bundleFile(bundlePath);
+ if (!bundleFile.open(QIODevice::ReadOnly)) {
+ qWarning("Couldn't open material_bundle.json");
+ resetModel();
+ return;
+ }
- QJsonDocument matBundleJsonDoc = QJsonDocument::fromJson(matPropsFile.readAll());
- if (matBundleJsonDoc.isNull()) {
- qWarning("Invalid material_bundle.json file");
- return;
- } else {
- m_matBundleObj = matBundleJsonDoc.object();
- }
+ QJsonDocument bundleJsonDoc = QJsonDocument::fromJson(bundleFile.readAll());
+ if (bundleJsonDoc.isNull()) {
+ qWarning("Invalid material_bundle.json file");
+ resetModel();
+ return;
}
- QString bundleId = m_matBundleObj.value("id").toString();
+ m_bundleObj = bundleJsonDoc.object();
+
+ QString bundleType = compUtils.materialsBundleType();
+ m_bundleId = compUtils.materialsBundleId();
- const QJsonObject catsObj = m_matBundleObj.value("categories").toObject();
+ const QJsonObject catsObj = m_bundleObj.value("categories").toObject();
const QStringList categories = catsObj.keys();
for (const QString &cat : categories) {
auto category = new ContentLibraryMaterialsCategory(this, cat);
@@ -281,16 +260,13 @@ void ContentLibraryMaterialsModel::loadMaterialBundle(const QDir &matBundleDir)
QStringList files;
const QJsonArray assetsArr = matObj.value("files").toArray();
- for (const auto /*QJson{Const,}ValueRef*/ &asset : assetsArr)
+ for (const QJsonValueConstRef &asset : assetsArr)
files.append(asset.toString());
- QUrl icon = QUrl::fromLocalFile(matBundleDir.filePath(matObj.value("icon").toString()));
+ QUrl icon = QUrl::fromLocalFile(bundleDir.filePath(matObj.value("icon").toString()));
QString qml = matObj.value("qml").toString();
- TypeName type = QLatin1String("%1.%2.%3")
- .arg(QmlDesignerPlugin::instance()->documentManager()
- .generatedComponentUtils().componentBundlesTypePrefix(),
- bundleId,
- qml.chopped(4)).toLatin1(); // chopped(4): remove .qml
+ TypeName type = QLatin1String("%1.%2")
+ .arg(bundleType, qml.chopped(4)).toLatin1(); // chopped(4): remove .qml
auto bundleMat = new ContentLibraryMaterial(category, matName, qml, type, icon, files,
m_downloadPath, m_baseUrl);
@@ -300,30 +276,23 @@ void ContentLibraryMaterialsModel::loadMaterialBundle(const QDir &matBundleDir)
m_bundleCategories.append(category);
}
- QStringList sharedFiles;
- const QJsonArray sharedFilesArr = m_matBundleObj.value("sharedFiles").toArray();
- for (const auto /*QJson{Const,}ValueRef*/ &file : sharedFilesArr)
- sharedFiles.append(file.toString());
+ m_bundleSharedFiles.clear();
+ const QJsonArray sharedFilesArr = m_bundleObj.value("sharedFiles").toArray();
+ for (const QJsonValueConstRef &file : sharedFilesArr)
+ m_bundleSharedFiles.append(file.toString());
QStringList missingSharedFiles;
- for (const QString &s : std::as_const(sharedFiles)) {
- const QString fullSharedFilePath = matBundleDir.filePath(s);
-
- if (!QFileInfo::exists(fullSharedFilePath))
+ for (const QString &s : std::as_const(m_bundleSharedFiles)) {
+ if (!QFileInfo::exists(bundleDir.filePath(s)))
missingSharedFiles.push_back(s);
}
- if (missingSharedFiles.length() > 0) {
- m_importerBundlePath = matBundleDir.path();
- m_importerBundleId = bundleId;
- m_importerSharedFiles = sharedFiles;
- downloadSharedFiles(matBundleDir, missingSharedFiles);
- } else {
- createImporter(matBundleDir.path(), bundleId, sharedFiles);
- }
+ if (missingSharedFiles.length() > 0)
+ downloadSharedFiles(bundleDir, missingSharedFiles);
- m_matBundleExists = true;
- emit matBundleExistsChanged();
+ m_bundleExists = true;
+ updateIsEmpty();
+ resetModel();
}
bool ContentLibraryMaterialsModel::hasRequiredQuick3DImport() const
@@ -333,12 +302,7 @@ bool ContentLibraryMaterialsModel::hasRequiredQuick3DImport() const
bool ContentLibraryMaterialsModel::matBundleExists() const
{
- return m_matBundleExists;
-}
-
-Internal::ContentLibraryBundleImporter *ContentLibraryMaterialsModel::bundleImporter() const
-{
- return m_importer;
+ return m_bundleExists;
}
void ContentLibraryMaterialsModel::setSearchText(const QString &searchText)
@@ -360,11 +324,11 @@ void ContentLibraryMaterialsModel::setSearchText(const QString &searchText)
updateIsEmpty();
}
-void ContentLibraryMaterialsModel::updateImportedState(const QStringList &importedMats)
+void ContentLibraryMaterialsModel::updateImportedState(const QStringList &importedItems)
{
bool changed = false;
for (ContentLibraryMaterialsCategory *cat : std::as_const(m_bundleCategories))
- changed |= cat->updateImportedState(importedMats);
+ changed |= cat->updateImportedState(importedItems);
if (changed)
resetModel();
@@ -400,42 +364,23 @@ void ContentLibraryMaterialsModel::applyToSelected(ContentLibraryMaterial *mat,
void ContentLibraryMaterialsModel::addToProject(ContentLibraryMaterial *mat)
{
- QString err = m_importer->importComponent(mat->qml(), mat->files());
+ QString err = m_widget->importer()->importComponent(mat->dirPath(), mat->type(), mat->qml(),
+ mat->files() + m_bundleSharedFiles);
- if (err.isEmpty()) {
- m_importerRunning = true;
- emit importerRunningChanged();
- } else {
+ if (err.isEmpty())
+ m_widget->setImporterRunning(true);
+ else
qWarning() << __FUNCTION__ << err;
- }
}
void ContentLibraryMaterialsModel::removeFromProject(ContentLibraryMaterial *mat)
{
- emit bundleMaterialAboutToUnimport(mat->type());
+ QString err = m_widget->importer()->unimportComponent(mat->type(), mat->qml());
- QString err = m_importer->unimportComponent(mat->qml());
-
- if (err.isEmpty()) {
- m_importerRunning = true;
- emit importerRunningChanged();
- } else {
+ if (err.isEmpty())
+ m_widget->setImporterRunning(true);
+ else
qWarning() << __FUNCTION__ << err;
- }
-}
-
-bool ContentLibraryMaterialsModel::hasModelSelection() const
-{
- return m_hasModelSelection;
-}
-
-void ContentLibraryMaterialsModel::setHasModelSelection(bool b)
-{
- if (b == m_hasModelSelection)
- return;
-
- m_hasModelSelection = b;
- emit hasModelSelectionChanged();
}
} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterialsmodel.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterialsmodel.h
index 21bd374137..c0840dc3cc 100644
--- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterialsmodel.h
+++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterialsmodel.h
@@ -3,22 +3,17 @@
#pragma once
-#include "nodemetainfo.h"
-
#include <QAbstractListModel>
-#include <QDir>
#include <QJsonObject>
+QT_FORWARD_DECLARE_CLASS(QDir)
+
namespace QmlDesigner {
class ContentLibraryMaterial;
class ContentLibraryMaterialsCategory;
class ContentLibraryWidget;
-namespace Internal {
-class ContentLibraryBundleImporter;
-}
-
class ContentLibraryMaterialsModel : public QAbstractListModel
{
Q_OBJECT
@@ -26,8 +21,6 @@ class ContentLibraryMaterialsModel : public QAbstractListModel
Q_PROPERTY(bool matBundleExists READ matBundleExists NOTIFY matBundleExistsChanged)
Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged)
Q_PROPERTY(bool hasRequiredQuick3DImport READ hasRequiredQuick3DImport NOTIFY hasRequiredQuick3DImportChanged)
- Q_PROPERTY(bool hasModelSelection READ hasModelSelection NOTIFY hasModelSelectionChanged)
- Q_PROPERTY(bool importerRunning MEMBER m_importerRunning NOTIFY importerRunningChanged)
public:
ContentLibraryMaterialsModel(ContentLibraryWidget *parent = nullptr);
@@ -38,40 +31,27 @@ public:
QHash<int, QByteArray> roleNames() const override;
void setSearchText(const QString &searchText);
- void updateImportedState(const QStringList &importedMats);
-
+ void updateImportedState(const QStringList &importedItems);
void setQuick3DImportVersion(int major, int minor);
bool hasRequiredQuick3DImport() const;
-
bool matBundleExists() const;
- bool hasModelSelection() const;
- void setHasModelSelection(bool b);
-
void resetModel();
void updateIsEmpty();
-
- Internal::ContentLibraryBundleImporter *bundleImporter() const;
+ void loadBundle();
Q_INVOKABLE void applyToSelected(QmlDesigner::ContentLibraryMaterial *mat, bool add = false);
Q_INVOKABLE void addToProject(QmlDesigner::ContentLibraryMaterial *mat);
Q_INVOKABLE void removeFromProject(QmlDesigner::ContentLibraryMaterial *mat);
+ QString bundleId() const;
+
signals:
void isEmptyChanged();
void hasRequiredQuick3DImportChanged();
- void hasModelSelectionChanged();
void materialVisibleChanged();
void applyToSelectedTriggered(QmlDesigner::ContentLibraryMaterial *mat, bool add = false);
-#ifdef QDS_USE_PROJECTSTORAGE
- void bundleMaterialImported(const QmlDesigner::TypeName &typeName);
-#else
- void bundleMaterialImported(const QmlDesigner::NodeMetaInfo &metaInfo);
-#endif
- void bundleMaterialAboutToUnimport(const QmlDesigner::TypeName &type);
- void bundleMaterialUnimported(const QmlDesigner::NodeMetaInfo &metaInfo);
- void importerRunningChanged();
void matBundleExistsChanged();
private:
@@ -80,29 +60,22 @@ private:
bool fetchBundleMetadata(const QDir &bundleDir);
bool isValidIndex(int idx) const;
void downloadSharedFiles(const QDir &targetDir, const QStringList &files);
- void createImporter(const QString &bundlePath, const QString &bundleId,
- const QStringList &sharedFiles);
ContentLibraryWidget *m_widget = nullptr;
QString m_searchText;
+ QString m_bundleId;
+ QStringList m_bundleSharedFiles;
QList<ContentLibraryMaterialsCategory *> m_bundleCategories;
- QJsonObject m_matBundleObj;
- Internal::ContentLibraryBundleImporter *m_importer = nullptr;
+ QJsonObject m_bundleObj;
bool m_isEmpty = true;
- bool m_matBundleExists = false;
- bool m_hasModelSelection = false;
- bool m_importerRunning = false;
+ bool m_bundleExists = false;
int m_quick3dMajorVersion = -1;
int m_quick3dMinorVersion = -1;
QString m_downloadPath;
QString m_baseUrl;
-
- QString m_importerBundlePath;
- QString m_importerBundleId;
- QStringList m_importerSharedFiles;
};
} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.cpp
index 80dd7e816f..d2c2df2baa 100644
--- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.cpp
+++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.cpp
@@ -120,6 +120,11 @@ void ContentLibraryTexture::doSetDownloaded()
m_toolTip = resolveToolTipText();
}
+bool ContentLibraryTexture::visible() const
+{
+ return m_visible;
+}
+
QString ContentLibraryTexture::parentDirPath() const
{
return m_dirPath;
diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.h
index 8f7197bc72..7f5db6d7d6 100644
--- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.h
+++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.h
@@ -46,6 +46,8 @@ public:
void setHasUpdate(bool value);
bool hasUpdate() const;
+ bool visible() const;
+
signals:
void textureVisibleChanged();
void textureToolTipChanged();
diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.cpp
index ed9723a151..8dcf4575f7 100644
--- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.cpp
+++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.cpp
@@ -4,7 +4,7 @@
#include "contentlibraryusermodel.h"
#include "contentlibrarybundleimporter.h"
-#include "contentlibraryeffect.h"
+#include "contentlibraryitem.h"
#include "contentlibrarymaterial.h"
#include "contentlibrarymaterialscategory.h"
#include "contentlibrarytexture.h"
@@ -12,18 +12,16 @@
#include <designerpaths.h>
#include <imageutils.h>
+#include <qmldesignerconstants.h>
#include <qmldesignerplugin.h>
+#include <uniquename.h>
#include <utils/algorithm.h>
-#include <utils/hostosinfo.h>
#include <utils/qtcassert.h>
-#include <QCoreApplication>
#include <QFileInfo>
#include <QJsonArray>
#include <QJsonDocument>
-#include <QQmlEngine>
-#include <QStandardPaths>
#include <QUrl>
namespace QmlDesigner {
@@ -32,10 +30,7 @@ ContentLibraryUserModel::ContentLibraryUserModel(ContentLibraryWidget *parent)
: QAbstractListModel(parent)
, m_widget(parent)
{
- m_userCategories = {tr("Materials"), tr("Textures")/*, tr("3D"), tr("Effects"), tr("2D components")*/}; // TODO
-
- loadMaterialBundle();
- loadTextureBundle();
+ m_userCategories = {tr("Materials"), tr("Textures"), tr("3D"), /*tr("Effects"), tr("2D components")*/}; // TODO
}
int ContentLibraryUserModel::rowCount(const QModelIndex &) const
@@ -52,18 +47,37 @@ QVariant ContentLibraryUserModel::data(const QModelIndex &index, int role) const
return m_userCategories.at(index.row());
if (role == ItemsRole) {
- if (index.row() == 0)
+ if (index.row() == MaterialsSectionIdx)
return QVariant::fromValue(m_userMaterials);
- if (index.row() == 1)
+ if (index.row() == TexturesSectionIdx)
return QVariant::fromValue(m_userTextures);
- if (index.row() == 2)
+ if (index.row() == Items3DSectionIdx)
return QVariant::fromValue(m_user3DItems);
- if (index.row() == 3)
+ if (index.row() == EffectsSectionIdx)
return QVariant::fromValue(m_userEffects);
}
- if (role == VisibleRole)
- return true; // TODO
+ if (role == NoMatchRole) {
+ if (index.row() == MaterialsSectionIdx)
+ return m_noMatchMaterials;
+ if (index.row() == TexturesSectionIdx)
+ return m_noMatchTextures;
+ if (index.row() == Items3DSectionIdx)
+ return m_noMatch3D;
+ if (index.row() == EffectsSectionIdx)
+ return m_noMatchEffects;
+ }
+
+ if (role == VisibleRole) {
+ if (index.row() == MaterialsSectionIdx)
+ return !m_userMaterials.isEmpty();
+ if (index.row() == TexturesSectionIdx)
+ return !m_userTextures.isEmpty();
+ if (index.row() == Items3DSectionIdx)
+ return !m_user3DItems.isEmpty();
+ if (index.row() == EffectsSectionIdx)
+ return !m_userEffects.isEmpty();
+ }
return {};
}
@@ -73,29 +87,56 @@ bool ContentLibraryUserModel::isValidIndex(int idx) const
return idx > -1 && idx < rowCount();
}
-void ContentLibraryUserModel::updateIsEmpty()
+void ContentLibraryUserModel::updateNoMatchMaterials()
{
- bool anyMatVisible = Utils::anyOf(m_userMaterials, [&](ContentLibraryMaterial *mat) {
- return mat->visible();
+ m_noMatchMaterials = Utils::allOf(m_userMaterials, [&](ContentLibraryMaterial *item) {
+ return !item->visible();
});
+}
- bool newEmpty = !anyMatVisible || !m_widget->hasMaterialLibrary() || !hasRequiredQuick3DImport();
+void ContentLibraryUserModel::updateNoMatchTextures()
+{
+ m_noMatchTextures = Utils::allOf(m_userTextures, [&](ContentLibraryTexture *item) {
+ return !item->visible();
+ });
+}
- if (newEmpty != m_isEmpty) {
- m_isEmpty = newEmpty;
- emit isEmptyChanged();
- }
+void ContentLibraryUserModel::updateNoMatch3D()
+{
+ m_noMatch3D = Utils::allOf(m_user3DItems, [&](ContentLibraryItem *item) {
+ return !item->visible();
+ });
}
void ContentLibraryUserModel::addMaterial(const QString &name, const QString &qml,
const QUrl &icon, const QStringList &files)
{
- auto libMat = new ContentLibraryMaterial(this, name, qml, qmlToModule(qml), icon, files,
- Paths::bundlesPathSetting().append("/User/materials"));
+ auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils();
+
+ QString typePrefix = compUtils.userMaterialsBundleType();
+ TypeName type = QLatin1String("%1.%2").arg(typePrefix, qml.chopped(4)).toLatin1();
+ auto libMat = new ContentLibraryMaterial(this, name, qml, type, icon, files,
+ Paths::bundlesPathSetting().append("/User/materials"));
m_userMaterials.append(libMat);
- int matSectionIdx = 0;
- emit dataChanged(index(matSectionIdx), index(matSectionIdx));
+
+ emit dataChanged(index(MaterialsSectionIdx), index(MaterialsSectionIdx));
+}
+
+void ContentLibraryUserModel::add3DItem(const QString &name, const QString &qml,
+ const QUrl &icon, const QStringList &files)
+{
+ auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils();
+
+ QString typePrefix = compUtils.user3DBundleType();
+ TypeName type = QLatin1String("%1.%2").arg(typePrefix, qml.chopped(4)).toLatin1();
+
+ m_user3DItems.append(new ContentLibraryItem(this, name, qml, type, icon, files));
+}
+
+void ContentLibraryUserModel::refresh3DSection()
+{
+ emit dataChanged(index(Items3DSectionIdx), index(Items3DSectionIdx));
}
void ContentLibraryUserModel::addTextures(const QStringList &paths)
@@ -117,51 +158,185 @@ void ContentLibraryUserModel::addTextures(const QStringList &paths)
m_userTextures.append(tex);
}
- int texSectionIdx = 1;
- emit dataChanged(index(texSectionIdx), index(texSectionIdx));
+ emit dataChanged(index(TexturesSectionIdx), index(TexturesSectionIdx));
}
-// returns unique library material's name and qml component
-QPair<QString, QString> ContentLibraryUserModel::getUniqueLibMaterialNameAndQml(const QString &matName) const
+void ContentLibraryUserModel::add3DInstance(ContentLibraryItem *bundleItem)
{
- QTC_ASSERT(!m_bundleObj.isEmpty(), return {});
+ QString err = m_widget->importer()->importComponent(m_bundlePath3D.path(), bundleItem->type(),
+ bundleItem->qml(),
+ bundleItem->files() + m_bundle3DSharedFiles);
- const QJsonObject matsObj = m_bundleObj.value("materials").toObject();
- const QStringList matNames = matsObj.keys();
+ if (err.isEmpty())
+ m_widget->setImporterRunning(true);
+ else
+ qWarning() << __FUNCTION__ << err;
+}
+
+void ContentLibraryUserModel::removeTexture(ContentLibraryTexture *tex)
+{
+ // remove resources
+ Utils::FilePath::fromString(tex->texturePath()).removeFile();
+ Utils::FilePath::fromString(tex->iconPath()).removeFile();
+
+ // remove from model
+ m_userTextures.removeOne(tex);
+ tex->deleteLater();
+
+ // update model
+ emit dataChanged(index(TexturesSectionIdx), index(TexturesSectionIdx));
+}
+
+void ContentLibraryUserModel::removeFromContentLib(QObject *item)
+{
+ if (auto mat = qobject_cast<ContentLibraryMaterial *>(item))
+ removeMaterialFromContentLib(mat);
+ else if (auto itm = qobject_cast<ContentLibraryItem *>(item))
+ remove3DFromContentLib(itm);
+}
- QStringList matQmls;
- for (const QString &matName : matNames)
- matQmls.append(matsObj.value(matName).toObject().value("qml").toString().chopped(4)); // remove .qml
+void ContentLibraryUserModel::removeMaterialFromContentLib(ContentLibraryMaterial *item)
+{
+ auto bundlePath = Utils::FilePath::fromString(Paths::bundlesPathSetting() + "/User/materials/");
- QString retName = matName.isEmpty() ? "Material" : matName;
- retName = retName.trimmed();
+ QJsonArray itemsArr = m_bundleObjMaterial.value("items").toArray();
- QString retQml = retName;
- retQml.remove(' ');
- if (retQml.at(0).isLower())
- retQml[0] = retQml.at(0).toUpper();
- retQml.prepend("My");
+ // remove qml and icon files
+ Utils::FilePath::fromString(item->qmlFilePath()).removeFile();
+ Utils::FilePath::fromUrl(item->icon()).removeFile();
- int num = 1;
- if (matNames.contains(retName) || matQmls.contains(retQml)) {
- while (matNames.contains(retName + QString::number(num))
- || matQmls.contains(retQml + QString::number(num))) {
- ++num;
+ // remove from the bundle json file
+ for (int i = 0; i < itemsArr.size(); ++i) {
+ if (itemsArr.at(i).toObject().value("qml") == item->qml()) {
+ itemsArr.removeAt(i);
+ break;
}
+ }
+ m_bundleObjMaterial.insert("items", itemsArr);
+
+ auto result = bundlePath.pathAppended(Constants::BUNDLE_JSON_FILENAME)
+ .writeFileContents(QJsonDocument(m_bundleObjMaterial).toJson());
+ if (!result)
+ qWarning() << __FUNCTION__ << result.error();
+
+ // delete dependency files if they are only used by the deleted material
+ QStringList allFiles;
+ for (const QJsonValueConstRef &itemRef : std::as_const(itemsArr))
+ allFiles.append(itemRef.toObject().value("files").toVariant().toStringList());
+
+ const QStringList itemFiles = item->files();
+ for (const QString &file : itemFiles) {
+ if (allFiles.count(file) == 0) // only used by the deleted item
+ bundlePath.pathAppended(file).removeFile();
+ }
+
+ // remove from model
+ m_userMaterials.removeOne(item);
+ item->deleteLater();
- retName += QString::number(num);
- retQml += QString::number(num);
+ // update model
+ emit dataChanged(index(MaterialsSectionIdx), index(MaterialsSectionIdx));
+}
+
+void ContentLibraryUserModel::remove3DFromContentLibByName(const QString &qmlFileName)
+{
+ ContentLibraryItem *itemToRemove = Utils::findOr(m_user3DItems, nullptr,
+ [&qmlFileName](ContentLibraryItem *item) {
+ return item->qml() == qmlFileName;
+ });
+
+ if (itemToRemove)
+ remove3DFromContentLib(itemToRemove);
+}
+
+void ContentLibraryUserModel::remove3DFromContentLib(ContentLibraryItem *item)
+{
+ QJsonArray itemsArr = m_bundleObj3D.value("items").toArray();
+
+ // remove qml and icon files
+ m_bundlePath3D.pathAppended(item->qml()).removeFile();
+ Utils::FilePath::fromUrl(item->icon()).removeFile();
+
+ // remove from the bundle json file
+ for (int i = 0; i < itemsArr.size(); ++i) {
+ if (itemsArr.at(i).toObject().value("qml") == item->qml()) {
+ itemsArr.removeAt(i);
+ break;
+ }
+ }
+ m_bundleObj3D.insert("items", itemsArr);
+
+ auto result = m_bundlePath3D.pathAppended(Constants::BUNDLE_JSON_FILENAME)
+ .writeFileContents(QJsonDocument(m_bundleObj3D).toJson());
+ if (!result)
+ qWarning() << __FUNCTION__ << result.error();
+
+ // delete dependency files if they are only used by the deleted item
+ QStringList allFiles;
+ for (const QJsonValueConstRef &itemRef : std::as_const(itemsArr))
+ allFiles.append(itemRef.toObject().value("files").toVariant().toStringList());
+
+ const QStringList itemFiles = item->files();
+ for (const QString &file : itemFiles) {
+ if (allFiles.count(file) == 0) // only used by the deleted item
+ m_bundlePath3D.pathAppended(file).removeFile();
}
- return {retName, retQml + ".qml"};
+ // remove from model
+ m_user3DItems.removeOne(item);
+ item->deleteLater();
+
+ // update model
+ emit dataChanged(index(Items3DSectionIdx), index(Items3DSectionIdx));
}
-TypeName ContentLibraryUserModel::qmlToModule(const QString &qmlName) const
+/**
+ * @brief Gets unique Qml component and icon file material names from a given name
+ * @param defaultName input name
+ * @return <Qml, icon> file names
+ */
+QPair<QString, QString> ContentLibraryUserModel::getUniqueLibMaterialNames(const QString &defaultName) const
{
- return QLatin1String("%1.%2.%3").arg(QmlDesignerPlugin::instance()->documentManager()
- .generatedComponentUtils().componentBundlesTypePrefix(),
- m_bundleId,
- qmlName.chopped(4)).toLatin1(); // chopped(4): remove .qml
+ return getUniqueLibItemNames(defaultName, m_bundleObjMaterial);
+}
+
+/**
+ * @brief Gets unique Qml component and icon file 3d item names from a given name
+ * @param defaultName input name
+ * @return <Qml, icon> file names
+ */
+QPair<QString, QString> ContentLibraryUserModel::getUniqueLib3DNames(const QString &defaultName) const
+{
+ return getUniqueLibItemNames(defaultName, m_bundleObj3D);
+}
+
+QPair<QString, QString> ContentLibraryUserModel::getUniqueLibItemNames(const QString &defaultName,
+ const QJsonObject &bundleObj) const
+{
+ QTC_ASSERT(!bundleObj.isEmpty(), return {});
+
+ const QJsonArray itemsArr = bundleObj.value("items").toArray();
+
+ QStringList itemQmls, itemIcons;
+ for (const QJsonValueConstRef &itemRef : itemsArr) {
+ const QJsonObject &obj = itemRef.toObject();
+ itemQmls.append(obj.value("qml").toString().chopped(4)); // remove .qml
+ itemIcons.append(QFileInfo(obj.value("icon").toString()).baseName());
+ }
+
+ QString baseQml = UniqueName::generateId(defaultName);
+ baseQml[0] = baseQml.at(0).toUpper();
+ baseQml.prepend("My");
+
+ QString uniqueQml = UniqueName::generate(baseQml, [&] (const QString &name) {
+ return itemQmls.contains(name);
+ });
+
+ QString uniqueIcon = UniqueName::generate(defaultName, [&] (const QString &name) {
+ return itemIcons.contains(name);
+ });
+
+ return {uniqueQml + ".qml", uniqueIcon + ".png"};
}
QHash<int, QByteArray> ContentLibraryUserModel::roleNames() const
@@ -169,121 +344,185 @@ QHash<int, QByteArray> ContentLibraryUserModel::roleNames() const
static const QHash<int, QByteArray> roles {
{NameRole, "categoryName"},
{VisibleRole, "categoryVisible"},
- {ItemsRole, "categoryItems"}
+ {ItemsRole, "categoryItems"},
+ {NoMatchRole, "categoryNoMatch"}
};
return roles;
}
-void ContentLibraryUserModel::createImporter(const QString &bundlePath, const QString &bundleId,
- const QStringList &sharedFiles)
-{
- m_importer = new Internal::ContentLibraryBundleImporter(bundlePath, bundleId, sharedFiles);
-#ifdef QDS_USE_PROJECTSTORAGE
- connect(m_importer,
- &Internal::ContentLibraryBundleImporter::importFinished,
- this,
- [&](const QmlDesigner::TypeName &typeName) {
- m_importerRunning = false;
- emit importerRunningChanged();
- if (typeName.size())
- emit bundleMaterialImported(typeName);
- });
-#else
- connect(m_importer,
- &Internal::ContentLibraryBundleImporter::importFinished,
- this,
- [&](const QmlDesigner::NodeMetaInfo &metaInfo) {
- m_importerRunning = false;
- emit importerRunningChanged();
- if (metaInfo.isValid())
- emit bundleMaterialImported(metaInfo);
- });
-#endif
-
- connect(m_importer, &Internal::ContentLibraryBundleImporter::unimportFinished, this,
- [&](const QmlDesigner::NodeMetaInfo &metaInfo) {
- Q_UNUSED(metaInfo)
- m_importerRunning = false;
- emit importerRunningChanged();
- emit bundleMaterialUnimported(metaInfo);
- });
+QJsonObject &ContentLibraryUserModel::bundleJsonMaterialObjectRef()
+{
+ return m_bundleObjMaterial;
+}
- resetModel();
- updateIsEmpty();
+QJsonObject &ContentLibraryUserModel::bundleJson3DObjectRef()
+{
+ return m_bundleObj3D;
}
-QJsonObject &ContentLibraryUserModel::bundleJsonObjectRef()
+void ContentLibraryUserModel::loadBundles()
{
- return m_bundleObj;
+ loadMaterialBundle();
+ load3DBundle();
+ loadTextureBundle();
}
void ContentLibraryUserModel::loadMaterialBundle()
{
- if (m_matBundleExists)
+ auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils();
+ if (m_matBundleExists && m_bundleIdMaterial == compUtils.userMaterialsBundleId())
return;
- QDir bundleDir{Paths::bundlesPathSetting() + "/User/materials"};
- bundleDir.mkpath(".");
-
- if (m_bundleObj.isEmpty()) {
- auto jsonFilePath = Utils::FilePath::fromString(bundleDir.filePath("user_materials_bundle.json"));
- if (!jsonFilePath.exists()) {
- QString jsonContent = "{\n";
- jsonContent += " \"id\": \"UserMaterialBundle\",\n";
- jsonContent += " \"materials\": {\n";
- jsonContent += " }\n";
- jsonContent += "}";
- jsonFilePath.writeFileContents(jsonContent.toLatin1());
- }
-
- QFile jsonFile(jsonFilePath.path());
- if (!jsonFile.open(QIODevice::ReadOnly)) {
- qWarning("Couldn't open user_materials_bundle.json");
+ // clean up
+ qDeleteAll(m_userMaterials);
+ m_userMaterials.clear();
+ m_matBundleExists = false;
+ m_noMatchMaterials = true;
+ m_bundleObjMaterial = {};
+ m_bundleIdMaterial.clear();
+
+ m_bundlePathMaterial = Utils::FilePath::fromString(Paths::bundlesPathSetting() + "/User/materials");
+ m_bundlePathMaterial.ensureWritableDir();
+ m_bundlePathMaterial.pathAppended("icons").ensureWritableDir();
+
+ auto jsonFilePath = m_bundlePathMaterial.pathAppended(Constants::BUNDLE_JSON_FILENAME);
+ if (!jsonFilePath.exists()) {
+ QString jsonContent = "{\n";
+ jsonContent += " \"id\": \"UserMaterials\",\n";
+ jsonContent += " \"items\": []\n";
+ jsonContent += "}";
+ Utils::expected_str<qint64> res = jsonFilePath.writeFileContents(jsonContent.toLatin1());
+ if (!res.has_value()) {
+ qWarning() << __FUNCTION__ << res.error();
+ emit dataChanged(index(MaterialsSectionIdx), index(MaterialsSectionIdx));
return;
}
+ }
- QJsonDocument matBundleJsonDoc = QJsonDocument::fromJson(jsonFile.readAll());
- if (matBundleJsonDoc.isNull()) {
- qWarning("Invalid user_materials_bundle.json file");
- return;
- } else {
- m_bundleObj = matBundleJsonDoc.object();
- }
+ Utils::expected_str<QByteArray> jsonContents = jsonFilePath.fileContents();
+ if (!jsonContents.has_value()) {
+ qWarning() << __FUNCTION__ << jsonContents.error();
+ emit dataChanged(index(MaterialsSectionIdx), index(MaterialsSectionIdx));
+ return;
}
- m_bundleId = m_bundleObj.value("id").toString();
+ QJsonDocument bundleJsonDoc = QJsonDocument::fromJson(jsonContents.value());
+ if (bundleJsonDoc.isNull()) {
+ qWarning() << __FUNCTION__ << "Invalid json file" << jsonFilePath;
+ emit dataChanged(index(MaterialsSectionIdx), index(MaterialsSectionIdx));
+ return;
+ }
- // parse materials
- const QJsonObject matsObj = m_bundleObj.value("materials").toObject();
- const QStringList materialNames = matsObj.keys();
- for (const QString &matName : materialNames) {
- const QJsonObject matObj = matsObj.value(matName).toObject();
+ m_bundleIdMaterial = compUtils.userMaterialsBundleId();
+ m_bundleObjMaterial = bundleJsonDoc.object();
+ m_bundleObjMaterial["id"] = m_bundleIdMaterial;
+ // parse items
+ QString typePrefix = compUtils.userMaterialsBundleType();
+ const QJsonArray itemsArr = m_bundleObjMaterial.value("items").toArray();
+ for (const QJsonValueConstRef &itemRef : itemsArr) {
+ const QJsonObject itemObj = itemRef.toObject();
+
+ QString name = itemObj.value("name").toString();
+ QString qml = itemObj.value("qml").toString();
+ TypeName type = QLatin1String("%1.%2").arg(typePrefix, qml.chopped(4)).toLatin1();
+ QUrl icon = m_bundlePathMaterial.pathAppended(itemObj.value("icon").toString()).toUrl();
QStringList files;
- const QJsonArray assetsArr = matObj.value("files").toArray();
- for (const auto /*QJson{Const,}ValueRef*/ &asset : assetsArr)
+ const QJsonArray assetsArr = itemObj.value("files").toArray();
+ for (const QJsonValueConstRef &asset : assetsArr)
files.append(asset.toString());
- QUrl icon = QUrl::fromLocalFile(bundleDir.filePath(matObj.value("icon").toString()));
- QString qml = matObj.value("qml").toString();
+ m_userMaterials.append(new ContentLibraryMaterial(this, name, qml, type, icon, files,
+ m_bundlePathMaterial.path(), ""));
+ }
+
+ m_bundleMaterialSharedFiles.clear();
+ const QJsonArray sharedFilesArr = m_bundleObjMaterial.value("sharedFiles").toArray();
+ for (const QJsonValueConstRef &file : sharedFilesArr)
+ m_bundleMaterialSharedFiles.append(file.toString());
- TypeName type = qmlToModule(qml);
+ m_matBundleExists = true;
+ updateNoMatchMaterials();
+ emit dataChanged(index(MaterialsSectionIdx), index(MaterialsSectionIdx));
+}
- auto userMat = new ContentLibraryMaterial(this, matName, qml, type, icon, files,
- bundleDir.path(), "");
+void ContentLibraryUserModel::load3DBundle()
+{
+ auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils();
- m_userMaterials.append(userMat);
+ if (m_bundle3DExists && m_bundleId3D == compUtils.user3DBundleId())
+ return;
+
+ // clean up
+ qDeleteAll(m_user3DItems);
+ m_user3DItems.clear();
+ m_bundle3DExists = false;
+ m_noMatch3D = true;
+ m_bundleObj3D = {};
+ m_bundleId3D.clear();
+
+ m_bundlePath3D = Utils::FilePath::fromString(Paths::bundlesPathSetting() + "/User/3d");
+ m_bundlePath3D.ensureWritableDir();
+ m_bundlePath3D.pathAppended("icons").ensureWritableDir();
+
+ auto jsonFilePath = m_bundlePath3D.pathAppended(Constants::BUNDLE_JSON_FILENAME);
+ if (!jsonFilePath.exists()) {
+ QByteArray jsonContent = "{\n";
+ jsonContent += " \"id\": \"User3D\",\n";
+ jsonContent += " \"items\": []\n";
+ jsonContent += "}";
+ Utils::expected_str<qint64> res = jsonFilePath.writeFileContents(jsonContent);
+ if (!res.has_value()) {
+ qWarning() << __FUNCTION__ << res.error();
+ emit dataChanged(index(Items3DSectionIdx), index(Items3DSectionIdx));
+ return;
+ }
}
- QStringList sharedFiles;
- const QJsonArray sharedFilesArr = m_bundleObj.value("sharedFiles").toArray();
- for (const auto /*QJson{Const,}ValueRef*/ &file : sharedFilesArr)
- sharedFiles.append(file.toString());
+ Utils::expected_str<QByteArray> jsonContents = jsonFilePath.fileContents();
+ if (!jsonContents.has_value()) {
+ qWarning() << __FUNCTION__ << jsonContents.error();
+ emit dataChanged(index(Items3DSectionIdx), index(Items3DSectionIdx));
+ return;
+ }
- createImporter(bundleDir.path(), m_bundleId, sharedFiles);
+ QJsonDocument bundleJsonDoc = QJsonDocument::fromJson(jsonContents.value());
+ if (bundleJsonDoc.isNull()) {
+ qWarning() << __FUNCTION__ << "Invalid json file" << jsonFilePath;
+ emit dataChanged(index(Items3DSectionIdx), index(Items3DSectionIdx));
+ return;
+ }
- m_matBundleExists = true;
- emit matBundleExistsChanged();
+ m_bundleId3D = compUtils.user3DBundleId();
+ m_bundleObj3D = bundleJsonDoc.object();
+ m_bundleObj3D["id"] = m_bundleId3D;
+
+ // parse items
+ QString typePrefix = compUtils.user3DBundleType();
+ const QJsonArray itemsArr = m_bundleObj3D.value("items").toArray();
+ for (const QJsonValueConstRef &itemRef : itemsArr) {
+ const QJsonObject itemObj = itemRef.toObject();
+
+ QString name = itemObj.value("name").toString();
+ QString qml = itemObj.value("qml").toString();
+ TypeName type = QLatin1String("%1.%2").arg(typePrefix, qml.chopped(4)).toLatin1();
+ QUrl icon = m_bundlePath3D.pathAppended(itemObj.value("icon").toString()).toUrl();
+ QStringList files;
+ const QJsonArray assetsArr = itemObj.value("files").toArray();
+ for (const QJsonValueConstRef &asset : assetsArr)
+ files.append(asset.toString());
+
+ m_user3DItems.append(new ContentLibraryItem(nullptr, name, qml, type, icon, files));
+ }
+
+ m_bundle3DSharedFiles.clear();
+ const QJsonArray sharedFilesArr = m_bundleObj3D.value("sharedFiles").toArray();
+ for (const QJsonValueConstRef &file : sharedFilesArr)
+ m_bundle3DSharedFiles.append(file.toString());
+
+ m_bundle3DExists = true;
+ updateNoMatch3D();
+ emit dataChanged(index(Items3DSectionIdx), index(Items3DSectionIdx));
}
void ContentLibraryUserModel::loadTextureBundle()
@@ -308,8 +547,8 @@ void ContentLibraryUserModel::loadTextureBundle()
m_userTextures.append(tex);
}
- int texSectionIdx = 1;
- emit dataChanged(index(texSectionIdx), index(texSectionIdx));
+ updateNoMatchTextures();
+ emit dataChanged(index(TexturesSectionIdx), index(TexturesSectionIdx));
}
bool ContentLibraryUserModel::hasRequiredQuick3DImport() const
@@ -322,11 +561,6 @@ bool ContentLibraryUserModel::matBundleExists() const
return m_matBundleExists;
}
-Internal::ContentLibraryBundleImporter *ContentLibraryUserModel::bundleImporter() const
-{
- return m_importer;
-}
-
void ContentLibraryUserModel::setSearchText(const QString &searchText)
{
QString lowerSearchText = searchText.toLower();
@@ -336,21 +570,39 @@ void ContentLibraryUserModel::setSearchText(const QString &searchText)
m_searchText = lowerSearchText;
- for (ContentLibraryMaterial *mat : std::as_const(m_userMaterials))
- mat->filter(m_searchText);
+ for (ContentLibraryMaterial *item : std::as_const(m_userMaterials))
+ item->filter(m_searchText);
+
+ for (ContentLibraryTexture *item : std::as_const(m_userTextures))
+ item->filter(m_searchText);
- updateIsEmpty();
+ for (ContentLibraryItem *item : std::as_const(m_user3DItems))
+ item->filter(m_searchText);
+
+ updateNoMatchMaterials();
+ updateNoMatchTextures();
+ updateNoMatch3D();
+ resetModel();
}
-void ContentLibraryUserModel::updateImportedState(const QStringList &importedMats)
+void ContentLibraryUserModel::updateMaterialsImportedState(const QStringList &importedItems)
{
bool changed = false;
-
for (ContentLibraryMaterial *mat : std::as_const(m_userMaterials))
- changed |= mat->setImported(importedMats.contains(mat->qml().chopped(4)));
+ changed |= mat->setImported(importedItems.contains(mat->qml().chopped(4)));
+
+ if (changed)
+ emit dataChanged(index(MaterialsSectionIdx), index(MaterialsSectionIdx));
+}
+
+void ContentLibraryUserModel::update3DImportedState(const QStringList &importedItems)
+{
+ bool changed = false;
+ for (ContentLibraryItem *item : std::as_const(m_user3DItems))
+ changed |= item->setImported(importedItems.contains(item->qml().chopped(4)));
if (changed)
- resetModel();
+ emit dataChanged(index(Items3DSectionIdx), index(Items3DSectionIdx));
}
void ContentLibraryUserModel::setQuick3DImportVersion(int major, int minor)
@@ -366,8 +618,6 @@ void ContentLibraryUserModel::setQuick3DImportVersion(int major, int minor)
return;
emit hasRequiredQuick3DImportChanged();
-
- updateIsEmpty();
}
void ContentLibraryUserModel::resetModel()
@@ -381,44 +631,56 @@ void ContentLibraryUserModel::applyToSelected(ContentLibraryMaterial *mat, bool
emit applyToSelectedTriggered(mat, add);
}
-void ContentLibraryUserModel::addToProject(ContentLibraryMaterial *mat)
+void ContentLibraryUserModel::addToProject(QObject *item)
{
- QString err = m_importer->importComponent(mat->qml(), mat->files());
-
- if (err.isEmpty()) {
- m_importerRunning = true;
- emit importerRunningChanged();
+ QString bundleDir;
+ TypeName type;
+ QString qmlFile;
+ QStringList files;
+ if (auto mat = qobject_cast<ContentLibraryMaterial *>(item)) {
+ bundleDir = mat->dirPath();
+ type = mat->type();
+ qmlFile = mat->qml();
+ files = mat->files() + m_bundleMaterialSharedFiles;
+ } else if (auto itm = qobject_cast<ContentLibraryItem *>(item)) {
+ bundleDir = m_bundlePath3D.toString();
+ type = itm->type();
+ qmlFile = itm->qml();
+ files = itm->files() + m_bundle3DSharedFiles;
} else {
- qWarning() << __FUNCTION__ << err;
+ qWarning() << __FUNCTION__ << "Unsupported Item";
+ return;
}
-}
-
-void ContentLibraryUserModel::removeFromProject(ContentLibraryMaterial *mat)
-{
- emit bundleMaterialAboutToUnimport(mat->type());
- QString err = m_importer->unimportComponent(mat->qml());
+ QString err = m_widget->importer()->importComponent(bundleDir, type, qmlFile, files);
- if (err.isEmpty()) {
- m_importerRunning = true;
- emit importerRunningChanged();
- } else {
+ if (err.isEmpty())
+ m_widget->setImporterRunning(true);
+ else
qWarning() << __FUNCTION__ << err;
- }
-}
-
-bool ContentLibraryUserModel::hasModelSelection() const
-{
- return m_hasModelSelection;
}
-void ContentLibraryUserModel::setHasModelSelection(bool b)
+void ContentLibraryUserModel::removeFromProject(QObject *item)
{
- if (b == m_hasModelSelection)
+ TypeName type;
+ QString qml;
+ if (auto mat = qobject_cast<ContentLibraryMaterial *>(item)) {
+ type = mat->type();
+ qml = mat->qml();
+ } else if (auto itm = qobject_cast<ContentLibraryItem *>(item)) {
+ type = itm->type();
+ qml = itm->qml();
+ } else {
+ qWarning() << __FUNCTION__ << "Unsupported Item";
return;
+ }
- m_hasModelSelection = b;
- emit hasModelSelectionChanged();
+ QString err = m_widget->importer()->unimportComponent(type, qml);
+
+ if (err.isEmpty())
+ m_widget->setImporterRunning(true);
+ else
+ qWarning() << __FUNCTION__ << err;
}
} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.h
index 3e9a96fd9d..2a7f9a66f3 100644
--- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.h
+++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.h
@@ -3,7 +3,7 @@
#pragma once
-#include "modelfwd.h"
+#include <utils/filepath.h>
#include <QAbstractListModel>
#include <QJsonObject>
@@ -12,29 +12,23 @@ QT_FORWARD_DECLARE_CLASS(QUrl)
namespace QmlDesigner {
-class ContentLibraryEffect;
+class ContentLibraryItem;
class ContentLibraryMaterial;
class ContentLibraryTexture;
class ContentLibraryWidget;
class NodeMetaInfo;
-namespace Internal {
-class ContentLibraryBundleImporter;
-}
-
class ContentLibraryUserModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(bool matBundleExists READ matBundleExists NOTIFY matBundleExistsChanged)
- Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged)
+ Q_PROPERTY(bool bundle3DExists MEMBER m_bundle3DExists NOTIFY bundle3DExistsChanged)
Q_PROPERTY(bool hasRequiredQuick3DImport READ hasRequiredQuick3DImport NOTIFY hasRequiredQuick3DImportChanged)
- Q_PROPERTY(bool hasModelSelection READ hasModelSelection NOTIFY hasModelSelectionChanged)
- Q_PROPERTY(bool importerRunning MEMBER m_importerRunning NOTIFY importerRunningChanged)
Q_PROPERTY(QList<ContentLibraryMaterial *> userMaterials MEMBER m_userMaterials NOTIFY userMaterialsChanged)
Q_PROPERTY(QList<ContentLibraryTexture *> userTextures MEMBER m_userTextures NOTIFY userTexturesChanged)
- Q_PROPERTY(QList<ContentLibraryEffect *> user3DItems MEMBER m_user3DItems NOTIFY user3DItemsChanged)
- Q_PROPERTY(QList<ContentLibraryEffect *> userEffects MEMBER m_userEffects NOTIFY userEffectsChanged)
+ Q_PROPERTY(QList<ContentLibraryItem *> user3DItems MEMBER m_user3DItems NOTIFY user3DItemsChanged)
+ Q_PROPERTY(QList<ContentLibraryItem *> userEffects MEMBER m_userEffects NOTIFY userEffectsChanged)
public:
ContentLibraryUserModel(ContentLibraryWidget *parent = nullptr);
@@ -44,10 +38,11 @@ public:
QHash<int, QByteArray> roleNames() const override;
void setSearchText(const QString &searchText);
- void updateImportedState(const QStringList &importedMats);
+ void updateMaterialsImportedState(const QStringList &importedItems);
+ void update3DImportedState(const QStringList &importedItems);
- QPair<QString, QString> getUniqueLibMaterialNameAndQml(const QString &matName) const;
- TypeName qmlToModule(const QString &qmlName) const;
+ QPair<QString, QString> getUniqueLibMaterialNames(const QString &defaultName = "Material") const;
+ QPair<QString, QString> getUniqueLib3DNames(const QString &defaultName = "Item") const;
void setQuick3DImportVersion(int major, int minor);
@@ -55,78 +50,86 @@ public:
bool matBundleExists() const;
- bool hasModelSelection() const;
- void setHasModelSelection(bool b);
-
void resetModel();
- void updateIsEmpty();
+ void updateNoMatchMaterials();
+ void updateNoMatchTextures();
+ void updateNoMatch3D();
void addMaterial(const QString &name, const QString &qml, const QUrl &icon, const QStringList &files);
+ void add3DItem(const QString &name, const QString &qml, const QUrl &icon, const QStringList &files);
+ void refresh3DSection();
void addTextures(const QStringList &paths);
+ void add3DInstance(ContentLibraryItem *bundleItem);
+
+ void remove3DFromContentLibByName(const QString &qmlFileName);
+
void setBundleObj(const QJsonObject &newBundleObj);
- QJsonObject &bundleJsonObjectRef();
+ QJsonObject &bundleJsonMaterialObjectRef();
+ QJsonObject &bundleJson3DObjectRef();
- Internal::ContentLibraryBundleImporter *bundleImporter() const;
+ void loadBundles();
Q_INVOKABLE void applyToSelected(QmlDesigner::ContentLibraryMaterial *mat, bool add = false);
- Q_INVOKABLE void addToProject(QmlDesigner::ContentLibraryMaterial *mat);
- Q_INVOKABLE void removeFromProject(QmlDesigner::ContentLibraryMaterial *mat);
+ Q_INVOKABLE void addToProject(QObject *item);
+ Q_INVOKABLE void removeFromProject(QObject *item);
+ Q_INVOKABLE void removeTexture(QmlDesigner::ContentLibraryTexture *tex);
+ Q_INVOKABLE void removeFromContentLib(QObject *item);
signals:
- void isEmptyChanged();
void hasRequiredQuick3DImportChanged();
- void hasModelSelectionChanged();
void userMaterialsChanged();
void userTexturesChanged();
void user3DItemsChanged();
void userEffectsChanged();
-
void applyToSelectedTriggered(QmlDesigner::ContentLibraryMaterial *mat, bool add = false);
-
-#ifdef QDS_USE_PROJECTSTORAGE
- void bundleMaterialImported(const QmlDesigner::TypeName &typeName);
-#else
- void bundleMaterialImported(const QmlDesigner::NodeMetaInfo &metaInfo);
-#endif
- void bundleMaterialAboutToUnimport(const QmlDesigner::TypeName &type);
- void bundleMaterialUnimported(const QmlDesigner::NodeMetaInfo &metaInfo);
- void importerRunningChanged();
void matBundleExistsChanged();
+ void bundle3DExistsChanged();
private:
+ enum SectionIndex { MaterialsSectionIdx = 0,
+ TexturesSectionIdx,
+ Items3DSectionIdx,
+ EffectsSectionIdx };
+
void loadMaterialBundle();
+ void load3DBundle();
void loadTextureBundle();
bool isValidIndex(int idx) const;
- void createImporter(const QString &bundlePath, const QString &bundleId,
- const QStringList &sharedFiles);
+ void removeMaterialFromContentLib(ContentLibraryMaterial *mat);
+ void remove3DFromContentLib(ContentLibraryItem *item);
+ QPair<QString, QString> getUniqueLibItemNames(const QString &defaultName,
+ const QJsonObject &bundleObj) const;
ContentLibraryWidget *m_widget = nullptr;
QString m_searchText;
- QString m_bundleId;
+ QString m_bundleIdMaterial;
+ QString m_bundleId3D;
+ QStringList m_bundleMaterialSharedFiles;
+ QStringList m_bundle3DSharedFiles;
+ Utils::FilePath m_bundlePathMaterial;
+ Utils::FilePath m_bundlePath3D;
QList<ContentLibraryMaterial *> m_userMaterials;
QList<ContentLibraryTexture *> m_userTextures;
- QList<ContentLibraryEffect *> m_userEffects;
- QList<ContentLibraryEffect *> m_user3DItems;
+ QList<ContentLibraryItem *> m_userEffects;
+ QList<ContentLibraryItem *> m_user3DItems;
QStringList m_userCategories;
- QJsonObject m_bundleObj;
- Internal::ContentLibraryBundleImporter *m_importer = nullptr;
+ QJsonObject m_bundleObjMaterial;
+ QJsonObject m_bundleObj3D;
- bool m_isEmpty = true;
+ bool m_noMatchMaterials = true;
+ bool m_noMatchTextures = true;
+ bool m_noMatch3D = true;
+ bool m_noMatchEffects = true;
bool m_matBundleExists = false;
- bool m_hasModelSelection = false;
- bool m_importerRunning = false;
+ bool m_bundle3DExists = false;
int m_quick3dMajorVersion = -1;
int m_quick3dMinorVersion = -1;
- QString m_importerBundlePath;
- QString m_importerBundleId;
- QStringList m_importerSharedFiles;
-
- enum Roles { NameRole = Qt::UserRole + 1, VisibleRole, ItemsRole };
+ enum Roles { NameRole = Qt::UserRole + 1, VisibleRole, ItemsRole, NoMatchRole };
};
} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp
index dd8a4d9919..9258faaf33 100644
--- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp
+++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp
@@ -3,10 +3,8 @@
#include "contentlibraryview.h"
-#include "asset.h"
-#include "bindingproperty.h"
#include "contentlibrarybundleimporter.h"
-#include "contentlibraryeffect.h"
+#include "contentlibraryitem.h"
#include "contentlibraryeffectsmodel.h"
#include "contentlibrarymaterial.h"
#include "contentlibrarymaterialsmodel.h"
@@ -14,18 +12,21 @@
#include "contentlibrarytexturesmodel.h"
#include "contentlibraryusermodel.h"
#include "contentlibrarywidget.h"
-#include "documentmanager.h"
-#include "externaldependenciesinterface.h"
-#include "nodelistproperty.h"
-#include "qmldesignerconstants.h"
-#include "qmlobjectnode.h"
-#include "variantproperty.h"
-#include "utils3d.h"
+#include <asset.h>
+#include <bindingproperty.h>
#include <designerpaths.h>
-
-#include <coreplugin/messagebox.h>
+#include <documentmanager.h>
#include <enumeration.h>
+#include <externaldependenciesinterface.h>
+#include <nodelistproperty.h>
+#include <qmldesignerconstants.h>
+#include <qmldesignerplugin.h>
+#include <qmlobjectnode.h>
+#include <uniquename.h>
+#include <utils3d.h>
+#include <variantproperty.h>
+
#include <utils/algorithm.h>
#ifndef QMLDESIGNER_TEST
@@ -39,13 +40,16 @@
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
+#include <QMessageBox>
#include <QPixmap>
#include <QVector3D>
namespace QmlDesigner {
-ContentLibraryView::ContentLibraryView(ExternalDependenciesInterface &externalDependencies)
+ContentLibraryView::ContentLibraryView(AsynchronousImageCache &imageCache,
+ ExternalDependenciesInterface &externalDependencies)
: AbstractView(externalDependencies)
+ , m_imageCache(imageCache)
, m_createTexture(this)
{}
@@ -70,9 +74,9 @@ WidgetInfo ContentLibraryView::widgetInfo()
[&] (QmlDesigner::ContentLibraryTexture *tex) {
m_draggedBundleTexture = tex;
});
- connect(m_widget, &ContentLibraryWidget::bundleEffectDragStarted, this,
- [&] (QmlDesigner::ContentLibraryEffect *eff) {
- m_draggedBundleEffect = eff;
+ connect(m_widget, &ContentLibraryWidget::bundleItemDragStarted, this,
+ [&] (QmlDesigner::ContentLibraryItem *item) {
+ m_draggedBundleItem = item;
});
connect(m_widget, &ContentLibraryWidget::addTextureRequested, this,
@@ -89,198 +93,150 @@ WidgetInfo ContentLibraryView::widgetInfo()
m_widget->environmentsModel()->setHasSceneEnv(sceneEnvExists);
});
- ContentLibraryMaterialsModel *materialsModel = m_widget->materialsModel().data();
-
- connect(materialsModel,
+ connect(m_widget->materialsModel(),
&ContentLibraryMaterialsModel::applyToSelectedTriggered,
this,
[&](ContentLibraryMaterial *bundleMat, bool add) {
- if (m_selectedModels.isEmpty())
- return;
+ if (m_selectedModels.isEmpty())
+ return;
- m_bundleMaterialTargets = m_selectedModels;
- m_bundleMaterialAddToSelected = add;
+ m_bundleMaterialTargets = m_selectedModels;
+ m_bundleMaterialAddToSelected = add;
- ModelNode defaultMat = getBundleMaterialDefaultInstance(bundleMat->type());
- if (defaultMat.isValid())
- applyBundleMaterialToDropTarget(defaultMat);
- else
- m_widget->materialsModel()->addToProject(bundleMat);
- });
+ ModelNode defaultMat = getBundleMaterialDefaultInstance(bundleMat->type());
+ if (defaultMat.isValid())
+ applyBundleMaterialToDropTarget(defaultMat);
+ else
+ m_widget->materialsModel()->addToProject(bundleMat);
+ });
-#ifdef QDS_USE_PROJECTSTORAGE
- connect(materialsModel,
- &ContentLibraryMaterialsModel::bundleMaterialImported,
- this,
- [&](const QmlDesigner::TypeName &typeName) {
- applyBundleMaterialToDropTarget({}, typeName);
- updateBundleMaterialsImportedState();
- });
-#else
- connect(materialsModel,
- &ContentLibraryMaterialsModel::bundleMaterialImported,
+ connect(m_widget->userModel(),
+ &ContentLibraryUserModel::applyToSelectedTriggered,
this,
- [&](const QmlDesigner::NodeMetaInfo &metaInfo) {
- applyBundleMaterialToDropTarget({}, metaInfo);
- updateBundleMaterialsImportedState();
- });
-#endif
+ [&](ContentLibraryMaterial *bundleMat, bool add) {
+ if (m_selectedModels.isEmpty())
+ return;
- connect(materialsModel, &ContentLibraryMaterialsModel::bundleMaterialAboutToUnimport, this,
- [&] (const QmlDesigner::TypeName &type) {
- // delete instances of the bundle material that is about to be unimported
- executeInTransaction("ContentLibraryView::widgetInfo", [&] {
- ModelNode matLib = Utils3D::materialLibraryNode(this);
- if (!matLib.isValid())
- return;
+ m_bundleMaterialTargets = m_selectedModels;
+ m_bundleMaterialAddToSelected = add;
- Utils::reverseForeach(matLib.directSubModelNodes(), [&](const ModelNode &mat) {
- if (mat.isValid() && mat.type() == type)
- QmlObjectNode(mat).destroy();
- });
- });
+ ModelNode defaultMat = getBundleMaterialDefaultInstance(bundleMat->type());
+ if (defaultMat.isValid())
+ applyBundleMaterialToDropTarget(defaultMat);
+ else
+ m_widget->userModel()->addToProject(bundleMat);
});
- connect(materialsModel, &ContentLibraryMaterialsModel::bundleMaterialUnimported, this,
- &ContentLibraryView::updateBundleMaterialsImportedState);
+ connectImporter();
+ }
- ContentLibraryEffectsModel *effectsModel = m_widget->effectsModel().data();
+ return createWidgetInfo(m_widget.data(),
+ "ContentLibrary",
+ WidgetInfo::LeftPane,
+ 0,
+ tr("Content Library"));
+}
+void ContentLibraryView::connectImporter()
+{
#ifdef QDS_USE_PROJECTSTORAGE
- connect(effectsModel,
- &ContentLibraryEffectsModel::bundleItemImported,
- this,
- [&](const QmlDesigner::TypeName &typeName) {
- QTC_ASSERT(typeName.size(), return);
-
- if (!m_bundleEffectTarget)
- m_bundleEffectTarget = Utils3D::active3DSceneNode(this);
+ connect(m_widget->importer(),
+ &ContentLibraryBundleImporter::importFinished,
+ this,
+ [&](const QmlDesigner::TypeName &typeName, const QString &bundleId) {
+ QTC_ASSERT(typeName.size(), return);
+ if (isMaterialBundle(bundleId)) {
+ applyBundleMaterialToDropTarget({}, typeName);
+ } else if (isItemBundle(bundleId)) {
+ if (!m_bundleItemTarget)
+ m_bundleItemTarget = Utils3D::active3DSceneNode(this);
- QTC_ASSERT(m_bundleEffectTarget, return);
+ QTC_ASSERT(m_bundleItemTarget, return);
executeInTransaction("ContentLibraryView::widgetInfo", [&] {
- QVector3D pos = m_bundleEffectPos.value<QVector3D>();
- ModelNode newEffNode = createModelNode(
+ QVector3D pos = m_bundleItemPos.value<QVector3D>();
+ ModelNode newNode = createModelNode(
typeName, -1, -1, {{"x", pos.x()}, {"y", pos.y()}, {"z", pos.z()}});
- m_bundleEffectTarget.defaultNodeListProperty().reparentHere(newEffNode);
+ m_bundleItemTarget.defaultNodeListProperty().reparentHere(newNode);
clearSelectedModelNodes();
- selectModelNode(newEffNode);
+ selectModelNode(newNode);
});
- updateBundleEffectsImportedState();
- m_bundleEffectTarget = {};
- m_bundleEffectPos = {};
- });
+ m_bundleItemTarget = {};
+ m_bundleItemPos = {};
+ }
+ });
#else
- connect(effectsModel,
- &ContentLibraryEffectsModel::bundleItemImported,
- this,
- [&](const QmlDesigner::NodeMetaInfo &metaInfo) {
- QTC_ASSERT(metaInfo.isValid(), return);
-
- if (!m_bundleEffectTarget)
- m_bundleEffectTarget = Utils3D::active3DSceneNode(this);
+ connect(m_widget->importer(),
+ &ContentLibraryBundleImporter::importFinished,
+ this,
+ [&](const QmlDesigner::NodeMetaInfo &metaInfo, const QString &bundleId) {
+ QTC_ASSERT(metaInfo.isValid(), return);
+ if (isMaterialBundle(bundleId)) {
+ applyBundleMaterialToDropTarget({}, metaInfo);
+ } else if (isItemBundle(bundleId)) {
+ if (!m_bundleItemTarget)
+ m_bundleItemTarget = Utils3D::active3DSceneNode(this);
- QTC_ASSERT(m_bundleEffectTarget, return);
+ QTC_ASSERT(m_bundleItemTarget, return);
- executeInTransaction("ContentLibraryView::widgetInfo", [&] {
- QVector3D pos = m_bundleEffectPos.value<QVector3D>();
- ModelNode newEffNode = createModelNode(metaInfo.typeName(),
+ executeInTransaction("ContentLibraryView::connectImporter", [&] {
+ QVector3D pos = m_bundleItemPos.value<QVector3D>();
+ ModelNode newNode = createModelNode(metaInfo.typeName(),
metaInfo.majorVersion(),
metaInfo.minorVersion(),
{{"x", pos.x()},
{"y", pos.y()},
{"z", pos.z()}});
- m_bundleEffectTarget.defaultNodeListProperty().reparentHere(newEffNode);
+ m_bundleItemTarget.defaultNodeListProperty().reparentHere(newNode);
clearSelectedModelNodes();
- selectModelNode(newEffNode);
+ selectModelNode(newNode);
});
- updateBundleEffectsImportedState();
- m_bundleEffectTarget = {};
- m_bundleEffectPos = {};
- });
+ m_bundleItemTarget = {};
+ m_bundleItemPos = {};
+ }
+ });
#endif
- connect(effectsModel, &ContentLibraryEffectsModel::bundleItemAboutToUnimport, this,
- [&] (const QmlDesigner::TypeName &type) {
- // delete instances of the bundle effect that is about to be unimported
- executeInTransaction("ContentLibraryView::widgetInfo", [&] {
- NodeMetaInfo metaInfo = model()->metaInfo(type);
- QList<ModelNode> effects = allModelNodesOfType(metaInfo);
- for (ModelNode &eff : effects)
- eff.destroy();
- });
- });
-
- connect(effectsModel, &ContentLibraryEffectsModel::bundleItemUnimported, this,
- &ContentLibraryView::updateBundleEffectsImportedState);
-
- connectUserBundle();
- }
-
- return createWidgetInfo(m_widget.data(),
- "ContentLibrary",
- WidgetInfo::LeftPane,
- 0,
- tr("Content Library"));
-}
-
-void ContentLibraryView::connectUserBundle()
-{
- ContentLibraryUserModel *userModel = m_widget->userModel().data();
- connect(userModel,
- &ContentLibraryUserModel::applyToSelectedTriggered,
- this,
- [&](ContentLibraryMaterial *bundleMat, bool add) {
- if (m_selectedModels.isEmpty())
+ connect(m_widget->importer(), &ContentLibraryBundleImporter::aboutToUnimport, this,
+ [&] (const QmlDesigner::TypeName &type, const QString &bundleId) {
+ if (isMaterialBundle(bundleId)) {
+ // delete instances of the bundle material that is about to be unimported
+ executeInTransaction("ContentLibraryView::connectImporter", [&] {
+ ModelNode matLib = Utils3D::materialLibraryNode(this);
+ if (!matLib.isValid())
return;
- m_bundleMaterialTargets = m_selectedModels;
- m_bundleMaterialAddToSelected = add;
-
- ModelNode defaultMat = getBundleMaterialDefaultInstance(bundleMat->type());
- if (defaultMat.isValid())
- applyBundleMaterialToDropTarget(defaultMat);
- else
- m_widget->userModel()->addToProject(bundleMat);
- });
-
-#ifdef QDS_USE_PROJECTSTORAGE
- connect(userModel,
- &ContentLibraryUserModel::bundleMaterialImported,
- this,
- [&](const QmlDesigner::TypeName &typeName) {
- applyBundleMaterialToDropTarget({}, typeName);
- updateBundleUserMaterialsImportedState();
+ Utils::reverseForeach(matLib.directSubModelNodes(), [&](const ModelNode &mat) {
+ if (mat.isValid() && mat.type() == type)
+ QmlObjectNode(mat).destroy();
+ });
});
-#else
- connect(userModel,
- &ContentLibraryUserModel::bundleMaterialImported,
- this,
- [&](const QmlDesigner::NodeMetaInfo &metaInfo) {
- applyBundleMaterialToDropTarget({}, metaInfo);
- updateBundleUserMaterialsImportedState();
+ } else if (isItemBundle(bundleId)) {
+ // delete instances of the bundle item that is about to be unimported
+ executeInTransaction("ContentLibraryView::connectImporter", [&] {
+ NodeMetaInfo metaInfo = model()->metaInfo(type);
+ QList<ModelNode> nodes = allModelNodesOfType(metaInfo);
+ for (ModelNode &node : nodes)
+ node.destroy();
});
-#endif
+ }
+ });
+}
- connect(userModel, &ContentLibraryUserModel::bundleMaterialAboutToUnimport, this,
- [&] (const QmlDesigner::TypeName &type) {
- // delete instances of the bundle material that is about to be unimported
- executeInTransaction("ContentLibraryView::connectUserModel", [&] {
- ModelNode matLib = Utils3D::materialLibraryNode(this);
- if (!matLib.isValid())
- return;
-
- Utils::reverseForeach(matLib.directSubModelNodes(), [&](const ModelNode &mat) {
- if (mat.isValid() && mat.type() == type)
- QmlObjectNode(mat).destroy();
- });
- });
- });
+bool ContentLibraryView::isMaterialBundle(const QString &bundleId) const
+{
+ auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils();
+ return bundleId == compUtils.materialsBundleId() || bundleId == compUtils.userMaterialsBundleId();
+}
- connect(userModel, &ContentLibraryUserModel::bundleMaterialUnimported, this,
- &ContentLibraryView::updateBundleUserMaterialsImportedState);
+// item bundle includes effects and 3D components
+bool ContentLibraryView::isItemBundle(const QString &bundleId) const
+{
+ auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils();
+ return bundleId == compUtils.effectsBundleId() || bundleId == compUtils.userEffectsBundleId()
+ || bundleId == compUtils.user3DBundleId();
}
void ContentLibraryView::modelAttached(Model *model)
@@ -290,7 +246,6 @@ void ContentLibraryView::modelAttached(Model *model)
m_hasQuick3DImport = model->hasImport("QtQuick3D");
updateBundlesQuick3DVersion();
- updateBundleMaterialsImportedState();
const bool hasLibrary = Utils3D::materialLibraryNode(this).isValid();
m_widget->setHasMaterialLibrary(hasLibrary);
@@ -302,8 +257,17 @@ void ContentLibraryView::modelAttached(Model *model)
m_widget->setHasActive3DScene(m_sceneId != -1);
m_widget->clearSearchFilter();
+ // bundles loading has to happen here, otherwise project path is not ready which will
+ // cause bundle items types to resolve incorrectly
+ m_widget->materialsModel()->loadBundle();
m_widget->effectsModel()->loadBundle();
- updateBundleEffectsImportedState();
+ m_widget->userModel()->loadBundles();
+
+ auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils();
+ m_widget->updateImportedState(compUtils.materialsBundleId());
+ m_widget->updateImportedState(compUtils.effectsBundleId());
+ m_widget->updateImportedState(compUtils.userMaterialsBundleId());
+ m_widget->updateImportedState(compUtils.user3DBundleId());
}
void ContentLibraryView::modelAboutToBeDetached(Model *model)
@@ -345,8 +309,7 @@ void ContentLibraryView::selectedNodesChanged(const QList<ModelNode> &selectedNo
return node.metaInfo().isQtQuick3DModel();
});
- m_widget->materialsModel()->setHasModelSelection(!m_selectedModels.isEmpty());
- m_widget->userModel()->setHasModelSelection(!m_selectedModels.isEmpty());
+ m_widget->setHasModelSelection(!m_selectedModels.isEmpty());
}
void ContentLibraryView::customNotification(const AbstractView *view,
@@ -387,18 +350,29 @@ void ContentLibraryView::customNotification(const AbstractView *view,
m_widget->addTexture(m_draggedBundleTexture);
m_draggedBundleTexture = nullptr;
- } else if (identifier == "drop_bundle_effect") {
+ } else if (identifier == "drop_bundle_item") {
QTC_ASSERT(nodeList.size() == 1, return);
- m_bundleEffectPos = data.size() == 1 ? data.first() : QVariant();
- m_widget->effectsModel()->addInstance(m_draggedBundleEffect);
- m_bundleEffectTarget = nodeList.first() ? nodeList.first() : Utils3D::active3DSceneNode(this);
+ auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils();
+ bool is3D = m_draggedBundleItem->type().startsWith(compUtils.user3DBundleType().toLatin1());
+
+ m_bundleItemPos = data.size() == 1 ? data.first() : QVariant();
+ if (is3D)
+ m_widget->userModel()->add3DInstance(m_draggedBundleItem);
+ else
+ m_widget->effectsModel()->addInstance(m_draggedBundleItem);
+ m_bundleItemTarget = nodeList.first() ? nodeList.first() : Utils3D::active3DSceneNode(this);
} else if (identifier == "add_material_to_content_lib") {
QTC_ASSERT(nodeList.size() == 1 && data.size() == 1, return);
addLibMaterial(nodeList.first(), data.first().value<QPixmap>());
} else if (identifier == "add_assets_to_content_lib") {
addLibAssets(data.first().toStringList());
+ } else if (identifier == "add_3d_to_content_lib") {
+ if (nodeList.first().isComponent())
+ addLib3DComponent(nodeList.first());
+ else
+ addLib3DItem(nodeList.first());
}
}
@@ -528,58 +502,49 @@ void ContentLibraryView::applyBundleMaterialToDropTarget(const ModelNode &bundle
#endif
// Add a project material to Content Library's user tab
-void ContentLibraryView::addLibMaterial(const ModelNode &mat, const QPixmap &icon)
+void ContentLibraryView::addLibMaterial(const ModelNode &node, const QPixmap &iconPixmap)
{
auto bundlePath = Utils::FilePath::fromString(Paths::bundlesPathSetting() + "/User/materials/");
- auto [name, qml] = m_widget->userModel()->getUniqueLibMaterialNameAndQml(
- mat.variantProperty("objectName").value().toString());
+ QString name = node.variantProperty("objectName").value().toString();
+ auto [qml, icon] = m_widget->userModel()->getUniqueLibMaterialNames(node.id());
- bundlePath.pathAppended("icons").createDir();
- bundlePath.pathAppended("images").createDir();
- bundlePath.pathAppended("shaders").createDir();
-
- QString iconPath = QLatin1String("icons/%1.png").arg(mat.id());
+ QString iconPath = QLatin1String("icons/%1").arg(icon);
QString fullIconPath = bundlePath.pathAppended(iconPath).toString();
// save icon
- bool iconSaved = icon.save(fullIconPath);
+ bool iconSaved = iconPixmap.save(fullIconPath);
if (!iconSaved)
qWarning() << __FUNCTION__ << "icon save failed";
// generate and save material Qml file
- const QStringList depAssets = writeLibMaterialQml(mat, qml);
+ const QStringList depAssets = writeLibItemQml(node, qml);
// add the material to the bundle json
- QJsonObject &jsonRef = m_widget->userModel()->bundleJsonObjectRef();
- QJsonObject matsObj = jsonRef.value("materials").toObject();
- QJsonObject matObj;
- matObj.insert("qml", qml);
- matObj.insert("icon", iconPath);
+ QJsonObject &jsonRef = m_widget->userModel()->bundleJsonMaterialObjectRef();
+ QJsonArray itemsArr = jsonRef.value("items").toArray();
+ QJsonObject itemObj;
+ itemObj.insert("name", name);
+ itemObj.insert("qml", qml);
+ itemObj.insert("icon", iconPath);
QJsonArray filesArr;
for (const QString &assetPath : depAssets)
filesArr.append(assetPath);
- matObj.insert("files", filesArr);
+ itemObj.insert("files", filesArr);
+
+ itemsArr.append(itemObj);
+ jsonRef["items"] = itemsArr;
- matsObj.insert(name, matObj);
- jsonRef.insert("materials", matsObj);
- auto result = bundlePath.pathAppended("user_materials_bundle.json")
+ auto result = bundlePath.pathAppended(Constants::BUNDLE_JSON_FILENAME)
.writeFileContents(QJsonDocument(jsonRef).toJson());
if (!result)
qWarning() << __FUNCTION__ << result.error();
// copy material assets to bundle folder
for (const QString &assetPath : depAssets) {
- Asset asset(assetPath);
- QString subDir;
- if (asset.isImage())
- subDir = "images";
- else if (asset.isShader())
- subDir = "shaders";
-
Utils::FilePath assetPathSource = DocumentManager::currentResourcePath().pathAppended(assetPath);
- Utils::FilePath assetPathTarget = bundlePath.pathAppended(QString("%1/%2")
- .arg(subDir, "/" + asset.fileName()));
+ Utils::FilePath assetPathTarget = bundlePath.pathAppended(assetPath);
+ assetPathTarget.parentDir().ensureWritableDir();
auto result = assetPathSource.copyFile(assetPathTarget);
if (!result)
@@ -589,14 +554,16 @@ void ContentLibraryView::addLibMaterial(const ModelNode &mat, const QPixmap &ico
m_widget->userModel()->addMaterial(name, qml, QUrl::fromLocalFile(fullIconPath), depAssets);
}
-QStringList ContentLibraryView::writeLibMaterialQml(const ModelNode &mat, const QString &qml)
+QStringList ContentLibraryView::writeLibItemQml(const ModelNode &node, const QString &qml)
{
QStringList depListIds;
- auto [qmlString, assets] = modelNodeToQmlString(mat, depListIds);
+ auto [qmlString, assets] = modelNodeToQmlString(node, depListIds);
qmlString.prepend("import QtQuick\nimport QtQuick3D\n\n");
- auto qmlPath = Utils::FilePath::fromString(Paths::bundlesPathSetting() + "/User/materials/" + qml);
+ QString itemType = QLatin1String(node.metaInfo().isQtQuick3DMaterial() ? "materials" : "3d");
+ auto qmlPath = Utils::FilePath::fromString(QLatin1String("%1/User/%2/%3")
+ .arg(Paths::bundlesPathSetting(), itemType, qml));
auto result = qmlPath.writeFileContents(qmlString.toUtf8());
if (!result)
qWarning() << __FUNCTION__ << result.error();
@@ -619,16 +586,24 @@ QPair<QString, QSet<QString>> ContentLibraryView::modelNodeToQmlString(const Mod
qml += indent + "id: " + (depth == 0 ? "root" : node.id()) + " \n\n";
+ const QList<PropertyName> excludedProps = {"x", "y", "z", "eulerRotation.x", "eulerRotation.y",
+ "eulerRotation.z", "scale.x", "scale.y", "scale.z",
+ "pivot.x", "pivot.y", "pivot.z"};
const QList<AbstractProperty> matProps = node.properties();
for (const AbstractProperty &p : matProps) {
+ if (excludedProps.contains(p.name()))
+ continue;
+
if (p.isVariantProperty()) {
QVariant pValue = p.toVariantProperty().value();
QString val;
if (strcmp(pValue.typeName(), "QString") == 0 || strcmp(pValue.typeName(), "QColor") == 0) {
val = QLatin1String("\"%1\"").arg(pValue.toString());
} else if (strcmp(pValue.typeName(), "QUrl") == 0) {
- val = QLatin1String("\"%1\"").arg(pValue.toString());
- assets.insert(pValue.toString());
+ QString pValueStr = pValue.toString();
+ val = QLatin1String("\"%1\"").arg(pValueStr);
+ if (!pValueStr.startsWith("#"))
+ assets.insert(pValue.toString());
} else if (strcmp(pValue.typeName(), "QmlDesigner::Enumeration") == 0) {
val = pValue.value<QmlDesigner::Enumeration>().toString();
} else {
@@ -662,19 +637,28 @@ void ContentLibraryView::addLibAssets(const QStringList &paths)
auto bundlePath = Utils::FilePath::fromString(Paths::bundlesPathSetting() + "/User/textures");
QStringList pathsInBundle;
+ const QStringList existingTextures = Utils::transform(bundlePath.dirEntries(QDir::Files),
+ [](const Utils::FilePath &path) {
+ return path.fileName();
+ });
+
for (const QString &path : paths) {
+ auto assetFilePath = Utils::FilePath::fromString(path);
+ if (existingTextures.contains(assetFilePath.fileName()))
+ continue;
+
Asset asset(path);
- auto assetPath = Utils::FilePath::fromString(path);
// save icon
- QString iconSavePath = bundlePath.pathAppended("icons/" + assetPath.baseName() + ".png").toString();
+ QString iconSavePath = bundlePath.pathAppended("icons/" + assetFilePath.baseName() + ".png")
+ .toString();
QPixmap icon = asset.pixmap({120, 120});
bool iconSaved = icon.save(iconSavePath);
if (!iconSaved)
qWarning() << __FUNCTION__ << "icon save failed";
// save asset
- auto result = assetPath.copyFile(bundlePath.pathAppended(asset.fileName()));
+ auto result = assetFilePath.copyFile(bundlePath.pathAppended(asset.fileName()));
if (!result)
qWarning() << __FUNCTION__ << result.error();
@@ -684,6 +668,158 @@ void ContentLibraryView::addLibAssets(const QStringList &paths)
m_widget->userModel()->addTextures(pathsInBundle);
}
+void ContentLibraryView::addLib3DComponent(const ModelNode &node)
+{
+ auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils();
+
+ QString compBaseName = node.simplifiedTypeName();
+ QString compFileName = compBaseName + ".qml";
+
+ Utils::FilePath compDir = DocumentManager::currentProjectDirPath()
+ .pathAppended(compUtils.import3dTypePath() + '/' + compBaseName);
+
+ auto bundlePath = Utils::FilePath::fromString(Paths::bundlesPathSetting() + "/User/3d/");
+
+ // confirm overwrite if an item with same name exists
+ if (bundlePath.pathAppended(compFileName).exists()) {
+ // Show a QML confirmation dialog before proceeding
+ QMessageBox::StandardButton reply = QMessageBox::question(m_widget, tr("3D Item Exists"),
+ tr("A 3D item with the same name '%1' already exists in the Content Library, are you sure you want to overwrite it?")
+ .arg(compFileName), QMessageBox::Yes | QMessageBox::No);
+ if (reply == QMessageBox::No)
+ return;
+
+ // before overwriting remove old item (to avoid partial items and dangling assets)
+ m_widget->userModel()->remove3DFromContentLibByName(compFileName);
+ }
+
+ // generate and save icon
+ QString iconPath = QLatin1String("icons/%1").arg(UniqueName::generateId(compBaseName) + ".png");
+ QString fullIconPath = bundlePath.pathAppended(iconPath).toString();
+ genAndSaveIcon(compDir.pathAppended(compFileName).path(), fullIconPath);
+
+ const Utils::FilePaths sourceFiles = compDir.dirEntries({{}, QDir::Files, QDirIterator::Subdirectories});
+ const QStringList ignoreList {"_importdata.json", "qmldir", compBaseName + ".hints"};
+ QStringList filesList; // 3D component's assets (dependencies)
+
+ for (const Utils::FilePath &sourcePath : sourceFiles) {
+ Utils::FilePath relativePath = sourcePath.relativePathFrom(compDir);
+ if (ignoreList.contains(sourcePath.fileName()) || relativePath.startsWith("source scene"))
+ continue;
+
+ Utils::FilePath targetPath = bundlePath.pathAppended(relativePath.path());
+ targetPath.parentDir().ensureWritableDir();
+
+ // copy item from project to user bundle
+ auto result = sourcePath.copyFile(targetPath);
+ if (!result)
+ qWarning() << __FUNCTION__ << result.error();
+
+ if (sourcePath.fileName() != compFileName) // skip component file (only collect dependencies)
+ filesList.append(relativePath.path());
+ }
+
+ // add the item to the bundle json
+ QJsonObject &jsonRef = m_widget->userModel()->bundleJson3DObjectRef();
+ QJsonArray itemsArr = jsonRef.value("items").toArray();
+ itemsArr.append(QJsonObject {
+ {"name", node.simplifiedTypeName()},
+ {"qml", compFileName},
+ {"icon", iconPath},
+ {"files", QJsonArray::fromStringList(filesList)}
+ });
+
+ jsonRef["items"] = itemsArr;
+
+ auto result = bundlePath.pathAppended(Constants::BUNDLE_JSON_FILENAME)
+ .writeFileContents(QJsonDocument(jsonRef).toJson());
+ if (!result)
+ qWarning() << __FUNCTION__ << result.error();
+
+ m_widget->userModel()->add3DItem(compBaseName, compFileName, QUrl::fromLocalFile(fullIconPath),
+ filesList);
+}
+
+void ContentLibraryView::addLib3DItem(const ModelNode &node)
+{
+ auto bundlePath = Utils::FilePath::fromString(Paths::bundlesPathSetting() + "/User/3d/");
+
+ QString name = node.variantProperty("objectName").value().toString();
+ auto [qml, icon] = m_widget->userModel()->getUniqueLib3DNames(node.id());
+ QString iconPath = QLatin1String("icons/%1").arg(icon);
+
+ if (name.isEmpty())
+ name = node.id();
+
+ // generate and save item Qml file
+ const QStringList depAssets = writeLibItemQml(node, qml);
+
+ // generate and save icon
+ QString qmlPath = QLatin1String("%1/User/3d/%2").arg(Paths::bundlesPathSetting(), qml);
+ QString fullIconPath = bundlePath.pathAppended(iconPath).toString();
+ genAndSaveIcon(qmlPath, fullIconPath);
+
+ // add the item to the bundle json
+ QJsonObject &jsonRef = m_widget->userModel()->bundleJson3DObjectRef();
+ QJsonArray itemsArr = jsonRef.value("items").toArray();
+ itemsArr.append(QJsonObject {
+ {"name", name},
+ {"qml", qml},
+ {"icon", iconPath},
+ {"files", QJsonArray::fromStringList(depAssets)}
+ });
+
+ jsonRef["items"] = itemsArr;
+
+ auto result = bundlePath.pathAppended(Constants::BUNDLE_JSON_FILENAME)
+ .writeFileContents(QJsonDocument(jsonRef).toJson());
+ if (!result)
+ qWarning() << __FUNCTION__ << result.error();
+
+ // copy item's assets to bundle folder
+ for (const QString &assetPath : depAssets) {
+ Utils::FilePath assetPathSource = DocumentManager::currentResourcePath().pathAppended(assetPath);
+ Utils::FilePath assetPathTarget = bundlePath.pathAppended(assetPath);
+ assetPathTarget.parentDir().ensureWritableDir();
+
+ auto result = assetPathSource.copyFile(assetPathTarget);
+ if (!result)
+ qWarning() << __FUNCTION__ << result.error();
+ }
+
+ m_widget->userModel()->add3DItem(name, qml, QUrl::fromLocalFile(fullIconPath), depAssets);
+}
+
+/**
+ * @brief Generates an icon image from a qml component
+ * @param qmlPath path to the qml component file to be rendered
+ * @param iconPath output save path of the generated icon
+ */
+void ContentLibraryView::genAndSaveIcon(const QString &qmlPath, const QString &iconPath)
+{
+ m_imageCache.requestSmallImage(
+ Utils::PathString{qmlPath},
+ [&, qmlPath, iconPath](const QImage &image) {
+ bool iconSaved = image.save(iconPath);
+ if (iconSaved)
+ m_widget->userModel()->refresh3DSection();
+ else
+ qWarning() << "ContentLibraryView::genAndSaveIcon(): icon save failed";
+ },
+ [&](ImageCache::AbortReason abortReason) {
+ if (abortReason == ImageCache::AbortReason::Abort) {
+ qWarning() << QLatin1String("ContentLibraryView::genAndSaveIcon(): icon generation "
+ "failed for path %1, reason: Abort").arg(qmlPath);
+ } else if (abortReason == ImageCache::AbortReason::Failed) {
+ qWarning() << QLatin1String("ContentLibraryView::genAndSaveIcon(): icon generation "
+ "failed for path %1, reason: Failed").arg(qmlPath);
+ } else if (abortReason == ImageCache::AbortReason::NoEntry) {
+ qWarning() << QLatin1String("ContentLibraryView::genAndSaveIcon(): icon generation "
+ "failed for path %1, reason: NoEntry").arg(qmlPath);
+ }
+ });
+}
+
ModelNode ContentLibraryView::getBundleMaterialDefaultInstance(const TypeName &type)
{
ModelNode matLib = Utils3D::materialLibraryNode(this);
@@ -724,7 +860,7 @@ ModelNode ContentLibraryView::createMaterial(const TypeName &typeName)
QString newName = QString::fromUtf8(typeName).replace(rgx, " \\1\\2").trimmed();
if (newName.endsWith(" Material"))
newName.chop(9); // remove trailing " Material"
- QString newId = model()->generateIdFromName(newName, "material");
+ QString newId = model()->generateNewId(newName, "material");
newMatNode.setIdWithRefactoring(newId);
VariantProperty objNameProp = newMatNode.variantProperty("objectName");
@@ -750,7 +886,7 @@ ModelNode ContentLibraryView::createMaterial(const NodeMetaInfo &metaInfo)
QString newName = QString::fromLatin1(metaInfo.simplifiedTypeName()).replace(rgx, " \\1\\2").trimmed();
if (newName.endsWith(" Material"))
newName.chop(9); // remove trailing " Material"
- QString newId = model()->generateIdFromName(newName, "material");
+ QString newId = model()->generateNewId(newName, "material");
newMatNode.setIdWithRefactoring(newId);
VariantProperty objNameProp = newMatNode.variantProperty("objectName");
@@ -762,63 +898,6 @@ ModelNode ContentLibraryView::createMaterial(const NodeMetaInfo &metaInfo)
}
#endif
-void ContentLibraryView::updateBundleMaterialsImportedState()
-{
- using namespace Utils;
-
- if (!m_widget->materialsModel()->bundleImporter())
- return;
-
- QStringList importedBundleMats;
-
- FilePath materialBundlePath = m_widget->materialsModel()->bundleImporter()->resolveBundleImportPath();
-
- if (materialBundlePath.exists()) {
- importedBundleMats = transform(materialBundlePath.dirEntries({{"*.qml"}, QDir::Files}),
- [](const FilePath &f) { return f.fileName().chopped(4); });
- }
-
- m_widget->materialsModel()->updateImportedState(importedBundleMats);
-}
-
-void ContentLibraryView::updateBundleUserMaterialsImportedState()
-{
- using namespace Utils;
-
- if (!m_widget->userModel()->bundleImporter())
- return;
-
- QStringList importedBundleMats;
-
- FilePath bundlePath = m_widget->userModel()->bundleImporter()->resolveBundleImportPath();
-
- if (bundlePath.exists()) {
- importedBundleMats = transform(bundlePath.dirEntries({{"*.qml"}, QDir::Files}),
- [](const FilePath &f) { return f.fileName().chopped(4); });
- }
-
- m_widget->userModel()->updateImportedState(importedBundleMats);
-}
-
-void ContentLibraryView::updateBundleEffectsImportedState()
-{
- using namespace Utils;
-
- if (!m_widget->effectsModel()->bundleImporter())
- return;
-
- QStringList importedBundleEffs;
-
- FilePath bundlePath = m_widget->effectsModel()->bundleImporter()->resolveBundleImportPath();
-
- if (bundlePath.exists()) {
- importedBundleEffs = transform(bundlePath.dirEntries({{"*.qml"}, QDir::Files}),
- [](const FilePath &f) { return f.fileName().chopped(4); });
- }
-
- m_widget->effectsModel()->updateImportedState(importedBundleEffs);
-}
-
void ContentLibraryView::updateBundlesQuick3DVersion()
{
bool hasImport = false;
@@ -853,6 +932,7 @@ void ContentLibraryView::updateBundlesQuick3DVersion()
#endif
m_widget->materialsModel()->setQuick3DImportVersion(major, minor);
m_widget->effectsModel()->setQuick3DImportVersion(major, minor);
+ m_widget->userModel()->setQuick3DImportVersion(major, minor);
}
} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h
index 03d42fa8bc..914a8b8ea0 100644
--- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h
+++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h
@@ -3,9 +3,10 @@
#pragma once
-#include "abstractview.h"
-#include "createtexture.h"
-#include "nodemetainfo.h"
+#include <asynchronousimagecache.h>
+#include <abstractview.h>
+#include <createtexture.h>
+#include <nodemetainfo.h>
#include <QObject>
#include <QPointer>
@@ -14,7 +15,7 @@ QT_FORWARD_DECLARE_CLASS(QPixmap)
namespace QmlDesigner {
-class ContentLibraryEffect;
+class ContentLibraryItem;
class ContentLibraryMaterial;
class ContentLibraryTexture;
class ContentLibraryWidget;
@@ -25,7 +26,8 @@ class ContentLibraryView : public AbstractView
Q_OBJECT
public:
- ContentLibraryView(ExternalDependenciesInterface &externalDependencies);
+ ContentLibraryView(AsynchronousImageCache &imageCache,
+ ExternalDependenciesInterface &externalDependencies);
~ContentLibraryView() override;
bool hasWidget() const override;
@@ -48,15 +50,17 @@ public:
const QVariant &data) override;
private:
- void connectUserBundle();
+ void connectImporter();
+ bool isMaterialBundle(const QString &bundleId) const;
+ bool isItemBundle(const QString &bundleId) const;
void active3DSceneChanged(qint32 sceneId);
- void updateBundleMaterialsImportedState();
- void updateBundleUserMaterialsImportedState();
- void updateBundleEffectsImportedState();
void updateBundlesQuick3DVersion();
- void addLibMaterial(const ModelNode &mat, const QPixmap &icon);
+ void addLibMaterial(const ModelNode &node, const QPixmap &iconPixmap);
void addLibAssets(const QStringList &paths);
- QStringList writeLibMaterialQml(const ModelNode &mat, const QString &qml);
+ void addLib3DComponent(const ModelNode &node);
+ void addLib3DItem(const ModelNode &node);
+ void genAndSaveIcon(const QString &qmlPath, const QString &iconPath);
+ QStringList writeLibItemQml(const ModelNode &node, const QString &qml);
QPair<QString, QSet<QString>> modelNodeToQmlString(const ModelNode &node, QStringList &depListIds,
int depth = 0);
@@ -74,12 +78,13 @@ private:
#endif
QPointer<ContentLibraryWidget> m_widget;
QList<ModelNode> m_bundleMaterialTargets;
- ModelNode m_bundleEffectTarget; // target of the dropped bundle effect
- QVariant m_bundleEffectPos; // pos of the dropped bundle effect
+ ModelNode m_bundleItemTarget; // target of the dropped bundle item
+ QVariant m_bundleItemPos; // pos of the dropped bundle item
QList<ModelNode> m_selectedModels; // selected 3D model nodes
ContentLibraryMaterial *m_draggedBundleMaterial = nullptr;
ContentLibraryTexture *m_draggedBundleTexture = nullptr;
- ContentLibraryEffect *m_draggedBundleEffect = nullptr;
+ ContentLibraryItem *m_draggedBundleItem = nullptr;
+ AsynchronousImageCache &m_imageCache;
bool m_bundleMaterialAddToSelected = false;
bool m_hasQuick3DImport = false;
qint32 m_sceneId = -1;
diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp
index 9375d43fd4..72bece4c98 100644
--- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp
+++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp
@@ -3,8 +3,9 @@
#include "contentlibrarywidget.h"
-#include "contentlibraryeffect.h"
+#include "contentlibrarybundleimporter.h"
#include "contentlibraryeffectsmodel.h"
+#include "contentlibraryitem.h"
#include "contentlibrarymaterial.h"
#include "contentlibrarymaterialsmodel.h"
#include "contentlibrarytexture.h"
@@ -18,6 +19,7 @@
#include <coreplugin/icore.h>
#include <designerpaths.h>
+#include <nodemetainfo.h>
#include <qmldesignerconstants.h>
#include <qmldesignerplugin.h>
@@ -40,7 +42,6 @@
#include <QQuickWidget>
#include <QRegularExpression>
#include <QShortcut>
-#include <QStandardPaths>
#include <QVBoxLayout>
namespace QmlDesigner {
@@ -67,18 +68,18 @@ bool ContentLibraryWidget::eventFilter(QObject *obj, QEvent *event)
Model *model = document->currentModel();
QTC_ASSERT(model, return false);
- if (m_effectToDrag) {
+ if (m_itemToDrag) {
QMouseEvent *me = static_cast<QMouseEvent *>(event);
if ((me->globalPos() - m_dragStartPoint).manhattanLength() > 20) {
QByteArray data;
QMimeData *mimeData = new QMimeData;
QDataStream stream(&data, QIODevice::WriteOnly);
- stream << m_effectToDrag->type();
- mimeData->setData(Constants::MIME_TYPE_BUNDLE_EFFECT, data);
+ stream << m_itemToDrag->type();
+ mimeData->setData(Constants::MIME_TYPE_BUNDLE_ITEM, data);
- emit bundleEffectDragStarted(m_effectToDrag);
- model->startDrag(mimeData, m_effectToDrag->icon().toLocalFile());
- m_effectToDrag = nullptr;
+ emit bundleItemDragStarted(m_itemToDrag);
+ model->startDrag(mimeData, m_itemToDrag->icon().toLocalFile());
+ m_itemToDrag = nullptr;
}
} else if (m_materialToDrag) {
QMouseEvent *me = static_cast<QMouseEvent *>(event);
@@ -112,7 +113,7 @@ bool ContentLibraryWidget::eventFilter(QObject *obj, QEvent *event)
}
}
} else if (event->type() == QMouseEvent::MouseButtonRelease) {
- m_effectToDrag = nullptr;
+ m_itemToDrag = nullptr;
m_materialToDrag = nullptr;
m_textureToDrag = nullptr;
setIsDragging(false);
@@ -122,7 +123,7 @@ bool ContentLibraryWidget::eventFilter(QObject *obj, QEvent *event)
}
ContentLibraryWidget::ContentLibraryWidget()
- : m_quickWidget(new StudioQuickWidget(this))
+ : m_quickWidget(Utils::makeUniqueObjectPtr<StudioQuickWidget>(this))
, m_materialsModel(new ContentLibraryMaterialsModel(this))
, m_texturesModel(new ContentLibraryTexturesModel("Textures", this))
, m_environmentsModel(new ContentLibraryTexturesModel("Environments", this))
@@ -155,7 +156,7 @@ ContentLibraryWidget::ContentLibraryWidget()
auto layout = new QVBoxLayout(this);
layout->setContentsMargins({});
layout->setSpacing(0);
- layout->addWidget(m_quickWidget.data());
+ layout->addWidget(m_quickWidget.get());
updateSearch();
@@ -177,6 +178,67 @@ ContentLibraryWidget::ContentLibraryWidget()
{"userModel", QVariant::fromValue(m_userModel.data())}});
reloadQmlSource();
+ createImporter();
+}
+
+void ContentLibraryWidget::createImporter()
+{
+ m_importer = new ContentLibraryBundleImporter();
+#ifdef QDS_USE_PROJECTSTORAGE
+ connect(m_importer,
+ &ContentLibraryBundleImporter::importFinished,
+ this,
+ [&](const QmlDesigner::TypeName &typeName, const QString &bundleId) {
+ setImporterRunning(false);
+ if (typeName.size())
+ updateImportedState(bundleId);
+ });
+#else
+ connect(m_importer,
+ &ContentLibraryBundleImporter::importFinished,
+ this,
+ [&](const QmlDesigner::NodeMetaInfo &metaInfo, const QString &bundleId) {
+ setImporterRunning(false);
+ if (metaInfo.isValid())
+ updateImportedState(bundleId);
+ });
+#endif
+
+ connect(m_importer, &ContentLibraryBundleImporter::unimportFinished, this,
+ [&](const QmlDesigner::NodeMetaInfo &metaInfo, const QString &bundleId) {
+ Q_UNUSED(metaInfo)
+ setImporterRunning(false);
+ updateImportedState(bundleId);
+ });
+}
+
+void ContentLibraryWidget::updateImportedState(const QString &bundleId)
+{
+ if (!m_importer)
+ return;
+
+ Utils::FilePath bundlePath = m_importer->resolveBundleImportPath(bundleId);
+
+ QStringList importedItems;
+ if (bundlePath.exists()) {
+ importedItems = transform(bundlePath.dirEntries({{"*.qml"}, QDir::Files}),
+ [](const Utils::FilePath &f) { return f.baseName(); });
+ }
+
+ auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils();
+ if (bundleId == compUtils.materialsBundleId())
+ m_materialsModel->updateImportedState(importedItems);
+ else if (bundleId == compUtils.effectsBundleId())
+ m_effectsModel->updateImportedState(importedItems);
+ else if (bundleId == compUtils.userMaterialsBundleId())
+ m_userModel->updateMaterialsImportedState(importedItems);
+ else if (bundleId == compUtils.user3DBundleId())
+ m_userModel->update3DImportedState(importedItems);
+}
+
+ContentLibraryBundleImporter *ContentLibraryWidget::importer() const
+{
+ return m_importer;
}
QVariantMap ContentLibraryWidget::readTextureBundleJson()
@@ -578,12 +640,6 @@ void ContentLibraryWidget::markTextureUpdated(const QString &textureKey)
m_environmentsModel->markTextureHasNoUpdates(subcategory, textureKey);
}
-bool ContentLibraryWidget::userBundleEnabled() const
-{
- // TODO: this method is to be removed after user bundle implementation is complete
- return Core::ICore::settings()->value("QML/Designer/UseExperimentalFeatures45", false).toBool();
-}
-
QSize ContentLibraryWidget::sizeHint() const
{
return {420, 420};
@@ -683,6 +739,20 @@ void ContentLibraryWidget::setIsQt6Project(bool b)
emit isQt6ProjectChanged();
}
+bool ContentLibraryWidget::importerRunning() const
+{
+ return m_importerRunning;
+}
+
+void ContentLibraryWidget::setImporterRunning(bool b)
+{
+ if (m_importerRunning == b)
+ return;
+
+ m_importerRunning = b;
+ emit importerRunningChanged();
+}
+
void ContentLibraryWidget::reloadQmlSource()
{
const QString materialBrowserQmlPath = qmlSourcesPath() + "/ContentLibrary.qml";
@@ -710,32 +780,9 @@ void ContentLibraryWidget::setIsDragging(bool val)
}
}
-QString ContentLibraryWidget::findTextureBundlePath()
+void ContentLibraryWidget::startDragItem(QmlDesigner::ContentLibraryItem *item, const QPointF &mousePos)
{
- QDir texBundleDir;
-
- if (!qEnvironmentVariable("TEXTURE_BUNDLE_PATH").isEmpty())
- texBundleDir.setPath(qEnvironmentVariable("TEXTURE_BUNDLE_PATH"));
- else if (Utils::HostOsInfo::isMacHost())
- texBundleDir.setPath(QCoreApplication::applicationDirPath() + "/../Resources/texture_bundle");
-
- // search for matBundleDir from exec dir and up
- if (texBundleDir.dirName() == ".") {
- texBundleDir.setPath(QCoreApplication::applicationDirPath());
- while (!texBundleDir.cd("texture_bundle") && texBundleDir.cdUp())
- ; // do nothing
-
- if (texBundleDir.dirName() != "texture_bundle") // bundlePathDir not found
- return {};
- }
-
- return texBundleDir.path();
-}
-
-void ContentLibraryWidget::startDragEffect(QmlDesigner::ContentLibraryEffect *eff,
- const QPointF &mousePos)
-{
- m_effectToDrag = eff;
+ m_itemToDrag = item;
m_dragStartPoint = mousePos.toPoint();
setIsDragging(true);
}
@@ -810,4 +857,18 @@ QPointer<ContentLibraryUserModel> ContentLibraryWidget::userModel() const
return m_userModel;
}
+bool ContentLibraryWidget::hasModelSelection() const
+{
+ return m_hasModelSelection;
+}
+
+void ContentLibraryWidget::setHasModelSelection(bool b)
+{
+ if (b == m_hasModelSelection)
+ return;
+
+ m_hasModelSelection = b;
+ emit hasModelSelectionChanged();
+}
+
} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h
index c4d51d0362..8e96d9d2f3 100644
--- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h
+++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h
@@ -5,6 +5,10 @@
#include "createtexture.h"
+#include <modelfwd.h>
+
+#include <utils/uniqueobjectptr.h>
+
#include <QFrame>
#include <QPointer>
@@ -18,13 +22,15 @@ class StudioQuickWidget;
namespace QmlDesigner {
-class ContentLibraryEffect;
+class ContentLibraryBundleImporter;
class ContentLibraryEffectsModel;
+class ContentLibraryItem;
class ContentLibraryMaterial;
class ContentLibraryMaterialsModel;
class ContentLibraryTexture;
class ContentLibraryTexturesModel;
class ContentLibraryUserModel;
+class NodeMetaInfo;
class ContentLibraryWidget : public QFrame
{
@@ -34,6 +40,8 @@ class ContentLibraryWidget : public QFrame
Q_PROPERTY(bool hasMaterialLibrary READ hasMaterialLibrary NOTIFY hasMaterialLibraryChanged)
Q_PROPERTY(bool hasActive3DScene READ hasActive3DScene WRITE setHasActive3DScene NOTIFY hasActive3DSceneChanged)
Q_PROPERTY(bool isQt6Project READ isQt6Project NOTIFY isQt6ProjectChanged)
+ Q_PROPERTY(bool importerRunning READ importerRunning WRITE setImporterRunning NOTIFY importerRunningChanged)
+ Q_PROPERTY(bool hasModelSelection READ hasModelSelection NOTIFY hasModelSelectionChanged)
// Needed for a workaround for a bug where after drag-n-dropping an item, the ScrollView scrolls to a random position
Q_PROPERTY(bool isDragging MEMBER m_isDragging NOTIFY isDraggingChanged)
@@ -58,9 +66,14 @@ public:
bool isQt6Project() const;
void setIsQt6Project(bool b);
- Q_INVOKABLE void handleSearchFilterChanged(const QString &filterText);
+ bool importerRunning() const;
+ void setImporterRunning(bool b);
+
+ bool hasModelSelection() const;
+ void setHasModelSelection(bool b);
void setMaterialsModel(QPointer<ContentLibraryMaterialsModel> newMaterialsModel);
+ void updateImportedState(const QString &bundleId);
QPointer<ContentLibraryMaterialsModel> materialsModel() const;
QPointer<ContentLibraryTexturesModel> texturesModel() const;
@@ -68,7 +81,8 @@ public:
QPointer<ContentLibraryEffectsModel> effectsModel() const;
QPointer<ContentLibraryUserModel> userModel() const;
- Q_INVOKABLE void startDragEffect(QmlDesigner::ContentLibraryEffect *eff, const QPointF &mousePos);
+ Q_INVOKABLE void handleSearchFilterChanged(const QString &filterText);
+ Q_INVOKABLE void startDragItem(QmlDesigner::ContentLibraryItem *item, const QPointF &mousePos);
Q_INVOKABLE void startDragMaterial(QmlDesigner::ContentLibraryMaterial *mat, const QPointF &mousePos);
Q_INVOKABLE void startDragTexture(QmlDesigner::ContentLibraryTexture *tex, const QPointF &mousePos);
Q_INVOKABLE void addImage(QmlDesigner::ContentLibraryTexture *tex);
@@ -76,12 +90,13 @@ public:
Q_INVOKABLE void addLightProbe(QmlDesigner::ContentLibraryTexture *tex);
Q_INVOKABLE void updateSceneEnvState();
Q_INVOKABLE void markTextureUpdated(const QString &textureKey);
- Q_INVOKABLE bool userBundleEnabled() const;
QSize sizeHint() const override;
+ ContentLibraryBundleImporter *importer() const;
+
signals:
- void bundleEffectDragStarted(QmlDesigner::ContentLibraryEffect *bundleEff);
+ void bundleItemDragStarted(QmlDesigner::ContentLibraryItem *item);
void bundleMaterialDragStarted(QmlDesigner::ContentLibraryMaterial *bundleMat);
void bundleTextureDragStarted(QmlDesigner::ContentLibraryTexture *bundleTex);
void addTextureRequested(const QString texPath, QmlDesigner::AddTextureMode mode);
@@ -91,6 +106,8 @@ signals:
void hasActive3DSceneChanged();
void isDraggingChanged();
void isQt6ProjectChanged();
+ void importerRunningChanged();
+ void hasModelSelectionChanged();
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
@@ -99,7 +116,6 @@ private:
void reloadQmlSource();
void updateSearch();
void setIsDragging(bool val);
- QString findTextureBundlePath();
void loadTextureBundles();
QVariantMap readTextureBundleJson();
bool fetchTextureBundleJson(const QDir &bundleDir);
@@ -110,19 +126,21 @@ private:
const QString &existingMetaFile, const QString downloadedMetaFile);
QStringList saveNewTextures(const QDir &bundleDir, const QStringList &newFiles);
void populateTextureBundleModels();
+ void createImporter();
- QScopedPointer<StudioQuickWidget> m_quickWidget;
+ Utils::UniqueObjectPtr<StudioQuickWidget> m_quickWidget;
QPointer<ContentLibraryMaterialsModel> m_materialsModel;
QPointer<ContentLibraryTexturesModel> m_texturesModel;
QPointer<ContentLibraryTexturesModel> m_environmentsModel;
QPointer<ContentLibraryEffectsModel> m_effectsModel;
QPointer<ContentLibraryUserModel> m_userModel;
+ ContentLibraryBundleImporter *m_importer = nullptr;
QShortcut *m_qmlSourceUpdateShortcut = nullptr;
QString m_filterText;
- ContentLibraryEffect *m_effectToDrag = nullptr;
+ ContentLibraryItem *m_itemToDrag = nullptr;
ContentLibraryMaterial *m_materialToDrag = nullptr;
ContentLibraryTexture *m_textureToDrag = nullptr;
QPoint m_dragStartPoint;
@@ -132,6 +150,8 @@ private:
bool m_hasQuick3DImport = false;
bool m_isDragging = false;
bool m_isQt6Project = false;
+ bool m_importerRunning = false;
+ bool m_hasModelSelection = false;
QString m_textureBundleUrl;
QString m_bundlePath;
};
diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/keyframeitem.cpp b/src/plugins/qmldesigner/components/curveeditor/detail/keyframeitem.cpp
index a1c229f57e..159e7c31ee 100644
--- a/src/plugins/qmldesigner/components/curveeditor/detail/keyframeitem.cpp
+++ b/src/plugins/qmldesigner/components/curveeditor/detail/keyframeitem.cpp
@@ -422,7 +422,7 @@ QVariant KeyframeItem::itemChange(QGraphicsItem::GraphicsItemChange change, cons
rseg.moveLeftTo(position);
if (legalLeft() && legalRight()) {
- if (qApp->keyboardModifiers().testFlag(Qt::ShiftModifier) && m_validPos.has_value()) {
+ if (qApp->keyboardModifiers().testFlag(Qt::ShiftModifier) && m_validPos) {
if (m_firstPos) {
auto firstToNow = QLineF(*m_firstPos, position);
if (std::abs(firstToNow.dx()) > std::abs(firstToNow.dy()))
diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dcanvas.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dcanvas.cpp
index 2e8ef8304f..63d5e958b1 100644
--- a/src/plugins/qmldesigner/components/edit3d/edit3dcanvas.cpp
+++ b/src/plugins/qmldesigner/components/edit3d/edit3dcanvas.cpp
@@ -51,6 +51,8 @@ Edit3DCanvas::Edit3DCanvas(Edit3DWidget *parent)
setAcceptDrops(true);
setFocusPolicy(Qt::ClickFocus);
m_busyIndicator->show();
+
+ installEventFilter(this);
}
void Edit3DCanvas::updateRenderImage(const QImage &img)
@@ -79,11 +81,20 @@ QWidget *Edit3DCanvas::busyIndicator() const
return m_busyIndicator;
}
+#ifdef Q_OS_MACOS
+extern "C" bool AXIsProcessTrusted();
+#endif
+
void Edit3DCanvas::setFlyMode(bool enabled, const QPoint &pos)
{
if (m_flyMode == enabled)
return;
+#ifdef Q_OS_MACOS
+ if (!AXIsProcessTrusted())
+ m_isTrusted = false;
+#endif
+
m_flyMode = enabled;
if (enabled) {
@@ -132,6 +143,23 @@ void Edit3DCanvas::setFlyMode(bool enabled, const QPoint &pos)
m_parent->view()->setFlyMode(enabled);
}
+bool Edit3DCanvas::eventFilter(QObject *obj, QEvent *event)
+{
+ if (m_flyMode && event->type() == QEvent::ShortcutOverride) {
+ // Suppress shortcuts that conflict with fly mode keys
+ const QList<int> controlKeys = { Qt::Key_W, Qt::Key_A, Qt::Key_S,
+ Qt::Key_D, Qt::Key_Q, Qt::Key_E,
+ Qt::Key_Up, Qt::Key_Down, Qt::Key_Left,
+ Qt::Key_Right, Qt::Key_PageDown, Qt::Key_PageUp,
+ Qt::Key_Alt, Qt::Key_Shift };
+ auto ke = static_cast<QKeyEvent *>(event);
+ if (controlKeys.contains(ke->key()))
+ event->accept();
+ }
+
+ return QObject::eventFilter(obj, event);
+}
+
void Edit3DCanvas::mousePressEvent(QMouseEvent *e)
{
m_contextMenuPending = false;
@@ -171,7 +199,8 @@ void Edit3DCanvas::mouseMoveEvent(QMouseEvent *e)
// We notify explicit camera rotation need for puppet rather than rely in mouse events,
// as mouse isn't grabbed on puppet side and can't handle fast movements that go out of
// edit camera mouse area. This also simplifies split view handling.
- QPointF diff = m_hiddenCursorPos - e->globalPos();
+ QPointF diff = m_isTrusted ? (m_hiddenCursorPos - e->globalPos()) : (m_lastCursorPos - e->globalPos());
+
if (e->buttons() == (Qt::LeftButton | Qt::RightButton)) {
m_parent->view()->emitView3DAction(View3DActionType::EditCameraMove,
QVector3D{float(-diff.x()), float(-diff.y()), 0.f});
@@ -182,13 +211,26 @@ void Edit3DCanvas::mouseMoveEvent(QMouseEvent *e)
// Skip first move to avoid undesirable jump occasionally when initiating flight mode
m_flyModeFirstUpdate = false;
}
- QCursor::setPos(m_hiddenCursorPos);
+
+ if (m_isTrusted)
+ QCursor::setPos(m_hiddenCursorPos);
+ else
+ m_lastCursorPos = e->globalPos();
}
}
void Edit3DCanvas::wheelEvent(QWheelEvent *e)
{
- m_parent->view()->sendInputEvent(e);
+ if (m_flyMode) {
+ // In fly mode, wheel controls the camera speed slider (value range 1-100)
+ double speed;
+ double mult;
+ m_parent->view()->getCameraSpeedAuxData(speed, mult);
+ speed = qMin(100., qMax(1., speed + double(e->angleDelta().y()) / 40.));
+ m_parent->view()->setCameraSpeedAuxData(speed, mult);
+ } else {
+ m_parent->view()->sendInputEvent(e);
+ }
QWidget::wheelEvent(e);
}
diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dcanvas.h b/src/plugins/qmldesigner/components/edit3d/edit3dcanvas.h
index 39207554a7..16c1063dd6 100644
--- a/src/plugins/qmldesigner/components/edit3d/edit3dcanvas.h
+++ b/src/plugins/qmldesigner/components/edit3d/edit3dcanvas.h
@@ -30,6 +30,7 @@ public:
bool isFlyMode() const { return m_flyMode; }
protected:
+ bool eventFilter(QObject *obj, QEvent *event) override;
void mousePressEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
void mouseDoubleClickEvent(QMouseEvent *e) override;
@@ -52,10 +53,12 @@ private:
qint32 m_activeScene = -1;
QElapsedTimer m_usageTimer;
qreal m_opacity = 1.0;
+ bool m_isTrusted = true;
QWidget *m_busyIndicator = nullptr;
bool m_flyMode = false;
QPoint m_flyModeStartCursorPos;
QPoint m_hiddenCursorPos;
+ QPoint m_lastCursorPos;
qint64 m_flyModeStartTime = 0;
bool m_flyModeFirstUpdate = false;
bool m_contextMenuPending = false;
diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp
index 4712b048b1..bb7404f252 100644
--- a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp
+++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp
@@ -20,9 +20,11 @@
#include "nodeinstanceview.h"
#include "qmldesignerconstants.h"
#include "qmldesignerplugin.h"
+#include "qmlitemnode.h"
#include "qmlvisualnode.h"
#include "seekerslider.h"
#include "snapconfiguration.h"
+#include "variantproperty.h"
#include <auxiliarydataproperties.h>
#include <model/modelutils.h>
@@ -133,6 +135,7 @@ void Edit3DView::updateActiveScene3D(const QVariantMap &sceneState)
const QString orientationKey = QStringLiteral("globalOrientation");
const QString editLightKey = QStringLiteral("showEditLight");
const QString gridKey = QStringLiteral("showGrid");
+ const QString showLookAtKey = QStringLiteral("showLookAt");
const QString selectionBoxKey = QStringLiteral("showSelectionBox");
const QString iconGizmoKey = QStringLiteral("showIconGizmo");
const QString cameraFrustumKey = QStringLiteral("showCameraFrustum");
@@ -187,6 +190,11 @@ void Edit3DView::updateActiveScene3D(const QVariantMap &sceneState)
else
m_showGridAction->action()->setChecked(false);
+ if (sceneState.contains(showLookAtKey))
+ m_showLookAtAction->action()->setChecked(sceneState[showLookAtKey].toBool());
+ else
+ m_showLookAtAction->action()->setChecked(false);
+
if (sceneState.contains(selectionBoxKey))
m_showSelectionBoxAction->action()->setChecked(sceneState[selectionBoxKey].toBool());
else
@@ -235,36 +243,10 @@ void Edit3DView::updateActiveScene3D(const QVariantMap &sceneState)
state.showWireframe = false;
}
- // Syncing background color only makes sense for children of View3D instances
- bool syncValue = false;
- bool syncEnabled = false;
- bool desiredSyncValue = false;
if (sceneState.contains(syncEnvBgKey))
- desiredSyncValue = sceneState[syncEnvBgKey].toBool();
- ModelNode checkNode = Utils3D::active3DSceneNode(this);
- const bool activeSceneValid = checkNode.isValid();
-
- while (checkNode.isValid()) {
- if (checkNode.metaInfo().isQtQuick3DView3D()) {
- syncValue = desiredSyncValue;
- syncEnabled = true;
- break;
- }
- if (checkNode.hasParentProperty())
- checkNode = checkNode.parentProperty().parentModelNode();
- else
- break;
- }
-
- if (activeSceneValid && syncValue != desiredSyncValue) {
- // Update actual toolstate as well if we overrode it.
- QTimer::singleShot(0, this, [this, syncValue]() {
- emitView3DAction(View3DActionType::SyncEnvBackground, syncValue);
- });
- }
-
- m_syncEnvBackgroundAction->action()->setChecked(syncValue);
- m_syncEnvBackgroundAction->action()->setEnabled(syncEnabled);
+ m_syncEnvBackgroundAction->action()->setChecked(sceneState[syncEnvBgKey].toBool());
+ else
+ m_syncEnvBackgroundAction->action()->setChecked(false);
// Selection context change updates visible and enabled states
SelectionContext selectionContext(this);
@@ -273,6 +255,8 @@ void Edit3DView::updateActiveScene3D(const QVariantMap &sceneState)
m_bakeLightsAction->currentContextChanged(selectionContext);
syncCameraSpeedToNewView();
+
+ storeCurrentSceneEnvironment();
}
void Edit3DView::modelAttached(Model *model)
@@ -350,11 +334,10 @@ void Edit3DView::handleEntriesChanged()
{EK_importedModels, {tr("Imported Models"), contextIcon(DesignerIcons::ImportedModelsIcon)}}};
#ifdef QDS_USE_PROJECTSTORAGE
- const auto &projectStorage = *model()->projectStorage();
auto append = [&](const NodeMetaInfo &metaInfo, ItemLibraryEntryKeys key) {
auto entries = metaInfo.itemLibrariesEntries();
if (entries.size())
- entriesMap[key].entryList.append(toItemLibraryEntries(entries, projectStorage));
+ entriesMap[key].entryList.append(toItemLibraryEntries(entries));
};
append(model()->qtQuick3DModelMetaInfo(), EK_primitives);
@@ -369,7 +352,7 @@ void Edit3DView::handleEntriesChanged()
.generatedComponentUtils()
.import3dTypePrefix();
- auto assetsModule = model()->module(import3dTypePrefix);
+ auto assetsModule = model()->module(import3dTypePrefix, Storage::ModuleKind::QmlLibrary);
for (const auto &metaInfo : model()->metaInfosForModule(assetsModule))
append(metaInfo, EK_importedModels);
@@ -386,9 +369,12 @@ void Edit3DView::handleEntriesChanged()
} else if (entry.typeName() == "QtQuick3D.OrthographicCamera"
|| entry.typeName() == "QtQuick3D.PerspectiveCamera") {
entryKey = EK_cameras;
- } else if (entry.typeName().startsWith(QmlDesignerPlugin::instance()->documentManager()
- .generatedComponentUtils().import3dTypePrefix().toUtf8())
- && NodeHints::fromItemLibraryEntry(entry).canBeDroppedInView3D()) {
+ } else if (entry.typeName().startsWith(QmlDesignerPlugin::instance()
+ ->documentManager()
+ .generatedComponentUtils()
+ .import3dTypePrefix()
+ .toUtf8())
+ && NodeHints::fromItemLibraryEntry(entry, model()).canBeDroppedInView3D()) {
entryKey = EK_importedModels;
} else {
continue;
@@ -505,7 +491,7 @@ void Edit3DView::nodeAtPosReady(const ModelNode &modelNode, const QVector3D &pos
} else if (m_nodeAtPosReqType == NodeAtPosReqType::BundleMaterialDrop) {
emitCustomNotification("drop_bundle_material", {modelNode}); // To ContentLibraryView
} else if (m_nodeAtPosReqType == NodeAtPosReqType::BundleEffectDrop) {
- emitCustomNotification("drop_bundle_effect", {modelNode}, {pos3d}); // To ContentLibraryView
+ emitCustomNotification("drop_bundle_item", {modelNode}, {pos3d}); // To ContentLibraryView
} else if (m_nodeAtPosReqType == NodeAtPosReqType::TextureDrop) {
emitCustomNotification("apply_texture_to_model3D", {modelNode, m_droppedModelNode});
} else if (m_nodeAtPosReqType == NodeAtPosReqType::AssetDrop) {
@@ -540,6 +526,21 @@ void Edit3DView::nodeRemoved(const ModelNode &,
updateAlignActionStates();
}
+void Edit3DView::propertiesRemoved(const QList<AbstractProperty> &propertyList)
+{
+ maybeStoreCurrentSceneEnvironment(propertyList);
+}
+
+void Edit3DView::bindingPropertiesChanged(const QList<BindingProperty> &propertyList, PropertyChangeFlags)
+{
+ maybeStoreCurrentSceneEnvironment(propertyList);
+}
+
+void Edit3DView::variantPropertiesChanged(const QList<VariantProperty> &propertyList, PropertyChangeFlags)
+{
+ maybeStoreCurrentSceneEnvironment(propertyList);
+}
+
void Edit3DView::sendInputEvent(QEvent *e) const
{
if (nodeInstanceView())
@@ -716,6 +717,30 @@ QPoint Edit3DView::resolveToolbarPopupPos(Edit3DAction *action) const
return pos;
}
+template<typename T, typename>
+void Edit3DView::maybeStoreCurrentSceneEnvironment(const QList<T> &propertyList)
+{
+ QSet<qint32> handledNodes;
+ QmlObjectNode sceneEnv;
+ for (const AbstractProperty &prop : propertyList) {
+ ModelNode node = prop.parentModelNode();
+ const qint32 id = node.internalId();
+ if (handledNodes.contains(id))
+ continue;
+
+ handledNodes.insert(id);
+ if (!node.metaInfo().isQtQuick3DSceneEnvironment())
+ continue;
+
+ if (!sceneEnv.isValid())
+ sceneEnv = currentSceneEnv();
+ if (sceneEnv == node) {
+ storeCurrentSceneEnvironment();
+ break;
+ }
+ }
+}
+
void Edit3DView::showContextMenu()
{
// If request for context menu is still pending, skip for now
@@ -736,32 +761,6 @@ void Edit3DView::showContextMenu()
void Edit3DView::setFlyMode(bool enabled)
{
emitView3DAction(View3DActionType::FlyModeToggle, enabled);
-
- // Disable any actions with conflicting hotkeys
- if (enabled) {
- m_flyModeDisabledActions.clear();
- const QList<QKeySequence> controlKeys = { Qt::Key_W, Qt::Key_A, Qt::Key_S,
- Qt::Key_D, Qt::Key_Q, Qt::Key_E,
- Qt::Key_Up, Qt::Key_Down, Qt::Key_Left,
- Qt::Key_Right, Qt::Key_PageDown, Qt::Key_PageUp};
- for (auto i = m_edit3DActions.cbegin(), end = m_edit3DActions.cend(); i != end; ++i) {
- for (const QKeySequence &controlKey : controlKeys) {
- if (Core::Command *cmd = m_edit3DWidget->actionToCommandHash().value(i.value()->action())) {
- if (cmd->keySequence().matches(controlKey) == QKeySequence::ExactMatch) {
- if (i.value()->action()->isEnabled()) {
- m_flyModeDisabledActions.append(i.value());
- i.value()->action()->setEnabled(false);
- }
- break;
- }
- }
- }
- }
- } else {
- for (Edit3DAction *action : std::as_const(m_flyModeDisabledActions))
- action->action()->setEnabled(true);
- m_flyModeDisabledActions.clear();
- }
}
void Edit3DView::syncSnapAuxPropsToSettings()
@@ -831,6 +830,75 @@ void Edit3DView::syncCameraSpeedToNewView()
setCameraSpeedAuxData(speed, multiplier);
}
+QmlObjectNode Edit3DView::currentSceneEnv()
+{
+ PropertyName envProp{"environment"};
+ ModelNode checkNode = Utils3D::active3DSceneNode(this);
+ while (checkNode.isValid()) {
+ if (checkNode.metaInfo().isQtQuick3DView3D()) {
+ QmlObjectNode sceneEnvNode = QmlItemNode(checkNode).bindingProperty(envProp)
+ .resolveToModelNode();
+ if (sceneEnvNode.isValid())
+ return sceneEnvNode;
+ break;
+ }
+ if (checkNode.hasParentProperty())
+ checkNode = checkNode.parentProperty().parentModelNode();
+ else
+ break;
+ }
+ return {};
+}
+
+void Edit3DView::storeCurrentSceneEnvironment()
+{
+ // If current active scene has scene environment, store relevant properties
+ QmlObjectNode sceneEnvNode = currentSceneEnv();
+ if (sceneEnvNode.isValid()) {
+ QVariantMap lastSceneEnvData;
+
+ auto insertPropValue = [](const PropertyName prop, const QmlObjectNode &node,
+ QVariantMap &map) {
+ if (!node.hasProperty(prop))
+ return;
+
+ map.insert(QString::fromUtf8(prop), node.modelValue(prop));
+ };
+
+ auto insertTextureProps = [&](const PropertyName prop) {
+ // For now we just grab the absolute path of texture source for simplicity
+ if (!sceneEnvNode.hasProperty(prop))
+ return;
+
+ QmlObjectNode bindNode = QmlItemNode(sceneEnvNode).bindingProperty(prop)
+ .resolveToModelNode();
+ if (bindNode.isValid()) {
+ QVariantMap props;
+ const PropertyName sourceProp = "source";
+ if (bindNode.hasProperty(sourceProp)) {
+ Utils::FilePath qmlPath = Utils::FilePath::fromUrl(
+ model()->fileUrl()).absolutePath();
+ Utils::FilePath sourcePath = Utils::FilePath::fromUrl(
+ bindNode.modelValue(sourceProp).toUrl());
+
+ sourcePath = qmlPath.resolvePath(sourcePath);
+
+ props.insert(QString::fromUtf8(sourceProp),
+ sourcePath.absoluteFilePath().toUrl());
+ }
+ lastSceneEnvData.insert(QString::fromUtf8(prop), props);
+ }
+ };
+
+ insertPropValue("backgroundMode", sceneEnvNode, lastSceneEnvData);
+ insertPropValue("clearColor", sceneEnvNode, lastSceneEnvData);
+ insertTextureProps("lightProbe");
+ insertTextureProps("skyBoxCubeMap");
+
+ emitView3DAction(View3DActionType::SetLastSceneEnvData, lastSceneEnvData);
+ }
+}
+
const QList<Edit3DView::SplitToolState> &Edit3DView::splitToolStates() const
{
return m_splitToolStates;
@@ -978,6 +1046,18 @@ void Edit3DView::createEdit3DActions()
nullptr,
QCoreApplication::translate("ShowGridAction", "Toggle the visibility of the helper grid."));
+ m_showLookAtAction = std::make_unique<Edit3DAction>(
+ QmlDesigner::Constants::EDIT3D_EDIT_SHOW_LOOKAT,
+ View3DActionType::ShowLookAt,
+ QCoreApplication::translate("ShowLookAtAction", "Show Look-at"),
+ QKeySequence(Qt::Key_L),
+ true,
+ true,
+ QIcon(),
+ this,
+ nullptr,
+ QCoreApplication::translate("ShowLookAtAction", "Toggle the visibility of the edit camera look-at indicator."));
+
m_showSelectionBoxAction = std::make_unique<Edit3DAction>(
QmlDesigner::Constants::EDIT3D_EDIT_SHOW_SELECTION_BOX,
View3DActionType::ShowSelectionBox,
@@ -1281,6 +1361,7 @@ void Edit3DView::createEdit3DActions()
m_rightActions << m_resetAction.get();
m_visibilityToggleActions << m_showGridAction.get();
+ m_visibilityToggleActions << m_showLookAtAction.get();
m_visibilityToggleActions << m_showSelectionBoxAction.get();
m_visibilityToggleActions << m_showIconGizmoAction.get();
m_visibilityToggleActions << m_showCameraFrustumAction.get();
diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.h b/src/plugins/qmldesigner/components/edit3d/edit3dview.h
index ade2ef6a8f..755efc0ae3 100644
--- a/src/plugins/qmldesigner/components/edit3d/edit3dview.h
+++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.h
@@ -8,6 +8,7 @@
#include <abstractview.h>
#include <modelcache.h>
+#include <qmlobjectnode.h>
#include <QImage>
#include <QPointer>
@@ -59,6 +60,11 @@ public:
PropertyChangeFlags propertyChange) override;
void nodeRemoved(const ModelNode &removedNode, const NodeAbstractProperty &parentProperty,
PropertyChangeFlags propertyChange) override;
+ void propertiesRemoved(const QList<AbstractProperty> &propertyList) override;
+ void bindingPropertiesChanged(const QList<BindingProperty> &propertyList,
+ PropertyChangeFlags propertyChange) override;
+ void variantPropertiesChanged(const QList<VariantProperty> &propertyList,
+ PropertyChangeFlags propertyChange) override;
void sendInputEvent(QEvent *e) const;
void edit3DViewResized(const QSize &size) const;
@@ -127,9 +133,14 @@ private:
void createSyncEnvBackgroundAction();
void createSeekerSliderAction();
void syncCameraSpeedToNewView();
+ QmlObjectNode currentSceneEnv();
+ void storeCurrentSceneEnvironment();
QPoint resolveToolbarPopupPos(Edit3DAction *action) const;
+ template<typename T, typename = typename std::enable_if<std::is_base_of<AbstractProperty , T>::value>::type>
+ void maybeStoreCurrentSceneEnvironment(const QList<T> &propertyList);
+
QPointer<Edit3DWidget> m_edit3DWidget;
QVector<Edit3DAction *> m_leftActions;
QVector<Edit3DAction *> m_rightActions;
@@ -148,6 +159,7 @@ private:
std::unique_ptr<Edit3DAction> m_orientationModeAction;
std::unique_ptr<Edit3DAction> m_editLightAction;
std::unique_ptr<Edit3DAction> m_showGridAction;
+ std::unique_ptr<Edit3DAction> m_showLookAtAction;
std::unique_ptr<Edit3DAction> m_showSelectionBoxAction;
std::unique_ptr<Edit3DAction> m_showIconGizmoAction;
std::unique_ptr<Edit3DAction> m_showCameraFrustumAction;
@@ -187,7 +199,6 @@ private:
int m_activeSplit = 0;
QList<SplitToolState> m_splitToolStates;
- QList<Edit3DAction *> m_flyModeDisabledActions;
ModelNode m_contextMenuPendingNode;
ModelNode m_pickView3dNode;
diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp
index 6f1cf2e183..f6bbf8d794 100644
--- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp
+++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp
@@ -2,37 +2,41 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "edit3dwidget.h"
-#include "designdocument.h"
-#include "designericons.h"
+
#include "edit3dactions.h"
#include "edit3dcanvas.h"
#include "edit3dtoolbarmenu.h"
#include "edit3dview.h"
-#include "externaldependenciesinterface.h"
-#include "materialutils.h"
-#include "metainfo.h"
-#include "modelnodeoperations.h"
-#include "nodeabstractproperty.h"
-#include "nodehints.h"
-#include "qmldesignerconstants.h"
-#include "qmldesignerplugin.h"
-#include "qmleditormenu.h"
-#include "qmlvisualnode.h"
-#include "viewmanager.h"
-#include <utils3d.h>
#include <auxiliarydataproperties.h>
#include <designeractionmanager.h>
+#include <designdocument.h>
+#include <designericons.h>
#include <designermcumanager.h>
+#include <externaldependenciesinterface.h>
+#include <generatedcomponentutils.h>
#include <import.h>
-#include <model/modelutils.h>
+#include <materialutils.h>
+#include <metainfo.h>
+#include <modelnodeoperations.h>
+#include <nodeabstractproperty.h>
+#include <nodehints.h>
#include <nodeinstanceview.h>
+#include <qmldesignerconstants.h>
+#include <qmldesignerplugin.h>
+#include <qmleditormenu.h>
+#include <qmlvisualnode.h>
#include <seekerslider.h>
+#include <toolbox.h>
+#include <viewmanager.h>
+#include <utils3d.h>
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/actionmanager/command.h>
#include <coreplugin/icore.h>
-#include <toolbox.h>
+
+#include <model/modelutils.h>
+
#include <utils/asset.h>
#include <utils/qtcassert.h>
#include <utils/utilsicons.h>
@@ -359,6 +363,14 @@ void Edit3DWidget::createContextMenu()
resetAction->setToolTip(tr("Reset all shading options for all viewports."));
m_contextMenu->addSeparator();
+
+ m_addToContentLibAction = m_contextMenu->addAction(
+ contextIcon(DesignerIcons::CreateIcon), // TODO: placeholder icon
+ tr("Add to Content Library"), [&] {
+ view()->emitCustomNotification("add_3d_to_content_lib", {m_contextMenuTarget});
+ });
+
+ m_contextMenu->addSeparator();
}
bool Edit3DWidget::isPasteAvailable() const
@@ -402,7 +414,7 @@ void Edit3DWidget::showOnboardingLabel()
" in the"
" <b>Assets</b>"
" view.");
- text = labelText.arg(Utils::creatorTheme()->color(Utils::Theme::TextColorLink).name());
+ text = labelText.arg(Utils::creatorColor(Utils::Theme::TextColorLink).name());
} else {
text = tr("3D view is not supported in Qt5 projects.");
}
@@ -608,14 +620,18 @@ void Edit3DWidget::showBackgroundColorMenu(bool show, const QPoint &pos)
void Edit3DWidget::showContextMenu(const QPoint &pos, const ModelNode &modelNode, const QVector3D &pos3d)
{
+ auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils();
+
m_contextMenuTarget = modelNode;
m_contextMenuPos3d = pos3d;
const bool isModel = modelNode.metaInfo().isQtQuick3DModel();
+ const bool isNode = modelNode.metaInfo().isQtQuick3DNode();
const bool allowAlign = view()->edit3DAction(View3DActionType::AlignCamerasToView)->action()->isEnabled();
const bool isSingleComponent = view()->hasSingleSelectedModelNode() && modelNode.isComponent();
const bool anyNodeSelected = view()->hasSelectedModelNodes();
const bool selectionExcludingRoot = anyNodeSelected && !view()->rootModelNode().isSelected();
+ const bool isInBundle = modelNode.type().startsWith(compUtils.componentBundlesTypePrefix().toLatin1());
if (m_createSubMenu)
m_createSubMenu->setEnabled(!isSceneLocked());
@@ -633,6 +649,7 @@ void Edit3DWidget::showContextMenu(const QPoint &pos, const ModelNode &modelNode
m_toggleGroupAction->setEnabled(true);
m_bakeLightsAction->setVisible(view()->bakeLightsAction()->action()->isVisible());
m_bakeLightsAction->setEnabled(view()->bakeLightsAction()->action()->isEnabled());
+ m_addToContentLibAction->setEnabled(isNode && !isInBundle);
if (m_view) {
int idx = m_view->activeSplit();
@@ -685,7 +702,7 @@ void Edit3DWidget::dragEnterEvent(QDragEnterEvent *dragEnterEvent)
} else if (actionManager.externalDragHasSupportedAssets(dragEnterEvent->mimeData())
|| dragEnterEvent->mimeData()->hasFormat(Constants::MIME_TYPE_MATERIAL)
|| dragEnterEvent->mimeData()->hasFormat(Constants::MIME_TYPE_BUNDLE_MATERIAL)
- || dragEnterEvent->mimeData()->hasFormat(Constants::MIME_TYPE_BUNDLE_EFFECT)
+ || dragEnterEvent->mimeData()->hasFormat(Constants::MIME_TYPE_BUNDLE_ITEM)
|| dragEnterEvent->mimeData()->hasFormat(Constants::MIME_TYPE_TEXTURE)) {
if (Utils3D::active3DSceneNode(m_view).isValid())
dragEnterEvent->acceptProposedAction();
@@ -694,7 +711,7 @@ void Edit3DWidget::dragEnterEvent(QDragEnterEvent *dragEnterEvent)
if (!data.isEmpty()) {
QDataStream stream(data);
stream >> m_draggedEntry;
- if (NodeHints::fromItemLibraryEntry(m_draggedEntry).canBeDroppedInView3D())
+ if (NodeHints::fromItemLibraryEntry(m_draggedEntry, view()->model()).canBeDroppedInView3D())
dragEnterEvent->acceptProposedAction();
}
}
@@ -730,8 +747,8 @@ void Edit3DWidget::dropEvent(QDropEvent *dropEvent)
return;
}
- // handle dropping bundle effects
- if (dropEvent->mimeData()->hasFormat(Constants::MIME_TYPE_BUNDLE_EFFECT)) {
+ // handle dropping bundle items
+ if (dropEvent->mimeData()->hasFormat(Constants::MIME_TYPE_BUNDLE_ITEM)) {
m_view->dropBundleEffect(pos);
m_view->model()->endDrag();
return;
@@ -770,9 +787,10 @@ void Edit3DWidget::dropEvent(QDropEvent *dropEvent)
->documentManager()
.generatedComponentUtils()
.import3dTypePrefix();
- auto metaInfo = model->metaInfo(model->module(import3dTypePrefix), fileName.toUtf8());
+ auto moduleId = model->module(import3dTypePrefix, Storage::ModuleKind::QmlLibrary);
+ auto metaInfo = model->metaInfo(moduleId, fileName.toUtf8());
if (auto entries = metaInfo.itemLibrariesEntries(); entries.size()) {
- auto entry = ItemLibraryEntry{entries.front(), *model->projectStorage()};
+ auto entry = ItemLibraryEntry{entries.front()};
QmlVisualNode::createQml3DNode(view(), entry, m_canvas->activeScene(), {}, false);
}
}
diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.h b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.h
index 211b044d41..97c0469668 100644
--- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.h
+++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.h
@@ -100,6 +100,7 @@ private:
QPointer<QAction> m_selectParentAction;
QPointer<QAction> m_toggleGroupAction;
QPointer<QAction> m_wireFrameAction;
+ QPointer<QAction> m_addToContentLibAction;
QHash<int, QPointer<QAction>> m_matOverrideActions;
QPointer<QMenu> m_createSubMenu;
ModelNode m_contextMenuTarget;
diff --git a/src/plugins/qmldesigner/components/formeditor/dragtool.cpp b/src/plugins/qmldesigner/components/formeditor/dragtool.cpp
index 0b7d199b50..a6494811b6 100644
--- a/src/plugins/qmldesigner/components/formeditor/dragtool.cpp
+++ b/src/plugins/qmldesigner/components/formeditor/dragtool.cpp
@@ -206,9 +206,16 @@ static ItemLibraryEntry itemLibraryEntryFromMimeData(const QMimeData *mimeData)
return itemLibraryEntry;
}
-static bool canBeDropped(const QMimeData *mimeData)
+static bool canBeDropped(const QMimeData *mimeData, Model *model)
{
- return NodeHints::fromItemLibraryEntry(itemLibraryEntryFromMimeData(mimeData)).canBeDroppedInFormEditor();
+#ifdef QDS_USE_PROJECTSTORAGE
+ auto itemLibraryEntry = itemLibraryEntryFromMimeData(mimeData);
+ NodeMetaInfo metaInfo{itemLibraryEntry.typeId(), model->projectStorage()};
+ return metaInfo.canBeDroppedInFormEditor() == FlagIs::True;
+#else
+ return NodeHints::fromItemLibraryEntry(itemLibraryEntryFromMimeData(mimeData), model)
+ .canBeDroppedInFormEditor();
+#endif
}
static bool hasItemLibraryInfo(const QMimeData *mimeData)
@@ -218,7 +225,7 @@ static bool hasItemLibraryInfo(const QMimeData *mimeData)
void DragTool::dropEvent(const QList<QGraphicsItem *> &itemList, QGraphicsSceneDragDropEvent *event)
{
- if (canBeDropped(event->mimeData())) {
+ if (canBeDropped(event->mimeData(), view()->model())) {
event->accept();
end(generateUseSnapping(event->modifiers()));
@@ -290,7 +297,7 @@ void DragTool::dropEvent(const QList<QGraphicsItem *> &itemList, QGraphicsSceneD
void DragTool::dragEnterEvent(const QList<QGraphicsItem *> &/*itemList*/, QGraphicsSceneDragDropEvent *event)
{
- if (canBeDropped(event->mimeData())) {
+ if (canBeDropped(event->mimeData(), view()->model())) {
m_blockMove = false;
if (hasItemLibraryInfo(event->mimeData())) {
@@ -306,7 +313,7 @@ void DragTool::dragEnterEvent(const QList<QGraphicsItem *> &/*itemList*/, QGraph
void DragTool::dragLeaveEvent(const QList<QGraphicsItem *> &/*itemList*/, QGraphicsSceneDragDropEvent *event)
{
- if (canBeDropped(event->mimeData())) {
+ if (canBeDropped(event->mimeData(), view()->model())) {
event->accept();
m_moveManipulator.end();
@@ -363,10 +370,8 @@ void DragTool::dragMoveEvent(const QList<QGraphicsItem *> &itemList, QGraphicsSc
->data(Constants::MIME_TYPE_ASSETS)).split(',');
QString assetType = AssetsLibraryWidget::getAssetTypeAndData(assetPaths[0]).first;
- if (!m_blockMove
- && !m_isAborted
- && canBeDropped(event->mimeData())
- && assetType != Constants::MIME_TYPE_ASSET_EFFECT) {
+ if (!m_blockMove && !m_isAborted && canBeDropped(event->mimeData(), view()->model())
+ && assetType != Constants::MIME_TYPE_ASSET_EFFECT) {
event->accept();
if (!m_dragNodes.isEmpty()) {
if (targetContainerItem) {
diff --git a/src/plugins/qmldesigner/components/formeditor/dragtool.h b/src/plugins/qmldesigner/components/formeditor/dragtool.h
index 1cd2c9f487..c1d5626d28 100644
--- a/src/plugins/qmldesigner/components/formeditor/dragtool.h
+++ b/src/plugins/qmldesigner/components/formeditor/dragtool.h
@@ -7,7 +7,6 @@
#include "selectionindicator.h"
#include <QObject>
-#include <QScopedPointer>
#include <QPointer>
namespace QmlDesigner {
diff --git a/src/plugins/qmldesigner/components/formeditor/formeditorannotationicon.cpp b/src/plugins/qmldesigner/components/formeditor/formeditorannotationicon.cpp
index 35a48a6b6d..dd239dd966 100644
--- a/src/plugins/qmldesigner/components/formeditor/formeditorannotationicon.cpp
+++ b/src/plugins/qmldesigner/components/formeditor/formeditorannotationicon.cpp
@@ -302,9 +302,9 @@ QGraphicsItem *FormEditorAnnotationIcon::createCommentBubble(QRectF rect, const
const QString &author, const QString &text,
const QString &date, QGraphicsItem *parent)
{
- static QColor textColor = Utils::creatorTheme()->color(Utils::Theme::DStextColor);
- static QColor backgroundColor = Utils::creatorTheme()->color(Utils::Theme::QmlDesigner_BackgroundColorDarker);
- static QColor frameColor = Utils::creatorTheme()->color(Utils::Theme::QmlDesigner_BackgroundColor);
+ static QColor textColor = Utils::creatorColor(Utils::Theme::DStextColor);
+ static QColor backgroundColor = Utils::creatorColor(Utils::Theme::QmlDesigner_BackgroundColorDarker);
+ static QColor frameColor = Utils::creatorColor(Utils::Theme::QmlDesigner_BackgroundColor);
QFont font;
font.setBold(true);
@@ -405,9 +405,9 @@ QGraphicsItem *FormEditorAnnotationIcon::createCommentBubble(QRectF rect, const
QGraphicsItem *FormEditorAnnotationIcon::createTitleBubble(const QRectF &rect, const QString &text, QGraphicsItem *parent)
{
- static QColor textColor = Utils::creatorTheme()->color(Utils::Theme::DStextColor);
- static QColor backgroundColor = Utils::creatorTheme()->color(Utils::Theme::QmlDesigner_BackgroundColorDarker);
- static QColor frameColor = Utils::creatorTheme()->color(Utils::Theme::QmlDesigner_BackgroundColor);
+ static QColor textColor = Utils::creatorColor(Utils::Theme::DStextColor);
+ static QColor backgroundColor = Utils::creatorColor(Utils::Theme::QmlDesigner_BackgroundColorDarker);
+ static QColor frameColor = Utils::creatorColor(Utils::Theme::QmlDesigner_BackgroundColor);
QFont font;
font.setBold(true);
diff --git a/src/plugins/qmldesigner/components/formeditor/formeditorgraphicsview.cpp b/src/plugins/qmldesigner/components/formeditor/formeditorgraphicsview.cpp
index 9549ce9dd4..4bd0b08c0a 100644
--- a/src/plugins/qmldesigner/components/formeditor/formeditorgraphicsview.cpp
+++ b/src/plugins/qmldesigner/components/formeditor/formeditorgraphicsview.cpp
@@ -215,7 +215,7 @@ void FormEditorGraphicsView::drawBackground(QPainter *painter, const QRectF &rec
painter->fillRect(rectangle.intersected(rootItemRect()), backgroundBrush());
}
- QPen pen(Utils::creatorTheme()->color(Utils::Theme::QmlDesigner_FormEditorSelectionColor));
+ QPen pen(Utils::creatorColor(Utils::Theme::QmlDesigner_FormEditorSelectionColor));
pen.setStyle(Qt::DotLine);
pen.setWidth(1);
diff --git a/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp b/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp
index 12da85e2c4..b3df6b8fe7 100644
--- a/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp
+++ b/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp
@@ -48,23 +48,19 @@ void drawIcon(QPainter *painter,
int iconSize,
const QColor &penColor)
{
- static QFontDatabase a;
-
const QString fontName = "qtds_propertyIconFont.ttf";
- Q_ASSERT(a.hasFamily(fontName));
+ QTC_ASSERT(QFontDatabase::hasFamily(fontName), return);
- if (a.hasFamily(fontName)) {
- QFont font(fontName);
- font.setPixelSize(fontSize);
+ QFont font(fontName);
+ font.setPixelSize(fontSize);
- painter->save();
- painter->setPen(penColor);
- painter->setFont(font);
- painter->drawText(QRectF(x, y, iconSize, iconSize), iconSymbol);
+ painter->save();
+ painter->setPen(penColor);
+ painter->setFont(font);
+ painter->drawText(QRectF(x, y, iconSize, iconSize), iconSymbol);
- painter->restore();
- }
+ painter->restore();
}
FormEditorScene *FormEditorItem::scene() const {
@@ -309,7 +305,7 @@ void FormEditorItem::paintBoundingRect(QPainter *painter) const
pen.setJoinStyle(Qt::MiterJoin);
const QColor frameColor(0xaa, 0xaa, 0xaa);
- static const QColor selectionColor = Utils::creatorTheme()->color(
+ static const QColor selectionColor = Utils::creatorColor(
Utils::Theme::QmlDesigner_FormEditorSelectionColor);
if (scene()->showBoundingRects()) {
diff --git a/src/plugins/qmldesigner/components/formeditor/formeditortoolbutton.cpp b/src/plugins/qmldesigner/components/formeditor/formeditortoolbutton.cpp
index 555d0d90e3..f3aa96ada4 100644
--- a/src/plugins/qmldesigner/components/formeditor/formeditortoolbutton.cpp
+++ b/src/plugins/qmldesigner/components/formeditor/formeditortoolbutton.cpp
@@ -42,7 +42,7 @@ void FormEditorToolButton::paint(QPainter *painter, const QStyleOptionGraphicsIt
QRectF adjustedRect(size().width() - toolButtonSize, size().height() - toolButtonSize, toolButtonSize, toolButtonSize);
painter->setPen(Qt::NoPen);
- static QColor selectionColor = Utils::creatorTheme()->color(Utils::Theme::QmlDesigner_FormEditorSelectionColor);
+ static QColor selectionColor = Utils::creatorColor(Utils::Theme::QmlDesigner_FormEditorSelectionColor);
if (m_state == Hovered)
painter->setBrush(selectionColor.lighter(110));
diff --git a/src/plugins/qmldesigner/components/formeditor/selectionindicator.cpp b/src/plugins/qmldesigner/components/formeditor/selectionindicator.cpp
index 0f5b5f4438..45d26f831e 100644
--- a/src/plugins/qmldesigner/components/formeditor/selectionindicator.cpp
+++ b/src/plugins/qmldesigner/components/formeditor/selectionindicator.cpp
@@ -78,7 +78,7 @@ void SelectionIndicator::setItems(const QList<FormEditorItem*> &itemList)
{
clear();
- static QColor selectionColor = Utils::creatorTheme()->color(Utils::Theme::QmlDesigner_FormEditorSelectionColor);
+ static QColor selectionColor = Utils::creatorColor(Utils::Theme::QmlDesigner_FormEditorSelectionColor);
for (FormEditorItem *item : itemList) {
if (!item->qmlItemNode().isValid())
@@ -119,7 +119,7 @@ void SelectionIndicator::setItems(const QList<FormEditorItem*> &itemList)
m_annotationItem = nullptr;
}
- static QColor textColor = Utils::creatorTheme()->color(Utils::Theme::QmlDesigner_FormEditorForegroundColor);
+ static QColor textColor = Utils::creatorColor(Utils::Theme::QmlDesigner_FormEditorForegroundColor);
textItem->setDefaultTextColor(textColor);
QPolygonF labelPolygon = boundingRectInLayerItemSpaceForItem(selectedItem, m_layerItem.data());
diff --git a/src/plugins/qmldesigner/components/integration/designdocument.cpp b/src/plugins/qmldesigner/components/integration/designdocument.cpp
index 804ac076e6..aa2dfd3b28 100644
--- a/src/plugins/qmldesigner/components/integration/designdocument.cpp
+++ b/src/plugins/qmldesigner/components/integration/designdocument.cpp
@@ -150,7 +150,10 @@ bool DesignDocument::loadInFileComponent(const ModelNode &componentNode)
if (!componentNode.isRootNode()) {
//change to subcomponent model
- changeToInFileComponentModel(createComponentTextModifier(m_documentTextModifier.data(), rewriterView(), componentText, componentNode));
+ changeToInFileComponentModel(createComponentTextModifier(m_documentTextModifier.get(),
+ rewriterView(),
+ componentText,
+ componentNode));
}
return true;
@@ -281,11 +284,10 @@ void DesignDocument::moveNodesToPosition(const QList<ModelNode> &nodes, const st
parentProperty.reparentHere(pastedNode);
QmlVisualNode visualNode(pastedNode);
- if (!firstVisualNode.has_value() && visualNode.isValid()){
+ if (!firstVisualNode && visualNode) {
firstVisualNode = visualNode;
- translationVect = (position.has_value() && firstVisualNode.has_value())
- ? position.value() - firstVisualNode->position()
- : QVector3D();
+ translationVect = (position && firstVisualNode) ? *position - firstVisualNode->position()
+ : QVector3D();
}
visualNode.translate(translationVect);
}
@@ -377,9 +379,12 @@ void DesignDocument::loadDocument(QPlainTextEdit *edit)
m_documentTextModifier.reset(new BaseTextEditModifier(qobject_cast<TextEditor::TextEditorWidget *>(plainTextEdit())));
- connect(m_documentTextModifier.data(), &TextModifier::textChanged, this, &DesignDocument::updateQrcFiles);
+ connect(m_documentTextModifier.get(),
+ &TextModifier::textChanged,
+ this,
+ &DesignDocument::updateQrcFiles);
- m_rewriterView->setTextModifier(m_documentTextModifier.data());
+ m_rewriterView->setTextModifier(m_documentTextModifier.get());
m_inFileComponentTextModifier.reset();
@@ -399,7 +404,7 @@ void DesignDocument::changeToDocumentModel()
if (edit)
edit->document()->clearUndoRedoStacks();
- m_rewriterView->setTextModifier(m_documentTextModifier.data());
+ m_rewriterView->setTextModifier(m_documentTextModifier.get());
m_inFileComponentModel.reset();
m_inFileComponentTextModifier.reset();
@@ -432,7 +437,7 @@ bool DesignDocument::hasProject() const
void DesignDocument::setModified()
{
- if (!m_documentTextModifier.isNull())
+ if (m_documentTextModifier)
m_documentTextModifier->textDocument()->setModified(true);
}
@@ -448,7 +453,7 @@ void DesignDocument::changeToInFileComponentModel(ComponentTextModifier *textMod
m_inFileComponentModel = createInFileComponentModel();
- m_rewriterView->setTextModifier(m_inFileComponentTextModifier.data());
+ m_rewriterView->setTextModifier(m_inFileComponentTextModifier.get());
viewManager().attachRewriterView();
viewManager().attachViewsExceptRewriterAndComponetView();
@@ -675,7 +680,7 @@ void DesignDocument::selectAll()
RewriterView *DesignDocument::rewriterView() const
{
- return m_rewriterView.data();
+ return m_rewriterView.get();
}
void DesignDocument::setEditor(Core::IEditor *editor)
diff --git a/src/plugins/qmldesigner/components/integration/designdocument.h b/src/plugins/qmldesigner/components/integration/designdocument.h
index 52089d67c1..1f67ff4b30 100644
--- a/src/plugins/qmldesigner/components/integration/designdocument.h
+++ b/src/plugins/qmldesigner/components/integration/designdocument.h
@@ -16,9 +16,10 @@
#include <QObject>
#include <QString>
-
#include <QStackedWidget>
+#include <memory>
+
QT_BEGIN_NAMESPACE
class QPlainTextEdit;
QT_END_NAMESPACE
@@ -143,12 +144,12 @@ private: // variables
ModelPointer m_documentModel;
ModelPointer m_inFileComponentModel;
QPointer<Core::IEditor> m_textEditor;
- QScopedPointer<BaseTextEditModifier> m_documentTextModifier;
- QScopedPointer<ComponentTextModifier> m_inFileComponentTextModifier;
+ std::unique_ptr<BaseTextEditModifier> m_documentTextModifier;
+ std::unique_ptr<ComponentTextModifier> m_inFileComponentTextModifier;
#ifndef QDS_USE_PROJECTSTORAGE
- QScopedPointer<SubComponentManager> m_subComponentManager;
+ std::unique_ptr<SubComponentManager> m_subComponentManager;
#endif
- QScopedPointer<RewriterView> m_rewriterView;
+ std::unique_ptr<RewriterView> m_rewriterView;
bool m_documentLoaded;
ProjectExplorer::Target *m_currentTarget;
ProjectStorageDependencies m_projectStorageDependencies;
diff --git a/src/plugins/qmldesigner/components/integration/designdocumentview.cpp b/src/plugins/qmldesigner/components/integration/designdocumentview.cpp
index 6ef95bf4c4..d97b9ff06f 100644
--- a/src/plugins/qmldesigner/components/integration/designdocumentview.cpp
+++ b/src/plugins/qmldesigner/components/integration/designdocumentview.cpp
@@ -23,6 +23,8 @@
#include <utils/algorithm.h>
#include <utils/qtcassert.h>
+#include <memory>
+
namespace QmlDesigner {
DesignDocumentView::DesignDocumentView(ExternalDependenciesInterface &externalDependencies)
@@ -116,14 +118,14 @@ QString DesignDocumentView::toText() const
textEdit.setPlainText(imports + QStringLiteral("Item {\n}\n"));
NotIndentingTextEditModifier modifier(&textEdit);
- QScopedPointer<RewriterView> rewriterView(
- new RewriterView(externalDependencies(), RewriterView::Amend));
+ std::unique_ptr<RewriterView> rewriterView = std::make_unique<RewriterView>(externalDependencies(),
+ RewriterView::Amend);
rewriterView->setCheckSemanticErrors(false);
rewriterView->setPossibleImportsEnabled(false);
rewriterView->setTextModifier(&modifier);
- outputModel->setRewriterView(rewriterView.data());
+ outputModel->setRewriterView(rewriterView.get());
- ModelMerger merger(rewriterView.data());
+ ModelMerger merger(rewriterView.get());
merger.replaceModel(rootModelNode());
diff --git a/src/plugins/qmldesigner/components/itemlibrary/import3dcanvas.cpp b/src/plugins/qmldesigner/components/itemlibrary/import3dcanvas.cpp
new file mode 100644
index 0000000000..608bf42eb3
--- /dev/null
+++ b/src/plugins/qmldesigner/components/itemlibrary/import3dcanvas.cpp
@@ -0,0 +1,82 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "import3dcanvas.h"
+
+#include <QImage>
+#include <QLinearGradient>
+#include <QMouseEvent>
+#include <QPainter>
+
+namespace QmlDesigner {
+
+static QImage createGradientImage(int width, int height) {
+ QImage image(width, height, QImage::Format_ARGB32_Premultiplied);
+
+ QLinearGradient gradient(0, 0, 0, height);
+ gradient.setColorAt(0, QColor(0x999999));
+ gradient.setColorAt(1, QColor(0x222222));
+
+ QPainter painter(&image);
+ painter.fillRect(0, 0, width, height, gradient);
+
+ return image;
+}
+
+Import3dCanvas::Import3dCanvas(QWidget *parent)
+ : QWidget(parent)
+{
+}
+
+void Import3dCanvas::updateRenderImage(const QImage &img)
+{
+ m_image = img;
+ update();
+}
+
+void Import3dCanvas::paintEvent([[maybe_unused]] QPaintEvent *e)
+{
+ QWidget::paintEvent(e);
+
+ QPainter painter(this);
+
+ if (m_image.isNull()) {
+ QImage image = createGradientImage(width(), height());
+ painter.drawImage(rect(), image, QRect(0, 0, image.width(), image.height()));
+ } else {
+ painter.drawImage(rect(), m_image, QRect(0, 0, m_image.width(), m_image.height()));
+ }
+}
+
+void Import3dCanvas::resizeEvent(QResizeEvent *)
+{
+ emit requestImageUpdate();
+}
+
+void Import3dCanvas::mousePressEvent(QMouseEvent *e)
+{
+ if (e->buttons() == Qt::LeftButton)
+ m_dragPos = e->position();
+ else
+ m_dragPos = {};
+}
+
+void Import3dCanvas::mouseReleaseEvent(QMouseEvent *)
+{
+ m_dragPos = {};
+}
+
+void Import3dCanvas::mouseMoveEvent(QMouseEvent *e)
+{
+ if (m_dragPos.isNull())
+ return;
+
+ const QPointF curPos = e->position();
+ const QPointF delta = curPos - m_dragPos;
+
+ m_dragPos = curPos;
+
+ emit requestRotation(delta);
+}
+
+}
diff --git a/src/plugins/qmldesigner/components/itemlibrary/import3dcanvas.h b/src/plugins/qmldesigner/components/itemlibrary/import3dcanvas.h
new file mode 100644
index 0000000000..72fb19acff
--- /dev/null
+++ b/src/plugins/qmldesigner/components/itemlibrary/import3dcanvas.h
@@ -0,0 +1,37 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#pragma once
+
+#include <QEvent>
+#include <QImage>
+#include <QPointF>
+#include <QWidget>
+
+namespace QmlDesigner {
+
+class Import3dCanvas : public QWidget
+{
+ Q_OBJECT
+
+public:
+ Import3dCanvas(QWidget *parent);
+
+ void updateRenderImage(const QImage &img);
+
+signals:
+ void requestImageUpdate();
+ void requestRotation(const QPointF &delta);
+
+protected:
+ void paintEvent(QPaintEvent *e) override;
+ void resizeEvent(QResizeEvent *e) override;
+ void mousePressEvent(QMouseEvent *e) override;
+ void mouseReleaseEvent(QMouseEvent *e) override;
+ void mouseMoveEvent(QMouseEvent *e) override;
+
+private:
+ QImage m_image;
+ QPointF m_dragPos;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/itemlibrary/import3dconnectionmanager.cpp b/src/plugins/qmldesigner/components/itemlibrary/import3dconnectionmanager.cpp
new file mode 100644
index 0000000000..4c455e3c6d
--- /dev/null
+++ b/src/plugins/qmldesigner/components/itemlibrary/import3dconnectionmanager.cpp
@@ -0,0 +1,47 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "import3dconnectionmanager.h"
+
+#include <imagecontainer.h>
+#include <puppettocreatorcommand.h>
+
+#include <QImage>
+
+namespace QmlDesigner {
+
+Import3dConnectionManager::Import3dConnectionManager()
+{
+ connections().clear(); // Remove default interactive puppets
+ connections().emplace_back("Import 3D", "import3dmode");
+}
+
+void Import3dConnectionManager::setPreviewImageCallback(ImageCallback callback)
+{
+ m_previewImageCallback = std::move(callback);
+}
+
+void Import3dConnectionManager::dispatchCommand(const QVariant &command,
+ ConnectionManagerInterface::Connection &connection)
+{
+ static const int commandType = QMetaType::type("PuppetToCreatorCommand");
+
+ if (command.typeId() == commandType) {
+ auto cmd = command.value<PuppetToCreatorCommand>();
+ switch (cmd.type()) {
+ case PuppetToCreatorCommand::Import3DPreviewImage: {
+ ImageContainer container = qvariant_cast<ImageContainer>(cmd.data());
+ QImage image = container.image();
+ if (!image.isNull())
+ m_previewImageCallback(image);
+ break;
+ }
+ default:
+ break;
+ }
+ } else {
+ InteractiveConnectionManager::dispatchCommand(command, connection);
+ }
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/itemlibrary/import3dconnectionmanager.h b/src/plugins/qmldesigner/components/itemlibrary/import3dconnectionmanager.h
new file mode 100644
index 0000000000..458d612ad2
--- /dev/null
+++ b/src/plugins/qmldesigner/components/itemlibrary/import3dconnectionmanager.h
@@ -0,0 +1,28 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "interactiveconnectionmanager.h"
+
+QT_FORWARD_DECLARE_CLASS(QImage)
+
+namespace QmlDesigner {
+
+class Import3dConnectionManager : public InteractiveConnectionManager
+{
+public:
+ using ImageCallback = std::function<void(const QImage &)>;
+
+ Import3dConnectionManager();
+
+ void setPreviewImageCallback(ImageCallback callback);
+
+protected:
+ void dispatchCommand(const QVariant &command, Connection &connection) override;
+
+private:
+ ImageCallback m_previewImageCallback;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.cpp
index 2c69072602..271410233b 100644
--- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.cpp
+++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.cpp
@@ -4,15 +4,19 @@
#include "itemlibraryassetimportdialog.h"
#include "ui_itemlibraryassetimportdialog.h"
+#include "import3dcanvas.h"
+#include "import3dconnectionmanager.h"
+
#include <model.h>
#include <model/modelutils.h>
+#include <nodeinstanceview.h>
#include <nodemetainfo.h>
#include <qmldesignerconstants.h>
#include <qmldesignerplugin.h>
+#include <rewriterview.h>
#include <variantproperty.h>
#include <theme.h>
-#include <utils/filepath.h>
#include <utils/outputformatter.h>
#include <projectexplorer/project.h>
@@ -74,9 +78,10 @@ ItemLibraryAssetImportDialog::ItemLibraryAssetImportDialog(
const QStringList &importFiles, const QString &defaulTargetDirectory,
const QVariantMap &supportedExts, const QVariantMap &supportedOpts,
const QJsonObject &defaultOpts, const QSet<QString> &preselectedFilesForOverwrite,
- QWidget *parent)
+ AbstractView *view, QWidget *parent)
: QDialog(parent)
, ui(new Ui::ItemLibraryAssetImportDialog)
+ , m_view(view)
, m_importer(this)
, m_preselectedFilesForOverwrite(preselectedFilesForOverwrite)
{
@@ -107,17 +112,15 @@ ItemLibraryAssetImportDialog::ItemLibraryAssetImportDialog(
if (m_quick3DFiles.size() != importFiles.size())
addWarning("Cannot import 3D and other assets simultaneously. Skipping non-3D assets.");
- ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Import"));
- connect(ui->buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked,
- this, &ItemLibraryAssetImportDialog::onImport);
+ connect(ui->importButton, &QPushButton::clicked, this, &ItemLibraryAssetImportDialog::onImport);
- ui->buttonBox->button(QDialogButtonBox::Ok)->setDefault(true);
+ ui->importButton->setDefault(true);
ui->advancedSettingsButton->setStyleSheet(
"QPushButton#advancedSettingsButton {background-color: transparent}");
ui->advancedSettingsButton->setStyleSheet(
QString("QPushButton { border: none; color :%1 }").arg(
- Utils::creatorTheme()->color(Utils::Theme::QmlDesigner_HighlightColor).name()));
+ Utils::creatorColor(Utils::Theme::QmlDesigner_HighlightColor).name()));
QStringList importPaths;
auto doc = QmlDesignerPlugin::instance()->currentDesignDocument();
@@ -210,10 +213,14 @@ ItemLibraryAssetImportDialog::ItemLibraryAssetImportDialog(
ui->tabWidget->setCurrentIndex(0);
}
- connect(ui->buttonBox->button(QDialogButtonBox::Close), &QPushButton::clicked,
+ connect(ui->closeButton, &QPushButton::clicked,
this, &ItemLibraryAssetImportDialog::onClose);
connect(ui->tabWidget, &QTabWidget::currentChanged,
this, &ItemLibraryAssetImportDialog::updateUi);
+ connect(canvas(), &Import3dCanvas::requestImageUpdate,
+ this, &ItemLibraryAssetImportDialog::onRequestImageUpdate);
+ connect(canvas(), &Import3dCanvas::requestRotation,
+ this, &ItemLibraryAssetImportDialog::onRequestRotation);
connect(&m_importer, &ItemLibraryAssetImporter::errorReported,
this, &ItemLibraryAssetImportDialog::addError);
@@ -227,23 +234,35 @@ ItemLibraryAssetImportDialog::ItemLibraryAssetImportDialog(
this, &ItemLibraryAssetImportDialog::onImportFinished);
connect(&m_importer, &ItemLibraryAssetImporter::progressChanged,
this, &ItemLibraryAssetImportDialog::setImportProgress);
-
- addInfo(tr("Select import options and press \"Import\" to import the following files:"));
- for (const auto &file : std::as_const(m_quick3DFiles))
- addInfo(file);
+ connect(&m_importer, &ItemLibraryAssetImporter::importReadyForPreview,
+ this, &ItemLibraryAssetImportDialog::onImportReadyForPreview);
connect(ui->advancedSettingsButton, &QPushButton::clicked,
this, &ItemLibraryAssetImportDialog::toggleAdvanced);
QTimer::singleShot(0, this, &ItemLibraryAssetImportDialog::updateUi);
+
+ if (m_quick3DFiles.size() != 1) {
+ addInfo(tr("Select import options and press \"Import\" to import the following files:"));
+ } else {
+ addInfo(tr("Importing:"));
+ QTimer::singleShot(0, this, &ItemLibraryAssetImportDialog::onImport);
+ }
+
+ for (const auto &file : std::as_const(m_quick3DFiles))
+ addInfo(file);
+
+ updateImportButtonState();
}
ItemLibraryAssetImportDialog::~ItemLibraryAssetImportDialog()
{
+ cleanupPreviewPuppet();
delete ui;
}
-void ItemLibraryAssetImportDialog::updateImport(const ModelNode &updateNode,
+void ItemLibraryAssetImportDialog::updateImport(AbstractView *view,
+ const ModelNode &updateNode,
const QVariantMap &supportedExts,
const QVariantMap &supportedOpts)
{
@@ -332,7 +351,8 @@ void ItemLibraryAssetImportDialog::updateImport(const ModelNode &updateNode,
{sourceInfo.absoluteFilePath()},
node.model()->fileUrl().toLocalFile(),
supportedExts, supportedOpts, options,
- preselectedFiles, Core::ICore::dialogParent());
+ preselectedFiles, view,
+ Core::ICore::dialogParent());
importDlg->show();
} else {
@@ -481,6 +501,7 @@ QGridLayout *ItemLibraryAssetImportDialog::createOptionsGrid(
QJsonValue value(optCheck->isChecked());
optObj.insert("value", value);
m_importOptions[optionsIndex].insert(optKey, optObj);
+ updateImportButtonState();
});
} else {
// Simple options also exist in advanced, so don't connect simple controls directly
@@ -488,13 +509,17 @@ QGridLayout *ItemLibraryAssetImportDialog::createOptionsGrid(
auto *advCheck = qobject_cast<QCheckBox *>(
m_labelToControlWidgetMaps[optionsIndex].value(optKey));
if (advCheck) {
- QObject::connect(optCheck, &QCheckBox::toggled, this, [optCheck, advCheck]() {
- if (advCheck->isChecked() != optCheck->isChecked())
+ QObject::connect(optCheck, &QCheckBox::toggled, this, [this, optCheck, advCheck]() {
+ if (advCheck->isChecked() != optCheck->isChecked()) {
advCheck->setChecked(optCheck->isChecked());
+ updateImportButtonState();
+ }
});
- QObject::connect(advCheck, &QCheckBox::toggled, this, [optCheck, advCheck]() {
- if (advCheck->isChecked() != optCheck->isChecked())
+ QObject::connect(advCheck, &QCheckBox::toggled, this, [this, optCheck, advCheck]() {
+ if (advCheck->isChecked() != optCheck->isChecked()) {
optCheck->setChecked(advCheck->isChecked());
+ updateImportButtonState();
+ }
});
}
}
@@ -530,6 +555,7 @@ QGridLayout *ItemLibraryAssetImportDialog::createOptionsGrid(
QJsonValue value(optSpin->value());
optObj.insert("value", value);
m_importOptions[optionsIndex].insert(optKey, optObj);
+ updateImportButtonState();
});
} else {
auto *advSpin = qobject_cast<QDoubleSpinBox *>(
@@ -537,14 +563,18 @@ QGridLayout *ItemLibraryAssetImportDialog::createOptionsGrid(
if (advSpin) {
// Connect corresponding advanced control
QObject::connect(optSpin, &QDoubleSpinBox::valueChanged,
- this, [optSpin, advSpin] {
- if (advSpin->value() != optSpin->value())
+ this, [this, optSpin, advSpin] {
+ if (advSpin->value() != optSpin->value()) {
advSpin->setValue(optSpin->value());
+ updateImportButtonState();
+ }
});
QObject::connect(advSpin, &QDoubleSpinBox::valueChanged,
- this, [optSpin, advSpin] {
- if (advSpin->value() != optSpin->value())
+ this, [this, optSpin, advSpin] {
+ if (advSpin->value() != optSpin->value()) {
optSpin->setValue(advSpin->value());
+ updateImportButtonState();
+ }
});
}
}
@@ -829,6 +859,145 @@ bool ItemLibraryAssetImportDialog::isHiddenOption(const QString &id)
return hiddenOptions.contains(id);
}
+void ItemLibraryAssetImportDialog::startPreview()
+{
+ cleanupPreviewPuppet();
+
+ // Preview is done via custom QML file added into the temporary folder of the preview
+ QString previewQml =
+R"(
+import QtQuick
+import QtQuick3D
+
+Rectangle {
+ id: root
+ width: %1
+ height: %2
+
+ property alias sceneNode: sceneNode
+ property alias view3d: view3d
+ property string extents
+ property string sceneModelName: "%3"
+
+ gradient: Gradient {
+ GradientStop { position: 1.0; color: "#222222" }
+ GradientStop { position: 0.0; color: "#999999" }
+ }
+
+ View3D {
+ id: view3d
+ anchors.fill: parent
+ camera: viewCamera
+
+ environment: SceneEnvironment {
+ antialiasingMode: SceneEnvironment.MSAA
+ antialiasingQuality: SceneEnvironment.VeryHigh
+ }
+
+ PerspectiveCamera {
+ id: viewCamera
+ x: 600
+ y: 600
+ z: 600
+ eulerRotation.x: -45
+ eulerRotation.y: -45
+ clipFar: 100000
+ clipNear: 10
+ }
+
+ DirectionalLight {
+ rotation: viewCamera.rotation
+ }
+
+ Node {
+ id: sceneNode
+ }
+ }
+
+ Text {
+ anchors.bottom: parent.bottom
+ anchors.left: parent.left
+ color: "white"
+ text: root.extents
+ font.pixelSize: 14
+ }
+}
+)";
+
+ QSize size = canvas()->size();
+ previewQml = previewQml.arg(size.width()).arg(size.height()).arg(m_previewCompName);
+
+ m_previewFile.writeFileContents(previewQml.toUtf8());
+
+ if (!m_previewFile.exists()) {
+ addWarning("Failed to write preview file.");
+ return;
+ }
+
+ m_connectionManager = new Import3dConnectionManager;
+ m_rewriterView = new RewriterView{m_view->externalDependencies(), RewriterView::Amend};
+ m_nodeInstanceView = new NodeInstanceView{*m_connectionManager, m_view->externalDependencies()};
+
+#ifdef QDS_USE_PROJECTSTORAGE
+ m_model = m_view->model()->createModel("Item");
+#else
+ m_model = QmlDesigner::Model::create("QtQuick/Item", 2, 1);
+ m_model->setFileUrl(m_previewFile.toUrl());
+#endif
+
+ auto textDocument = std::make_unique<QTextDocument>(previewQml);
+ auto modifier = std::make_unique<NotIndentingTextEditModifier>(textDocument.get(),
+ QTextCursor{textDocument.get()});
+ m_rewriterView->setTextModifier(modifier.get());
+ m_model->setRewriterView(m_rewriterView);
+
+ if (!m_rewriterView->errors().isEmpty()) {
+ addWarning("Preview scene creation failed.");
+ cleanupPreviewPuppet();
+ return;
+ }
+
+ m_nodeInstanceView->setTarget(m_view->nodeInstanceView()->target());
+
+ auto previewImageCallback = [this](const QImage &image) {
+ canvas()->updateRenderImage(image);
+ };
+
+ auto crashCallback = [&] {
+ addWarning("Preview process crashed.");
+ cleanupPreviewPuppet();
+ };
+
+ m_connectionManager->setPreviewImageCallback(std::move(previewImageCallback));
+ m_nodeInstanceView->setCrashCallback(std::move(crashCallback));
+
+ m_model->setNodeInstanceView(m_nodeInstanceView);
+}
+
+void ItemLibraryAssetImportDialog::cleanupPreviewPuppet()
+{
+ if (m_model) {
+ m_model->setNodeInstanceView({});
+ m_model->setRewriterView({});
+ m_model.reset();
+ }
+
+ if (m_nodeInstanceView)
+ m_nodeInstanceView->setCrashCallback({});
+
+ if (m_connectionManager)
+ m_connectionManager->setPreviewImageCallback({});
+
+ delete m_rewriterView;
+ delete m_nodeInstanceView;
+ delete m_connectionManager;
+}
+
+Import3dCanvas *ItemLibraryAssetImportDialog::canvas()
+{
+ return ui->import3dcanvas;
+}
+
void ItemLibraryAssetImportDialog::resizeEvent(QResizeEvent *event)
{
m_dialogHeight = event->size().height();
@@ -837,8 +1006,13 @@ void ItemLibraryAssetImportDialog::resizeEvent(QResizeEvent *event)
void ItemLibraryAssetImportDialog::setCloseButtonState(bool importing)
{
- ui->buttonBox->button(QDialogButtonBox::Close)->setEnabled(true);
- ui->buttonBox->button(QDialogButtonBox::Close)->setText(importing ? tr("Cancel") : tr("Close"));
+ ui->closeButton->setEnabled(true);
+ ui->closeButton->setText(importing ? tr("Cancel") : tr("Close"));
+}
+
+void ItemLibraryAssetImportDialog::updateImportButtonState()
+{
+ ui->importButton->setText(m_previewOptions == m_importOptions ? tr("Accept") : tr("Import"));
}
void ItemLibraryAssetImportDialog::addError(const QString &error, const QString &srcPath)
@@ -860,14 +1034,25 @@ void ItemLibraryAssetImportDialog::addInfo(const QString &info, const QString &s
void ItemLibraryAssetImportDialog::onImport()
{
- ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
+ ui->importButton->setEnabled(false);
+
+ if (!m_previewCompName.isEmpty() && m_previewOptions == m_importOptions) {
+ cleanupPreviewPuppet();
+ m_importer.finalizeQuick3DImport();
+ return;
+ }
+
setCloseButtonState(true);
ui->progressBar->setValue(0);
if (!m_quick3DFiles.isEmpty()) {
- m_importer.importQuick3D(m_quick3DFiles, m_quick3DImportPath,
- m_importOptions, m_extToImportOptionsMap,
- m_preselectedFilesForOverwrite);
+ if (!m_previewCompName.isEmpty()) {
+ m_importer.reImportQuick3D(m_previewCompName, m_importOptions);
+ } else {
+ m_importer.importQuick3D(m_quick3DFiles, m_quick3DImportPath,
+ m_importOptions, m_extToImportOptionsMap,
+ m_preselectedFilesForOverwrite);
+ }
}
}
@@ -881,10 +1066,37 @@ void ItemLibraryAssetImportDialog::setImportProgress(int value, const QString &t
ui->progressBar->setValue(value);
}
+void ItemLibraryAssetImportDialog::onImportReadyForPreview(const QString &path, const QString &compName)
+{
+ addInfo(tr("Import is ready for preview."));
+ if (m_previewCompName.isEmpty())
+ addInfo(tr("Click \"Accept\" to finish the import or adjust options and click \"Import\" to import again."));
+
+ m_previewFile = Utils::FilePath::fromString(path).pathAppended(m_importer.previewFileName());
+ m_previewCompName = compName;
+ m_previewOptions = m_importOptions;
+ QTimer::singleShot(0, this, &ItemLibraryAssetImportDialog::startPreview);
+
+ ui->importButton->setEnabled(true);
+ updateImportButtonState();
+}
+
+void ItemLibraryAssetImportDialog::onRequestImageUpdate()
+{
+ if (m_nodeInstanceView)
+ m_nodeInstanceView->view3DAction(View3DActionType::Import3dUpdatePreviewImage, canvas()->size());
+}
+
+void ItemLibraryAssetImportDialog::onRequestRotation(const QPointF &delta)
+{
+ if (m_nodeInstanceView)
+ m_nodeInstanceView->view3DAction(View3DActionType::Import3dRotatePreviewModel, delta);
+}
+
void ItemLibraryAssetImportDialog::onImportNearlyFinished()
{
// Canceling import is no longer doable
- ui->buttonBox->button(QDialogButtonBox::Close)->setEnabled(false);
+ ui->closeButton->setEnabled(false);
}
void ItemLibraryAssetImportDialog::onImportFinished()
@@ -894,19 +1106,28 @@ void ItemLibraryAssetImportDialog::onImportFinished()
QString interruptStr = tr("Import interrupted.");
addError(interruptStr);
setImportProgress(0, interruptStr);
+ if (m_explicitClose)
+ QTimer::singleShot(1000, this, &ItemLibraryAssetImportDialog::doClose);
} else {
QString doneStr = tr("Import done.");
addInfo(doneStr);
setImportProgress(100, doneStr);
if (m_closeOnFinish) {
// Add small delay to allow user to visually confirm import finishing
- QTimer::singleShot(1000, this, &ItemLibraryAssetImportDialog::onClose);
+ QTimer::singleShot(1000, this, &ItemLibraryAssetImportDialog::doClose);
}
}
}
void ItemLibraryAssetImportDialog::onClose()
{
+ ui->importButton->setEnabled(false);
+ m_explicitClose = true;
+ doClose();
+}
+
+void ItemLibraryAssetImportDialog::doClose()
+{
if (m_importer.isImporting()) {
addInfo(tr("Canceling import."));
m_importer.cancelImport();
diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.h b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.h
index c5da478232..e7c4933056 100644
--- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.h
+++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.h
@@ -3,14 +3,19 @@
#pragma once
#include "itemlibraryassetimporter.h"
-#include "modelnode.h"
+
+#include <modelnode.h>
+
+#include <utils/filepath.h>
#include <QDialog>
#include <QJsonObject>
+#include <QPointer>
#include <QSet>
QT_BEGIN_NAMESPACE
class QGridLayout;
+class QPushButton;
QT_END_NAMESPACE
namespace Utils {
@@ -19,6 +24,10 @@ class OutputFormatter;
namespace QmlDesigner {
class ItemLibraryAssetImporter;
+class Import3dCanvas;
+class Import3dConnectionManager;
+class NodeInstanceView;
+class RewriterView;
namespace Ui {
class ItemLibraryAssetImportDialog;
@@ -35,10 +44,12 @@ public:
const QVariantMap &supportedOpts,
const QJsonObject &defaultOpts,
const QSet<QString> &preselectedFilesForOverwrite,
+ AbstractView *view,
QWidget *parent = nullptr);
~ItemLibraryAssetImportDialog();
- static void updateImport(const ModelNode &updateNode,
+ static void updateImport(AbstractView *view,
+ const ModelNode &updateNode,
const QVariantMap &supportedExts,
const QVariantMap &supportedOpts);
@@ -52,12 +63,17 @@ private slots:
private:
void setCloseButtonState(bool importing);
+ void updateImportButtonState();
void onImport();
void setImportProgress(int value, const QString &text);
+ void onImportReadyForPreview(const QString &path, const QString &compName);
+ void onRequestImageUpdate();
+ void onRequestRotation(const QPointF &delta);
void onImportNearlyFinished();
void onImportFinished();
void onClose();
+ void doClose();
void toggleAdvanced();
void createTab(const QString &tabLabel, int optionsIndex, const QJsonObject &groups);
@@ -69,8 +85,19 @@ private:
bool isSimpleOption(const QString &id);
bool isHiddenOption(const QString &id);
+ void startPreview();
+ void cleanupPreviewPuppet();
+ Import3dCanvas *canvas();
+
Ui::ItemLibraryAssetImportDialog *ui = nullptr;
Utils::OutputFormatter *m_outputFormatter = nullptr;
+ QPointer<Import3dConnectionManager> m_connectionManager;
+ QPointer<NodeInstanceView> m_nodeInstanceView;
+ QPointer<RewriterView> m_rewriterView;
+ QPointer<AbstractView> m_view;
+ ModelPointer m_model;
+ Utils::FilePath m_previewFile;
+ QString m_previewCompName;
struct OptionsData
{
@@ -83,6 +110,7 @@ private:
QString m_quick3DImportPath;
ItemLibraryAssetImporter m_importer;
QVector<QJsonObject> m_importOptions;
+ QVector<QJsonObject> m_previewOptions;
QHash<QString, int> m_extToImportOptionsMap;
QSet<QString> m_preselectedFilesForOverwrite;
bool m_closeOnFinish = true;
@@ -91,5 +119,6 @@ private:
OptionsData m_advancedData;
bool m_advancedMode = false;
int m_dialogHeight = 350;
+ bool m_explicitClose = false;
};
}
diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.ui b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.ui
index 081bc36a3d..e0b9d925fc 100644
--- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.ui
+++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.ui
@@ -6,15 +6,15 @@
<rect>
<x>0</x>
<y>0</y>
- <width>630</width>
+ <width>1100</width>
<height>350</height>
</rect>
</property>
<property name="windowTitle">
<string>Asset Import</string>
</property>
- <layout class="QFormLayout" name="formLayout">
- <item row="0" column="0" colspan="2">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTabWidget" name="tabWidget">
@@ -24,6 +24,12 @@
<verstretch>2</verstretch>
</sizepolicy>
</property>
+ <property name="minimumSize">
+ <size>
+ <width>550</width>
+ <height>0</height>
+ </size>
+ </property>
<property name="currentIndex">
<number>0</number>
</property>
@@ -98,6 +104,12 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
<property name="text">
<string/>
</property>
@@ -111,16 +123,64 @@
</widget>
</item>
<item>
- <widget class="QDialogButtonBox" name="buttonBox">
- <property name="standardButtons">
- <set>QDialogButtonBox::Close|QDialogButtonBox::Ok</set>
- </property>
- </widget>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <spacer name="horizontalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="closeButton">
+ <property name="text">
+ <string>Close</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="importButton">
+ <property name="text">
+ <string>Import</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
</item>
</layout>
</item>
+ <item>
+ <widget class="Import3dCanvas" name="import3dcanvas" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>1</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>300</width>
+ <height>300</height>
+ </size>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
+ <customwidgets>
+ <customwidget>
+ <class>Import3dCanvas</class>
+ <extends>QWidget</extends>
+ <header>import3dcanvas.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
<resources/>
<connections/>
</ui>
diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp
index ed1f8041e9..010d00a970 100644
--- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp
+++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp
@@ -21,6 +21,7 @@
#include <utils/algorithm.h>
#include <utils/async.h>
+#include <utils/filepath.h>
#include <utils/qtcassert.h>
#include <QApplication>
@@ -58,8 +59,6 @@ void ItemLibraryAssetImporter::importQuick3D(const QStringList &inputFiles,
const QHash<QString, int> &extToImportOptionsMap,
const QSet<QString> &preselectedFilesForOverwrite)
{
- if (m_isImporting)
- cancelImport();
reset();
m_isImporting = true;
@@ -92,6 +91,53 @@ void ItemLibraryAssetImporter::importQuick3D(const QStringList &inputFiles,
}
}
+void ItemLibraryAssetImporter::reImportQuick3D(const QString &assetName,
+ const QVector<QJsonObject> &options)
+{
+ if (!assetName.isEmpty() && !m_parseData.contains(assetName)) {
+ addError(tr("Attempted to reimport non-existing asset: %1").arg(assetName));
+ return;
+ }
+
+ ParseData &pd = m_parseData[assetName];
+ // Change outDir just in case reimport generates different files
+ QDir oldDir = pd.outDir;
+ QString assetFolder = generateAssetFolderName(pd.assetName);
+ pd.outDir.cdUp();
+ pd.outDir.mkpath(assetFolder);
+
+ if (!pd.outDir.cd(assetFolder)) {
+ addError(tr("Could not access temporary asset directory: \"%1\".")
+ .arg(pd.outDir.filePath(assetFolder)));
+ return;
+ }
+
+ if (oldDir.absolutePath().contains(tempDirNameBase()))
+ oldDir.removeRecursively();
+
+ m_isImporting = false;
+ m_cancelled = false;
+
+ m_puppetProcess.reset();
+ m_requiredImports.clear();
+ m_currentImportId = 0;
+ m_puppetQueue.clear();
+
+ for (ParseData &pd : m_parseData)
+ pd.importId = -1;
+
+ pd.options = options[pd.optionsIndex];
+ pd.importId = 1;
+
+ m_importFiles.remove(assetName);
+
+ m_importIdToAssetNameMap.clear();
+ m_importIdToAssetNameMap[pd.importId] = assetName;
+
+ m_puppetQueue.append(pd.importId);
+ startNextImportProcess();
+}
+
bool ItemLibraryAssetImporter::isImporting() const
{
return m_isImporting;
@@ -104,19 +150,19 @@ void ItemLibraryAssetImporter::cancelImport()
notifyFinished();
}
-void ItemLibraryAssetImporter::addError(const QString &errMsg, const QString &srcPath) const
+void ItemLibraryAssetImporter::addError(const QString &errMsg, const QString &srcPath)
{
qCDebug(importerLog) << "Error: "<< errMsg << srcPath;
emit errorReported(errMsg, srcPath);
}
-void ItemLibraryAssetImporter::addWarning(const QString &warningMsg, const QString &srcPath) const
+void ItemLibraryAssetImporter::addWarning(const QString &warningMsg, const QString &srcPath)
{
qCDebug(importerLog) << "Warning: " << warningMsg << srcPath;
emit warningReported(warningMsg, srcPath);
}
-void ItemLibraryAssetImporter::addInfo(const QString &infoMsg, const QString &srcPath) const
+void ItemLibraryAssetImporter::addInfo(const QString &infoMsg, const QString &srcPath)
{
qCDebug(importerLog) << "Info: " << infoMsg << srcPath;
emit infoReported(infoMsg, srcPath);
@@ -127,8 +173,8 @@ void ItemLibraryAssetImporter::importProcessFinished([[maybe_unused]] int exitCo
{
m_puppetProcess.reset();
- if (m_parseData.contains(m_currentImportId)) {
- const ParseData &pd = m_parseData[m_currentImportId];
+ if (m_importIdToAssetNameMap.contains(m_currentImportId)) {
+ const ParseData &pd = m_parseData[m_importIdToAssetNameMap[m_currentImportId]];
QString errStr;
if (exitStatus == QProcess::ExitStatus::CrashExit) {
errStr = tr("Import process crashed.");
@@ -151,11 +197,12 @@ void ItemLibraryAssetImporter::importProcessFinished([[maybe_unused]] int exitCo
addError(tr("Asset import process failed: \"%1\".")
.arg(pd.sourceInfo.absoluteFilePath()));
addError(errStr);
- m_parseData.remove(m_currentImportId);
+ m_parseData.remove(m_importIdToAssetNameMap[m_currentImportId]);
+ m_importIdToAssetNameMap.remove(m_currentImportId);
}
}
- int finishedCount = m_parseData.size() - m_puppetQueue.size();
+ int finishedCount = m_importIdToAssetNameMap.size() - m_puppetQueue.size();
if (!m_puppetQueue.isEmpty())
startNextImportProcess();
@@ -163,7 +210,7 @@ void ItemLibraryAssetImporter::importProcessFinished([[maybe_unused]] int exitCo
notifyProgress(100);
QTimer::singleShot(0, this, &ItemLibraryAssetImporter::postImport);
} else {
- notifyProgress(int(100. * (double(finishedCount) / double(m_parseData.size()))));
+ notifyProgress(int(100. * (double(finishedCount) / double(m_importIdToAssetNameMap.size()))));
}
}
@@ -179,7 +226,7 @@ void ItemLibraryAssetImporter::reset()
m_cancelled = false;
delete m_tempDir;
- m_tempDir = new QTemporaryDir;
+ m_tempDir = new QTemporaryDir(QDir::tempPath() + tempDirNameBase());
m_importFiles.clear();
m_overwrittenImports.clear();
m_puppetProcess.reset();
@@ -187,6 +234,7 @@ void ItemLibraryAssetImporter::reset()
m_requiredImports.clear();
m_currentImportId = 0;
m_puppetQueue.clear();
+ m_importIdToAssetNameMap.clear();
}
void ItemLibraryAssetImporter::parseFiles(const QStringList &filePaths,
@@ -208,9 +256,11 @@ void ItemLibraryAssetImporter::parseFiles(const QStringList &filePaths,
int index = extToImportOptionsMap.value(QFileInfo(file).suffix());
ParseData pd;
pd.options = options[index];
+ pd.optionsIndex = index;
if (preParseQuick3DAsset(file, pd, preselectedFilesForOverwrite)) {
pd.importId = ++m_importIdCounter;
- m_parseData.insert(pd.importId, pd);
+ m_importIdToAssetNameMap[pd.importId] = pd.assetName;
+ m_parseData.insert(pd.assetName, pd);
}
notifyProgress(qRound(++count * quota), progressTitle);
}
@@ -239,7 +289,7 @@ bool ItemLibraryAssetImporter::preParseQuick3DAsset(const QString &file, ParseDa
pd.targetDirPath = pd.targetDir.filePath(pd.assetName);
- if (pd.outDir.exists(pd.assetName)) {
+ if (m_parseData.contains(pd.assetName)) {
addWarning(tr("Skipped import of duplicate asset: \"%1\".").arg(pd.assetName));
return false;
}
@@ -288,11 +338,12 @@ bool ItemLibraryAssetImporter::preParseQuick3DAsset(const QString &file, ParseDa
}
}
- pd.outDir.mkpath(pd.assetName);
+ QString assetFolder = generateAssetFolderName(pd.assetName);
+ pd.outDir.mkpath(assetFolder);
- if (!pd.outDir.cd(pd.assetName)) {
+ if (!pd.outDir.cd(assetFolder)) {
addError(tr("Could not access temporary asset directory: \"%1\".")
- .arg(pd.outDir.filePath(pd.assetName)));
+ .arg(pd.outDir.filePath(assetFolder)));
return false;
}
return true;
@@ -425,7 +476,7 @@ void ItemLibraryAssetImporter::postParseQuick3DAsset(ParseData &pd)
// Copy the original asset into a subdirectory
assetFiles.insert(sourcePath, sourceSceneTargetFilePath(pd));
- m_importFiles.insert(assetFiles);
+ m_importFiles.insert(pd.assetName, assetFiles);
}
void ItemLibraryAssetImporter::copyImportedFiles()
@@ -500,6 +551,12 @@ void ItemLibraryAssetImporter::keepUiAlive() const
QApplication::processEvents();
}
+QString ItemLibraryAssetImporter::generateAssetFolderName(const QString &assetName) const
+{
+ static int counter = 0;
+ return assetName + "_QDS_" + QString::number(counter++);
+}
+
ItemLibraryAssetImporter::OverwriteResult ItemLibraryAssetImporter::confirmAssetOverwrite(const QString &assetName)
{
const QString title = tr("Overwrite Existing Asset?");
@@ -534,7 +591,8 @@ void ItemLibraryAssetImporter::startNextImportProcess()
if (model && view) {
bool done = false;
while (!m_puppetQueue.isEmpty() && !done) {
- const ParseData pd = m_parseData.value(m_puppetQueue.takeLast());
+ const ParseData pd = m_parseData.value(
+ m_importIdToAssetNameMap.value(m_puppetQueue.takeLast()));
QStringList puppetArgs;
QJsonDocument optDoc(pd.options);
@@ -557,7 +615,8 @@ void ItemLibraryAssetImporter::startNextImportProcess()
} else {
addError(tr("Failed to start import 3D asset process."),
pd.sourceInfo.absoluteFilePath());
- m_parseData.remove(pd.importId);
+ const QString assetName = m_importIdToAssetNameMap.take(pd.importId);
+ m_parseData.remove(assetName);
m_puppetProcess.reset();
}
}
@@ -573,8 +632,16 @@ void ItemLibraryAssetImporter::postImport()
postParseQuick3DAsset(pd);
}
- if (!isCancelled())
- finalizeQuick3DImport();
+ if (!isCancelled()) {
+ // TODO: Currently we only support import preview for single imports
+ if (m_parseData.size() != 1) {
+ finalizeQuick3DImport();
+ } else {
+ const ParseData &pd = m_parseData[m_parseData.keys().first()];
+ const QString importedComponentName = pd.assetName;
+ emit importReadyForPreview(pd.outDir.absolutePath(), importedComponentName);
+ }
+ }
}
void ItemLibraryAssetImporter::finalizeQuick3DImport()
diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.h b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.h
index 8ad7b5a2de..abe9690951 100644
--- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.h
+++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.h
@@ -2,8 +2,6 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
-#include "import.h"
-
#include <qprocessuniqueptr.h>
#include <QSet>
@@ -35,20 +33,28 @@ public:
const QHash<QString, int> &extToImportOptionsMap,
const QSet<QString> &preselectedFilesForOverwrite);
+ void reImportQuick3D(const QString &assetName, const QVector<QJsonObject> &options);
+
bool isImporting() const;
void cancelImport();
bool isCancelled() const;
- void addError(const QString &errMsg, const QString &srcPath = {}) const;
- void addWarning(const QString &warningMsg, const QString &srcPath = {}) const;
- void addInfo(const QString &infoMsg, const QString &srcPath = {}) const;
+ void addError(const QString &errMsg, const QString &srcPath = {});
+ void addWarning(const QString &warningMsg, const QString &srcPath = {});
+ void addInfo(const QString &infoMsg, const QString &srcPath = {});
+
+ QString previewFileName() const { return "QDSImport3dPreviewScene.qml"; }
+ QString tempDirNameBase() const { return "/qds3dimport"; }
+
+ void finalizeQuick3DImport();
signals:
- void errorReported(const QString &, const QString &) const;
- void warningReported(const QString &, const QString &) const;
- void infoReported(const QString &, const QString &) const;
- void progressChanged(int value, const QString &text) const;
- void importNearlyFinished() const;
+ void errorReported(const QString &, const QString &);
+ void warningReported(const QString &, const QString &);
+ void infoReported(const QString &, const QString &);
+ void progressChanged(int value, const QString &text);
+ void importReadyForPreview(const QString &path, const QString &compName);
+ void importNearlyFinished();
void importFinished();
private slots:
@@ -63,7 +69,8 @@ private:
QFileInfo sourceInfo;
QString assetName;
QString originalAssetName;
- int importId;
+ int importId = -1;
+ int optionsIndex = -1;
};
void notifyFinished();
@@ -79,6 +86,7 @@ private:
void notifyProgress(int value, const QString &text);
void notifyProgress(int value);
void keepUiAlive() const;
+ QString generateAssetFolderName(const QString &assetName) const;
enum class OverwriteResult {
Skip,
@@ -89,10 +97,9 @@ private:
OverwriteResult confirmAssetOverwrite(const QString &assetName);
void startNextImportProcess();
void postImport();
- void finalizeQuick3DImport();
QString sourceSceneTargetFilePath(const ParseData &pd);
- QSet<QHash<QString, QString>> m_importFiles;
+ QHash<QString, QHash<QString, QString>> m_importFiles; // Key: asset name
QHash<QString, QStringList> m_overwrittenImports;
bool m_isImporting = false;
bool m_cancelled = false;
@@ -101,7 +108,8 @@ private:
QProcessUniquePointer m_puppetProcess;
int m_importIdCounter = 0;
int m_currentImportId = 0;
- QHash<int, ParseData> m_parseData;
+ QHash<int, QString> m_importIdToAssetNameMap;
+ QHash<QString, ParseData> m_parseData; // Key: asset name
QString m_progressTitle;
QStringList m_requiredImports;
QList<int> m_puppetQueue;
diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp
index 3bff210520..8ef9512e26 100644
--- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp
+++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp
@@ -313,51 +313,54 @@ void ItemLibraryModel::update(Model *model)
beginResetModel();
clearSections();
- GeneratedComponentUtils compUtils = QmlDesignerPlugin::instance()->documentManager()
- .generatedComponentUtils();
+ const QString projectName = DocumentManager::currentProjectName();
+
+ auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils();
QStringList excludedImports {
- compUtils.componentBundlesTypePrefix() + ".MaterialBundle",
- compUtils.componentBundlesTypePrefix() + ".EffectBundle"
+ projectName,
+ compUtils.materialsBundleType(),
+ compUtils.effectsBundleType(),
+ compUtils.userMaterialsBundleType(),
+ compUtils.user3DBundleType(),
+ compUtils.userEffectsBundleType()
};
// create import sections
- const QString projectName = DocumentManager::currentProjectName();
const Imports usedImports = model->usedImports();
QHash<QString, ItemLibraryImport *> importHash;
for (const Import &import : model->imports()) {
- if (import.url() != projectName) {
- if (excludedImports.contains(import.url())
- || import.url().startsWith(compUtils.composedEffectsTypePrefix())) {
- continue;
- }
- bool addNew = true;
- bool isQuick3DAsset = import.url().startsWith(compUtils.import3dTypePrefix());
- QString importUrl = import.url();
- if (isQuick3DAsset)
- importUrl = ItemLibraryImport::quick3DAssetsTitle();
- else if (import.isFileImport())
- importUrl = import.toString(true, true).remove("\"");
-
- ItemLibraryImport *oldImport = importHash.value(importUrl);
- if (oldImport && oldImport->sectionType() == ItemLibraryImport::SectionType::Quick3DAssets
- && isQuick3DAsset) {
- addNew = false; // add only 1 Quick3DAssets import section
- } else if (oldImport && oldImport->importEntry().url() == import.url()) {
- // Retain the higher version if multiples exist
- if (oldImport->importEntry().toVersion() >= import.toVersion() || import.hasVersion())
- addNew = false;
- else
- delete oldImport;
- }
+ if (excludedImports.contains(import.url())
+ || import.url().startsWith(compUtils.composedEffectsTypePrefix())) {
+ continue;
+ }
- if (addNew) {
- auto sectionType = isQuick3DAsset ? ItemLibraryImport::SectionType::Quick3DAssets
- : ItemLibraryImport::SectionType::Default;
- ItemLibraryImport *itemLibImport = new ItemLibraryImport(import, this, sectionType);
- itemLibImport->setImportUsed(usedImports.contains(import));
- importHash.insert(importUrl, itemLibImport);
- }
+ bool addNew = true;
+ bool isQuick3DAsset = import.url().startsWith(compUtils.import3dTypePrefix());
+ QString importUrl = import.url();
+ if (isQuick3DAsset)
+ importUrl = ItemLibraryImport::quick3DAssetsTitle();
+ else if (import.isFileImport())
+ importUrl = import.toString(true, true).remove("\"");
+
+ ItemLibraryImport *oldImport = importHash.value(importUrl);
+ if (oldImport && oldImport->sectionType() == ItemLibraryImport::SectionType::Quick3DAssets
+ && isQuick3DAsset) {
+ addNew = false; // add only 1 Quick3DAssets import section
+ } else if (oldImport && oldImport->importEntry().url() == import.url()) {
+ // Retain the higher version if multiples exist
+ if (oldImport->importEntry().toVersion() >= import.toVersion() || import.hasVersion())
+ addNew = false;
+ else
+ delete oldImport;
+ }
+
+ if (addNew) {
+ auto sectionType = isQuick3DAsset ? ItemLibraryImport::SectionType::Quick3DAssets
+ : ItemLibraryImport::SectionType::Default;
+ ItemLibraryImport *itemLibImport = new ItemLibraryImport(import, this, sectionType);
+ itemLibImport->setImportUsed(usedImports.contains(import));
+ importHash.insert(importUrl, itemLibImport);
}
}
@@ -373,7 +376,7 @@ void ItemLibraryModel::update(Model *model)
NodeMetaInfo metaInfo;
if constexpr (useProjectStorage())
- metaInfo = entry.metaInfo();
+ metaInfo = NodeMetaInfo{entry.typeId(), model->projectStorage()};
else
metaInfo = model->metaInfo(entry.typeName());
@@ -385,7 +388,8 @@ void ItemLibraryModel::update(Model *model)
|| metaInfo.majorVersion() < 0);
#endif
bool isItem = valid && metaInfo.isQtQuickItem();
- bool forceVisibility = valid && NodeHints::fromItemLibraryEntry(entry).visibleInLibrary();
+ bool forceVisibility = valid
+ && NodeHints::fromItemLibraryEntry(entry, model).visibleInLibrary();
if (m_flowMode) {
isItem = metaInfo.isFlowViewItem();
diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp
index feff523b9c..e4f4ffcd9c 100644
--- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp
+++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp
@@ -164,7 +164,7 @@ void ItemLibraryView::updateImport3DSupport(const QVariantMap &supportMap)
auto importDlg = new ItemLibraryAssetImportDialog(fileNames, defaultDir,
m_importableExtensions3DMap,
m_importOptions3DMap, {}, {},
- Core::ICore::dialogParent());
+ this, Core::ICore::dialogParent());
int result = importDlg->exec();
return result == QDialog::Accepted ? AddFilesResult::succeeded() : AddFilesResult::cancelled();
@@ -198,7 +198,7 @@ void ItemLibraryView::customNotification(const AbstractView *view, const QString
const QList<ModelNode> &nodeList, const QList<QVariant> &data)
{
if (identifier == "UpdateImported3DAsset" && nodeList.size() > 0) {
- ItemLibraryAssetImportDialog::updateImport(nodeList[0],
+ ItemLibraryAssetImportDialog::updateImport(this, nodeList[0],
m_importableExtensions3DMap,
m_importOptions3DMap);
diff --git a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.cpp b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.cpp
index b6009edc77..6ab97b4307 100644
--- a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.cpp
+++ b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.cpp
@@ -32,7 +32,7 @@ public:
if (value.typeId() == QVariant::Bool)
return value;
- if (value.typeId() == QVariant::String) {
+ if (value.typeId() == QMetaType::QString) {
const QString text = value.toString();
if (text == "true")
return QVariant(true);
diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp
index d36e78512b..b74f310741 100644
--- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp
+++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp
@@ -366,6 +366,9 @@ void MaterialBrowserModel::selectMaterial(int idx, bool force)
if (idx != m_selectedIndex || force) {
m_selectedIndex = idx;
emit selectedIndexChanged(idx);
+
+ m_selectedMaterialIsComponent = selectedMaterial().isComponent();
+ emit selectedMaterialIsComponentChanged();
}
}
@@ -434,22 +437,20 @@ void MaterialBrowserModel::copyMaterialProperties(int idx, const QString &sectio
QJsonObject propsSpecObj = m_propertyGroupsObj.value(m_copiedMaterialType).toObject();
if (propsSpecObj.contains(section)) { // should always be true
const QJsonArray propNames = propsSpecObj.value(section).toArray();
- // auto == QJsonValueConstRef after 04dc959d49e5e3 / Qt 6.4, QJsonValueRef before
- for (const auto &propName : propNames)
+ for (const QJsonValueConstRef &propName : propNames)
copiedProps.append(propName.toString().toLatin1());
if (section == "Base") { // add QtQuick3D.Material base props as well
QJsonObject propsMatObj = m_propertyGroupsObj.value("Material").toObject();
const QJsonArray propNames = propsMatObj.value("Base").toArray();
- // auto == QJsonValueConstRef after 04dc959d49e5e3 / Qt 6.4, QJsonValueRef before
- for (const auto &propName : propNames)
+ for (const QJsonValueConstRef &propName : propNames)
copiedProps.append(propName.toString().toLatin1());
}
}
}
m_copiedMaterialProps.clear();
- for (const auto &propName : copiedProps) {
+ for (const PropertyName &propName : copiedProps) {
PropertyCopyData data;
data.name = propName;
data.isValid = m_allPropsCopied || validProps.contains(propName);
diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h
index 337dce0550..3b6b64ec86 100644
--- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h
+++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h
@@ -3,7 +3,7 @@
#pragma once
-#include "modelnode.h"
+#include <modelnode.h>
#include <QAbstractListModel>
#include <QJsonObject>
@@ -20,6 +20,7 @@ class MaterialBrowserModel : public QAbstractListModel
Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged)
Q_PROPERTY(int selectedIndex MEMBER m_selectedIndex NOTIFY selectedIndexChanged)
+ Q_PROPERTY(bool selectedMaterialIsComponent MEMBER m_selectedMaterialIsComponent NOTIFY selectedMaterialIsComponentChanged)
Q_PROPERTY(bool hasQuick3DImport READ hasQuick3DImport WRITE setHasQuick3DImport NOTIFY hasQuick3DImportChanged)
Q_PROPERTY(bool hasModelSelection READ hasModelSelection WRITE setHasModelSelection NOTIFY hasModelSelectionChanged)
Q_PROPERTY(bool hasMaterialLibrary READ hasMaterialLibrary WRITE setHasMaterialLibrary NOTIFY hasMaterialLibraryChanged)
@@ -110,6 +111,7 @@ signals:
const QList<QmlDesigner::MaterialBrowserModel::PropertyCopyData> &props,
bool all);
void isQt6ProjectChanged();
+ void selectedMaterialIsComponentChanged();
private:
bool isValidIndex(int idx) const;
@@ -132,6 +134,7 @@ private:
bool m_hasMaterialLibrary = false;
bool m_allPropsCopied = true;
bool m_isQt6Project = false;
+ bool m_selectedMaterialIsComponent = false;
QString m_copiedMaterialType;
QPointer<MaterialBrowserView> m_view;
diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp
index 8bd5761728..7d90dffffc 100644
--- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp
+++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp
@@ -446,8 +446,13 @@ void QmlDesigner::MaterialBrowserView::loadPropertyGroups()
if (!m_hasQuick3DImport || m_propertyGroupsLoaded || !model())
return;
+#ifdef QDS_USE_PROJECTSTORAGE
+ // TODO
+ QString matPropsPath;
+#else
QString matPropsPath = model()->metaInfo("QtQuick3D.Material").importDirectoryPath()
+ "/designer/propertyGroups.json";
+#endif
m_propertyGroupsLoaded = m_widget->materialBrowserModel()->loadPropertyGroups(matPropsPath);
}
diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp
index 7cf0a875bc..8723611be0 100644
--- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp
+++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp
@@ -150,7 +150,7 @@ MaterialBrowserWidget::MaterialBrowserWidget(AsynchronousImageCache &imageCache,
: m_materialBrowserView(view)
, m_materialBrowserModel(new MaterialBrowserModel(view, this))
, m_materialBrowserTexturesModel(new MaterialBrowserTexturesModel(view, this))
- , m_quickWidget(new StudioQuickWidget(this))
+ , m_quickWidget(Utils::makeUniqueObjectPtr<StudioQuickWidget>(this))
, m_previewImageProvider(new PreviewImageProvider())
{
QImage defaultImage;
@@ -179,7 +179,7 @@ MaterialBrowserWidget::MaterialBrowserWidget(AsynchronousImageCache &imageCache,
auto layout = new QVBoxLayout(this);
layout->setContentsMargins({});
layout->setSpacing(0);
- layout->addWidget(m_quickWidget.data());
+ layout->addWidget(m_quickWidget.get());
updateSearch();
@@ -411,7 +411,7 @@ void MaterialBrowserWidget::setIsDragging(bool val)
StudioQuickWidget *MaterialBrowserWidget::quickWidget() const
{
- return m_quickWidget.data();
+ return m_quickWidget.get();
}
void MaterialBrowserWidget::clearPreviewCache()
@@ -434,10 +434,4 @@ QPointer<MaterialBrowserTexturesModel> MaterialBrowserWidget::materialBrowserTex
return m_materialBrowserTexturesModel;
}
-bool MaterialBrowserWidget::userBundleEnabled() const
-{
- // TODO: this method is to be removed after user bundle implementation is complete
- return Core::ICore::settings()->value("QML/Designer/UseExperimentalFeatures45", false).toBool();
-}
-
} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h
index 97c994e565..6506283f85 100644
--- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h
+++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h
@@ -7,6 +7,8 @@
#include <coreplugin/icontext.h>
+#include <utils/uniqueobjectptr.h>
+
#include <QFrame>
QT_BEGIN_NAMESPACE
@@ -61,7 +63,6 @@ public:
Q_INVOKABLE void acceptTextureDropOnMaterial(int matIndex, const QString &texId);
Q_INVOKABLE void focusMaterialSection(bool focusMatSec);
Q_INVOKABLE void addMaterialToContentLibrary();
- Q_INVOKABLE bool userBundleEnabled() const;
StudioQuickWidget *quickWidget() const;
@@ -85,7 +86,7 @@ private:
QPointer<MaterialBrowserView> m_materialBrowserView;
QPointer<MaterialBrowserModel> m_materialBrowserModel;
QPointer<MaterialBrowserTexturesModel> m_materialBrowserTexturesModel;
- QScopedPointer<StudioQuickWidget> m_quickWidget;
+ Utils::UniqueObjectPtr<StudioQuickWidget> m_quickWidget;
QShortcut *m_qmlSourceUpdateShortcut = nullptr;
PreviewImageProvider *m_previewImageProvider = nullptr;
diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorqmlbackend.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditorqmlbackend.cpp
index ecc460ae51..0e508f8f36 100644
--- a/src/plugins/qmldesigner/components/materialeditor/materialeditorqmlbackend.cpp
+++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorqmlbackend.cpp
@@ -80,8 +80,8 @@ public:
MaterialEditorQmlBackend::MaterialEditorQmlBackend(MaterialEditorView *materialEditor)
: m_quickWidget(Utils::makeUniqueObjectPtr<QQuickWidget>())
- , m_materialEditorTransaction(new MaterialEditorTransaction(materialEditor))
- , m_contextObject(new MaterialEditorContextObject(m_quickWidget.get()))
+ , m_materialEditorTransaction(std::make_unique<MaterialEditorTransaction>(materialEditor))
+ , m_contextObject(std::make_unique<MaterialEditorContextObject>(m_quickWidget.get()))
, m_materialEditorImageProvider(new MaterialEditorImageProvider())
{
m_quickWidget->setObjectName(Constants::OBJECT_NAME_MATERIAL_EDITOR);
@@ -90,7 +90,7 @@ MaterialEditorQmlBackend::MaterialEditorQmlBackend(MaterialEditorView *materialE
m_quickWidget->engine()->addImageProvider("materialEditor", m_materialEditorImageProvider);
m_contextObject->setBackendValues(&m_backendValuesPropertyMap);
m_contextObject->setModel(materialEditor->model());
- context()->setContextObject(m_contextObject.data());
+ context()->setContextObject(m_contextObject.get());
QObject::connect(&m_backendValuesPropertyMap, &DesignerPropertyMap::valueChanged,
materialEditor, &MaterialEditorView::changeValue);
@@ -193,7 +193,7 @@ QQmlContext *MaterialEditorQmlBackend::context() const
MaterialEditorContextObject *MaterialEditorQmlBackend::contextObject() const
{
- return m_contextObject.data();
+ return m_contextObject.get();
}
QQuickWidget *MaterialEditorQmlBackend::widget() const
@@ -224,7 +224,7 @@ DesignerPropertyMap &MaterialEditorQmlBackend::backendValuesPropertyMap()
MaterialEditorTransaction *MaterialEditorQmlBackend::materialEditorTransaction() const
{
- return m_materialEditorTransaction.data();
+ return m_materialEditorTransaction.get();
}
PropertyEditorValue *MaterialEditorQmlBackend::propertyValueForName(const QString &propertyName)
@@ -267,12 +267,9 @@ void MaterialEditorQmlBackend::setup(const QmlObjectNode &selectedMaterialNode,
// anchors
m_backendAnchorBinding.setup(selectedMaterialNode.modelNode());
- context()->setContextProperties(
- QVector<QQmlContext::PropertyPair>{
- {{"anchorBackend"}, QVariant::fromValue(&m_backendAnchorBinding)},
- {{"transaction"}, QVariant::fromValue(m_materialEditorTransaction.data())}
- }
- );
+ context()->setContextProperties(QVector<QQmlContext::PropertyPair>{
+ {{"anchorBackend"}, QVariant::fromValue(&m_backendAnchorBinding)},
+ {{"transaction"}, QVariant::fromValue(m_materialEditorTransaction.get())}});
contextObject()->setSpecificsUrl(qmlSpecificsFile);
contextObject()->setStateName(stateName);
diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorqmlbackend.h b/src/plugins/qmldesigner/components/materialeditor/materialeditorqmlbackend.h
index 0792a635ca..9fd5fc2399 100644
--- a/src/plugins/qmldesigner/components/materialeditor/materialeditorqmlbackend.h
+++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorqmlbackend.h
@@ -11,6 +11,8 @@
#include <nodemetainfo.h>
+#include <memory>
+
class PropertyEditorValue;
QT_BEGIN_NAMESPACE
@@ -67,8 +69,8 @@ private:
Utils::UniqueObjectPtr<QQuickWidget> m_quickWidget = nullptr;
QmlAnchorBindingProxy m_backendAnchorBinding;
QmlModelNodeProxy m_backendModelNode;
- QScopedPointer<MaterialEditorTransaction> m_materialEditorTransaction;
- QScopedPointer<MaterialEditorContextObject> m_contextObject;
+ std::unique_ptr<MaterialEditorTransaction> m_materialEditorTransaction;
+ std::unique_ptr<MaterialEditorContextObject> m_contextObject;
QPointer<MaterialEditorImageProvider> m_materialEditorImageProvider;
};
diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp
index e083310cdb..21114267cb 100644
--- a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp
+++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp
@@ -24,7 +24,6 @@
#include "qmldesignerplugin.h"
#include "qmltimeline.h"
#include "variantproperty.h"
-#include <itemlibraryentry.h>
#include <utils3d.h>
#include <coreplugin/icore.h>
@@ -63,10 +62,6 @@ MaterialEditorView::MaterialEditorView(ExternalDependenciesInterface &externalDe
}
});
- m_typeUpdateTimer.setSingleShot(true);
- m_typeUpdateTimer.setInterval(500);
- connect(&m_typeUpdateTimer, &QTimer::timeout, this, &MaterialEditorView::updatePossibleTypes);
-
QmlDesignerPlugin::trackWidgetFocusTime(m_stackedWidget, Constants::EVENT_MATERIALEDITOR_TIME);
MaterialEditorDynamicPropertiesProxyModel::registerDeclarativeType();
@@ -333,8 +328,10 @@ void MaterialEditorView::resetView()
setupQmlBackend();
- if (m_qmlBackEnd)
+ if (m_qmlBackEnd) {
m_qmlBackEnd->emitSelectionChanged();
+ updatePossibleTypes();
+ }
QTimer::singleShot(0, this, &MaterialEditorView::requestPreviewRender);
@@ -605,7 +602,6 @@ void MaterialEditorView::setupQmlBackend()
else
m_dynamicPropertiesModel->reset();
- delayedTypeUpdate();
initPreviewData();
m_stackedWidget->setCurrentWidget(m_qmlBackEnd->widget());
@@ -690,21 +686,6 @@ void MaterialEditorView::initPreviewData()
}
}
-void MaterialEditorView::delayedTypeUpdate()
-{
- m_typeUpdateTimer.start();
-}
-
-[[maybe_unused]] static Import entryToImport(const ItemLibraryEntry &entry)
-{
- if (entry.majorVersion() == -1 && entry.minorVersion() == -1)
- return Import::createFileImport(entry.requiredImport());
-
- return Import::createLibraryImport(entry.requiredImport(),
- QString::number(entry.majorVersion()) + QLatin1Char('.') +
- QString::number(entry.minorVersion()));
-}
-
void MaterialEditorView::updatePossibleTypes()
{
QTC_ASSERT(model(), return);
@@ -712,49 +693,21 @@ void MaterialEditorView::updatePossibleTypes()
if (!m_qmlBackEnd)
return;
-#ifdef QDS_USE_PROJECTSTORAGE
- auto heirs = model()->qtQuick3DMaterialMetaInfo().heirs();
- heirs.push_back(model()->qtQuick3DMaterialMetaInfo());
- auto entries = Utils::transform<ItemLibraryEntries>(heirs, [&](const auto &heir) {
- return toItemLibraryEntries(heir.itemLibrariesEntries(), *model()->projectStorage());
- });
+ static const QStringList basicTypes {
+ "CustomMaterial",
+ "DefaultMaterial",
+ "PrincipledMaterial",
+ "SpecularGlossyMaterial"
+ };
- // I am unsure about the code intention here
-#else // Ensure basic types are always first
- QStringList nonQuick3dTypes;
- QStringList allTypes;
-
- const QList<ItemLibraryEntry> itemLibEntries = m_itemLibraryInfo->entries();
- for (const ItemLibraryEntry &entry : itemLibEntries) {
- NodeMetaInfo metaInfo = model()->metaInfo(entry.typeName());
- bool valid = metaInfo.isValid()
- && (metaInfo.majorVersion() >= entry.majorVersion()
- || metaInfo.majorVersion() < 0);
- if (valid && metaInfo.isQtQuick3DMaterial()) {
- bool addImport = entry.requiredImport().isEmpty();
- if (!addImport) {
- Import import = entryToImport(entry);
- addImport = model()->hasImport(import, true, true);
- }
- if (addImport) {
- const QList<QByteArray> typeSplit = entry.typeName().split('.');
- const QString typeName = QString::fromLatin1(typeSplit.last());
- if (typeSplit.size() == 2 && typeSplit.first() == "QtQuick3D") {
- if (!allTypes.contains(typeName))
- allTypes.append(typeName);
- } else if (!nonQuick3dTypes.contains(typeName)) {
- nonQuick3dTypes.append(typeName);
- }
- }
- }
- }
+ const QString matType = m_selectedMaterial.simplifiedTypeName();
- allTypes.sort();
- nonQuick3dTypes.sort();
- allTypes.append(nonQuick3dTypes);
+ if (basicTypes.contains(matType)) {
+ m_qmlBackEnd->contextObject()->setPossibleTypes(basicTypes);
+ return;
+ }
- m_qmlBackEnd->contextObject()->setPossibleTypes(allTypes);
-#endif
+ m_qmlBackEnd->contextObject()->setPossibleTypes({matType});
}
void MaterialEditorView::modelAttached(Model *model)
@@ -774,20 +727,6 @@ void MaterialEditorView::modelAttached(Model *model)
m_ensureMatLibTimer.start(500);
}
-#ifndef QDS_USE_PROJECTSTORAGE
- if (m_itemLibraryInfo.data() != model->metaInfo().itemLibraryInfo()) {
- if (m_itemLibraryInfo) {
- disconnect(m_itemLibraryInfo.data(), &ItemLibraryInfo::entriesChanged,
- this, &MaterialEditorView::delayedTypeUpdate);
- }
- m_itemLibraryInfo = model->metaInfo().itemLibraryInfo();
- if (m_itemLibraryInfo) {
- connect(m_itemLibraryInfo.data(), &ItemLibraryInfo::entriesChanged,
- this, &MaterialEditorView::delayedTypeUpdate);
- }
- }
-#endif
-
if (!m_setupCompleted) {
reloadQml();
m_setupCompleted = true;
@@ -801,7 +740,8 @@ void MaterialEditorView::modelAboutToBeDetached(Model *model)
{
AbstractView::modelAboutToBeDetached(model);
m_dynamicPropertiesModel->reset();
- m_qmlBackEnd->materialEditorTransaction()->end();
+ if (auto transaction = m_qmlBackEnd->materialEditorTransaction())
+ transaction->end();
m_qmlBackEnd->contextObject()->setHasMaterialLibrary(false);
m_selectedMaterial = {};
}
@@ -938,7 +878,8 @@ void MaterialEditorView::selectedNodesChanged(const QList<ModelNode> &selectedNo
m_selectedModels.append(node);
}
- m_qmlBackEnd->contextObject()->setHasModelSelection(!m_selectedModels.isEmpty());
+ if (m_qmlBackEnd)
+ m_qmlBackEnd->contextObject()->setHasModelSelection(!m_selectedModels.isEmpty());
}
void MaterialEditorView::currentStateChanged(const ModelNode &node)
@@ -1020,7 +961,7 @@ void MaterialEditorView::renameMaterial(ModelNode &material, const QString &newN
return;
executeInTransaction(__FUNCTION__, [&] {
- material.setIdWithRefactoring(model()->generateIdFromName(newName, "material"));
+ material.setIdWithRefactoring(model()->generateNewId(newName, "material"));
VariantProperty objNameProp = material.variantProperty("objectName");
objNameProp.setValue(newName);
@@ -1057,7 +998,7 @@ void MaterialEditorView::duplicateMaterial(const ModelNode &material)
QString newName = sourceMat.modelNode().variantProperty("objectName").value().toString() + " copy";
VariantProperty objNameProp = duplicateMatNode.variantProperty("objectName");
objNameProp.setValue(newName);
- duplicateMatNode.setIdWithoutRefactoring(model()->generateIdFromName(newName, "material"));
+ duplicateMatNode.setIdWithoutRefactoring(model()->generateNewId(newName, "material"));
// sync properties. Only the base state is duplicated.
const QList<AbstractProperty> props = material.properties();
diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.h b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.h
index c201742bd5..11bea46063 100644
--- a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.h
+++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.h
@@ -109,12 +109,10 @@ private:
bool noValidSelection() const;
void initPreviewData();
- void delayedTypeUpdate();
void updatePossibleTypes();
ModelNode m_selectedMaterial;
QTimer m_ensureMatLibTimer;
- QTimer m_typeUpdateTimer;
QShortcut *m_updateShortcut = nullptr;
int m_timerId = 0;
QStackedWidget *m_stackedWidget = nullptr;
diff --git a/src/plugins/qmldesigner/components/navigator/choosefrompropertylistdialog.cpp b/src/plugins/qmldesigner/components/navigator/choosefrompropertylistdialog.cpp
index a3ab5f2cd7..fee3218af0 100644
--- a/src/plugins/qmldesigner/components/navigator/choosefrompropertylistdialog.cpp
+++ b/src/plugins/qmldesigner/components/navigator/choosefrompropertylistdialog.cpp
@@ -102,10 +102,9 @@ ChooseFromPropertyListFilter::ChooseFromPropertyListFilter(const NodeMetaInfo &i
#ifdef QDS_USE_PROJECTSTORAGE
// TODO add the types here or use the module
#else
- } else if (insertInfo.typeName().startsWith(
- QString("%1.MaterialBundle").arg(QmlDesignerPlugin::instance()->documentManager()
- .generatedComponentUtils().componentBundlesTypePrefix())
- .toUtf8())) {
+ } else if (insertInfo.typeName().startsWith(
+ QmlDesignerPlugin::instance()->documentManager()
+ .generatedComponentUtils().materialsBundleType().toUtf8())) {
if (parentInfo.isQtQuick3DModel())
propertyList.append("materials");
#endif
diff --git a/src/plugins/qmldesigner/components/navigator/iconcheckboxitemdelegate.cpp b/src/plugins/qmldesigner/components/navigator/iconcheckboxitemdelegate.cpp
index 5b36bee7f9..09cf5945e8 100644
--- a/src/plugins/qmldesigner/components/navigator/iconcheckboxitemdelegate.cpp
+++ b/src/plugins/qmldesigner/components/navigator/iconcheckboxitemdelegate.cpp
@@ -79,15 +79,12 @@ void IconCheckboxItemDelegate::paint(QPainter *painter,
if (rowIsPropertyRole(modelIndex.model(), modelIndex) || getModelNode(modelIndex).isRootNode())
return; // Do not paint icons for property rows or root node
- QWindow *window = dynamic_cast<QWidget*>(painter->device())->window()->windowHandle();
- QTC_ASSERT(window, return);
-
const QSize iconSize(16, 16);
QPoint iconPosition(styleOption.rect.left() + (styleOption.rect.width() - iconSize.width()) / 2,
styleOption.rect.top() + 2 + delegateMargin);
const QIcon::State state = isChecked(modelIndex) ? QIcon::State::On : QIcon::State::Off;
- const QPixmap iconPixmap = m_icon.pixmap(window, iconSize, mode, state);
+ const QPixmap iconPixmap = m_icon.pixmap(iconSize, painter->device()->devicePixelRatio(), mode, state);
// Shift the lock icon (last column) slightly to the left due to vertical scrollbar width
if (modelIndex.column() == NavigatorTreeModel::ColumnType::Lock)
diff --git a/src/plugins/qmldesigner/components/navigator/navigatorsearchwidget.cpp b/src/plugins/qmldesigner/components/navigator/navigatorsearchwidget.cpp
index 24d4377ef2..e7fe0ebb77 100644
--- a/src/plugins/qmldesigner/components/navigator/navigatorsearchwidget.cpp
+++ b/src/plugins/qmldesigner/components/navigator/navigatorsearchwidget.cpp
@@ -97,10 +97,10 @@ void LineEdit::paintEvent(QPaintEvent *event)
QPalette p(palette());
p.setColor(QPalette::Active,
QPalette::PlaceholderText,
- Utils::creatorTheme()->color(Utils::Theme::DSplaceholderTextColor));
+ Utils::creatorColor(Utils::Theme::DSplaceholderTextColor));
p.setColor(QPalette::Inactive,
QPalette::PlaceholderText,
- Utils::creatorTheme()->color(Utils::Theme::DSplaceholderTextColor));
+ Utils::creatorColor(Utils::Theme::DSplaceholderTextColor));
setPalette(p);
}
QLineEdit::paintEvent(event);
diff --git a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp
index d305753ead..c5fa30fc7d 100644
--- a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp
+++ b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp
@@ -455,7 +455,7 @@ QStringList NavigatorTreeModel::mimeTypes() const
Constants::MIME_TYPE_MATERIAL,
Constants::MIME_TYPE_BUNDLE_TEXTURE,
Constants::MIME_TYPE_BUNDLE_MATERIAL,
- Constants::MIME_TYPE_BUNDLE_EFFECT,
+ Constants::MIME_TYPE_BUNDLE_ITEM,
Constants::MIME_TYPE_ASSETS});
return types;
@@ -570,9 +570,9 @@ bool NavigatorTreeModel::dropMimeData(const QMimeData *mimeData,
} else if (mimeData->hasFormat(Constants::MIME_TYPE_BUNDLE_MATERIAL)) {
if (targetNode.isValid())
m_view->emitCustomNotification("drop_bundle_material", {targetNode}); // To ContentLibraryView
- } else if (mimeData->hasFormat(Constants::MIME_TYPE_BUNDLE_EFFECT)) {
+ } else if (mimeData->hasFormat(Constants::MIME_TYPE_BUNDLE_ITEM)) {
if (targetNode.isValid())
- m_view->emitCustomNotification("drop_bundle_effect", {targetNode}); // To ContentLibraryView
+ m_view->emitCustomNotification("drop_bundle_item", {targetNode}); // To ContentLibraryView
} else if (mimeData->hasFormat(Constants::MIME_TYPE_ASSETS)) {
const QStringList assetsPaths = QString::fromUtf8(mimeData->data(Constants::MIME_TYPE_ASSETS)).split(',');
NodeAbstractProperty targetProperty;
@@ -705,7 +705,7 @@ void NavigatorTreeModel::handleItemLibraryItemDrop(const QMimeData *mimeData, in
const ItemLibraryEntry itemLibraryEntry =
createItemLibraryEntryFromMimeData(mimeData->data(Constants::MIME_TYPE_ITEM_LIBRARY_INFO));
- const NodeHints hints = NodeHints::fromItemLibraryEntry(itemLibraryEntry);
+ const NodeHints hints = NodeHints::fromItemLibraryEntry(itemLibraryEntry, m_view->model());
const QString targetPropertyName = hints.forceNonDefaultProperty();
diff --git a/src/plugins/qmldesigner/components/navigator/previewtooltip.cpp b/src/plugins/qmldesigner/components/navigator/previewtooltip.cpp
index f99b964f2d..e5e42697e2 100644
--- a/src/plugins/qmldesigner/components/navigator/previewtooltip.cpp
+++ b/src/plugins/qmldesigner/components/navigator/previewtooltip.cpp
@@ -21,7 +21,8 @@ PreviewToolTip::PreviewToolTip(QWidget *parent)
m_ui->idLabel->setElideMode(Qt::ElideLeft);
m_ui->typeLabel->setElideMode(Qt::ElideLeft);
m_ui->infoLabel->setElideMode(Qt::ElideLeft);
- setStyleSheet(QString("QWidget { background-color: %1 }").arg(Utils::creatorTheme()->color(Utils::Theme::BackgroundColorNormal).name()));
+ setStyleSheet(QString("QWidget { background-color: %1 }")
+ .arg(Utils::creatorColor(Utils::Theme::BackgroundColorNormal).name()));
m_ui->imageLabel->setStyleSheet("background-color: rgba(0, 0, 0, 0)");
static QPixmap checkers;
diff --git a/src/plugins/qmldesigner/components/previewtooltip/previewimagetooltip.cpp b/src/plugins/qmldesigner/components/previewtooltip/previewimagetooltip.cpp
index 248a9ec429..4034254420 100644
--- a/src/plugins/qmldesigner/components/previewtooltip/previewimagetooltip.cpp
+++ b/src/plugins/qmldesigner/components/previewtooltip/previewimagetooltip.cpp
@@ -20,7 +20,8 @@ PreviewImageTooltip::PreviewImageTooltip(QWidget *parent)
m_ui->nameLabel->setElideMode(Qt::ElideLeft);
m_ui->pathLabel->setElideMode(Qt::ElideLeft);
m_ui->infoLabel->setElideMode(Qt::ElideLeft);
- setStyleSheet(QString("QWidget { background-color: %1 }").arg(Utils::creatorTheme()->color(Utils::Theme::BackgroundColorNormal).name()));
+ setStyleSheet(QString("QWidget { background-color: %1 }")
+ .arg(Utils::creatorColor(Utils::Theme::BackgroundColorNormal).name()));
}
PreviewImageTooltip::~PreviewImageTooltip() = default;
diff --git a/src/plugins/qmldesigner/components/previewtooltip/previewtooltipbackend.cpp b/src/plugins/qmldesigner/components/previewtooltip/previewtooltipbackend.cpp
index 16328ae532..b6d5e2ae47 100644
--- a/src/plugins/qmldesigner/components/previewtooltip/previewtooltipbackend.cpp
+++ b/src/plugins/qmldesigner/components/previewtooltip/previewtooltipbackend.cpp
@@ -42,7 +42,18 @@ void PreviewTooltipBackend::showTooltip()
}
});
},
- [](auto) {},
+ [&](ImageCache::AbortReason abortReason) {
+ if (abortReason == ImageCache::AbortReason::Abort) {
+ qWarning() << QLatin1String("PreviewTooltipBackend::showTooltip(): preview generation "
+ "failed for path %1, reason: Abort").arg(m_path);
+ } else if (abortReason == ImageCache::AbortReason::Failed) {
+ qWarning() << QLatin1String("PreviewTooltipBackend::showTooltip(): preview generation "
+ "failed for path %1, reason: Failed").arg(m_path);
+ } else if (abortReason == ImageCache::AbortReason::NoEntry) {
+ qWarning() << QLatin1String("PreviewTooltipBackend::showTooltip(): preview generation "
+ "failed for path %1, reason: NoEntry").arg(m_path);
+ }
+ },
Utils::PathString{m_extraId},
m_auxiliaryData);
diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.cpp
index 591ce5a57f..b6d119a424 100644
--- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.cpp
+++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.cpp
@@ -586,8 +586,7 @@ int PropertyEditorContextObject::devicePixelRatio()
QStringList PropertyEditorContextObject::styleNamesForFamily(const QString &family)
{
- const QFontDatabase dataBase;
- return dataBase.styles(family);
+ return QFontDatabase::styles(family);
}
QStringList PropertyEditorContextObject::allStatesForId(const QString &id)
diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp
index eea53c3f5a..c397d445b1 100644
--- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp
+++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp
@@ -83,16 +83,17 @@ namespace QmlDesigner {
PropertyEditorQmlBackend::PropertyEditorQmlBackend(PropertyEditorView *propertyEditor,
AsynchronousImageCache &imageCache)
: m_view(Utils::makeUniqueObjectPtr<Quick2PropertyEditorView>(imageCache))
- , m_propertyEditorTransaction(new PropertyEditorTransaction(propertyEditor))
- , m_dummyPropertyEditorValue(new PropertyEditorValue())
- , m_contextObject(new PropertyEditorContextObject(m_view.get()))
+ , m_propertyEditorTransaction(std::make_unique<PropertyEditorTransaction>(propertyEditor))
+ , m_dummyPropertyEditorValue(std::make_unique<PropertyEditorValue>())
+ , m_contextObject(std::make_unique<PropertyEditorContextObject>(m_view.get()))
{
m_view->engine()->setOutputWarningsToStandardError(QmlDesignerPlugin::instance()
->settings().value(DesignerSettingsKey::SHOW_PROPERTYEDITOR_WARNINGS).toBool());
m_view->engine()->addImportPath(propertyEditorResourcesPath() + "/imports");
m_dummyPropertyEditorValue->setValue(QLatin1String("#000000"));
- context()->setContextProperty(QLatin1String("dummyBackendValue"), m_dummyPropertyEditorValue.data());
+ context()->setContextProperty(QLatin1String("dummyBackendValue"),
+ m_dummyPropertyEditorValue.get());
m_contextObject->setBackendValues(&m_backendValuesPropertyMap);
m_contextObject->setModel(propertyEditor->model());
m_contextObject->insertInQmlContext(context());
@@ -402,7 +403,7 @@ QQmlContext *PropertyEditorQmlBackend::context()
PropertyEditorContextObject *PropertyEditorQmlBackend::contextObject()
{
- return m_contextObject.data();
+ return m_contextObject.get();
}
QQuickWidget *PropertyEditorQmlBackend::widget()
@@ -432,7 +433,7 @@ DesignerPropertyMap &PropertyEditorQmlBackend::backendValuesPropertyMap() {
}
PropertyEditorTransaction *PropertyEditorQmlBackend::propertyEditorTransaction() {
- return m_propertyEditorTransaction.data();
+ return m_propertyEditorTransaction.get();
}
PropertyEditorValue *PropertyEditorQmlBackend::propertyValueForName(const QString &propertyName)
@@ -495,12 +496,9 @@ void PropertyEditorQmlBackend::setup(const QmlObjectNode &qmlObjectNode, const Q
// anchors
m_backendAnchorBinding.setup(qmlObjectNode.modelNode());
- context()->setContextProperties(
- QVector<QQmlContext::PropertyPair>{
- {{"anchorBackend"}, QVariant::fromValue(&m_backendAnchorBinding)},
- {{"transaction"}, QVariant::fromValue(m_propertyEditorTransaction.data())}
- }
- );
+ context()->setContextProperties(QVector<QQmlContext::PropertyPair>{
+ {{"anchorBackend"}, QVariant::fromValue(&m_backendAnchorBinding)},
+ {{"transaction"}, QVariant::fromValue(m_propertyEditorTransaction.get())}});
contextObject()->setHasMultiSelection(
!qmlObjectNode.view()->singleSelectedModelNode().isValid());
@@ -592,13 +590,10 @@ void PropertyEditorQmlBackend::initialSetup(const TypeName &typeName, const QUrl
QObject::connect(valueObject, &PropertyEditorValue::valueChanged, &backendValuesPropertyMap(), &DesignerPropertyMap::valueChanged);
m_backendValuesPropertyMap.insert(QLatin1String("id"), QVariant::fromValue(valueObject));
- context()->setContextProperties(
- QVector<QQmlContext::PropertyPair>{
- {{"anchorBackend"}, QVariant::fromValue(&m_backendAnchorBinding)},
- {{"modelNodeBackend"}, QVariant::fromValue(&m_backendModelNode)},
- {{"transaction"}, QVariant::fromValue(m_propertyEditorTransaction.data())}
- }
- );
+ context()->setContextProperties(QVector<QQmlContext::PropertyPair>{
+ {{"anchorBackend"}, QVariant::fromValue(&m_backendAnchorBinding)},
+ {{"modelNodeBackend"}, QVariant::fromValue(&m_backendModelNode)},
+ {{"transaction"}, QVariant::fromValue(m_propertyEditorTransaction.get())}});
contextObject()->setSpecificsUrl(qmlSpecificsFile);
diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h
index 120b7b4abc..1c9b808ac3 100644
--- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h
+++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h
@@ -16,6 +16,8 @@
#include <QQmlPropertyMap>
+#include <memory>
+
class PropertyEditorValue;
namespace QmlDesigner {
@@ -109,9 +111,9 @@ private:
Utils::UniqueObjectPtr<Quick2PropertyEditorView> m_view = nullptr;
QmlAnchorBindingProxy m_backendAnchorBinding;
QmlModelNodeProxy m_backendModelNode;
- QScopedPointer<PropertyEditorTransaction> m_propertyEditorTransaction;
- QScopedPointer<PropertyEditorValue> m_dummyPropertyEditorValue;
- QScopedPointer<PropertyEditorContextObject> m_contextObject;
+ std::unique_ptr<PropertyEditorTransaction> m_propertyEditorTransaction;
+ std::unique_ptr<PropertyEditorValue> m_dummyPropertyEditorValue;
+ std::unique_ptr<PropertyEditorContextObject> m_contextObject;
};
} //QmlDesigner
diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp
index 70686f31ae..27319c15f5 100644
--- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp
+++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp
@@ -22,7 +22,6 @@
#include <utils/qtcassert.h>
#include <QRegularExpression>
-#include <QScopedPointer>
#include <QUrl>
namespace QmlDesigner {
@@ -60,10 +59,10 @@ static bool cleverColorCompare(const QVariant &value1, const QVariant &value2)
return c1.name() == c2.name() && c1.alpha() == c2.alpha();
}
- if (value1.typeId() == QVariant::String && value2.typeId() == QVariant::Color)
+ if (value1.typeId() == QMetaType::QString && value2.typeId() == QVariant::Color)
return cleverColorCompare(QVariant(QColor(value1.toString())), value2);
- if (value1.typeId() == QVariant::Color && value2.typeId() == QVariant::String)
+ if (value1.typeId() == QVariant::Color && value2.typeId() == QMetaType::QString)
return cleverColorCompare(value1, QVariant(QColor(value2.toString())));
return false;
@@ -549,6 +548,23 @@ void PropertyEditorValue::setForceBound(bool b)
emit isBoundChanged();
}
+void PropertyEditorValue::insertKeyframe()
+{
+ if (!m_modelNode.isValid())
+ return;
+
+ /*If we add more code here we have to forward the property editor view */
+ AbstractView *view = m_modelNode.view();
+
+ QmlTimeline timeline = view->currentTimeline();
+
+ QTC_ASSERT(timeline.isValid(), return );
+ QTC_ASSERT(m_modelNode.isValid(), return );
+
+ view->executeInTransaction("PropertyEditorContextObject::insertKeyframe",
+ [&] { timeline.insertKeyframe(m_modelNode, name()); });
+}
+
QStringList PropertyEditorValue::generateStringList(const QString &string) const
{
QString copy = string;
diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.h b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.h
index 70a51fffc2..c4b09f6b5a 100644
--- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.h
+++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.h
@@ -172,6 +172,8 @@ public:
Q_INVOKABLE void setForceBound(bool b);
+ Q_INVOKABLE void insertKeyframe();
+
public slots:
void resetValue();
void setEnumeration(const QString &scope, const QString &name);
diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp
index b22b39e238..e0d5759617 100644
--- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp
+++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp
@@ -38,7 +38,6 @@
#include <QFileSystemWatcher>
#include <QQuickItem>
#include <QScopeGuard>
-#include <QScopedPointer>
#include <QShortcut>
#include <QTimer>
diff --git a/src/plugins/qmldesigner/components/propertyeditor/qmlmodelnodeproxy.cpp b/src/plugins/qmldesigner/components/propertyeditor/qmlmodelnodeproxy.cpp
index fc39b37137..1cff0e55e6 100644
--- a/src/plugins/qmldesigner/components/propertyeditor/qmlmodelnodeproxy.cpp
+++ b/src/plugins/qmldesigner/components/propertyeditor/qmlmodelnodeproxy.cpp
@@ -161,13 +161,12 @@ void QmlModelNodeProxy::createModelNode(int internalIdParent,
const QString &typeName,
const QString &requiredImport)
{
- QTC_ASSERT(m_qmlObjectNode.isValid(), return );
+ auto parentModelNode = m_qmlObjectNode.modelNode();
- auto modelNode = m_qmlObjectNode.modelNode();
+ QTC_ASSERT(parentModelNode.isValid(), return );
- AbstractView *view = modelNode.view();
+ AbstractView *view = parentModelNode.view();
- auto parentModelNode = m_qmlObjectNode.modelNode();
if (internalIdParent >= 0)
parentModelNode = view->modelNodeForInternalId(internalIdParent);
@@ -186,7 +185,7 @@ void QmlModelNodeProxy::createModelNode(int internalIdParent,
#ifdef QDS_USE_PROJECTSTORAGE
ModelNode newNode = view->createModelNode(typeName.toUtf8());
#else
- NodeMetaInfo metaInfo = modelNode.model()->metaInfo(typeName.toUtf8());
+ NodeMetaInfo metaInfo = parentModelNode.model()->metaInfo(typeName.toUtf8());
ModelNode newNode = view->createModelNode(metaInfo.typeName(),
metaInfo.majorVersion(),
metaInfo.minorVersion());
@@ -200,14 +199,17 @@ void QmlModelNodeProxy::moveNode(int internalIdParent,
int fromIndex,
int toIndex)
{
- QTC_ASSERT(m_qmlObjectNode.isValid(), return );
+ ModelNode modelNode = m_qmlObjectNode.modelNode();
- ModelNode node = m_qmlObjectNode.view()->modelNodeForInternalId(internalIdParent);
+ QTC_ASSERT(modelNode.isValid(), return );
- QTC_ASSERT(node.isValid(), return );
+ if (internalIdParent >= 0)
+ modelNode = m_qmlObjectNode.view()->modelNodeForInternalId(internalIdParent);
+
+ QTC_ASSERT(modelNode.isValid(), return );
AbstractView *view = m_qmlObjectNode.view();
- view->executeInTransaction("QmlModelNodeProxy::swapNode", [&] {
- node.nodeListProperty(propertyName.toUtf8()).slide(fromIndex, toIndex);
+ view->executeInTransaction("QmlModelNodeProxy::moveNode", [&] {
+ modelNode.nodeListProperty(propertyName.toUtf8()).slide(fromIndex, toIndex);
});
}
diff --git a/src/plugins/qmldesigner/components/texteditor/texteditorstatusbar.cpp b/src/plugins/qmldesigner/components/texteditor/texteditorstatusbar.cpp
index a8981ff2f0..3f6f6769fb 100644
--- a/src/plugins/qmldesigner/components/texteditor/texteditorstatusbar.cpp
+++ b/src/plugins/qmldesigner/components/texteditor/texteditorstatusbar.cpp
@@ -15,7 +15,8 @@ TextEditorStatusBar::TextEditorStatusBar(QWidget *parent) : QToolBar(parent), m_
addWidget(m_label);
/* We have to set another .css, since the central widget has already a style sheet */
- m_label->setStyleSheet(QString("QLabel { color :%1 }").arg(Utils::creatorTheme()->color(Utils::Theme::TextColorError).name()));
+ m_label->setStyleSheet(QString("QLabel { color :%1 }")
+ .arg(Utils::creatorColor(Utils::Theme::TextColorError).name()));
}
void TextEditorStatusBar::clearText()
diff --git a/src/plugins/qmldesigner/components/texteditor/texteditorview.cpp b/src/plugins/qmldesigner/components/texteditor/texteditorview.cpp
index d400251648..2e52b3358b 100644
--- a/src/plugins/qmldesigner/components/texteditor/texteditorview.cpp
+++ b/src/plugins/qmldesigner/components/texteditor/texteditorview.cpp
@@ -96,7 +96,8 @@ void TextEditorView::modelAboutToBeDetached(Model *model)
{
AbstractView::modelAboutToBeDetached(model);
- m_widget->setTextEditor(nullptr);
+ if (m_widget)
+ m_widget->setTextEditor(nullptr);
// in case the user closed it explicit we do not want to do anything with the editor
if (Core::ModeManager::currentModeId() == Core::Constants::MODE_DESIGN) {
diff --git a/src/plugins/qmldesigner/components/texttool/textedititemwidget.cpp b/src/plugins/qmldesigner/components/texttool/textedititemwidget.cpp
index b9c5868bd8..dc57cf30c8 100644
--- a/src/plugins/qmldesigner/components/texttool/textedititemwidget.cpp
+++ b/src/plugins/qmldesigner/components/texttool/textedititemwidget.cpp
@@ -38,26 +38,26 @@ void TextEditItemWidget::paint(QPainter *painter, const QStyleOptionGraphicsItem
QLineEdit* TextEditItemWidget::lineEdit() const
{
- if (m_lineEdit.isNull()) {
- m_lineEdit.reset(new QLineEdit);
+ if (!m_lineEdit) {
+ m_lineEdit = std::make_unique<QLineEdit>();
m_lineEdit->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter);
QPalette palette = m_lineEdit->palette();
- static QColor selectionColor = Utils::creatorTheme()->color(Utils::Theme::QmlDesigner_FormEditorSelectionColor);
+ static QColor selectionColor = Utils::creatorColor(Utils::Theme::QmlDesigner_FormEditorSelectionColor);
palette.setColor(QPalette::Highlight, selectionColor);
palette.setColor(QPalette::HighlightedText, Qt::white);
palette.setColor(QPalette::Base, Qt::white);
palette.setColor(QPalette::Text, Qt::black);
m_lineEdit->setPalette(palette);
}
- return m_lineEdit.data();
+ return m_lineEdit.get();
}
QTextEdit* TextEditItemWidget::textEdit() const
{
- if (m_textEdit.isNull()) {
- m_textEdit.reset(new QTextEdit);
+ if (!m_textEdit) {
+ m_textEdit = std::make_unique<QTextEdit>();
QPalette palette = m_textEdit->palette();
- static QColor selectionColor = Utils::creatorTheme()->color(Utils::Theme::QmlDesigner_FormEditorSelectionColor);
+ static QColor selectionColor = Utils::creatorColor(Utils::Theme::QmlDesigner_FormEditorSelectionColor);
palette.setColor(QPalette::Highlight, selectionColor);
palette.setColor(QPalette::HighlightedText, Qt::white);
palette.setColor(QPalette::Base, Qt::white);
@@ -65,7 +65,7 @@ QTextEdit* TextEditItemWidget::textEdit() const
m_textEdit->setPalette(palette);
}
- return m_textEdit.data();
+ return m_textEdit.get();
}
void TextEditItemWidget::activateTextEdit(const QSize &maximumSize)
@@ -83,19 +83,19 @@ void TextEditItemWidget::activateLineEdit()
QString TextEditItemWidget::text() const
{
- if (widget() == m_lineEdit.data())
+ if (widget() == m_lineEdit.get())
return m_lineEdit->text();
- else if (widget() == m_textEdit.data())
+ else if (widget() == m_textEdit.get())
return m_textEdit->toPlainText();
return QString();
}
void TextEditItemWidget::updateText(const QString &text)
{
- if (widget() == m_lineEdit.data()) {
+ if (widget() == m_lineEdit.get()) {
m_lineEdit->setText(text);
m_lineEdit->selectAll();
- } else if (widget() == m_textEdit.data()) {
+ } else if (widget() == m_textEdit.get()) {
m_textEdit->setText(text);
m_textEdit->selectAll();
}
diff --git a/src/plugins/qmldesigner/components/texttool/textedititemwidget.h b/src/plugins/qmldesigner/components/texttool/textedititemwidget.h
index 8aa6c409f9..f975f1a3da 100644
--- a/src/plugins/qmldesigner/components/texttool/textedititemwidget.h
+++ b/src/plugins/qmldesigner/components/texttool/textedititemwidget.h
@@ -3,7 +3,8 @@
#pragma once
#include <QGraphicsProxyWidget>
-#include <QScopedPointer>
+
+#include <memory>
QT_BEGIN_NAMESPACE
class QTextEdit;
@@ -32,7 +33,7 @@ protected:
QString text() const;
private:
- mutable QScopedPointer<QLineEdit> m_lineEdit;
- mutable QScopedPointer<QTextEdit> m_textEdit;
+ mutable std::unique_ptr<QLineEdit> m_lineEdit;
+ mutable std::unique_ptr<QTextEdit> m_textEdit;
};
} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.cpp b/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.cpp
index 972574554b..415d943723 100644
--- a/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.cpp
+++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.cpp
@@ -42,10 +42,11 @@ static QObject *variantToQObject(const QVariant &value)
namespace QmlDesigner {
-TextureEditorQmlBackend::TextureEditorQmlBackend(TextureEditorView *textureEditor, AsynchronousImageCache &imageCache)
- : m_quickWidget(new QQuickWidget)
- , m_textureEditorTransaction(new TextureEditorTransaction(textureEditor))
- , m_contextObject(new TextureEditorContextObject(m_quickWidget->rootContext()))
+TextureEditorQmlBackend::TextureEditorQmlBackend(TextureEditorView *textureEditor,
+ AsynchronousImageCache &imageCache)
+ : m_quickWidget(Utils::makeUniqueObjectPtr<QQuickWidget>())
+ , m_textureEditorTransaction(std::make_unique<TextureEditorTransaction>(textureEditor))
+ , m_contextObject(std::make_unique<TextureEditorContextObject>(m_quickWidget->rootContext()))
{
QImage defaultImage;
defaultImage.load(Utils::StyleHelper::dpiSpecificImageFile(":/textureeditor/images/texture_default.png"));
@@ -56,7 +57,7 @@ TextureEditorQmlBackend::TextureEditorQmlBackend(TextureEditorView *textureEdito
m_quickWidget->engine()->addImageProvider("qmldesigner_thumbnails", m_textureEditorImageProvider);
m_contextObject->setBackendValues(&m_backendValuesPropertyMap);
m_contextObject->setModel(textureEditor->model());
- context()->setContextObject(m_contextObject.data());
+ context()->setContextObject(m_contextObject.get());
QObject::connect(&m_backendValuesPropertyMap, &DesignerPropertyMap::valueChanged,
textureEditor, &TextureEditorView::changeValue);
@@ -159,7 +160,7 @@ QQmlContext *TextureEditorQmlBackend::context() const
TextureEditorContextObject *TextureEditorQmlBackend::contextObject() const
{
- return m_contextObject.data();
+ return m_contextObject.get();
}
QQuickWidget *TextureEditorQmlBackend::widget() const
@@ -184,7 +185,7 @@ DesignerPropertyMap &TextureEditorQmlBackend::backendValuesPropertyMap()
TextureEditorTransaction *TextureEditorQmlBackend::textureEditorTransaction() const
{
- return m_textureEditorTransaction.data();
+ return m_textureEditorTransaction.get();
}
PropertyEditorValue *TextureEditorQmlBackend::propertyValueForName(const QString &propertyName)
@@ -227,12 +228,9 @@ void TextureEditorQmlBackend::setup(const QmlObjectNode &selectedTextureNode, co
// anchors
m_backendAnchorBinding.setup(selectedTextureNode.modelNode());
- context()->setContextProperties(
- QVector<QQmlContext::PropertyPair>{
- {{"anchorBackend"}, QVariant::fromValue(&m_backendAnchorBinding)},
- {{"transaction"}, QVariant::fromValue(m_textureEditorTransaction.data())}
- }
- );
+ context()->setContextProperties(QVector<QQmlContext::PropertyPair>{
+ {{"anchorBackend"}, QVariant::fromValue(&m_backendAnchorBinding)},
+ {{"transaction"}, QVariant::fromValue(m_textureEditorTransaction.get())}});
contextObject()->setSpecificsUrl(qmlSpecificsFile);
contextObject()->setStateName(stateName);
diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.h b/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.h
index c8a113f7d3..f69c46a569 100644
--- a/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.h
+++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.h
@@ -11,6 +11,8 @@
#include <nodemetainfo.h>
+#include <memory>
+
class PropertyEditorValue;
QT_BEGIN_NAMESPACE
@@ -64,11 +66,11 @@ private:
// this needs be destructed after m_quickWidget->engine() is destructed
DesignerPropertyMap m_backendValuesPropertyMap;
- Utils::UniqueObjectPtr<QQuickWidget> m_quickWidget = nullptr;
+ Utils::UniqueObjectPtr<QQuickWidget> m_quickWidget;
QmlAnchorBindingProxy m_backendAnchorBinding;
QmlModelNodeProxy m_backendModelNode;
- QScopedPointer<TextureEditorTransaction> m_textureEditorTransaction;
- QScopedPointer<TextureEditorContextObject> m_contextObject;
+ std::unique_ptr<TextureEditorTransaction> m_textureEditorTransaction;
+ std::unique_ptr<TextureEditorContextObject> m_contextObject;
AssetImageProvider *m_textureEditorImageProvider = nullptr;
};
diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp b/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp
index 5de3730c97..a637431c4d 100644
--- a/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp
+++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp
@@ -43,7 +43,6 @@
#include <QFileInfo>
#include <QQuickWidget>
#include <QQuickItem>
-#include <QScopedPointer>
#include <QStackedWidget>
#include <QShortcut>
#include <QTimer>
@@ -698,7 +697,8 @@ void TextureEditorView::selectedNodesChanged(const QList<ModelNode> &selectedNod
m_selectedModel = selectedNodeList.at(0);
bool hasValidSelection = QmlObjectNode(m_selectedModel).hasBindingProperty("materials");
- m_qmlBackEnd->contextObject()->setHasSingleModelSelection(hasValidSelection);
+ if (m_qmlBackEnd)
+ m_qmlBackEnd->contextObject()->setHasSingleModelSelection(hasValidSelection);
}
void TextureEditorView::currentStateChanged(const ModelNode &node)
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineanimationform.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelineanimationform.cpp
index e65c05c39f..56e22dab1b 100644
--- a/src/plugins/qmldesigner/components/timelineeditor/timelineanimationform.cpp
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelineanimationform.cpp
@@ -97,10 +97,10 @@ TimelineAnimationForm::TimelineAnimationForm(QWidget *parent)
using namespace Layouting;
Grid {
Span(4, mainL), br,
- empty(), br,
+ empty, br,
idL, Span(2, m_idLineEdit), Span(2, Row{ runningL, m_running }), br,
- empty(), startFrameL, m_startFrame, endFrameL, m_endFrame, durationL, m_duration, br,
- empty(), continuousL, m_continuous, loopsL, m_loops, pingPongL, m_pingPong, str, br,
+ empty, startFrameL, m_startFrame, endFrameL, m_endFrame, durationL, m_duration, br,
+ empty, continuousL, m_continuous, loopsL, m_loops, pingPongL, m_pingPong, str, br,
tr("Transition to state:"), transitionToStateL, m_transitionToState, br,
}.attachTo(this);
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineform.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelineform.cpp
index 08915b6577..2d1e70cd77 100644
--- a/src/plugins/qmldesigner/components/timelineeditor/timelineform.cpp
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelineform.cpp
@@ -78,8 +78,8 @@ TimelineForm::TimelineForm(QWidget *parent)
Grid {
Span(2, mainL), br,
idL, m_idLineEdit, br,
- empty(), Row { startFrameL, m_startFrame, st(), endFrameL, m_endFrame }, str, br,
- empty(), Row { m_expressionBinding, m_animation, st() }, br,
+ empty, Row { startFrameL, m_startFrame, st, endFrameL, m_endFrame }, str, br,
+ empty, Row { m_expressionBinding, m_animation, st }, br,
expressionBindingL, m_expressionBindingLineEdit, br,
}.attachTo(this);
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinepropertyitem.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinepropertyitem.cpp
index 698ef0f03b..762bd85c8b 100644
--- a/src/plugins/qmldesigner/components/timelineeditor/timelinepropertyitem.cpp
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinepropertyitem.cpp
@@ -35,7 +35,6 @@
#include <QLineEdit>
#include <QMenu>
#include <QPainter>
-#include <QScopedPointer>
#include <algorithm>
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinesettingsmodel.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinesettingsmodel.cpp
index 39c1f01ce6..9b85599ead 100644
--- a/src/plugins/qmldesigner/components/timelineeditor/timelinesettingsmodel.cpp
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinesettingsmodel.cpp
@@ -68,7 +68,7 @@ TimelineEditorDelegate::TimelineEditorDelegate(QWidget *parent)
if (factory == nullptr) {
factory = new QItemEditorFactory;
QItemEditorCreatorBase *creator = new QItemEditorCreator<QComboBox>("currentText");
- factory->registerEditor(QVariant::String, creator);
+ factory->registerEditor(QMetaType::QString, creator);
}
setItemEditorFactory(factory);
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.cpp
index 591926d3f5..592cf0dff9 100644
--- a/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.cpp
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.cpp
@@ -221,10 +221,10 @@ TimelineWidget::TimelineWidget(TimelineView *view)
{
QPalette timelinePalette;
- timelinePalette.setColor(QPalette::Text, Utils::creatorTheme()->color(
+ timelinePalette.setColor(QPalette::Text, Utils::creatorColor(
Utils::Theme::DStextColor));
timelinePalette.setColor(QPalette::WindowText, timelinePalette.color(QPalette::Text));
- timelinePalette.setColor(QPalette::Window, Utils::creatorTheme()->color(
+ timelinePalette.setColor(QPalette::Window, Utils::creatorColor(
Utils::Theme::QmlDesigner_BackgroundColorDarkAlternate));
onboardingTopLabel->setPalette(timelinePalette);
diff --git a/src/plugins/qmldesigner/designercore/generatedcomponentutils.cpp b/src/plugins/qmldesigner/designercore/generatedcomponentutils.cpp
index 5ee5790b53..da40c4d387 100644
--- a/src/plugins/qmldesigner/designercore/generatedcomponentutils.cpp
+++ b/src/plugins/qmldesigner/designercore/generatedcomponentutils.cpp
@@ -2,11 +2,36 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "generatedcomponentutils.h"
-
#include <qmldesignerconstants.h>
namespace QmlDesigner {
+bool couldBeProjectModule(const Utils::FilePath &path, const QString &projectName)
+{
+ if (!path.exists())
+ return false;
+
+ Utils::FilePath qmlDirPath = path.pathAppended("qmldir");
+ if (qmlDirPath.exists()) {
+ Utils::expected_str<QByteArray> qmldirContents = qmlDirPath.fileContents();
+ if (!qmldirContents.has_value())
+ return false;
+
+ const QString expectedLine = QLatin1String("module %1").arg(projectName);
+ QByteArray fileContents = *qmldirContents;
+ QTextStream stream(fileContents);
+ while (!stream.atEnd()) {
+ QString lineData = stream.readLine().trimmed();
+ if (lineData.startsWith(u"module "))
+ return lineData == expectedLine;
+ }
+ }
+ if (path.endsWith(projectName))
+ return true;
+
+ return false;
+}
+
GeneratedComponentUtils::GeneratedComponentUtils(ExternalDependenciesInterface &externalDependencies)
: m_externalDependencies(externalDependencies)
{
@@ -60,7 +85,10 @@ Utils::FilePath GeneratedComponentUtils::componentBundlesBasePath() const
if (basePath.isEmpty())
return {};
- return basePath.resolvePath(QLatin1String(Constants::COMPONENT_BUNDLES_TYPE));
+ if (basePath.endsWith(Constants::GENERATED_COMPONENTS_FOLDER))
+ return basePath.resolvePath(QLatin1String(Constants::COMPONENT_BUNDLES_TYPE));
+
+ return basePath.resolvePath(QLatin1String(Constants::OLD_COMPONENT_BUNDLES_TYPE));
}
Utils::FilePath GeneratedComponentUtils::import3dBasePath() const
@@ -77,6 +105,62 @@ Utils::FilePath GeneratedComponentUtils::import3dBasePath() const
return basePath.resolvePath(QLatin1String(Constants::QUICK_3D_COMPONENTS_FOLDER));
}
+Utils::FilePath GeneratedComponentUtils::materialBundlePath() const
+{
+ Utils::FilePath basePath = componentBundlesBasePath();
+
+ if (basePath.isEmpty())
+ return {};
+
+ if (basePath.endsWith(Constants::OLD_COMPONENT_BUNDLES_TYPE))
+ return basePath.resolvePath(QLatin1String(Constants::OLD_COMPONENT_BUNDLES_MATERIAL_BUNDLE_TYPE));
+
+ return basePath.resolvePath(QLatin1String(Constants::COMPONENT_BUNDLES_MATERIAL_BUNDLE_TYPE));
+}
+
+Utils::FilePath GeneratedComponentUtils::effectBundlePath() const
+{
+ Utils::FilePath basePath = componentBundlesBasePath();
+
+ if (basePath.isEmpty())
+ return {};
+
+ if (basePath.endsWith(Constants::OLD_COMPONENT_BUNDLES_TYPE))
+ return basePath.resolvePath(QLatin1String(Constants::OLD_COMPONENT_BUNDLES_EFFECT_BUNDLE_TYPE));
+
+ return basePath.resolvePath(QLatin1String(Constants::COMPONENT_BUNDLES_EFFECT_BUNDLE_TYPE));
+}
+
+Utils::FilePath GeneratedComponentUtils::projectModulePath(bool generateIfNotExists) const
+{
+ using Utils::FilePath;
+ FilePath projectPath = FilePath::fromString(m_externalDependencies.currentProjectDirPath());
+
+ if (projectPath.isEmpty())
+ return {};
+
+ const QString projectName = m_externalDependencies.projectName();
+
+ FilePath newImportDirectory = projectPath.pathAppended(projectName);
+ if (couldBeProjectModule(newImportDirectory, projectName))
+ return newImportDirectory;
+
+ FilePath oldImportDirectory = projectPath.resolvePath(QLatin1String("imports/") + projectName);
+ if (couldBeProjectModule(oldImportDirectory, projectName))
+ return oldImportDirectory;
+
+ for (const QString &path : m_externalDependencies.projectModulePaths()) {
+ FilePath dir = FilePath::fromString(path);
+ if (couldBeProjectModule(dir, projectName))
+ return dir;
+ }
+
+ if (generateIfNotExists)
+ newImportDirectory.createDir();
+
+ return newImportDirectory;
+}
+
bool GeneratedComponentUtils::isImport3dPath(const QString &path) const
{
return path.contains('/' + QLatin1String(Constants::OLD_QUICK_3D_ASSETS_FOLDER))
@@ -87,9 +171,21 @@ bool GeneratedComponentUtils::isImport3dPath(const QString &path) const
bool GeneratedComponentUtils::isComposedEffectPath(const QString &path) const
{
return path.contains(Constants::OLD_EFFECTS_IMPORT_FOLDER)
- || path.contains('/' + QLatin1String(Constants::COMPOSED_EFFECTS_TYPE));
+ || path.contains(QLatin1String(Constants::GENERATED_COMPONENTS_FOLDER) + '/'
+ + QLatin1String(Constants::COMPOSED_EFFECTS_TYPE));
+}
+
+bool GeneratedComponentUtils::isBundlePath(const QString &path) const
+{
+ return path.contains(componentBundlesTypePrefix().replace('.', '/'));
}
+bool GeneratedComponentUtils::isGeneratedPath(const QString &path) const
+{
+ return path.startsWith(generatedComponentsPath().toFSPathString());
+}
+
+
QString GeneratedComponentUtils::generatedComponentTypePrefix() const
{
Utils::FilePath basePath = generatedComponentsPath();
@@ -123,7 +219,7 @@ QString GeneratedComponentUtils::componentBundlesTypePrefix() const
if (basePrefix.endsWith(Constants::GENERATED_COMPONENTS_FOLDER))
return basePrefix + '.' + QLatin1String(Constants::COMPONENT_BUNDLES_TYPE);
- return Constants::COMPONENT_BUNDLES_TYPE;
+ return Constants::OLD_COMPONENT_BUNDLES_TYPE;
}
QString GeneratedComponentUtils::composedEffectsTypePrefix() const
@@ -136,4 +232,60 @@ QString GeneratedComponentUtils::composedEffectsTypePrefix() const
return Constants::OLD_EFFECTS_FOLDER;
}
+QString GeneratedComponentUtils::materialsBundleId() const
+{
+ bool isNewImportDir = generatedComponentTypePrefix().endsWith(Constants::GENERATED_COMPONENTS_FOLDER);
+
+ return QLatin1String(isNewImportDir ? Constants::COMPONENT_BUNDLES_MATERIAL_BUNDLE_TYPE
+ : Constants::OLD_COMPONENT_BUNDLES_MATERIAL_BUNDLE_TYPE);
+}
+
+QString GeneratedComponentUtils::effectsBundleId() const
+{
+ bool isNewImportDir = generatedComponentTypePrefix().endsWith(Constants::GENERATED_COMPONENTS_FOLDER);
+
+ return QLatin1String(isNewImportDir ? Constants::COMPONENT_BUNDLES_EFFECT_BUNDLE_TYPE
+ : Constants::OLD_COMPONENT_BUNDLES_EFFECT_BUNDLE_TYPE);
+}
+
+QString GeneratedComponentUtils::userMaterialsBundleId() const
+{
+ return QLatin1String(Constants::COMPONENT_BUNDLES_USER_MATERIAL_BUNDLE_TYPE);
+}
+
+QString GeneratedComponentUtils::userEffectsBundleId() const
+{
+ return QLatin1String(Constants::COMPONENT_BUNDLES_USER_EFFECT_BUNDLE_TYPE);
+}
+
+QString GeneratedComponentUtils::user3DBundleId() const
+{
+ return QLatin1String(Constants::COMPONENT_BUNDLES_USER_3D_BUNDLE_TYPE);
+}
+
+QString GeneratedComponentUtils::materialsBundleType() const
+{
+ return componentBundlesTypePrefix() + '.' + materialsBundleId();
+}
+
+QString GeneratedComponentUtils::effectsBundleType() const
+{
+ return componentBundlesTypePrefix() + '.' + effectsBundleId();
+}
+
+QString GeneratedComponentUtils::userMaterialsBundleType() const
+{
+ return componentBundlesTypePrefix() + '.' + userMaterialsBundleId();
+}
+
+QString GeneratedComponentUtils::userEffectsBundleType() const
+{
+ return componentBundlesTypePrefix() + '.' + userEffectsBundleId();
+}
+
+QString GeneratedComponentUtils::user3DBundleType() const
+{
+ return componentBundlesTypePrefix() + '.' + user3DBundleId();
+}
+
} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/designercore/generatedcomponentutils.h b/src/plugins/qmldesigner/designercore/generatedcomponentutils.h
index e2e9baf3e7..ceddb405a3 100644
--- a/src/plugins/qmldesigner/designercore/generatedcomponentutils.h
+++ b/src/plugins/qmldesigner/designercore/generatedcomponentutils.h
@@ -21,9 +21,14 @@ public:
Utils::FilePath composedEffectPath(const QString &effectPath) const;
Utils::FilePath componentBundlesBasePath() const;
Utils::FilePath import3dBasePath() const;
+ Utils::FilePath materialBundlePath() const;
+ Utils::FilePath effectBundlePath() const;
+ Utils::FilePath projectModulePath(bool generateIfNotExists = false) const;
bool isImport3dPath(const QString &path) const;
bool isComposedEffectPath(const QString &path) const;
+ bool isBundlePath(const QString &path) const;
+ bool isGeneratedPath(const QString &path) const;
QString generatedComponentTypePrefix() const;
QString import3dTypePrefix() const;
@@ -31,6 +36,18 @@ public:
QString componentBundlesTypePrefix() const;
QString composedEffectsTypePrefix() const;
+ QString materialsBundleId() const;
+ QString effectsBundleId() const;
+ QString userMaterialsBundleId() const;
+ QString userEffectsBundleId() const;
+ QString user3DBundleId() const;
+
+ QString materialsBundleType() const;
+ QString effectsBundleType() const;
+ QString userMaterialsBundleType() const;
+ QString userEffectsBundleType() const;
+ QString user3DBundleType() const;
+
private:
ExternalDependenciesInterface &m_externalDependencies;
};
diff --git a/src/plugins/qmldesigner/designercore/include/externaldependenciesinterface.h b/src/plugins/qmldesigner/designercore/include/externaldependenciesinterface.h
index 71ddeb7dc1..9055f51b6a 100644
--- a/src/plugins/qmldesigner/designercore/include/externaldependenciesinterface.h
+++ b/src/plugins/qmldesigner/designercore/include/externaldependenciesinterface.h
@@ -28,6 +28,7 @@ public:
virtual QString qmlPuppetFallbackDirectory() const = 0;
virtual QString defaultPuppetToplevelBuildDirectory() const = 0;
virtual QUrl projectUrl() const = 0;
+ virtual QString projectName() const = 0;
virtual QString currentProjectDirPath() const = 0;
virtual QUrl currentResourcePath() const = 0;
virtual void parseItemLibraryDescriptions() = 0;
diff --git a/src/plugins/qmldesigner/designercore/include/itemlibraryentry.h b/src/plugins/qmldesigner/designercore/include/itemlibraryentry.h
index f88f9e35c6..2d0f2ef31e 100644
--- a/src/plugins/qmldesigner/designercore/include/itemlibraryentry.h
+++ b/src/plugins/qmldesigner/designercore/include/itemlibraryentry.h
@@ -42,13 +42,16 @@ class QMLDESIGNERCORE_EXPORT ItemLibraryEntry
public:
ItemLibraryEntry();
- explicit ItemLibraryEntry(const Storage::Info::ItemLibraryEntry &entry,
- const ProjectStorageType &projectStorage);
- ~ItemLibraryEntry() = default;
+ ItemLibraryEntry(const ItemLibraryEntry &) = default;
+ ItemLibraryEntry &operator=(const ItemLibraryEntry &) = default;
+ ItemLibraryEntry(ItemLibraryEntry &&) = default;
+ ItemLibraryEntry &operator=(ItemLibraryEntry &&) = default;
+ explicit ItemLibraryEntry(const Storage::Info::ItemLibraryEntry &entry);
+ ~ItemLibraryEntry();
QString name() const;
TypeName typeName() const;
- const NodeMetaInfo &metaInfo() const;
+ TypeId typeId() const;
QIcon typeIcon() const;
QString libraryEntryIconPath() const;
int majorVersion() const;
@@ -86,7 +89,7 @@ private:
using ItemLibraryEntries = QList<ItemLibraryEntry>;
QMLDESIGNERCORE_EXPORT QList<ItemLibraryEntry> toItemLibraryEntries(
- const Storage::Info::ItemLibraryEntries &entries, const ProjectStorageType &projectStorage);
+ const Storage::Info::ItemLibraryEntries &entries);
} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/designercore/include/model.h b/src/plugins/qmldesigner/designercore/include/model.h
index 85f129cdbc..39b5cdaa81 100644
--- a/src/plugins/qmldesigner/designercore/include/model.h
+++ b/src/plugins/qmldesigner/designercore/include/model.h
@@ -137,7 +137,7 @@ public:
ModelPointer createModel(const TypeName &typeName,
std::unique_ptr<ModelResourceManagementInterface> resourceManagement = {});
- QUrl fileUrl() const;
+ const QUrl &fileUrl() const;
SourceId fileUrlSourceId() const;
void setFileUrl(const QUrl &url);
@@ -147,7 +147,7 @@ public:
void setMetaInfo(const MetaInfo &metaInfo);
#endif
- Module module(Utils::SmallStringView moduleName);
+ Module module(Utils::SmallStringView moduleName, Storage::ModuleKind moduleKind);
NodeMetaInfo metaInfo(const TypeName &typeName, int majorVersion = -1, int minorVersion = -1) const;
NodeMetaInfo metaInfo(Module module,
Utils::SmallStringView typeName,
@@ -255,10 +255,7 @@ public:
bool hasId(const QString &id) const;
bool hasImport(const QString &importUrl) const;
- QString generateNewId(const QString &prefixName,
- const QString &fallbackPrefix = "element",
- std::optional<std::function<bool(const QString &)>> isDuplicate = {}) const;
- QString generateIdFromName(const QString &name, const QString &fallbackId = "element") const;
+ QString generateNewId(const QString &prefixName, const QString &fallbackPrefix = "element") const;
void startDrag(QMimeData *mimeData, const QPixmap &icon);
void endDrag();
diff --git a/src/plugins/qmldesigner/designercore/include/nodehints.h b/src/plugins/qmldesigner/designercore/include/nodehints.h
index 9e67c2d99b..99470db65f 100644
--- a/src/plugins/qmldesigner/designercore/include/nodehints.h
+++ b/src/plugins/qmldesigner/designercore/include/nodehints.h
@@ -3,9 +3,11 @@
#pragma once
+#include "modelnode.h"
+#include "nodemetainfo.h"
+
#include <QList>
#include <QString>
-#include "modelnode.h"
#include "qmldesignercorelib_global.h"
#include "invalidmetainfoexception.h"
@@ -54,18 +56,19 @@ public:
QHash<QString, QString> hints() const;
static NodeHints fromModelNode(const ModelNode &modelNode);
- static NodeHints fromItemLibraryEntry(const ItemLibraryEntry &entry);
+ static NodeHints fromItemLibraryEntry(const ItemLibraryEntry &entry, Model *model);
private:
explicit NodeHints(const ModelNode &modelNode);
explicit NodeHints(const NodeMetaInfo &metaInfo);
- explicit NodeHints(const ItemLibraryEntry &entry);
+ explicit NodeHints(const ItemLibraryEntry &entry, Model *model);
const ModelNode &modelNode() const;
bool isValid() const;
Model *model() const;
bool evaluateBooleanExpression(const QString &hintName, bool defaultValue, const ModelNode potentialParent = ModelNode()) const;
ModelNode m_modelNode;
+ NodeMetaInfo m_metaInfo;
QHash<QString, QString> m_hints;
};
diff --git a/src/plugins/qmldesigner/designercore/include/nodemetainfo.h b/src/plugins/qmldesigner/designercore/include/nodemetainfo.h
index ba2e2cda65..fd3f2f9be8 100644
--- a/src/plugins/qmldesigner/designercore/include/nodemetainfo.h
+++ b/src/plugins/qmldesigner/designercore/include/nodemetainfo.h
@@ -28,10 +28,14 @@ QT_END_NAMESPACE
[[deprecated( \
"In most cases you don't need them anymore because the import is setting them!")]]
# define DEPRECATED_COMPONENT_FILE_NAME [[deprecated("Use sourceId() instead.")]]
+# define DEPRECATED_IMPORT_DIRECTORY_PATH [[deprecated("Use allExportedTypeNames().")]]
+# define DEPRECATED_REQUIRED_IMPORT_STRING [[deprecated("Use allExportedTypeNames().")]]
#else
# define DEPRECATED_TYPENAME
# define DEPRECATED_VERSION_NUMBER
# define DEPRECATED_COMPONENT_FILE_NAME
+# define DEPRECATED_IMPORT_DIRECTORY_PATH
+# define DEPRECATED_REQUIRED_IMPORT_STRING
#endif
namespace QmlDesigner {
@@ -181,6 +185,7 @@ public:
bool isQtQuick3DLight() const;
bool isQtQuickListModel() const;
bool isQtQuickListView() const;
+ bool isQtQuickGridView() const;
bool isQtQuick3DMaterial() const;
bool isQtQuick3DModel() const;
bool isQtQuick3DNode() const;
@@ -237,8 +242,8 @@ public:
bool usesCustomParser() const;
bool isEnumeration() const;
- QString importDirectoryPath() const;
- QString requiredImportString() const;
+ DEPRECATED_IMPORT_DIRECTORY_PATH QString importDirectoryPath() const;
+ DEPRECATED_REQUIRED_IMPORT_STRING QString requiredImportString() const;
friend bool operator==(const NodeMetaInfo &first, const NodeMetaInfo &second)
{
diff --git a/src/plugins/qmldesigner/designercore/include/projectstorageids.h b/src/plugins/qmldesigner/designercore/include/projectstorageids.h
index 0b157e55e7..7c2e8c4b25 100644
--- a/src/plugins/qmldesigner/designercore/include/projectstorageids.h
+++ b/src/plugins/qmldesigner/designercore/include/projectstorageids.h
@@ -48,6 +48,8 @@ using EnumerationDeclarationIds = std::vector<EnumerationDeclarationId>;
using SourceContextId = Sqlite::BasicId<BasicIdType::SourceContext, int>;
using SourceContextIds = std::vector<SourceContextId>;
+template<std::size_t size>
+using SmallSourceContextIds = QVarLengthArray<SourceContextId, size>;
using SourceId = Sqlite::BasicId<BasicIdType::Source, int>;
using SourceIds = std::vector<SourceId>;
diff --git a/src/plugins/qmldesigner/designercore/include/rewriterview.h b/src/plugins/qmldesigner/designercore/include/rewriterview.h
index 0134349682..92c79c7863 100644
--- a/src/plugins/qmldesigner/designercore/include/rewriterview.h
+++ b/src/plugins/qmldesigner/designercore/include/rewriterview.h
@@ -8,11 +8,11 @@
#include "documentmessage.h"
#include "rewritertransaction.h"
-#include <QScopedPointer>
#include <QTimer>
#include <QUrl>
#include <functional>
+#include <memory>
namespace QmlJS {
class Document;
@@ -203,9 +203,9 @@ private: //variables
bool m_checkLinkErrors = true;
DifferenceHandling m_differenceHandling;
- QScopedPointer<Internal::ModelNodePositionStorage> m_positionStorage;
- QScopedPointer<Internal::ModelToTextMerger> m_modelToTextMerger;
- QScopedPointer<Internal::TextToModelMerger> m_textToModelMerger;
+ std::unique_ptr<Internal::ModelNodePositionStorage> m_positionStorage;
+ std::unique_ptr<Internal::ModelToTextMerger> m_modelToTextMerger;
+ std::unique_ptr<Internal::TextToModelMerger> m_textToModelMerger;
QList<DocumentMessage> m_errors;
QList<DocumentMessage> m_warnings;
RewriterTransaction m_removeDefaultPropertyTransaction;
diff --git a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp
index 1b965db66a..484f18e42b 100644
--- a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp
+++ b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp
@@ -91,7 +91,6 @@
#include <QMultiHash>
#include <QPainter>
#include <QPicture>
-#include <QScopedPointer>
#include <QTimerEvent>
#include <QUrl>
@@ -1031,8 +1030,20 @@ TypeName createQualifiedTypeName(const ModelNode &node)
auto exportedTypes = node.metaInfo().exportedTypeNamesForSourceId(model->fileUrlSourceId());
if (exportedTypes.size()) {
const auto &exportedType = exportedTypes.front();
- Utils::PathString typeName = model->projectStorage()->moduleName(exportedType.moduleId);
- typeName += '/';
+ using Storage::ModuleKind;
+ auto module = model->projectStorage()->module(exportedType.moduleId);
+ Utils::PathString typeName;
+ switch (module.kind) {
+ case ModuleKind::QmlLibrary:
+ typeName += module.name;
+ typeName += '/';
+ break;
+ case ModuleKind::PathLibrary:
+ break;
+ case ModuleKind::CppLibrary:
+ break;
+ }
+
typeName += exportedType.name;
return typeName.toQByteArray();
@@ -1205,6 +1216,13 @@ CreateSceneCommand NodeInstanceView::createCreateSceneCommand()
if (stateNode.isValid() && stateNode.metaInfo().isQtQuickState())
stateInstanceId = stateNode.internalId();
+ QHash<QString, QVariantMap> sceneStates = m_edit3DToolStates[model()->fileUrl()];
+ QHash<QString, QVariantMap> projectStates = m_edit3DToolStates[
+ QUrl::fromLocalFile(m_externalDependencies.currentProjectDirPath())];
+ const QString ptsId = "@PTS";
+ if (projectStates.contains(ptsId))
+ sceneStates.insert(ptsId, projectStates[ptsId]);
+
return CreateSceneCommand(instanceContainerList,
reparentContainerList,
idContainerList,
@@ -1215,7 +1233,7 @@ CreateSceneCommand NodeInstanceView::createCreateSceneCommand()
mockupTypesVector,
model()->fileUrl(),
m_externalDependencies.currentResourcePath(),
- m_edit3DToolStates[model()->fileUrl()],
+ sceneStates,
lastUsedLanguage,
m_captureImageMinimumSize,
m_captureImageMaximumSize,
@@ -1733,7 +1751,12 @@ void NodeInstanceView::handlePuppetToCreatorCommand(const PuppetToCreatorCommand
auto data = qvariant_cast<QVariantList>(command.data());
if (data.size() == 3) {
QString qmlId = data[0].toString();
- m_edit3DToolStates[model()->fileUrl()][qmlId].insert(data[1].toString(), data[2]);
+ QUrl mainKey;
+ if (qmlId == "@PTS") // Project tool state
+ mainKey = QUrl::fromLocalFile(m_externalDependencies.currentProjectDirPath());
+ else
+ mainKey = model()->fileUrl();
+ m_edit3DToolStates[mainKey][qmlId].insert(data[1].toString(), data[2]);
}
}
} else if (command.type() == PuppetToCreatorCommand::Render3DView) {
@@ -1847,7 +1870,7 @@ QVariant NodeInstanceView::modelNodePreviewImageDataToVariant(const ModelNodePre
placeHolder = {150, 150};
// Placeholder has transparency, but we don't want to show the checkerboard, so
// paint in the correct background color
- placeHolder.fill(Utils::creatorTheme()->color(Utils::Theme::BackgroundColorNormal));
+ placeHolder.fill(Utils::creatorColor(Utils::Theme::BackgroundColorNormal));
QPainter painter(&placeHolder);
painter.drawPixmap(0, 0, 150, 150, placeHolderSrc);
}
diff --git a/src/plugins/qmldesigner/designercore/metainfo/itemlibraryentry.cpp b/src/plugins/qmldesigner/designercore/metainfo/itemlibraryentry.cpp
index 806da7e7c4..2aec766002 100644
--- a/src/plugins/qmldesigner/designercore/metainfo/itemlibraryentry.cpp
+++ b/src/plugins/qmldesigner/designercore/metainfo/itemlibraryentry.cpp
@@ -22,7 +22,7 @@ class ItemLibraryEntryData
public:
QString name;
TypeName typeName;
- NodeMetaInfo metaInfo;
+ TypeId typeId;
QString category;
int majorVersion{-1};
int minorVersion{-1};
@@ -64,12 +64,12 @@ ItemLibraryEntry::ItemLibraryEntry()
: m_data(std::make_shared<Internal::ItemLibraryEntryData>())
{}
-ItemLibraryEntry::ItemLibraryEntry(const Storage::Info::ItemLibraryEntry &entry,
- const ProjectStorageType &projectStorage)
+ItemLibraryEntry::ItemLibraryEntry(const Storage::Info::ItemLibraryEntry &entry)
: ItemLibraryEntry{}
{
m_data->name = entry.name.toQString();
- m_data->metaInfo = {entry.typeId, &projectStorage};
+ m_data->typeId = entry.typeId;
+ m_data->typeName = entry.typeName.toQByteArray();
m_data->category = entry.category.toQString();
if (entry.iconPath.size())
m_data->libraryEntryIconPath = entry.iconPath.toQString();
@@ -87,6 +87,8 @@ ItemLibraryEntry::ItemLibraryEntry(const Storage::Info::ItemLibraryEntry &entry,
m_data->extraFilePaths.emplace_back(extraFilePath.toQString());
}
+ItemLibraryEntry::~ItemLibraryEntry() = default;
+
QString ItemLibraryEntry::name() const
{
return m_data->name;
@@ -97,9 +99,9 @@ TypeName ItemLibraryEntry::typeName() const
return m_data->typeName;
}
-const NodeMetaInfo &ItemLibraryEntry::metaInfo() const
+TypeId ItemLibraryEntry::typeId() const
{
- return m_data->metaInfo;
+ return m_data->typeId;
}
QString ItemLibraryEntry::qmlSource() const
@@ -245,6 +247,7 @@ QDataStream &operator<<(QDataStream &stream, const ItemLibraryEntry &itemLibrary
stream << itemLibraryEntry.m_data->qmlSource;
stream << itemLibraryEntry.m_data->customComponentSource;
stream << itemLibraryEntry.m_data->extraFilePaths;
+ stream << itemLibraryEntry.m_data->typeId.internalId();
return stream;
}
@@ -270,6 +273,9 @@ QDataStream &operator>>(QDataStream &stream, ItemLibraryEntry &itemLibraryEntry)
stream >> itemLibraryEntry.m_data->qmlSource;
stream >> itemLibraryEntry.m_data->customComponentSource;
stream >> itemLibraryEntry.m_data->extraFilePaths;
+ TypeId::DatabaseType internalTypeId;
+ stream >> internalTypeId;
+ itemLibraryEntry.m_data->typeId = TypeId::create(internalTypeId);
return stream;
}
@@ -295,11 +301,10 @@ QDebug operator<<(QDebug debug, const ItemLibraryEntry &itemLibraryEntry)
return debug.space();
}
-QList<ItemLibraryEntry> toItemLibraryEntries(const Storage::Info::ItemLibraryEntries &entries,
- const ProjectStorageType &projectStorage)
+QList<ItemLibraryEntry> toItemLibraryEntries(const Storage::Info::ItemLibraryEntries &entries)
{
return Utils::transform<QList<ItemLibraryEntry>>(entries, [&](const auto &entry) {
- return ItemLibraryEntry{entry, projectStorage};
+ return ItemLibraryEntry{entry};
});
}
diff --git a/src/plugins/qmldesigner/designercore/metainfo/metainforeader.cpp b/src/plugins/qmldesigner/designercore/metainfo/metainforeader.cpp
index af61ef6573..0addc6884d 100644
--- a/src/plugins/qmldesigner/designercore/metainfo/metainforeader.cpp
+++ b/src/plugins/qmldesigner/designercore/metainfo/metainforeader.cpp
@@ -272,7 +272,7 @@ inline QString deEscape(const QString &value)
inline QVariant deEscapeVariant(const QVariant &value)
{
- if (value.typeId() == QVariant::String)
+ if (value.typeId() == QMetaType::QString)
return deEscape(value.toString());
return value;
}
diff --git a/src/plugins/qmldesigner/designercore/metainfo/nodehints.cpp b/src/plugins/qmldesigner/designercore/metainfo/nodehints.cpp
index 32e68a3cdc..1f9a3e42bd 100644
--- a/src/plugins/qmldesigner/designercore/metainfo/nodehints.cpp
+++ b/src/plugins/qmldesigner/designercore/metainfo/nodehints.cpp
@@ -106,14 +106,15 @@ QmlDesigner::NodeHints::NodeHints(const ModelNode &node)
}
NodeHints::NodeHints(const NodeMetaInfo &metaInfo)
+ : m_metaInfo{metaInfo}
{
for (const auto &[name, expression] : metaInfo.typeHints())
m_hints.insert(name.toQString(), expression.toQString());
}
-NodeHints::NodeHints(const ItemLibraryEntry &entry)
+NodeHints::NodeHints(const ItemLibraryEntry &entry, [[maybe_unused]] Model *model)
#ifdef QDS_USE_PROJECTSTORAGE
- : NodeHints{entry.metaInfo()}
+ : NodeHints{NodeMetaInfo{entry.typeId(), model->projectStorage()}}
#endif
{
if constexpr (!useProjectStorage())
@@ -135,7 +136,7 @@ bool NodeHints::canBeContainerFor(const ModelNode &potenialChild) const
if (!isValid())
return true;
- auto flagIs = m_modelNode.metaInfo().canBeContainer();
+ auto flagIs = m_metaInfo.canBeContainer();
if (flagIs != FlagIs::Set)
return convert(flagIs);
@@ -151,7 +152,7 @@ bool NodeHints::forceClip() const
if (isSwipeView(modelNode()))
return true;
- auto flagIs = m_modelNode.metaInfo().forceClip();
+ auto flagIs = m_metaInfo.forceClip();
if (flagIs != FlagIs::Set)
return convert(flagIs);
@@ -167,7 +168,7 @@ bool NodeHints::doesLayoutChildren() const
if (isSwipeView(modelNode()))
return true;
- auto flagIs = m_modelNode.metaInfo().doesLayoutChildren();
+ auto flagIs = m_metaInfo.doesLayoutChildren();
if (flagIs != FlagIs::Set)
return convert(flagIs);
@@ -177,7 +178,7 @@ bool NodeHints::doesLayoutChildren() const
bool NodeHints::canBeDroppedInFormEditor() const
{
- auto flagIs = m_modelNode.metaInfo().canBeDroppedInFormEditor();
+ auto flagIs = m_metaInfo.canBeDroppedInFormEditor();
if (flagIs != FlagIs::Set)
return convert(flagIs);
@@ -187,7 +188,7 @@ bool NodeHints::canBeDroppedInFormEditor() const
bool NodeHints::canBeDroppedInNavigator() const
{
- auto flagIs = m_modelNode.metaInfo().canBeDroppedInNavigator();
+ auto flagIs = m_metaInfo.canBeDroppedInNavigator();
if (flagIs != FlagIs::Set)
return convert(flagIs);
@@ -197,7 +198,7 @@ bool NodeHints::canBeDroppedInNavigator() const
bool NodeHints::canBeDroppedInView3D() const
{
- auto flagIs = m_modelNode.metaInfo().canBeDroppedInView3D();
+ auto flagIs = m_metaInfo.canBeDroppedInView3D();
if (flagIs != FlagIs::Set)
return convert(flagIs);
@@ -210,7 +211,7 @@ bool NodeHints::isMovable() const
if (!isValid())
return true;
- auto flagIs = m_modelNode.metaInfo().isMovable();
+ auto flagIs = m_metaInfo.isMovable();
if (flagIs != FlagIs::Set)
return convert(flagIs);
@@ -223,7 +224,7 @@ bool NodeHints::isResizable() const
if (!isValid())
return true;
- auto flagIs = m_modelNode.metaInfo().isResizable();
+ auto flagIs = m_metaInfo.isResizable();
if (flagIs != FlagIs::Set)
return convert(flagIs);
@@ -236,7 +237,7 @@ bool NodeHints::hasFormEditorItem() const
if (!isValid())
return true;
- auto flagIs = m_modelNode.metaInfo().hasFormEditorItem();
+ auto flagIs = m_metaInfo.hasFormEditorItem();
if (flagIs != FlagIs::Set)
return convert(flagIs);
@@ -252,7 +253,7 @@ bool NodeHints::isStackedContainer() const
if (isSwipeView(modelNode()))
return true;
- auto flagIs = m_modelNode.metaInfo().isStackedContainer();
+ auto flagIs = m_metaInfo.isStackedContainer();
if (flagIs != FlagIs::Set)
return convert(flagIs);
@@ -299,7 +300,7 @@ bool NodeHints::takesOverRenderingOfChildren() const
if (!isValid())
return false;
- auto flagIs = m_modelNode.metaInfo().takesOverRenderingOfChildren();
+ auto flagIs = m_metaInfo.takesOverRenderingOfChildren();
if (flagIs != FlagIs::Set)
return convert(flagIs);
@@ -312,7 +313,7 @@ bool NodeHints::visibleInNavigator() const
if (!isValid())
return false;
- auto flagIs = m_modelNode.metaInfo().visibleInNavigator();
+ auto flagIs = m_metaInfo.visibleInNavigator();
if (flagIs != FlagIs::Set)
return convert(flagIs);
@@ -322,7 +323,7 @@ bool NodeHints::visibleInNavigator() const
bool NodeHints::visibleInLibrary() const
{
- auto flagIs = m_modelNode.metaInfo().visibleInLibrary();
+ auto flagIs = m_metaInfo.visibleInLibrary();
if (flagIs != FlagIs::Set)
return convert(flagIs);
@@ -391,9 +392,9 @@ NodeHints NodeHints::fromModelNode(const ModelNode &modelNode)
return NodeHints(modelNode);
}
-NodeHints NodeHints::fromItemLibraryEntry(const ItemLibraryEntry &entry)
+NodeHints NodeHints::fromItemLibraryEntry(const ItemLibraryEntry &entry, Model *model)
{
- return NodeHints(entry);
+ return NodeHints(entry, model);
}
const ModelNode &NodeHints::modelNode() const
diff --git a/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp b/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp
index b8c3e610be..c656634272 100644
--- a/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp
+++ b/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp
@@ -53,6 +53,10 @@ NodeMetaInfo object will result in an InvalidMetaInfoException being thrown.
namespace {
+using Storage::ModuleKind;
+
+auto category = MetaInfoTracing::category;
+
struct TypeDescription
{
QString className;
@@ -1493,15 +1497,20 @@ MetaInfoType NodeMetaInfo::type() const
{
if constexpr (useProjectStorage()) {
if (isValid()) {
- switch (typeData().traits.kind) {
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"get type"_t, category(), keyValue("type id", m_typeId)};
+ auto kind = typeData().traits.kind;
+ tracer.end(keyValue("type kind", kind));
+
+ switch (kind) {
case Storage::TypeTraitsKind::Reference:
return MetaInfoType::Reference;
case Storage::TypeTraitsKind::Value:
return MetaInfoType::Value;
case Storage::TypeTraitsKind::Sequence:
return MetaInfoType::Sequence;
- default:
- break;
+ case Storage::TypeTraitsKind::None:
+ return MetaInfoType::None;
}
}
}
@@ -1511,16 +1520,38 @@ MetaInfoType NodeMetaInfo::type() const
bool NodeMetaInfo::isFileComponent() const
{
- if constexpr (useProjectStorage())
- return isValid() && typeData().traits.isFileComponent;
- else
+ if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return {};
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is file component"_t, category(), keyValue("type id", m_typeId)};
+
+ auto isFileComponent = typeData().traits.isFileComponent;
+
+ tracer.end(keyValue("is file component", isFileComponent));
+
+ return isFileComponent;
+
+ } else {
return isValid() && m_privateData->isFileComponent();
+ }
}
bool NodeMetaInfo::isProjectComponent() const
{
if constexpr (useProjectStorage()) {
- return isValid() && typeData().traits.isProjectComponent;
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is project component"_t, category(), keyValue("type id", m_typeId)};
+
+ auto isProjectComponent = typeData().traits.isProjectComponent;
+
+ tracer.end(keyValue("is project component", isProjectComponent));
+
+ return isProjectComponent;
}
return false;
@@ -1529,7 +1560,17 @@ bool NodeMetaInfo::isProjectComponent() const
bool NodeMetaInfo::isInProjectModule() const
{
if constexpr (useProjectStorage()) {
- return isValid() && typeData().traits.isInProjectModule;
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is project module"_t, category(), keyValue("type id", m_typeId)};
+
+ auto isInProjectModule = typeData().traits.isInProjectModule;
+
+ tracer.end(keyValue("is project module", isInProjectModule));
+
+ return isInProjectModule;
}
return false;
@@ -1538,10 +1579,17 @@ bool NodeMetaInfo::isInProjectModule() const
FlagIs NodeMetaInfo::canBeContainer() const
{
if constexpr (useProjectStorage()) {
- if (isValid())
- return typeData().traits.canBeContainer;
+ if (!isValid())
+ return FlagIs::False;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"can be container"_t, category(), keyValue("type id", m_typeId)};
- return FlagIs::False;
+ auto canBeContainer = typeData().traits.canBeContainer;
+
+ tracer.end(keyValue("can be container", canBeContainer));
+
+ return canBeContainer;
}
return FlagIs::Set;
@@ -1550,10 +1598,17 @@ FlagIs NodeMetaInfo::canBeContainer() const
FlagIs NodeMetaInfo::forceClip() const
{
if constexpr (useProjectStorage()) {
- if (isValid())
- return typeData().traits.forceClip;
+ if (!isValid())
+ return FlagIs::False;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"force clip"_t, category(), keyValue("type id", m_typeId)};
+
+ auto forceClip = typeData().traits.forceClip;
- return FlagIs::False;
+ tracer.end(keyValue("force clip", forceClip));
+
+ return forceClip;
}
return FlagIs::Set;
@@ -1562,10 +1617,17 @@ FlagIs NodeMetaInfo::forceClip() const
FlagIs NodeMetaInfo::doesLayoutChildren() const
{
if constexpr (useProjectStorage()) {
- if (isValid())
- return typeData().traits.doesLayoutChildren;
+ if (!isValid())
+ return FlagIs::False;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"does layout children"_t, category(), keyValue("type id", m_typeId)};
+
+ auto doesLayoutChildren = typeData().traits.doesLayoutChildren;
+
+ tracer.end(keyValue("does layout children", doesLayoutChildren));
- return FlagIs::False;
+ return doesLayoutChildren;
}
return FlagIs::Set;
@@ -1574,10 +1636,19 @@ FlagIs NodeMetaInfo::doesLayoutChildren() const
FlagIs NodeMetaInfo::canBeDroppedInFormEditor() const
{
if constexpr (useProjectStorage()) {
- if (isValid())
- return typeData().traits.canBeDroppedInFormEditor;
+ if (!isValid())
+ return FlagIs::False;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"can be dropped in form editor"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
+ auto canBeDroppedInFormEditor = typeData().traits.canBeDroppedInFormEditor;
+
+ tracer.end(keyValue("can be dropped in form editor", canBeDroppedInFormEditor));
- return FlagIs::False;
+ return canBeDroppedInFormEditor;
}
return FlagIs::Set;
@@ -1586,10 +1657,19 @@ FlagIs NodeMetaInfo::canBeDroppedInFormEditor() const
FlagIs NodeMetaInfo::canBeDroppedInNavigator() const
{
if constexpr (useProjectStorage()) {
- if (isValid())
- return typeData().traits.canBeDroppedInNavigator;
+ if (!isValid())
+ return FlagIs::False;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"can be dropped in navigator"_t,
+ category(),
+ keyValue("type id", m_typeId)};
- return FlagIs::False;
+ auto canBeDroppedInNavigator = typeData().traits.canBeDroppedInNavigator;
+
+ tracer.end(keyValue("can be dropped in navigator", canBeDroppedInNavigator));
+
+ return canBeDroppedInNavigator;
}
return FlagIs::Set;
@@ -1598,10 +1678,19 @@ FlagIs NodeMetaInfo::canBeDroppedInNavigator() const
FlagIs NodeMetaInfo::canBeDroppedInView3D() const
{
if constexpr (useProjectStorage()) {
- if (isValid())
- return typeData().traits.canBeDroppedInView3D;
+ if (!isValid())
+ return FlagIs::False;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"can be dropped in view3d"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
+ auto canBeDroppedInView3D = typeData().traits.canBeDroppedInView3D;
- return FlagIs::False;
+ tracer.end(keyValue("can be dropped in view3d", canBeDroppedInView3D));
+
+ return canBeDroppedInView3D;
}
return FlagIs::Set;
@@ -1610,10 +1699,17 @@ FlagIs NodeMetaInfo::canBeDroppedInView3D() const
FlagIs NodeMetaInfo::isMovable() const
{
if constexpr (useProjectStorage()) {
- if (isValid())
- return typeData().traits.isMovable;
+ if (!isValid())
+ return FlagIs::False;
- return FlagIs::False;
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is movable"_t, category(), keyValue("type id", m_typeId)};
+
+ auto isMovable = typeData().traits.isMovable;
+
+ tracer.end(keyValue("is movable", isMovable));
+
+ return isMovable;
}
return FlagIs::Set;
@@ -1622,10 +1718,17 @@ FlagIs NodeMetaInfo::isMovable() const
FlagIs NodeMetaInfo::isResizable() const
{
if constexpr (useProjectStorage()) {
- if (isValid())
- return typeData().traits.isResizable;
+ if (!isValid())
+ return FlagIs::False;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is resizable"_t, category(), keyValue("type id", m_typeId)};
- return FlagIs::False;
+ auto isResizable = typeData().traits.isResizable;
+
+ tracer.end(keyValue("is resizable", isResizable));
+
+ return isResizable;
}
return FlagIs::Set;
@@ -1634,10 +1737,17 @@ FlagIs NodeMetaInfo::isResizable() const
FlagIs NodeMetaInfo::hasFormEditorItem() const
{
if constexpr (useProjectStorage()) {
- if (isValid())
- return typeData().traits.hasFormEditorItem;
+ if (!isValid())
+ return FlagIs::False;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"has form editor item"_t, category(), keyValue("type id", m_typeId)};
- return FlagIs::False;
+ auto hasFormEditorItem = typeData().traits.hasFormEditorItem;
+
+ tracer.end(keyValue("has form editor item", hasFormEditorItem));
+
+ return hasFormEditorItem;
}
return FlagIs::Set;
@@ -1646,10 +1756,17 @@ FlagIs NodeMetaInfo::hasFormEditorItem() const
FlagIs NodeMetaInfo::isStackedContainer() const
{
if constexpr (useProjectStorage()) {
- if (isValid())
- return typeData().traits.isStackedContainer;
+ if (!isValid())
+ return FlagIs::False;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is stacked container"_t, category(), keyValue("type id", m_typeId)};
+
+ auto isStackedContainer = typeData().traits.isStackedContainer;
- return FlagIs::False;
+ tracer.end(keyValue("is stacked container", isStackedContainer));
+
+ return isStackedContainer;
}
return FlagIs::Set;
@@ -1658,10 +1775,19 @@ FlagIs NodeMetaInfo::isStackedContainer() const
FlagIs NodeMetaInfo::takesOverRenderingOfChildren() const
{
if constexpr (useProjectStorage()) {
- if (isValid())
- return typeData().traits.takesOverRenderingOfChildren;
+ if (!isValid())
+ return FlagIs::False;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"takes over rendering of children"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
+ auto takesOverRenderingOfChildren = typeData().traits.takesOverRenderingOfChildren;
+
+ tracer.end(keyValue("takes over rendering of children", takesOverRenderingOfChildren));
- return FlagIs::False;
+ return takesOverRenderingOfChildren;
}
return FlagIs::Set;
@@ -1670,10 +1796,17 @@ FlagIs NodeMetaInfo::takesOverRenderingOfChildren() const
FlagIs NodeMetaInfo::visibleInNavigator() const
{
if constexpr (useProjectStorage()) {
- if (isValid())
- return typeData().traits.visibleInNavigator;
+ if (!isValid())
+ return FlagIs::False;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"visible in navigator"_t, category(), keyValue("type id", m_typeId)};
- return FlagIs::False;
+ auto visibleInNavigator = typeData().traits.visibleInNavigator;
+
+ tracer.end(keyValue("visible in navigator", visibleInNavigator));
+
+ return visibleInNavigator;
}
return FlagIs::Set;
@@ -1682,10 +1815,17 @@ FlagIs NodeMetaInfo::visibleInNavigator() const
FlagIs NodeMetaInfo::visibleInLibrary() const
{
if constexpr (useProjectStorage()) {
- if (isValid())
- return typeData().traits.visibleInLibrary;
+ if (!isValid())
+ return FlagIs::False;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"visible in library"_t, category(), keyValue("type id", m_typeId)};
+
+ auto visibleInLibrary = typeData().traits.visibleInLibrary;
- return FlagIs::False;
+ tracer.end(keyValue("visible in library", visibleInLibrary));
+
+ return visibleInLibrary;
}
return FlagIs::Set;
@@ -1697,6 +1837,12 @@ namespace {
TypeId typeId,
Utils::SmallStringView propertyName)
{
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"get combound property id"_t,
+ category(),
+ keyValue("type id", typeId),
+ keyValue("property name", propertyName)};
+
auto begin = propertyName.begin();
const auto end = propertyName.end();
@@ -1712,11 +1858,17 @@ namespace {
if (propertyId && found != end) {
begin = std::next(found);
- return projectStorage.propertyDeclarationId(propertyTypeId, {begin, end});
+ auto id = projectStorage.propertyDeclarationId(propertyTypeId, {begin, end});
+
+ tracer.end(keyValue("property id", id));
+
+ return id;
}
}
}
+ tracer.end(keyValue("property id", propertyId));
+
return propertyId;
}
@@ -1724,10 +1876,24 @@ namespace {
bool NodeMetaInfo::hasProperty(Utils::SmallStringView propertyName) const
{
- if constexpr (useProjectStorage())
- return isValid() && bool(propertyId(*m_projectStorage, m_typeId, propertyName));
- else
+ if constexpr (useProjectStorage()) {
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"has property"_t,
+ category(),
+ keyValue("type id", m_typeId),
+ keyValue("property name", propertyName)};
+
+ if (!isValid())
+ return false;
+
+ auto hasPropertyId = bool(propertyId(*m_projectStorage, m_typeId, propertyName));
+
+ tracer.end(keyValue("has property", hasPropertyId));
+
+ return hasPropertyId;
+ } else {
return isValid() && m_privateData->properties().contains(QByteArrayView(propertyName));
+ }
}
PropertyMetaInfos NodeMetaInfo::properties() const
@@ -1736,12 +1902,14 @@ PropertyMetaInfos NodeMetaInfo::properties() const
return {};
if constexpr (useProjectStorage()) {
- if (isValid()) {
- return Utils::transform<PropertyMetaInfos>(
- m_projectStorage->propertyDeclarationIds(m_typeId), [&](auto id) {
- return PropertyMetaInfo{id, m_projectStorage};
- });
- }
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"get properties"_t, category(), keyValue("type id", m_typeId)};
+
+ return Utils::transform<PropertyMetaInfos>(
+ m_projectStorage->propertyDeclarationIds(m_typeId), [&](auto id) {
+ return PropertyMetaInfo{id, m_projectStorage};
+ });
+
} else {
const auto &properties = m_privateData->properties();
@@ -1753,19 +1921,22 @@ PropertyMetaInfos NodeMetaInfo::properties() const
return propertyMetaInfos;
}
-
- return {};
}
PropertyMetaInfos NodeMetaInfo::localProperties() const
{
+ if (!isValid())
+ return {};
+
if constexpr (useProjectStorage()) {
- if (isValid()) {
- return Utils::transform<PropertyMetaInfos>(
- m_projectStorage->localPropertyDeclarationIds(m_typeId), [&](auto id) {
- return PropertyMetaInfo{id, m_projectStorage};
- });
- }
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"get local properties"_t, category(), keyValue("type id", m_typeId)};
+
+ return Utils::transform<PropertyMetaInfos>(
+ m_projectStorage->localPropertyDeclarationIds(m_typeId), [&](auto id) {
+ return PropertyMetaInfo{id, m_projectStorage};
+ });
+
} else {
const auto &properties = m_privateData->localProperties();
@@ -1777,71 +1948,82 @@ PropertyMetaInfos NodeMetaInfo::localProperties() const
return propertyMetaInfos;
}
-
- return {};
}
PropertyMetaInfo NodeMetaInfo::property(const PropertyName &propertyName) const
{
+ if (!isValid())
+ return {};
+
if constexpr (useProjectStorage()) {
- if (isValid())
- return {propertyId(*m_projectStorage, m_typeId, propertyName), m_projectStorage};
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"get property"_t,
+ category(),
+ keyValue("type id", m_typeId),
+ keyValue("property name", propertyName)};
+
+ return {propertyId(*m_projectStorage, m_typeId, propertyName), m_projectStorage};
} else {
if (hasProperty(propertyName)) {
return PropertyMetaInfo{m_privateData, propertyName};
}
+ return {};
}
-
- return {};
}
PropertyNameList NodeMetaInfo::signalNames() const
{
+ if (!isValid())
+ return {};
+
if constexpr (useProjectStorage()) {
- if (isValid()) {
- return Utils::transform<PropertyNameList>(m_projectStorage->signalDeclarationNames(
- m_typeId),
- [&](const auto &name) {
- return name.toQByteArray();
- });
- }
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"get signal names"_t, category(), keyValue("type id", m_typeId)};
+
+ return Utils::transform<PropertyNameList>(m_projectStorage->signalDeclarationNames(m_typeId),
+ [&](const auto &name) {
+ return name.toQByteArray();
+ });
+
} else {
- if (isValid())
- return m_privateData->signalNames();
+ return m_privateData->signalNames();
}
-
- return {};
}
PropertyNameList NodeMetaInfo::slotNames() const
{
+ if (!isValid())
+ return {};
+
if constexpr (useProjectStorage()) {
- if (isValid()) {
- return Utils::transform<PropertyNameList>(m_projectStorage->functionDeclarationNames(
- m_typeId),
- [&](const auto &name) {
- return name.toQByteArray();
- });
- }
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"get slot names"_t, category(), keyValue("type id", m_typeId)};
+ return Utils::transform<PropertyNameList>(m_projectStorage->functionDeclarationNames(m_typeId),
+ [&](const auto &name) {
+ return name.toQByteArray();
+ });
} else {
- if (isValid())
- return m_privateData->slotNames();
+ return m_privateData->slotNames();
}
-
- return {};
}
PropertyName NodeMetaInfo::defaultPropertyName() const
{
+ if (!isValid())
+ return {};
+
if constexpr (useProjectStorage()) {
- if (isValid()) {
- if (auto name = m_projectStorage->propertyName(defaultPropertyDeclarationId())) {
- return name->toQByteArray();
- }
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"get default property name"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+ if (auto name = m_projectStorage->propertyName(defaultPropertyDeclarationId())) {
+ tracer.end(keyValue("default property name", name));
+ return name->toQByteArray();
}
+
} else {
- if (isValid())
- return m_privateData->defaultPropertyName();
+ return m_privateData->defaultPropertyName();
}
return {};
@@ -1849,88 +2031,128 @@ PropertyName NodeMetaInfo::defaultPropertyName() const
PropertyMetaInfo NodeMetaInfo::defaultProperty() const
{
+ if (!isValid())
+ return {};
+
if constexpr (useProjectStorage()) {
- if (isValid()) {
- return PropertyMetaInfo(defaultPropertyDeclarationId(), m_projectStorage);
- }
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"get default property"_t, category(), keyValue("type id", m_typeId)};
+
+ auto id = defaultPropertyDeclarationId();
+
+ tracer.end(keyValue("default property id", id));
+
+ return PropertyMetaInfo(id, m_projectStorage);
} else {
return property(defaultPropertyName());
}
-
- return {};
}
bool NodeMetaInfo::hasDefaultProperty() const
{
- if constexpr (useProjectStorage())
- return isValid() && bool(defaultPropertyDeclarationId());
- else
+ if (!isValid())
+ return false;
+
+ if constexpr (useProjectStorage()) {
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"has default property"_t, category(), keyValue("type id", m_typeId)};
+ auto hasDefaultProperty = bool(defaultPropertyDeclarationId());
+ tracer.end(keyValue("has default property", hasDefaultProperty));
+
+ return hasDefaultProperty;
+ } else {
return !defaultPropertyName().isEmpty();
+ }
}
std::vector<NodeMetaInfo> NodeMetaInfo::selfAndPrototypes() const
{
+ if (!isValid())
+ return {};
+
if constexpr (useProjectStorage()) {
- if (isValid()) {
- return Utils::transform<NodeMetaInfos>(
- m_projectStorage->prototypeAndSelfIds(m_typeId), [&](TypeId typeId) {
- return NodeMetaInfo{typeId, m_projectStorage};
- });
- }
- } else {
- if (isValid()) {
- NodeMetaInfos hierarchy = {*this};
- Model *model = m_privateData->model();
- for (const TypeDescription &type : m_privateData->prototypes()) {
- auto &last = hierarchy.emplace_back(model,
- type.className.toUtf8(),
- type.majorVersion,
- type.minorVersion);
- if (!last.isValid())
- hierarchy.pop_back();
- }
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"get self and prototypes"_t,
+ category(),
+ keyValue("type id", m_typeId)};
- return hierarchy;
+ return Utils::transform<NodeMetaInfos>(m_projectStorage->prototypeAndSelfIds(m_typeId),
+ [&](TypeId typeId) {
+ return NodeMetaInfo{typeId, m_projectStorage};
+ });
+ } else {
+ NodeMetaInfos hierarchy = {*this};
+ Model *model = m_privateData->model();
+ for (const TypeDescription &type : m_privateData->prototypes()) {
+ auto &last = hierarchy.emplace_back(model,
+ type.className.toUtf8(),
+ type.majorVersion,
+ type.minorVersion);
+ if (!last.isValid())
+ hierarchy.pop_back();
}
- }
- return {};
+ return hierarchy;
+ }
}
NodeMetaInfos NodeMetaInfo::prototypes() const
{
+ if (!isValid())
+ return {};
+
if constexpr (useProjectStorage()) {
- if (isValid()) {
- return Utils::transform<NodeMetaInfos>(
- m_projectStorage->prototypeIds(m_typeId), [&](TypeId typeId) {
- return NodeMetaInfo{typeId, m_projectStorage};
- });
- }
- } else {
- if (isValid()) {
- NodeMetaInfos hierarchy;
- Model *model = m_privateData->model();
- for (const TypeDescription &type : m_privateData->prototypes()) {
- auto &last = hierarchy.emplace_back(model,
- type.className.toUtf8(),
- type.majorVersion,
- type.minorVersion);
- if (!last.isValid())
- hierarchy.pop_back();
- }
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"get prototypes"_t, category(), keyValue("type id", m_typeId)};
+ return Utils::transform<NodeMetaInfos>(m_projectStorage->prototypeIds(m_typeId),
+ [&](TypeId typeId) {
+ return NodeMetaInfo{typeId, m_projectStorage};
+ });
- return hierarchy;
+ } else {
+ NodeMetaInfos hierarchy;
+ Model *model = m_privateData->model();
+ for (const TypeDescription &type : m_privateData->prototypes()) {
+ auto &last = hierarchy.emplace_back(model,
+ type.className.toUtf8(),
+ type.majorVersion,
+ type.minorVersion);
+ if (!last.isValid())
+ hierarchy.pop_back();
}
+
+ return hierarchy;
}
+}
- return {};
+namespace {
+template<const char *moduleName, const char *typeName, ModuleKind moduleKind = ModuleKind::QmlLibrary>
+bool isBasedOnCommonType(NotNullPointer<const ProjectStorageType> projectStorage, TypeId typeId)
+{
+ if (!typeId)
+ return false;
+
+ auto base = projectStorage->commonTypeId<moduleName, typeName, moduleKind>();
+
+ return projectStorage->isBasedOn(typeId, base);
}
+} // namespace
bool NodeMetaInfo::defaultPropertyIsComponent() const
{
- if (hasDefaultProperty())
- return defaultProperty().propertyType().isQmlComponent();
+ if (!isValid())
+ return false;
- return false;
+ if (useProjectStorage()) {
+ auto id = defaultPropertyDeclarationId();
+ auto propertyDeclaration = m_projectStorage->propertyDeclaration(id);
+
+ using namespace Storage::Info;
+ return isBasedOnCommonType<QML, Component>(m_projectStorage, propertyDeclaration->typeId);
+ } else {
+ if (hasDefaultProperty())
+ return defaultProperty().propertyType().isQmlComponent();
+ return false;
+ }
}
TypeName NodeMetaInfo::displayName() const
@@ -1976,10 +2198,16 @@ int NodeMetaInfo::minorVersion() const
Storage::Info::ExportedTypeNames NodeMetaInfo::allExportedTypeNames() const
{
+ if (!isValid())
+ return {};
+
if constexpr (useProjectStorage()) {
- if (isValid()) {
- return m_projectStorage->exportedTypeNames(m_typeId);
- }
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"get all exported type names"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
+ return m_projectStorage->exportedTypeNames(m_typeId);
}
return {};
@@ -1987,10 +2215,17 @@ Storage::Info::ExportedTypeNames NodeMetaInfo::allExportedTypeNames() const
Storage::Info::ExportedTypeNames NodeMetaInfo::exportedTypeNamesForSourceId(SourceId sourceId) const
{
+ if (!isValid())
+ return {};
+
if constexpr (useProjectStorage()) {
- if (isValid()) {
- return m_projectStorage->exportedTypeNames(m_typeId, sourceId);
- }
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"get exported type names for source id"_t,
+ category(),
+ keyValue("type id", m_typeId),
+ keyValue("source id", sourceId)};
+
+ return m_projectStorage->exportedTypeNames(m_typeId, sourceId);
}
return {};
@@ -1998,9 +2233,18 @@ Storage::Info::ExportedTypeNames NodeMetaInfo::exportedTypeNamesForSourceId(Sour
Storage::Info::TypeHints NodeMetaInfo::typeHints() const
{
+ if (!isValid())
+ return {};
+
if constexpr (useProjectStorage()) {
- if (isValid())
- return m_projectStorage->typeHints(m_typeId);
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"get type hints"_t, category(), keyValue("type id", m_typeId)};
+
+ auto hints = m_projectStorage->typeHints(m_typeId);
+
+ tracer.end(keyValue("type hints", hints));
+
+ return hints;
}
return {};
@@ -2008,9 +2252,18 @@ Storage::Info::TypeHints NodeMetaInfo::typeHints() const
Utils::PathString NodeMetaInfo::iconPath() const
{
+ if (!isValid())
+ return {};
+
if constexpr (useProjectStorage()) {
- if (isValid())
- return m_projectStorage->typeIconPath(m_typeId);
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"get icon path"_t, category(), keyValue("type id", m_typeId)};
+
+ auto iconPath = m_projectStorage->typeIconPath(m_typeId);
+
+ tracer.end(keyValue("icon path", iconPath));
+
+ return iconPath;
}
return {};
@@ -2018,9 +2271,20 @@ Utils::PathString NodeMetaInfo::iconPath() const
Storage::Info::ItemLibraryEntries NodeMetaInfo::itemLibrariesEntries() const
{
+ if (!isValid())
+ return {};
+
if constexpr (useProjectStorage()) {
- if (isValid())
- return m_projectStorage->itemLibraryEntries(m_typeId);
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"get item library entries"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
+ auto entries = m_projectStorage->itemLibraryEntries(m_typeId);
+
+ tracer.end(keyValue("item library entries", entries));
+
+ return entries;
}
return {};
@@ -2028,10 +2292,18 @@ Storage::Info::ItemLibraryEntries NodeMetaInfo::itemLibrariesEntries() const
SourceId NodeMetaInfo::sourceId() const
{
+ if (!isValid())
+ return SourceId{};
+
if constexpr (useProjectStorage()) {
- if (isValid()) {
- return typeData().sourceId;
- }
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"get source id"_t, category(), keyValue("type id", m_typeId)};
+
+ auto id = typeData().sourceId;
+
+ tracer.end(keyValue("source id", id));
+
+ return id;
}
return SourceId{};
@@ -2064,18 +2336,31 @@ QString NodeMetaInfo::requiredImportString() const
if (!isValid())
return {};
- Import imp = m_privateData->requiredImport();
- if (!imp.isEmpty())
- return imp.toImportString();
+ if constexpr (!useProjectStorage()) {
+ Import imp = m_privateData->requiredImport();
+ if (!imp.isEmpty())
+ return imp.toImportString();
+ }
return {};
}
SourceId NodeMetaInfo::propertyEditorPathId() const
{
+ if (!isValid())
+ return SourceId{};
+
if (useProjectStorage()) {
- if (isValid())
- return m_projectStorage->propertyEditorPathId(m_typeId);
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"get property editor path id"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
+ auto id = m_projectStorage->propertyEditorPathId(m_typeId);
+
+ tracer.end(keyValue("property editor path id", id));
+
+ return id;
}
return SourceId{};
@@ -2133,9 +2418,13 @@ bool NodeMetaInfo::isSubclassOf(const TypeName &type, int majorVersion, int mino
bool NodeMetaInfo::isSuitableForMouseAreaFill() const
{
if constexpr (useProjectStorage()) {
- if (!isValid()) {
+ if (!isValid())
return false;
- }
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is suitable for mouse area fill"_t,
+ category(),
+ keyValue("type id", m_typeId)};
using namespace Storage::Info;
auto itemId = m_projectStorage->commonTypeId<QtQuick, Item>();
@@ -2143,11 +2432,16 @@ bool NodeMetaInfo::isSuitableForMouseAreaFill() const
auto controlsControlId = m_projectStorage->commonTypeId<QtQuick_Controls, Control>();
auto templatesControlId = m_projectStorage->commonTypeId<QtQuick_Templates, Control>();
- return m_projectStorage->isBasedOn(m_typeId,
- itemId,
- mouseAreaId,
- controlsControlId,
- templatesControlId);
+ auto isSuitableForMouseAreaFill = m_projectStorage->isBasedOn(m_typeId,
+ itemId,
+ mouseAreaId,
+ controlsControlId,
+ templatesControlId);
+
+ tracer.end(keyValue("is suitable for mouse area fill", isSuitableForMouseAreaFill));
+
+ return isSuitableForMouseAreaFill;
+
} else {
return isSubclassOf("QtQuick.Item") && !isSubclassOf("QtQuick.MouseArea")
&& !isSubclassOf("QtQuick.Controls.Control")
@@ -2158,6 +2452,15 @@ bool NodeMetaInfo::isSuitableForMouseAreaFill() const
bool NodeMetaInfo::isBasedOn(const NodeMetaInfo &metaInfo) const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is based on"_t,
+ category(),
+ keyValue("type id", m_typeId),
+ keyValue("meta info type id", metaInfo.m_typeId)};
+
return m_projectStorage->isBasedOn(m_typeId, metaInfo.m_typeId);
} else {
if (!isValid())
@@ -2171,6 +2474,12 @@ bool NodeMetaInfo::isBasedOn(const NodeMetaInfo &metaInfo) const
bool NodeMetaInfo::isBasedOn(const NodeMetaInfo &metaInfo1, const NodeMetaInfo &metaInfo2) const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is based on"_t, category(), keyValue("type id", m_typeId)};
+
return m_projectStorage->isBasedOn(m_typeId, metaInfo1.m_typeId, metaInfo2.m_typeId);
} else {
if (!isValid())
@@ -2189,6 +2498,12 @@ bool NodeMetaInfo::isBasedOn(const NodeMetaInfo &metaInfo1,
const NodeMetaInfo &metaInfo3) const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is based on"_t, category(), keyValue("type id", m_typeId)};
+
return m_projectStorage->isBasedOn(m_typeId,
metaInfo1.m_typeId,
metaInfo2.m_typeId,
@@ -2213,6 +2528,12 @@ bool NodeMetaInfo::isBasedOn(const NodeMetaInfo &metaInfo1,
const NodeMetaInfo &metaInfo4) const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is based on"_t, category(), keyValue("type id", m_typeId)};
+
return m_projectStorage->isBasedOn(m_typeId,
metaInfo1.m_typeId,
metaInfo2.m_typeId,
@@ -2240,6 +2561,12 @@ bool NodeMetaInfo::isBasedOn(const NodeMetaInfo &metaInfo1,
const NodeMetaInfo &metaInfo5) const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is based on"_t, category(), keyValue("type id", m_typeId)};
+
return m_projectStorage->isBasedOn(m_typeId,
metaInfo1.m_typeId,
metaInfo2.m_typeId,
@@ -2272,6 +2599,12 @@ bool NodeMetaInfo::isBasedOn(const NodeMetaInfo &metaInfo1,
const NodeMetaInfo &metaInfo6) const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is based on"_t, category(), keyValue("type id", m_typeId)};
+
return m_projectStorage->isBasedOn(m_typeId,
metaInfo1.m_typeId,
metaInfo2.m_typeId,
@@ -2309,6 +2642,12 @@ bool NodeMetaInfo::isBasedOn(const NodeMetaInfo &metaInfo1,
const NodeMetaInfo &metaInfo7) const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is based on"_t, category(), keyValue("type id", m_typeId)};
+
return m_projectStorage->isBasedOn(m_typeId,
metaInfo1.m_typeId,
metaInfo2.m_typeId,
@@ -2341,26 +2680,14 @@ bool NodeMetaInfo::isBasedOn(const NodeMetaInfo &metaInfo1,
}
}
-namespace {
-template<const char *moduleName, const char *typeName>
-bool isBasedOnCommonType(NotNullPointer<const ProjectStorageType> projectStorage, TypeId typeId)
-{
- if (!typeId) {
- return false;
- }
-
- auto base = projectStorage->commonTypeId<moduleName, typeName>();
-
- return projectStorage->isBasedOn(typeId, base);
-}
-} // namespace
-
bool NodeMetaInfo::isGraphicalItem() const
{
if constexpr (useProjectStorage()) {
- if (!isValid()) {
+ if (!isValid())
return false;
- }
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is graphical item"_t, category(), keyValue("type id", m_typeId)};
using namespace Storage::Info;
auto itemId = m_projectStorage->commonTypeId<QtQuick, Item>();
@@ -2380,6 +2707,12 @@ bool NodeMetaInfo::isGraphicalItem() const
bool NodeMetaInfo::isQtObject() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is Qt object"_t, category(), keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QML, QtObject>(m_projectStorage, m_typeId);
} else {
@@ -2390,6 +2723,14 @@ bool NodeMetaInfo::isQtObject() const
bool NodeMetaInfo::isQtQmlConnections() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is Qt Qml connections"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQml, Connections>(m_projectStorage, m_typeId);
} else {
@@ -2400,9 +2741,11 @@ bool NodeMetaInfo::isQtQmlConnections() const
bool NodeMetaInfo::isLayoutable() const
{
if constexpr (useProjectStorage()) {
- if (!isValid()) {
+ if (!isValid())
return false;
- }
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is layoutable"_t, category(), keyValue("type id", m_typeId)};
using namespace Storage::Info;
auto positionerId = m_projectStorage->commonTypeId<QtQuick, Positioner>();
@@ -2421,6 +2764,14 @@ bool NodeMetaInfo::isLayoutable() const
bool NodeMetaInfo::isQtQuickLayoutsLayout() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick.Layouts.Layout"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick_Layouts, Layout>(m_projectStorage, m_typeId);
} else {
@@ -2431,9 +2782,11 @@ bool NodeMetaInfo::isQtQuickLayoutsLayout() const
bool NodeMetaInfo::isView() const
{
if constexpr (useProjectStorage()) {
- if (!isValid()) {
+ if (!isValid())
return false;
- }
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is view"_t, category(), keyValue("type id", m_typeId)};
using namespace Storage::Info;
auto listViewId = m_projectStorage->commonTypeId<QtQuick, ListView>();
@@ -2450,7 +2803,13 @@ bool NodeMetaInfo::isView() const
bool NodeMetaInfo::usesCustomParser() const
{
if constexpr (useProjectStorage()) {
- return isValid() && typeData().traits.usesCustomParser;
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"uses custom parser"_t, category(), keyValue("type id", m_typeId)};
+
+ return typeData().traits.usesCustomParser;
} else {
if (!isValid())
return false;
@@ -2476,8 +2835,14 @@ bool isTypeId(TypeId typeId, TypeIds... otherTypeIds)
bool NodeMetaInfo::isVector2D() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is vector2d"_t, category(), keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
- return isValid() && isTypeId(m_typeId, m_projectStorage->commonTypeId<QtQuick, vector2d>());
+ return isTypeId(m_typeId, m_projectStorage->commonTypeId<QtQuick, vector2d>());
} else {
if (!m_privateData)
return false;
@@ -2491,8 +2856,14 @@ bool NodeMetaInfo::isVector2D() const
bool NodeMetaInfo::isVector3D() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is vector3d"_t, category(), keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
- return isValid() && isTypeId(m_typeId, m_projectStorage->commonTypeId<QtQuick, vector3d>());
+ return isTypeId(m_typeId, m_projectStorage->commonTypeId<QtQuick, vector3d>());
} else {
if (!m_privateData)
return false;
@@ -2506,8 +2877,14 @@ bool NodeMetaInfo::isVector3D() const
bool NodeMetaInfo::isVector4D() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is vector4d"_t, category(), keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
- return isValid() && isTypeId(m_typeId, m_projectStorage->commonTypeId<QtQuick, vector4d>());
+ return isTypeId(m_typeId, m_projectStorage->commonTypeId<QtQuick, vector4d>());
} else {
if (!m_privateData)
return false;
@@ -2521,6 +2898,14 @@ bool NodeMetaInfo::isVector4D() const
bool NodeMetaInfo::isQtQuickPropertyChanges() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick.PropertyChanges"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick, Storage::Info::PropertyChanges>(m_projectStorage,
m_typeId);
@@ -2532,6 +2917,14 @@ bool NodeMetaInfo::isQtQuickPropertyChanges() const
bool NodeMetaInfo::isQtSafeRendererSafeRendererPicture() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is Qt.SafeRenderer.SafeRendererPicture"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<Qt_SafeRenderer, SafeRendererPicture>(m_projectStorage, m_typeId);
} else {
@@ -2542,6 +2935,14 @@ bool NodeMetaInfo::isQtSafeRendererSafeRendererPicture() const
bool NodeMetaInfo::isQtSafeRendererSafePicture() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is Qt.SafeRenderer.SafePicture"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<Qt_SafeRenderer, SafePicture>(m_projectStorage, m_typeId);
} else {
@@ -2552,6 +2953,14 @@ bool NodeMetaInfo::isQtSafeRendererSafePicture() const
bool NodeMetaInfo::isQtQuickTimelineKeyframe() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick.Timeline.Keyframe"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick_Timeline, Keyframe>(m_projectStorage, m_typeId);
@@ -2563,6 +2972,14 @@ bool NodeMetaInfo::isQtQuickTimelineKeyframe() const
bool NodeMetaInfo::isQtQuickTimelineTimelineAnimation() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick.Timeline.TimelineAnimation"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick_Timeline, TimelineAnimation>(m_projectStorage, m_typeId);
} else {
@@ -2573,6 +2990,14 @@ bool NodeMetaInfo::isQtQuickTimelineTimelineAnimation() const
bool NodeMetaInfo::isQtQuickTimelineTimeline() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick.Timeline.Timeline"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick_Timeline, Timeline>(m_projectStorage, m_typeId);
} else {
@@ -2583,6 +3008,14 @@ bool NodeMetaInfo::isQtQuickTimelineTimeline() const
bool NodeMetaInfo::isQtQuickTimelineKeyframeGroup() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick.Timeline.KeyframeGroup"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick_Timeline, KeyframeGroup>(m_projectStorage, m_typeId);
} else {
@@ -2593,9 +3026,11 @@ bool NodeMetaInfo::isQtQuickTimelineKeyframeGroup() const
bool NodeMetaInfo::isListOrGridView() const
{
if constexpr (useProjectStorage()) {
- if (!isValid()) {
+ if (!isValid())
return false;
- }
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is list or grid view"_t, category(), keyValue("type id", m_typeId)};
using namespace Storage::Info;
auto listViewId = m_projectStorage->commonTypeId<QtQuick, ListView>();
@@ -2609,9 +3044,11 @@ bool NodeMetaInfo::isListOrGridView() const
bool NodeMetaInfo::isNumber() const
{
if constexpr (useProjectStorage()) {
- if (!isValid()) {
+ if (!isValid())
return false;
- }
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is number"_t, category(), keyValue("type id", m_typeId)};
using namespace Storage::Info;
auto intId = m_projectStorage->builtinTypeId<int>();
@@ -2632,6 +3069,14 @@ bool NodeMetaInfo::isNumber() const
bool NodeMetaInfo::isQtQuickExtrasPicture() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick.Extras.Picture"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick_Extras, Picture>(m_projectStorage, m_typeId);
} else {
@@ -2642,6 +3087,12 @@ bool NodeMetaInfo::isQtQuickExtrasPicture() const
bool NodeMetaInfo::isQtQuickImage() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick.Image"_t, category(), keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick, Image>(m_projectStorage, m_typeId);
@@ -2653,6 +3104,14 @@ bool NodeMetaInfo::isQtQuickImage() const
bool NodeMetaInfo::isQtQuickBorderImage() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick.BorderImage"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick, BorderImage>(m_projectStorage, m_typeId);
@@ -2664,7 +3123,13 @@ bool NodeMetaInfo::isQtQuickBorderImage() const
bool NodeMetaInfo::isAlias() const
{
if constexpr (useProjectStorage()) {
- return false; // there is no type alias
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is alias"_t, category(), keyValue("type id", m_typeId)};
+
+ return false; // all types are already resolved
} else {
return isValid() && m_privateData->qualfiedTypeName() == "alias";
}
@@ -2673,6 +3138,14 @@ bool NodeMetaInfo::isAlias() const
bool NodeMetaInfo::isQtQuickPositioner() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick.Positioner"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick, Positioner>(m_projectStorage, m_typeId);
@@ -2684,6 +3157,14 @@ bool NodeMetaInfo::isQtQuickPositioner() const
bool NodeMetaInfo::isQtQuickPropertyAnimation() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick.PropertyAnimation"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick, PropertyAnimation>(m_projectStorage, m_typeId);
} else {
@@ -2694,6 +3175,12 @@ bool NodeMetaInfo::isQtQuickPropertyAnimation() const
bool NodeMetaInfo::isQtQuickRepeater() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick.Repeater"_t, category(), keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick, Repeater>(m_projectStorage, m_typeId);
} else {
@@ -2704,6 +3191,14 @@ bool NodeMetaInfo::isQtQuickRepeater() const
bool NodeMetaInfo::isQtQuickControlsTabBar() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick.Controls.TabBar"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick_Controls, TabBar>(m_projectStorage, m_typeId);
} else {
@@ -2714,6 +3209,14 @@ bool NodeMetaInfo::isQtQuickControlsTabBar() const
bool NodeMetaInfo::isQtQuickControlsSwipeView() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick.Controls.SwipeView"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick_Controls, SwipeView>(m_projectStorage, m_typeId);
} else {
@@ -2724,6 +3227,12 @@ bool NodeMetaInfo::isQtQuickControlsSwipeView() const
bool NodeMetaInfo::isQtQuick3DCamera() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick3D.Camera"_t, category(), keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick3D, Camera>(m_projectStorage, m_typeId);
} else {
@@ -2734,6 +3243,14 @@ bool NodeMetaInfo::isQtQuick3DCamera() const
bool NodeMetaInfo::isQtQuick3DBakedLightmap() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick3D.BakedLightmap"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick3D, BakedLightmap>(m_projectStorage, m_typeId);
} else {
@@ -2744,6 +3261,12 @@ bool NodeMetaInfo::isQtQuick3DBakedLightmap() const
bool NodeMetaInfo::isQtQuick3DBuffer() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick3D.Buffer"_t, category(), keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick3D, Buffer>(m_projectStorage, m_typeId);
} else {
@@ -2754,6 +3277,14 @@ bool NodeMetaInfo::isQtQuick3DBuffer() const
bool NodeMetaInfo::isQtQuick3DInstanceListEntry() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick3D.InstanceListEntry"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick3D, InstanceListEntry>(m_projectStorage, m_typeId);
} else {
@@ -2764,6 +3295,12 @@ bool NodeMetaInfo::isQtQuick3DInstanceListEntry() const
bool NodeMetaInfo::isQtQuick3DLight() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick3D.Light"_t, category(), keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick3D, Light>(m_projectStorage, m_typeId);
} else {
@@ -2774,6 +3311,14 @@ bool NodeMetaInfo::isQtQuick3DLight() const
bool NodeMetaInfo::isQtQmlModelsListElement() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQml.Models.ListElement"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQml_Models, ListElement>(m_projectStorage, m_typeId);
} else {
@@ -2784,6 +3329,12 @@ bool NodeMetaInfo::isQtQmlModelsListElement() const
bool NodeMetaInfo::isQtQuickListModel() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick.ListModel"_t, category(), keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQml_Models, ListModel>(m_projectStorage, m_typeId);
} else {
@@ -2794,6 +3345,12 @@ bool NodeMetaInfo::isQtQuickListModel() const
bool NodeMetaInfo::isQtQuickListView() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick.ListView"_t, category(), keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick, ListView>(m_projectStorage, m_typeId);
} else {
@@ -2801,9 +3358,33 @@ bool NodeMetaInfo::isQtQuickListView() const
}
}
+bool QmlDesigner::NodeMetaInfo::isQtQuickGridView() const
+{
+ if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick.GridView"_t, category(), keyValue("type id", m_typeId)};
+
+ using namespace Storage::Info;
+ return isBasedOnCommonType<QtQuick, GridView>(m_projectStorage, m_typeId);
+ } else {
+ return isValid() && (isSubclassOf("QtQuick.GridView"));
+ }
+}
+
bool NodeMetaInfo::isQtQuick3DInstanceList() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick3D.InstanceList"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick3D, InstanceList>(m_projectStorage, m_typeId);
} else {
@@ -2814,6 +3395,14 @@ bool NodeMetaInfo::isQtQuick3DInstanceList() const
bool NodeMetaInfo::isQtQuick3DParticles3DParticle3D() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick3D.Particles3D.Particle3D"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick3D_Particles3D, Particle3D>(m_projectStorage, m_typeId);
} else {
@@ -2824,6 +3413,14 @@ bool NodeMetaInfo::isQtQuick3DParticles3DParticle3D() const
bool NodeMetaInfo::isQtQuick3DParticles3DParticleEmitter3D() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick3D.Particles3D.ParticleEmitter3D"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick3D_Particles3D, ParticleEmitter3D>(m_projectStorage,
m_typeId);
@@ -2835,6 +3432,14 @@ bool NodeMetaInfo::isQtQuick3DParticles3DParticleEmitter3D() const
bool NodeMetaInfo::isQtQuick3DParticles3DAttractor3D() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick3D.Particles3D.Attractor3D"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick3D_Particles3D, Attractor3D>(m_projectStorage, m_typeId);
} else {
@@ -2845,8 +3450,16 @@ bool NodeMetaInfo::isQtQuick3DParticles3DAttractor3D() const
bool NodeMetaInfo::isQtQuick3DParticlesAbstractShape() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick3D.Particles3D.AbstractShape"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
- return isBasedOnCommonType<QtQuick3D_Particles3D_cppnative, QQuick3DParticleAbstractShape>(
+ return isBasedOnCommonType<QtQuick3D_Particles3D, QQuick3DParticleAbstractShape, ModuleKind::CppLibrary>(
m_projectStorage, m_typeId);
} else {
return isValid() && isSubclassOf("QQuick3DParticleAbstractShape");
@@ -2856,6 +3469,12 @@ bool NodeMetaInfo::isQtQuick3DParticlesAbstractShape() const
bool NodeMetaInfo::isQtQuickItem() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick.Item"_t, category(), keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick, Item>(m_projectStorage, m_typeId);
} else {
@@ -2866,6 +3485,12 @@ bool NodeMetaInfo::isQtQuickItem() const
bool NodeMetaInfo::isQtQuickPath() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick.Path"_t, category(), keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick, Path>(m_projectStorage, m_typeId);
} else {
@@ -2876,6 +3501,14 @@ bool NodeMetaInfo::isQtQuickPath() const
bool NodeMetaInfo::isQtQuickPauseAnimation() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick.PauseAnimation"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick, PauseAnimation>(m_projectStorage, m_typeId);
} else {
@@ -2886,6 +3519,14 @@ bool NodeMetaInfo::isQtQuickPauseAnimation() const
bool NodeMetaInfo::isQtQuickTransition() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick.Transition"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick, Transition>(m_projectStorage, m_typeId);
} else {
@@ -2896,6 +3537,14 @@ bool NodeMetaInfo::isQtQuickTransition() const
bool NodeMetaInfo::isQtQuickWindowWindow() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick.Window.Window"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick_Window, Window>(m_projectStorage, m_typeId);
} else {
@@ -2906,6 +3555,12 @@ bool NodeMetaInfo::isQtQuickWindowWindow() const
bool NodeMetaInfo::isQtQuickLoader() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick.Loader"_t, category(), keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick, Loader>(m_projectStorage, m_typeId);
} else {
@@ -2916,6 +3571,12 @@ bool NodeMetaInfo::isQtQuickLoader() const
bool NodeMetaInfo::isQtQuickState() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick.State"_t, category(), keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick, State>(m_projectStorage, m_typeId);
} else {
@@ -2926,9 +3587,17 @@ bool NodeMetaInfo::isQtQuickState() const
bool NodeMetaInfo::isQtQuickStateOperation() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick.StateOperation"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
- return isBasedOnCommonType<QtQuick_cppnative, QQuickStateOperation>(m_projectStorage,
- m_typeId);
+ return isBasedOnCommonType<QtQuick, QQuickStateOperation, ModuleKind::CppLibrary>(m_projectStorage,
+ m_typeId);
} else {
return isValid() && isSubclassOf("<cpp>.QQuickStateOperation");
}
@@ -2937,6 +3606,12 @@ bool NodeMetaInfo::isQtQuickStateOperation() const
bool NodeMetaInfo::isQtQuickText() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick.Text"_t, category(), keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick, Text>(m_projectStorage, m_typeId);
} else {
@@ -2947,6 +3622,14 @@ bool NodeMetaInfo::isQtQuickText() const
bool NodeMetaInfo::isQtMultimediaSoundEffect() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtMultimedia.SoundEffect"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtMultimedia, SoundEffect>(m_projectStorage, m_typeId);
} else {
@@ -2957,9 +3640,11 @@ bool NodeMetaInfo::isQtMultimediaSoundEffect() const
bool NodeMetaInfo::isFlowViewItem() const
{
if constexpr (useProjectStorage()) {
- if (!isValid()) {
+ if (!isValid())
return false;
- }
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is FlowView.ViewItem"_t, category(), keyValue("type id", m_typeId)};
using namespace Storage::Info;
auto flowItemId = m_projectStorage->commonTypeId<FlowView, FlowItem>();
@@ -2976,6 +3661,12 @@ bool NodeMetaInfo::isFlowViewItem() const
bool NodeMetaInfo::isFlowViewFlowItem() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is FlowView.FlowItem"_t, category(), keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<FlowView, FlowItem>(m_projectStorage, m_typeId);
} else {
@@ -2986,6 +3677,12 @@ bool NodeMetaInfo::isFlowViewFlowItem() const
bool NodeMetaInfo::isFlowViewFlowView() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is FlowView.FlowView"_t, category(), keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<FlowView, FlowView>(m_projectStorage, m_typeId);
} else {
@@ -3006,6 +3703,14 @@ bool NodeMetaInfo::isFlowViewFlowActionArea() const
bool NodeMetaInfo::isFlowViewFlowTransition() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is FlowView.FlowTransition"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<FlowView, FlowTransition>(m_projectStorage, m_typeId);
} else {
@@ -3016,6 +3721,14 @@ bool NodeMetaInfo::isFlowViewFlowTransition() const
bool NodeMetaInfo::isFlowViewFlowDecision() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is FlowView.FlowDecision"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<FlowView, FlowDecision>(m_projectStorage, m_typeId);
} else {
@@ -3026,6 +3739,14 @@ bool NodeMetaInfo::isFlowViewFlowDecision() const
bool NodeMetaInfo::isFlowViewFlowWildcard() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is FlowView.FlowWildcard"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<FlowView, FlowWildcard>(m_projectStorage, m_typeId);
} else {
@@ -3036,6 +3757,14 @@ bool NodeMetaInfo::isFlowViewFlowWildcard() const
bool NodeMetaInfo::isQtQuickStudioComponentsGroupItem() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick.Studio.Components.GroupItem"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick_Studio_Components, GroupItem>(m_projectStorage, m_typeId);
} else {
@@ -3046,6 +3775,14 @@ bool NodeMetaInfo::isQtQuickStudioComponentsGroupItem() const
bool NodeMetaInfo::isQtQuickStudioUtilsJsonListModel() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick.Studio.Utils.JsonListModel"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick_Studio_Components, JsonListModel>(m_projectStorage,
m_typeId);
@@ -3057,6 +3794,12 @@ bool NodeMetaInfo::isQtQuickStudioUtilsJsonListModel() const
bool NodeMetaInfo::isQmlComponent() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QML.Component"_t, category(), keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QML, Component>(m_projectStorage, m_typeId);
} else {
@@ -3072,6 +3815,12 @@ bool NodeMetaInfo::isQmlComponent() const
bool NodeMetaInfo::isFont() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is font"_t, category(), keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isValid() && isTypeId(m_typeId, m_projectStorage->commonTypeId<QtQuick, font>());
} else {
@@ -3082,6 +3831,12 @@ bool NodeMetaInfo::isFont() const
bool NodeMetaInfo::isColor() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is color"_t, category(), keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isValid() && isTypeId(m_typeId, m_projectStorage->builtinTypeId<QColor>());
} else {
@@ -3097,6 +3852,12 @@ bool NodeMetaInfo::isColor() const
bool NodeMetaInfo::isBool() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is bool"_t, category(), keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isValid() && isTypeId(m_typeId, m_projectStorage->builtinTypeId<bool>());
} else {
@@ -3112,6 +3873,12 @@ bool NodeMetaInfo::isBool() const
bool NodeMetaInfo::isInteger() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is integer"_t, category(), keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isValid() && isTypeId(m_typeId, m_projectStorage->builtinTypeId<int>());
} else {
@@ -3127,9 +3894,11 @@ bool NodeMetaInfo::isInteger() const
bool NodeMetaInfo::isFloat() const
{
if constexpr (useProjectStorage()) {
- if (!isValid()) {
+ if (!isValid())
return false;
- }
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is float"_t, category(), keyValue("type id", m_typeId)};
using namespace Storage::Info;
auto floatId = m_projectStorage->builtinTypeId<float>();
@@ -3149,6 +3918,12 @@ bool NodeMetaInfo::isFloat() const
bool NodeMetaInfo::isVariant() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is variant"_t, category(), keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isValid() && isTypeId(m_typeId, m_projectStorage->builtinTypeId<QVariant>());
} else {
@@ -3164,6 +3939,12 @@ bool NodeMetaInfo::isVariant() const
bool NodeMetaInfo::isString() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is string"_t, category(), keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isValid() && isTypeId(m_typeId, m_projectStorage->builtinTypeId<QString>());
} else {
@@ -3179,6 +3960,12 @@ bool NodeMetaInfo::isString() const
bool NodeMetaInfo::isUrl() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is url"_t, category(), keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isValid() && isTypeId(m_typeId, m_projectStorage->builtinTypeId<QUrl>());
} else {
@@ -3194,6 +3981,12 @@ bool NodeMetaInfo::isUrl() const
bool NodeMetaInfo::isQtQuick3DTexture() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick3D.Texture"_t, category(), keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick3D, Texture>(m_projectStorage, m_typeId);
} else {
@@ -3205,6 +3998,12 @@ bool NodeMetaInfo::isQtQuick3DTexture() const
bool NodeMetaInfo::isQtQuick3DShader() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick3D.Shader"_t, category(), keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick3D, Shader>(m_projectStorage, m_typeId);
} else {
@@ -3215,6 +4014,12 @@ bool NodeMetaInfo::isQtQuick3DShader() const
bool NodeMetaInfo::isQtQuick3DPass() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick3D.Pass"_t, category(), keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick3D, Pass>(m_projectStorage, m_typeId);
} else {
@@ -3225,6 +4030,12 @@ bool NodeMetaInfo::isQtQuick3DPass() const
bool NodeMetaInfo::isQtQuick3DCommand() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick3D.Command"_t, category(), keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick3D, Command>(m_projectStorage, m_typeId);
} else {
@@ -3235,6 +4046,14 @@ bool NodeMetaInfo::isQtQuick3DCommand() const
bool NodeMetaInfo::isQtQuick3DDefaultMaterial() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick3D.DefaultMaterial"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick3D, DefaultMaterial>(m_projectStorage, m_typeId);
} else {
@@ -3255,6 +4074,12 @@ bool NodeMetaInfo::isQtQuick3DMaterial() const
bool NodeMetaInfo::isQtQuick3DModel() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick3D.Model"_t, category(), keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick3D, Storage::Info::Model>(m_projectStorage, m_typeId);
} else {
@@ -3265,6 +4090,12 @@ bool NodeMetaInfo::isQtQuick3DModel() const
bool NodeMetaInfo::isQtQuick3DNode() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick3D.Node"_t, category(), keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick3D, Node>(m_projectStorage, m_typeId);
} else {
@@ -3275,6 +4106,14 @@ bool NodeMetaInfo::isQtQuick3DNode() const
bool NodeMetaInfo::isQtQuick3DParticles3DAffector3D() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick3D.Particles3D.Affector3D"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick3D_Particles3D, Affector3D>(m_projectStorage, m_typeId);
} else {
@@ -3285,6 +4124,12 @@ bool NodeMetaInfo::isQtQuick3DParticles3DAffector3D() const
bool NodeMetaInfo::isQtQuick3DView3D() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick3D.View3D"_t, category(), keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick3D, View3D>(m_projectStorage, m_typeId);
} else {
@@ -3295,6 +4140,14 @@ bool NodeMetaInfo::isQtQuick3DView3D() const
bool NodeMetaInfo::isQtQuick3DPrincipledMaterial() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick3D.PrincipledMaterial"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick3D, PrincipledMaterial>(m_projectStorage, m_typeId);
} else {
@@ -3305,6 +4158,14 @@ bool NodeMetaInfo::isQtQuick3DPrincipledMaterial() const
bool NodeMetaInfo::isQtQuick3DSpecularGlossyMaterial() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick3D.SpecularGlossyMaterial"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick3D, SpecularGlossyMaterial>(m_projectStorage, m_typeId);
} else {
@@ -3315,6 +4176,14 @@ bool NodeMetaInfo::isQtQuick3DSpecularGlossyMaterial() const
bool NodeMetaInfo::isQtQuick3DParticles3DSpriteParticle3D() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick3D.Particles3D.SpriteParticle3D"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick3D_Particles3D, SpriteParticle3D>(m_projectStorage,
m_typeId);
@@ -3326,6 +4195,14 @@ bool NodeMetaInfo::isQtQuick3DParticles3DSpriteParticle3D() const
bool NodeMetaInfo::isQtQuick3DTextureInput() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick3D.TextureInput"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick3D, TextureInput>(m_projectStorage, m_typeId);
} else {
@@ -3336,6 +4213,14 @@ bool NodeMetaInfo::isQtQuick3DTextureInput() const
bool NodeMetaInfo::isQtQuick3DCubeMapTexture() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick3D.CubeMapTexture"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick3D, CubeMapTexture>(m_projectStorage, m_typeId);
} else {
@@ -3348,6 +4233,14 @@ bool NodeMetaInfo::isQtQuick3DCubeMapTexture() const
bool NodeMetaInfo::isQtQuick3DSceneEnvironment() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick3D.SceneEnvironment"_t,
+ category(),
+ keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick3D, SceneEnvironment>(m_projectStorage, m_typeId);
} else {
@@ -3358,6 +4251,12 @@ bool NodeMetaInfo::isQtQuick3DSceneEnvironment() const
bool NodeMetaInfo::isQtQuick3DEffect() const
{
if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is QtQuick3D.Effect"_t, category(), keyValue("type id", m_typeId)};
+
using namespace Storage::Info;
return isBasedOnCommonType<QtQuick3D, Effect>(m_projectStorage, m_typeId);
} else {
@@ -3367,8 +4266,15 @@ bool NodeMetaInfo::isQtQuick3DEffect() const
bool NodeMetaInfo::isEnumeration() const
{
- if constexpr (useProjectStorage())
- return isValid() && typeData().traits.isEnum;
+ if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return false;
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is enumeration"_t, category(), keyValue("type id", m_typeId)};
+
+ return typeData().traits.isEnum;
+ }
return false;
}
@@ -3393,8 +4299,15 @@ PropertyMetaInfo::~PropertyMetaInfo() = default;
NodeMetaInfo PropertyMetaInfo::propertyType() const
{
if constexpr (useProjectStorage()) {
- if (isValid())
- return {propertyData().propertyTypeId, m_projectStorage};
+ if (!isValid())
+ return {};
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"get property type"_t,
+ category(),
+ keyValue("property declaration id", m_id)};
+
+ return {propertyData().propertyTypeId, m_projectStorage};
} else {
if (isValid())
return NodeMetaInfo{nodeMetaInfoPrivateData()->model(),
@@ -3409,8 +4322,15 @@ NodeMetaInfo PropertyMetaInfo::propertyType() const
NodeMetaInfo PropertyMetaInfo::type() const
{
if constexpr (useProjectStorage()) {
- if (isValid())
- return NodeMetaInfo(propertyData().typeId, m_projectStorage);
+ if (!isValid())
+ return {};
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"get property owner type "_t,
+ category(),
+ keyValue("property declaration id", m_id)};
+
+ return NodeMetaInfo(propertyData().typeId, m_projectStorage);
}
return {};
@@ -3418,59 +4338,121 @@ NodeMetaInfo PropertyMetaInfo::type() const
PropertyName PropertyMetaInfo::name() const
{
- if (isValid()) {
- if constexpr (useProjectStorage())
- return PropertyName(Utils::SmallStringView(propertyData().name));
- else
- return propertyName();
- }
+ if (!isValid())
+ return {};
- return {};
+ if constexpr (useProjectStorage()) {
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"get property name"_t,
+ category(),
+ keyValue("property declaration id", m_id)};
+
+ return PropertyName(Utils::SmallStringView(propertyData().name));
+ } else {
+ return propertyName();
+ }
}
bool PropertyMetaInfo::isWritable() const
{
- if constexpr (useProjectStorage())
- return isValid() && !(propertyData().traits & Storage::PropertyDeclarationTraits::IsReadOnly);
- else
+ if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return {};
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is property writable"_t,
+ category(),
+ keyValue("property declaration id", m_id)};
+
+ return !(propertyData().traits & Storage::PropertyDeclarationTraits::IsReadOnly);
+ } else {
return isValid() && nodeMetaInfoPrivateData()->isPropertyWritable(propertyName());
+ }
}
bool PropertyMetaInfo::isReadOnly() const
{
- return !isWritable();
+ if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return {};
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is property read only"_t,
+ category(),
+ keyValue("property declaration id", m_id)};
+
+ return propertyData().traits & Storage::PropertyDeclarationTraits::IsReadOnly;
+ } else {
+ return !isWritable();
+ }
}
bool PropertyMetaInfo::isListProperty() const
{
- if constexpr (useProjectStorage())
- return isValid() && propertyData().traits & Storage::PropertyDeclarationTraits::IsList;
- else
+ if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return {};
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is list property"_t,
+ category(),
+ keyValue("property declaration id", m_id)};
+
+ return propertyData().traits & Storage::PropertyDeclarationTraits::IsList;
+ } else {
return isValid() && nodeMetaInfoPrivateData()->isPropertyList(propertyName());
+ }
}
bool PropertyMetaInfo::isEnumType() const
{
- if constexpr (useProjectStorage())
+ if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return {};
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is enum type"_t,
+ category(),
+ keyValue("property has enumeration type", m_id)};
+
return propertyType().isEnumeration();
- else
+ } else {
return isValid() && nodeMetaInfoPrivateData()->isPropertyEnum(propertyName());
+ }
}
bool PropertyMetaInfo::isPrivate() const
{
- if constexpr (useProjectStorage())
+ if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return {};
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is private property"_t,
+ category(),
+ keyValue("property declaration id", m_id)};
+
return isValid() && propertyData().name.startsWith("__");
- else
+ } else {
return isValid() && propertyName().startsWith("__");
+ }
}
bool PropertyMetaInfo::isPointer() const
{
- if constexpr (useProjectStorage())
+ if constexpr (useProjectStorage()) {
+ if (!isValid())
+ return {};
+
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"is pointer property"_t,
+ category(),
+ keyValue("property declaration id", m_id)};
+
return isValid() && (propertyData().traits & Storage::PropertyDeclarationTraits::IsPointer);
- else
+ } else {
return isValid() && nodeMetaInfoPrivateData()->isPropertyPointer(propertyName());
+ }
}
namespace {
@@ -3487,6 +4469,11 @@ QVariant PropertyMetaInfo::castedValue(const QVariant &value) const
return {};
if constexpr (!useProjectStorage()) {
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"cast value"_t,
+ category(),
+ keyValue("property declaration id", m_id)};
+
const QVariant variant = value;
QVariant copyVariant = variant;
const TypeName &typeName = propertyTypeName();
@@ -3504,7 +4491,7 @@ QVariant PropertyMetaInfo::castedValue(const QVariant &value) const
return variant;
} else if (typeId == QVariant::UserType && typeName == "var") {
return variant;
- } else if (variant.typeId() == QVariant::List) {
+ } else if (variant.typeId() == QMetaType::QVariantList) {
// TODO: check the contents of the list
return variant;
} else if (typeName == "var" || typeName == "variant") {
diff --git a/src/plugins/qmldesigner/designercore/metainfo/subcomponentmanager.cpp b/src/plugins/qmldesigner/designercore/metainfo/subcomponentmanager.cpp
index 16d9217f6a..29a093f199 100644
--- a/src/plugins/qmldesigner/designercore/metainfo/subcomponentmanager.cpp
+++ b/src/plugins/qmldesigner/designercore/metainfo/subcomponentmanager.cpp
@@ -345,11 +345,8 @@ void SubComponentManager::unregisterQmlFile(const QFileInfo &fileInfo, const QSt
void SubComponentManager::registerQmlFile(const QFileInfo &fileInfo, const QString &qualifier,
bool addToLibrary)
{
- if (!addToLibrary || !model()
- || m_componentUtils.isImport3dPath(fileInfo.path())
- || m_componentUtils.isComposedEffectPath(fileInfo.path())) {
+ if (!addToLibrary || !model() || m_componentUtils.isGeneratedPath(fileInfo.path()))
return;
- }
QString componentName = fileInfo.baseName();
const QString baseComponentName = componentName;
diff --git a/src/plugins/qmldesigner/designercore/model/model.cpp b/src/plugins/qmldesigner/designercore/model/model.cpp
index 4f0bfba1ce..d4eea26378 100644
--- a/src/plugins/qmldesigner/designercore/model/model.cpp
+++ b/src/plugins/qmldesigner/designercore/model/model.cpp
@@ -9,7 +9,6 @@
#include "../projectstorage/sourcepath.h"
#include "../projectstorage/sourcepathcache.h"
#include "abstractview.h"
-#include "auxiliarydataproperties.h"
#include "internalbindingproperty.h"
#include "internalnodeabstractproperty.h"
#include "internalnodelistproperty.h"
@@ -33,6 +32,8 @@
#include "signalhandlerproperty.h"
#include "variantproperty.h"
+#include <uniquename.h>
+
#include <projectstorage/projectstorage.h>
#include <qmljs/qmljsmodelmanagerinterface.h>
@@ -46,8 +47,6 @@
#include <QRegularExpression>
#include <qcompilerdetection.h>
-#include <string>
-
/*!
\defgroup CoreModel
*/
@@ -170,10 +169,10 @@ Storage::Imports createStorageImports(const Imports &imports,
SourceId fileId)
{
return Utils::transform<Storage::Imports>(imports, [&](const Import &import) {
- return Storage::Import{projectStorage.moduleId(Utils::SmallString{import.url()}),
- import.majorVersion(),
- import.minorVersion(),
- fileId};
+ using Storage::ModuleKind;
+ auto moduleKind = import.isLibraryImport() ? ModuleKind::QmlLibrary : ModuleKind::PathLibrary;
+ auto moduleId = projectStorage.moduleId(Utils::SmallString{import.url()}, moduleKind);
+ return Storage::Import{moduleId, import.majorVersion(), import.minorVersion(), fileId};
});
}
@@ -244,7 +243,7 @@ void ModelPrivate::notifyUsedImportsChanged(const Imports &usedImports)
}
}
-QUrl ModelPrivate::fileUrl() const
+const QUrl &ModelPrivate::fileUrl() const
{
return m_fileUrl;
}
@@ -390,7 +389,11 @@ ImportedTypeNameId ModelPrivate::importedTypeNameId(Utils::SmallStringView typeN
return import.alias() == aliasName;
});
if (found != m_imports.end()) {
- ModuleId moduleId = projectStorage->moduleId(Utils::PathString{found->url()});
+ using Storage::ModuleKind;
+ auto moduleKind = found->isLibraryImport() ? ModuleKind::QmlLibrary
+ : ModuleKind::PathLibrary;
+ ModuleId moduleId = projectStorage->moduleId(Utils::PathString{found->url()},
+ moduleKind);
ImportId importId = projectStorage->importId(
Storage::Import{moduleId, found->majorVersion(), found->minorVersion(), m_sourceId});
return projectStorage->importedTypeNameId(importId, shortTypeName);
@@ -1860,90 +1863,18 @@ bool Model::hasImport(const QString &importUrl) const
});
}
-static QString firstCharToLower(const QString &string)
-{
- QString resultString = string;
-
- if (!resultString.isEmpty())
- resultString[0] = resultString.at(0).toLower();
-
- return resultString;
-}
-
-QString Model::generateNewId(const QString &prefixName,
- const QString &fallbackPrefix,
- std::optional<std::function<bool(const QString &)>> isDuplicate) const
+QString Model::generateNewId(const QString &prefixName, const QString &fallbackPrefix) const
{
- // First try just the prefixName without number as postfix, then continue with 2 and further
- // as postfix until id does not already exist.
- // Properties of the root node are not allowed for ids, because they are available in the
- // complete context without qualification.
-
- int counter = 0;
-
- static const QRegularExpression nonWordCharsRegex("\\W");
- QString newBaseId = firstCharToLower(prefixName);
- newBaseId.remove(nonWordCharsRegex);
-
- if (!newBaseId.isEmpty()) {
- QChar firstChar = newBaseId.at(0);
- if (firstChar.isDigit())
- newBaseId.prepend('_');
- } else {
- newBaseId = fallbackPrefix;
- }
-
- QString newId = newBaseId;
+ QString newId = prefixName;
- if (!isDuplicate.has_value())
- isDuplicate = std::bind(&Model::hasId, this, std::placeholders::_1);
+ if (newId.isEmpty())
+ newId = fallbackPrefix;
- while (!ModelNode::isValidId(newId) || isDuplicate.value()(newId)
- || d->rootNode()->property(newId.toUtf8())) {
- ++counter;
- newId = QStringView(u"%1%2").arg(firstCharToLower(newBaseId)).arg(counter);
- }
-
- return newId;
-}
-
-// Generate a unique camelCase id from a name
-// note: this methods does the same as generateNewId(). The 2 methods should be merged into one
-QString Model::generateIdFromName(const QString &name, const QString &fallbackId) const
-{
- QString newId;
- if (name.isEmpty()) {
- newId = fallbackId;
- } else {
- // convert to camel case
- QStringList nameWords = name.split(" ");
- nameWords[0] = nameWords[0].at(0).toLower() + nameWords[0].mid(1);
- for (int i = 1; i < nameWords.size(); ++i)
- nameWords[i] = nameWords[i].at(0).toUpper() + nameWords[i].mid(1);
- newId = nameWords.join("");
-
- // if id starts with a number prepend an underscore
- if (newId.at(0).isDigit())
- newId.prepend('_');
- }
-
- // If the new id is not valid (e.g. qml keyword match), try fixing it by prepending underscore
- if (!ModelNode::isValidId(newId))
- newId.prepend("_");
-
- QRegularExpression rgx("\\d+$"); // matches a number at the end of a string
- while (hasId(newId)) { // id exists
- QRegularExpressionMatch match = rgx.match(newId);
- if (match.hasMatch()) { // ends with a number, increment it
- QString numStr = match.captured();
- int num = numStr.toInt() + 1;
- newId = newId.mid(0, match.capturedStart()) + QString::number(num);
- } else {
- newId.append('1');
- }
- }
-
- return newId;
+ return UniqueName::generateId(prefixName, [&] (const QString &id) {
+ // Properties of the root node are not allowed for ids, because they are available in the
+ // complete context without qualification.
+ return hasId(id) || d->rootNode()->property(id.toUtf8());
+ });
}
void Model::startDrag(QMimeData *mimeData, const QPixmap &icon)
@@ -2115,7 +2046,7 @@ void Model::clearMetaInfoCache()
\brief Returns the URL against which relative URLs within the model should be resolved.
\return The base URL.
*/
-QUrl Model::fileUrl() const
+const QUrl &Model::fileUrl() const
{
return d->fileUrl();
}
@@ -2556,8 +2487,7 @@ QList<ItemLibraryEntry> Model::itemLibraryEntries() const
{
#ifdef QDS_USE_PROJECTSTORAGE
using namespace Storage::Info;
- return toItemLibraryEntries(d->projectStorage->itemLibraryEntries(d->m_sourceId),
- *d->projectStorage);
+ return toItemLibraryEntries(d->projectStorage->itemLibraryEntries(d->m_sourceId));
#else
return d->metaInfo().itemLibraryInfo()->entries();
#endif
@@ -2623,11 +2553,10 @@ MetaInfo Model::metaInfo()
}
#endif
-Module Model::module(Utils::SmallStringView moduleName)
+Module Model::module(Utils::SmallStringView moduleName, Storage::ModuleKind moduleKind)
{
- if constexpr (useProjectStorage()) {
- return Module(d->projectStorage->moduleId(moduleName), d->projectStorage);
- }
+ if constexpr (useProjectStorage())
+ return Module(d->projectStorage->moduleId(moduleName, moduleKind), d->projectStorage);
return {};
}
diff --git a/src/plugins/qmldesigner/designercore/model/model_p.h b/src/plugins/qmldesigner/designercore/model/model_p.h
index a3e972f329..cb082fd1d7 100644
--- a/src/plugins/qmldesigner/designercore/model/model_p.h
+++ b/src/plugins/qmldesigner/designercore/model/model_p.h
@@ -122,7 +122,7 @@ public:
ModelPrivate(const ModelPrivate &) = delete;
ModelPrivate &operator=(const ModelPrivate &) = delete;
- QUrl fileUrl() const;
+ const QUrl &fileUrl() const;
void setFileUrl(const QUrl &url);
InternalNodePointer createNode(const TypeName &typeName,
diff --git a/src/plugins/qmldesigner/designercore/model/propertycontainer.cpp b/src/plugins/qmldesigner/designercore/model/propertycontainer.cpp
index cd7cebbb73..8eaa7947de 100644
--- a/src/plugins/qmldesigner/designercore/model/propertycontainer.cpp
+++ b/src/plugins/qmldesigner/designercore/model/propertycontainer.cpp
@@ -41,7 +41,7 @@ PropertyName PropertyContainer::name() const
QVariant PropertyContainer::value() const
{
- if (m_value.typeId() == QVariant::String)
+ if (m_value.typeId() == QMetaType::QString)
m_value = PropertyParser::read(m_type, m_value.toString());
return m_value;
}
diff --git a/src/plugins/qmldesigner/designercore/model/qmlvisualnode.cpp b/src/plugins/qmldesigner/designercore/model/qmlvisualnode.cpp
index c84f234257..726d3d52af 100644
--- a/src/plugins/qmldesigner/designercore/model/qmlvisualnode.cpp
+++ b/src/plugins/qmldesigner/designercore/model/qmlvisualnode.cpp
@@ -24,6 +24,8 @@
#include <QDir>
#include <QRandomGenerator>
+#include <memory>
+
namespace QmlDesigner {
static char imagePlaceHolder[] = "qrc:/qtquickplugin/images/template_image.png";
@@ -182,8 +184,8 @@ void QmlVisualNode::scatter(const ModelNode &targetNode, const std::optional<int
if (!scatter)
return;
- if (offset.has_value()) { // offset
- double offsetValue = offset.value();
+ if (offset) { // offset
+ double offsetValue = *offset;
this->translate(QVector3D(offsetValue, offsetValue, offsetValue));
} else { // scatter in range
const double scatterRange = 20.;
@@ -250,8 +252,7 @@ QmlObjectNode QmlVisualNode::createQmlObjectNode(AbstractView *view,
NodeAbstractProperty parentProperty = parentQmlItemNode.defaultNodeAbstractProperty();
-
- NodeHints hints = NodeHints::fromItemLibraryEntry(itemLibraryEntry);
+ NodeHints hints = NodeHints::fromItemLibraryEntry(itemLibraryEntry, view->model());
const PropertyName forceNonDefaultProperty = hints.forceNonDefaultProperty().toUtf8();
QmlObjectNode newNode = QmlItemNode::createQmlObjectNode(view,
@@ -289,17 +290,17 @@ static QmlObjectNode createQmlObjectNodeFromSource(AbstractView *view,
textEdit.setPlainText(source);
NotIndentingTextEditModifier modifier(&textEdit);
- QScopedPointer<RewriterView> rewriterView(
- new RewriterView(view->externalDependencies(), RewriterView::Amend));
+ std::unique_ptr<RewriterView> rewriterView = std::make_unique<RewriterView>(
+ view->externalDependencies(), RewriterView::Amend);
rewriterView->setCheckSemanticErrors(false);
rewriterView->setTextModifier(&modifier);
rewriterView->setAllowComponentRoot(true);
rewriterView->setPossibleImportsEnabled(false);
- inputModel->setRewriterView(rewriterView.data());
+ inputModel->setRewriterView(rewriterView.get());
if (rewriterView->errors().isEmpty() && rewriterView->rootModelNode().isValid()) {
ModelNode rootModelNode = rewriterView->rootModelNode();
- inputModel->detachView(rewriterView.data());
+ inputModel->detachView(rewriterView.get());
QmlVisualNode(rootModelNode).setPosition(position);
ModelMerger merger(view);
return merger.insertModel(rootModelNode);
@@ -329,7 +330,7 @@ QmlObjectNode QmlVisualNode::createQmlObjectNode(AbstractView *view,
{
QmlObjectNode newQmlObjectNode;
- NodeHints hints = NodeHints::fromItemLibraryEntry(itemLibraryEntry);
+ NodeHints hints = NodeHints::fromItemLibraryEntry(itemLibraryEntry, view->model());
auto createNodeFunc = [=, &newQmlObjectNode, &parentProperty]() {
#ifndef QDS_USE_PROJECTSTORAGE
@@ -361,13 +362,17 @@ QmlObjectNode QmlVisualNode::createQmlObjectNode(AbstractView *view,
propertyPairList.append(position.propertyPairList());
ModelNode::NodeSourceType nodeSourceType = ModelNode::NodeWithoutSource;
- if (itemLibraryEntry.typeName() == "QtQml.Component")
- nodeSourceType = ModelNode::NodeWithComponentSource;
#ifdef QDS_USE_PROJECTSTORAGE
+ NodeMetaInfo metaInfo{itemLibraryEntry.typeId(), view->model()->projectStorage()};
+ if (metaInfo.isQmlComponent())
+ nodeSourceType = ModelNode::NodeWithComponentSource;
newQmlObjectNode = QmlObjectNode(view->createModelNode(
itemLibraryEntry.typeName(), propertyPairList, {}, {}, nodeSourceType));
#else
+ if (itemLibraryEntry.typeName() == "QtQml.Component")
+ nodeSourceType = ModelNode::NodeWithComponentSource;
+
newQmlObjectNode = QmlObjectNode(view->createModelNode(itemLibraryEntry.typeName(),
majorVersion,
minorVersion,
diff --git a/src/plugins/qmldesigner/designercore/model/rewriteactioncompressor.cpp b/src/plugins/qmldesigner/designercore/model/rewriteactioncompressor.cpp
index c4d96bb250..edee6840ef 100644
--- a/src/plugins/qmldesigner/designercore/model/rewriteactioncompressor.cpp
+++ b/src/plugins/qmldesigner/designercore/model/rewriteactioncompressor.cpp
@@ -58,7 +58,6 @@ void RewriteActionCompressor::compressImports(QList<RewriteAction *> &actions) c
actionsToRemove.append(action);
actionsToRemove.append(addImportAction);
addedImports.remove(import);
- delete addImportAction;
} else {
removedImports.insert(import, action);
}
@@ -67,13 +66,11 @@ void RewriteActionCompressor::compressImports(QList<RewriteAction *> &actions) c
if (RewriteAction *duplicateAction = addedImports.value(import, 0)) {
actionsToRemove.append(duplicateAction);
addedImports.remove(import);
- delete duplicateAction;
addedImports.insert(import, action);
} else if (RewriteAction *removeAction = removedImports.value(import, 0)) {
actionsToRemove.append(action);
actionsToRemove.append(removeAction);
removedImports.remove(import);
- delete removeAction;
} else {
addedImports.insert(import, action);
}
diff --git a/src/plugins/qmldesigner/designercore/model/rewriterview.cpp b/src/plugins/qmldesigner/designercore/model/rewriterview.cpp
index 17d40daca3..3c481573d2 100644
--- a/src/plugins/qmldesigner/designercore/model/rewriterview.cpp
+++ b/src/plugins/qmldesigner/designercore/model/rewriterview.cpp
@@ -16,12 +16,14 @@
#include <filemanager/objectlengthcalculator.h>
#include <modelnode.h>
#include <modelnodepositionstorage.h>
+#include <nodemetainfo.h>
#include <nodeproperty.h>
+#include <projectstorage/projectstorage.h>
+#include <qmlobjectnode.h>
+#include <qmltimelinekeyframegroup.h>
#include <rewritingexception.h>
#include <signalhandlerproperty.h>
#include <variantproperty.h>
-#include <qmlobjectnode.h>
-#include <qmltimelinekeyframegroup.h>
#include <qmljs/parser/qmljsengine_p.h>
#include <qmljs/qmljsmodelmanagerinterface.h>
@@ -56,9 +58,9 @@ RewriterView::RewriterView(ExternalDependenciesInterface &externalDependencies,
DifferenceHandling differenceHandling)
: AbstractView{externalDependencies}
, m_differenceHandling(differenceHandling)
- , m_positionStorage(new ModelNodePositionStorage)
- , m_modelToTextMerger(new Internal::ModelToTextMerger(this))
- , m_textToModelMerger(new Internal::TextToModelMerger(this))
+ , m_positionStorage(std::make_unique<ModelNodePositionStorage>())
+ , m_modelToTextMerger(std::make_unique<Internal::ModelToTextMerger>(this))
+ , m_textToModelMerger(std::make_unique<Internal::TextToModelMerger>(this))
{
m_amendTimer.setSingleShot(true);
@@ -78,12 +80,12 @@ RewriterView::~RewriterView() = default;
Internal::ModelToTextMerger *RewriterView::modelToTextMerger() const
{
- return m_modelToTextMerger.data();
+ return m_modelToTextMerger.get();
}
Internal::TextToModelMerger *RewriterView::textToModelMerger() const
{
- return m_textToModelMerger.data();
+ return m_textToModelMerger.get();
}
void RewriterView::modelAttached(Model *model)
@@ -92,7 +94,7 @@ void RewriterView::modelAttached(Model *model)
AbstractView::modelAttached(model);
- ModelAmender differenceHandler(m_textToModelMerger.data());
+ ModelAmender differenceHandler(m_textToModelMerger.get());
const QString qmlSource = m_textModifier->text();
if (m_textToModelMerger->load(qmlSource, differenceHandler))
m_lastCorrectQmlSource = qmlSource;
@@ -104,6 +106,7 @@ void RewriterView::modelAttached(Model *model)
m_modelAttachPending = true;
QTimer::singleShot(1000, this, [this, model](){
modelAttached(model);
+ restoreAuxiliaryData();
});
}
}
@@ -492,7 +495,7 @@ void RewriterView::amendQmlText()
const QString newQmlText = m_textModifier->text();
- ModelAmender differenceHandler(m_textToModelMerger.data());
+ ModelAmender differenceHandler(m_textToModelMerger.get());
if (m_textToModelMerger->load(newQmlText, differenceHandler))
m_lastCorrectQmlSource = newQmlText;
emitCustomNotification(EndRewriterAmend);
@@ -593,7 +596,7 @@ QString RewriterView::auxiliaryDataAsQML() const
hasAuxData = true;
QString strValue = value.toString();
- auto metaType = static_cast<QMetaType::Type>(value.type());
+ const int metaType = value.typeId();
if (metaType == QMetaType::QString
|| metaType == QMetaType::QColor) {
@@ -698,7 +701,7 @@ void RewriterView::forceAmend()
Internal::ModelNodePositionStorage *RewriterView::positionStorage() const
{
- return m_positionStorage.data();
+ return m_positionStorage.get();
}
QList<DocumentMessage> RewriterView::warnings() const
@@ -755,7 +758,7 @@ void RewriterView::resetToLastCorrectQml()
{
m_textModifier->textDocument()->undo();
m_textModifier->textDocument()->clearUndoRedoStacks(QTextDocument::RedoStack);
- ModelAmender differenceHandler(m_textToModelMerger.data());
+ ModelAmender differenceHandler(m_textToModelMerger.get());
Internal::WriteLocker::unlock(model());
m_textToModelMerger->load(m_textModifier->text(), differenceHandler);
Internal::WriteLocker::lock(model());
@@ -1004,6 +1007,61 @@ QSet<QPair<QString, QString> > RewriterView::qrcMapping() const
return m_textToModelMerger->qrcMapping();
}
+namespace {
+#ifdef QDS_USE_PROJECTSTORAGE
+
+ModuleIds generateModuleIds(const ModelNodes &nodes)
+{
+ ModuleIds moduleIds;
+ moduleIds.reserve(Utils::usize(nodes));
+ for (const auto &node : nodes) {
+ auto exportedNames = node.metaInfo().allExportedTypeNames();
+ if (exportedNames.size())
+ moduleIds.push_back(exportedNames.front().moduleId);
+ }
+
+ std::sort(moduleIds.begin(), moduleIds.end());
+ moduleIds.erase(std::unique(moduleIds.begin(), moduleIds.end()), moduleIds.end());
+
+ return moduleIds;
+}
+
+QStringList generateImports(ModuleIds moduleIds, const ProjectStorageType &projectStorage)
+{
+ QStringList imports;
+ imports.reserve(std::ssize(moduleIds));
+
+ for (auto moduleId : moduleIds) {
+ using Storage::ModuleKind;
+ auto module = projectStorage.module(moduleId);
+ switch (module.kind) {
+ case ModuleKind::QmlLibrary:
+ imports.push_back("import " + module.name.toQString());
+ break;
+ case ModuleKind::PathLibrary:
+ imports.push_back("import \"" + module.name.toQString() + "\"");
+ break;
+ case ModuleKind::CppLibrary:
+ break;
+ }
+ }
+
+ return imports;
+}
+
+QStringList generateImports(const ModelNodes &nodes)
+{
+ if (nodes.empty())
+ return {};
+
+ auto moduleIds = generateModuleIds(nodes);
+
+ return generateImports(moduleIds, *nodes.front().model()->projectStorage());
+}
+
+#endif
+} // namespace
+
void RewriterView::moveToComponent(const ModelNode &modelNode)
{
if (!modelNode.isValid())
@@ -1012,20 +1070,26 @@ void RewriterView::moveToComponent(const ModelNode &modelNode)
int offset = nodeOffset(modelNode);
const QList<ModelNode> nodes = modelNode.allSubModelNodesAndThisNode();
- QSet<QString> directPaths;
+#ifdef QDS_USE_PROJECTSTORAGE
+ auto directPaths = generateImports(nodes);
+#else
+ QSet<QString> directPathsSet;
// Always add QtQuick import
QString quickImport = model()->qtQuickItemMetaInfo().requiredImportString();
if (!quickImport.isEmpty())
- directPaths.insert(quickImport);
+ directPathsSet.insert(quickImport);
for (const ModelNode &partialNode : nodes) {
QString importStr = partialNode.metaInfo().requiredImportString();
if (importStr.size())
- directPaths << importStr;
+ directPathsSet << importStr;
}
- QString importData = Utils::sorted(directPaths.values()).join(QChar::LineFeed);
+ auto directPaths = directPathsSet.values();
+#endif
+
+ QString importData = Utils::sorted(directPaths).join(QChar::LineFeed);
if (importData.size())
importData.append(QString(2, QChar::LineFeed));
@@ -1091,7 +1155,7 @@ void RewriterView::qmlTextChanged()
switch (m_differenceHandling) {
case Validate: {
- ModelValidator differenceHandler(m_textToModelMerger.data());
+ ModelValidator differenceHandler(m_textToModelMerger.get());
if (m_textToModelMerger->load(newQmlText, differenceHandler))
m_lastCorrectQmlSource = newQmlText;
break;
diff --git a/src/plugins/qmldesigner/designercore/model/stylesheetmerger.cpp b/src/plugins/qmldesigner/designercore/model/stylesheetmerger.cpp
index 16877b61db..4f744d54e6 100644
--- a/src/plugins/qmldesigner/designercore/model/stylesheetmerger.cpp
+++ b/src/plugins/qmldesigner/designercore/model/stylesheetmerger.cpp
@@ -23,6 +23,8 @@
#include <QQueue>
#include <QRegularExpression>
+#include <memory>
+
namespace {
QPoint pointForModelNode(const QmlDesigner::ModelNode &node)
@@ -641,10 +643,10 @@ void StylesheetMerger::styleMerge(const QString &qmlTemplateString,
textEditTemplate.setPlainText(imports + qmlTemplateString);
NotIndentingTextEditModifier textModifierTemplate(&textEditTemplate);
- QScopedPointer<RewriterView> templateRewriterView(
- new RewriterView(externalDependencies, RewriterView::Amend));
+ std::unique_ptr<RewriterView> templateRewriterView = std::make_unique<RewriterView>(
+ externalDependencies, RewriterView::Amend);
templateRewriterView->setTextModifier(&textModifierTemplate);
- templateModel->attachView(templateRewriterView.data());
+ templateModel->attachView(templateRewriterView.get());
templateRewriterView->setCheckSemanticErrors(false);
templateRewriterView->setPossibleImportsEnabled(false);
@@ -665,12 +667,12 @@ void StylesheetMerger::styleMerge(const QString &qmlTemplateString,
textEditStyle.setPlainText(parentRewriterView->textModifierContent());
NotIndentingTextEditModifier textModifierStyle(&textEditStyle);
- QScopedPointer<RewriterView> styleRewriterView(
- new RewriterView(externalDependencies, RewriterView::Amend));
+ std::unique_ptr<RewriterView> styleRewriterView = std::make_unique<RewriterView>(
+ externalDependencies, RewriterView::Amend);
styleRewriterView->setTextModifier(&textModifierStyle);
- styleModel->attachView(styleRewriterView.data());
+ styleModel->attachView(styleRewriterView.get());
- StylesheetMerger merger(templateRewriterView.data(), styleRewriterView.data());
+ StylesheetMerger merger(templateRewriterView.get(), styleRewriterView.get());
try {
merger.merge();
diff --git a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp
index 1c1aba5feb..b66270dca1 100644
--- a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp
+++ b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp
@@ -94,21 +94,23 @@ bool isGlobalQtEnums(QStringView value)
bool isKnownEnumScopes(QStringView value)
{
- static constexpr auto list = Utils::to_array<std::u16string_view>({u"TextInput",
- u"TextEdit",
- u"Material",
- u"Universal",
- u"Font",
- u"Shape",
- u"ShapePath",
- u"AbstractButton",
- u"Text",
- u"ShaderEffectSource",
- u"Grid",
- u"ItemLayer",
- u"ImageLayer",
- u"SpriteLayer",
- u"Light"});
+ static constexpr auto list = Utils::to_array<std::u16string_view>(
+ {u"TextInput",
+ u"TextEdit",
+ u"Material",
+ u"Universal",
+ u"Font",
+ u"Shape",
+ u"ShapePath",
+ u"AbstractButton",
+ u"Text",
+ u"ShaderEffectSource",
+ u"Grid",
+ u"ItemLayer",
+ u"ImageLayer",
+ u"SpriteLayer",
+ u"Light",
+ u"ExtendedSceneEnvironment.GlowBlendMode"});
return std::find(std::begin(list), std::end(list), QmlDesigner::ModelUtils::toStdStringView(value))
!= std::end(list);
@@ -559,6 +561,11 @@ public:
//Check for known enum scopes used globally
if (isKnownEnumScopes(astValueList.constFirst()))
return QVariant::fromValue(Enumeration(astValue));
+ } else if (astValueList.size() == 3) {
+ QString enumName = astValueList.constFirst() + '.' + astValueList.at(1);
+ if (isKnownEnumScopes(enumName))
+ return QVariant::fromValue(
+ Enumeration(enumName.toUtf8(), astValueList.constLast().toUtf8()));
}
auto eStmt = AST::cast<AST::ExpressionStatement *>(rhs);
diff --git a/src/plugins/qmldesigner/designercore/projectstorage/commontypecache.h b/src/plugins/qmldesigner/designercore/projectstorage/commontypecache.h
index 35658c005f..76305b1fbe 100644
--- a/src/plugins/qmldesigner/designercore/projectstorage/commontypecache.h
+++ b/src/plugins/qmldesigner/designercore/projectstorage/commontypecache.h
@@ -84,7 +84,6 @@ inline constexpr char PrincipledMaterial[] = "PrincipledMaterial";
inline constexpr char PropertyAnimation[] = "PropertyAnimation";
inline constexpr char PropertyChanges[] = "PropertyChanges";
inline constexpr char QML[] = "QML";
-inline constexpr char QML_cppnative[] = "QML-cppnative";
inline constexpr char QQuick3DParticleAbstractShape[] = "QQuick3DParticleAbstractShape";
inline constexpr char QQuickStateOperation[] = "QQuickStateOperation";
inline constexpr char QtMultimedia[] = "QtMultimedia";
@@ -94,7 +93,6 @@ inline constexpr char QtQml_Models[] = "QtQml.Models";
inline constexpr char QtQml_XmlListModel[] = "QtQml.XmlListModel";
inline constexpr char QtQuick3D[] = "QtQuick3D";
inline constexpr char QtQuick3D_Particles3D[] = "QtQuick3D.Particles3D";
-inline constexpr char QtQuick3D_Particles3D_cppnative[] = "QtQuick3D.Particles3D-cppnative";
inline constexpr char QtQuick[] = "QtQuick";
inline constexpr char QtQuick_Controls[] = "QtQuick.Controls";
inline constexpr char QtQuick_Dialogs[] = "QtQuick.Dialogs";
@@ -104,7 +102,6 @@ inline constexpr char QtQuick_Studio_Components[] = "QtQuick.Studio.Components";
inline constexpr char QtQuick_Templates[] = "QtQuick.Templates";
inline constexpr char QtQuick_Timeline[] = "QtQuick.Timeline";
inline constexpr char QtQuick_Window[] = "QtQuick.Window";
-inline constexpr char QtQuick_cppnative[] = "QtQuick-cppnative";
inline constexpr char Qt_SafeRenderer[] = "Qt.SafeRenderer";
inline constexpr char Rectangle[] = "Rectangle";
inline constexpr char Repeater[] = "Repeater";
@@ -149,7 +146,7 @@ struct BaseCacheType
QmlDesigner::TypeId typeId;
};
-template<const char *moduleName_, const char *typeName_>
+template<const char *moduleName_, ModuleKind moduleKind, const char *typeName_>
struct CacheType : public BaseCacheType
{
};
@@ -157,106 +154,107 @@ struct CacheType : public BaseCacheType
template<typename ProjectStorage>
class CommonTypeCache
{
- using CommonTypes = std::tuple<CacheType<FlowView, FlowActionArea>,
- CacheType<FlowView, FlowDecision>,
- CacheType<FlowView, FlowItem>,
- CacheType<FlowView, FlowTransition>,
- CacheType<FlowView, FlowView>,
- CacheType<FlowView, FlowWildcard>,
- CacheType<QML, BoolType>,
- CacheType<QML, Component>,
- CacheType<QML, DoubleType>,
- CacheType<QML, IntType>,
- CacheType<QML, QtObject>,
- CacheType<QML, date>,
- CacheType<QML, string>,
- CacheType<QML, url>,
- CacheType<QML, var>,
- CacheType<QML_cppnative, FloatType>,
- CacheType<QML_cppnative, UIntType>,
- CacheType<QtQml, Connections>,
- CacheType<QtMultimedia, SoundEffect>,
- CacheType<QtQml_Models, ListElement>,
- CacheType<QtQml_Models, ListModel>,
- CacheType<QtQml_XmlListModel, XmlListModelRole>,
- CacheType<QtQuick, BorderImage>,
- CacheType<QtQuick, GridView>,
- CacheType<QtQuick, Image>,
- CacheType<QtQuick, Item>,
- CacheType<QtQuick, ListView>,
- CacheType<QtQuick, Loader>,
- CacheType<QtQuick, MouseArea>,
- CacheType<QtQuick, Path>,
- CacheType<QtQuick, PathView>,
- CacheType<QtQuick, PauseAnimation>,
- CacheType<QtQuick, Positioner>,
- CacheType<QtQuick, PropertyAnimation>,
- CacheType<QtQuick, PropertyChanges>,
- CacheType<QtQuick, Rectangle>,
- CacheType<QtQuick, Repeater>,
- CacheType<QtQuick, State>,
- CacheType<QtQuick, StateGroup>,
- CacheType<QtQuick, Text>,
- CacheType<QtQuick, TextEdit>,
- CacheType<QtQuick, Transition>,
- CacheType<QtQuick, color>,
- CacheType<QtQuick, font>,
- CacheType<QtQuick, vector2d>,
- CacheType<QtQuick, vector3d>,
- CacheType<QtQuick, vector4d>,
- CacheType<QtQuick3D, BakedLightmap>,
- CacheType<QtQuick3D, Buffer>,
- CacheType<QtQuick3D, Camera>,
- CacheType<QtQuick3D, Command>,
- CacheType<QtQuick3D, CubeMapTexture>,
- CacheType<QtQuick3D, DefaultMaterial>,
- CacheType<QtQuick3D, DirectionalLight>,
- CacheType<QtQuick3D, Effect>,
- CacheType<QtQuick3D, InstanceList>,
- CacheType<QtQuick3D, InstanceListEntry>,
- CacheType<QtQuick3D, Light>,
- CacheType<QtQuick3D, Material>,
- CacheType<QtQuick3D, Model>,
- CacheType<QtQuick3D, Node>,
- CacheType<QtQuick3D, OrthographicCamera>,
- CacheType<QtQuick3D, Pass>,
- CacheType<QtQuick3D, PerspectiveCamera>,
- CacheType<QtQuick3D, PointLight>,
- CacheType<QtQuick3D, PrincipledMaterial>,
- CacheType<QtQuick3D, SceneEnvironment>,
- CacheType<QtQuick3D, Shader>,
- CacheType<QtQuick3D, SpecularGlossyMaterial>,
- CacheType<QtQuick3D, SpotLight>,
- CacheType<QtQuick3D, Texture>,
- CacheType<QtQuick3D, TextureInput>,
- CacheType<QtQuick3D, View3D>,
- CacheType<QtQuick3D_Particles3D, Affector3D>,
- CacheType<QtQuick3D_Particles3D, Attractor3D>,
- CacheType<QtQuick3D_Particles3D, Model>,
- CacheType<QtQuick3D_Particles3D, Particle3D>,
- CacheType<QtQuick3D_Particles3D, ParticleEmitter3D>,
- CacheType<QtQuick3D_Particles3D, SpriteParticle3D>,
- CacheType<QtQuick3D_Particles3D_cppnative, QQuick3DParticleAbstractShape>,
- CacheType<QtQuick_Controls, Control>,
- CacheType<QtQuick_Controls, Popup>,
- CacheType<QtQuick_Controls, SplitView>,
- CacheType<QtQuick_Controls, SwipeView>,
- CacheType<QtQuick_Controls, TabBar>,
- CacheType<QtQuick_Controls, TextArea>,
- CacheType<QtQuick_Dialogs, Dialog>,
- CacheType<QtQuick_Extras, Picture>,
- CacheType<QtQuick_Layouts, Layout>,
- CacheType<QtQuick_Studio_Components, GroupItem>,
- CacheType<QtQuick_Studio_Components, JsonListModel>,
- CacheType<QtQuick_Templates, Control>,
- CacheType<QtQuick_Timeline, Keyframe>,
- CacheType<QtQuick_Timeline, KeyframeGroup>,
- CacheType<QtQuick_Timeline, Timeline>,
- CacheType<QtQuick_Timeline, TimelineAnimation>,
- CacheType<QtQuick_cppnative, QQuickStateOperation>,
- CacheType<Qt_SafeRenderer, SafePicture>,
- CacheType<Qt_SafeRenderer, SafeRendererPicture>,
- CacheType<QtQuick_Window, Window>>;
+ using CommonTypes = std::tuple<
+ CacheType<FlowView, ModuleKind::QmlLibrary, FlowActionArea>,
+ CacheType<FlowView, ModuleKind::QmlLibrary, FlowDecision>,
+ CacheType<FlowView, ModuleKind::QmlLibrary, FlowItem>,
+ CacheType<FlowView, ModuleKind::QmlLibrary, FlowTransition>,
+ CacheType<FlowView, ModuleKind::QmlLibrary, FlowView>,
+ CacheType<FlowView, ModuleKind::QmlLibrary, FlowWildcard>,
+ CacheType<QML, ModuleKind::QmlLibrary, BoolType>,
+ CacheType<QML, ModuleKind::QmlLibrary, Component>,
+ CacheType<QML, ModuleKind::QmlLibrary, DoubleType>,
+ CacheType<QML, ModuleKind::QmlLibrary, IntType>,
+ CacheType<QML, ModuleKind::QmlLibrary, QtObject>,
+ CacheType<QML, ModuleKind::QmlLibrary, date>,
+ CacheType<QML, ModuleKind::QmlLibrary, string>,
+ CacheType<QML, ModuleKind::QmlLibrary, url>,
+ CacheType<QML, ModuleKind::QmlLibrary, var>,
+ CacheType<QML, ModuleKind::CppLibrary, FloatType>,
+ CacheType<QML, ModuleKind::CppLibrary, UIntType>,
+ CacheType<QtQml, ModuleKind::QmlLibrary, Connections>,
+ CacheType<QtMultimedia, ModuleKind::QmlLibrary, SoundEffect>,
+ CacheType<QtQml_Models, ModuleKind::QmlLibrary, ListElement>,
+ CacheType<QtQml_Models, ModuleKind::QmlLibrary, ListModel>,
+ CacheType<QtQml_XmlListModel, ModuleKind::QmlLibrary, XmlListModelRole>,
+ CacheType<QtQuick, ModuleKind::QmlLibrary, BorderImage>,
+ CacheType<QtQuick, ModuleKind::QmlLibrary, GridView>,
+ CacheType<QtQuick, ModuleKind::QmlLibrary, Image>,
+ CacheType<QtQuick, ModuleKind::QmlLibrary, Item>,
+ CacheType<QtQuick, ModuleKind::QmlLibrary, ListView>,
+ CacheType<QtQuick, ModuleKind::QmlLibrary, Loader>,
+ CacheType<QtQuick, ModuleKind::QmlLibrary, MouseArea>,
+ CacheType<QtQuick, ModuleKind::QmlLibrary, Path>,
+ CacheType<QtQuick, ModuleKind::QmlLibrary, PathView>,
+ CacheType<QtQuick, ModuleKind::QmlLibrary, PauseAnimation>,
+ CacheType<QtQuick, ModuleKind::QmlLibrary, Positioner>,
+ CacheType<QtQuick, ModuleKind::QmlLibrary, PropertyAnimation>,
+ CacheType<QtQuick, ModuleKind::QmlLibrary, PropertyChanges>,
+ CacheType<QtQuick, ModuleKind::QmlLibrary, Rectangle>,
+ CacheType<QtQuick, ModuleKind::QmlLibrary, Repeater>,
+ CacheType<QtQuick, ModuleKind::QmlLibrary, State>,
+ CacheType<QtQuick, ModuleKind::QmlLibrary, StateGroup>,
+ CacheType<QtQuick, ModuleKind::QmlLibrary, Text>,
+ CacheType<QtQuick, ModuleKind::QmlLibrary, TextEdit>,
+ CacheType<QtQuick, ModuleKind::QmlLibrary, Transition>,
+ CacheType<QtQuick, ModuleKind::QmlLibrary, color>,
+ CacheType<QtQuick, ModuleKind::QmlLibrary, font>,
+ CacheType<QtQuick, ModuleKind::QmlLibrary, vector2d>,
+ CacheType<QtQuick, ModuleKind::QmlLibrary, vector3d>,
+ CacheType<QtQuick, ModuleKind::QmlLibrary, vector4d>,
+ CacheType<QtQuick3D, ModuleKind::QmlLibrary, BakedLightmap>,
+ CacheType<QtQuick3D, ModuleKind::QmlLibrary, Buffer>,
+ CacheType<QtQuick3D, ModuleKind::QmlLibrary, Camera>,
+ CacheType<QtQuick3D, ModuleKind::QmlLibrary, Command>,
+ CacheType<QtQuick3D, ModuleKind::QmlLibrary, CubeMapTexture>,
+ CacheType<QtQuick3D, ModuleKind::QmlLibrary, DefaultMaterial>,
+ CacheType<QtQuick3D, ModuleKind::QmlLibrary, DirectionalLight>,
+ CacheType<QtQuick3D, ModuleKind::QmlLibrary, Effect>,
+ CacheType<QtQuick3D, ModuleKind::QmlLibrary, InstanceList>,
+ CacheType<QtQuick3D, ModuleKind::QmlLibrary, InstanceListEntry>,
+ CacheType<QtQuick3D, ModuleKind::QmlLibrary, Light>,
+ CacheType<QtQuick3D, ModuleKind::QmlLibrary, Material>,
+ CacheType<QtQuick3D, ModuleKind::QmlLibrary, Model>,
+ CacheType<QtQuick3D, ModuleKind::QmlLibrary, Node>,
+ CacheType<QtQuick3D, ModuleKind::QmlLibrary, OrthographicCamera>,
+ CacheType<QtQuick3D, ModuleKind::QmlLibrary, Pass>,
+ CacheType<QtQuick3D, ModuleKind::QmlLibrary, PerspectiveCamera>,
+ CacheType<QtQuick3D, ModuleKind::QmlLibrary, PointLight>,
+ CacheType<QtQuick3D, ModuleKind::QmlLibrary, PrincipledMaterial>,
+ CacheType<QtQuick3D, ModuleKind::QmlLibrary, SceneEnvironment>,
+ CacheType<QtQuick3D, ModuleKind::QmlLibrary, Shader>,
+ CacheType<QtQuick3D, ModuleKind::QmlLibrary, SpecularGlossyMaterial>,
+ CacheType<QtQuick3D, ModuleKind::QmlLibrary, SpotLight>,
+ CacheType<QtQuick3D, ModuleKind::QmlLibrary, Texture>,
+ CacheType<QtQuick3D, ModuleKind::QmlLibrary, TextureInput>,
+ CacheType<QtQuick3D, ModuleKind::QmlLibrary, View3D>,
+ CacheType<QtQuick3D_Particles3D, ModuleKind::QmlLibrary, Affector3D>,
+ CacheType<QtQuick3D_Particles3D, ModuleKind::QmlLibrary, Attractor3D>,
+ CacheType<QtQuick3D_Particles3D, ModuleKind::QmlLibrary, Model>,
+ CacheType<QtQuick3D_Particles3D, ModuleKind::QmlLibrary, Particle3D>,
+ CacheType<QtQuick3D_Particles3D, ModuleKind::QmlLibrary, ParticleEmitter3D>,
+ CacheType<QtQuick3D_Particles3D, ModuleKind::QmlLibrary, SpriteParticle3D>,
+ CacheType<QtQuick3D_Particles3D, ModuleKind::CppLibrary, QQuick3DParticleAbstractShape>,
+ CacheType<QtQuick_Controls, ModuleKind::QmlLibrary, Control>,
+ CacheType<QtQuick_Controls, ModuleKind::QmlLibrary, Popup>,
+ CacheType<QtQuick_Controls, ModuleKind::QmlLibrary, SplitView>,
+ CacheType<QtQuick_Controls, ModuleKind::QmlLibrary, SwipeView>,
+ CacheType<QtQuick_Controls, ModuleKind::QmlLibrary, TabBar>,
+ CacheType<QtQuick_Controls, ModuleKind::QmlLibrary, TextArea>,
+ CacheType<QtQuick_Dialogs, ModuleKind::QmlLibrary, Dialog>,
+ CacheType<QtQuick_Extras, ModuleKind::QmlLibrary, Picture>,
+ CacheType<QtQuick_Layouts, ModuleKind::QmlLibrary, Layout>,
+ CacheType<QtQuick_Studio_Components, ModuleKind::QmlLibrary, GroupItem>,
+ CacheType<QtQuick_Studio_Components, ModuleKind::QmlLibrary, JsonListModel>,
+ CacheType<QtQuick_Templates, ModuleKind::QmlLibrary, Control>,
+ CacheType<QtQuick_Timeline, ModuleKind::QmlLibrary, Keyframe>,
+ CacheType<QtQuick_Timeline, ModuleKind::QmlLibrary, KeyframeGroup>,
+ CacheType<QtQuick_Timeline, ModuleKind::QmlLibrary, Timeline>,
+ CacheType<QtQuick_Timeline, ModuleKind::QmlLibrary, TimelineAnimation>,
+ CacheType<QtQuick, ModuleKind::CppLibrary, QQuickStateOperation>,
+ CacheType<Qt_SafeRenderer, ModuleKind::QmlLibrary, SafePicture>,
+ CacheType<Qt_SafeRenderer, ModuleKind::QmlLibrary, SafeRendererPicture>,
+ CacheType<QtQuick_Window, ModuleKind::QmlLibrary, Window>>;
public:
CommonTypeCache(const ProjectStorage &projectStorage)
@@ -283,14 +281,14 @@ public:
std::fill(std::begin(m_typesWithoutProperties), std ::end(m_typesWithoutProperties), TypeId{});
}
- template<const char *moduleName, const char *typeName>
+ template<const char *moduleName, const char *typeName, ModuleKind moduleKind = ModuleKind::QmlLibrary>
TypeId typeId() const
{
- auto &type = std::get<CacheType<moduleName, typeName>>(m_types);
+ auto &type = std::get<CacheType<moduleName, moduleKind, typeName>>(m_types);
if (type.typeId)
return type.typeId;
- return refreshTypedId(type, moduleName, typeName);
+ return refreshTypedId(type, moduleName, moduleKind, typeName);
}
template<const char *typeName>
@@ -307,11 +305,11 @@ public:
else if constexpr (std::is_same_v<Type, int>)
return typeId<QML, IntType>();
else if constexpr (std::is_same_v<Type, uint>)
- return typeId<QML_cppnative, UIntType>();
+ return typeId<QML, UIntType, ModuleKind::CppLibrary>();
else if constexpr (std::is_same_v<Type, bool>)
return typeId<QML, BoolType>();
else if constexpr (std::is_same_v<Type, float>)
- return typeId<QML_cppnative, FloatType>();
+ return typeId<QML, FloatType, ModuleKind::CppLibrary>();
else if constexpr (std::is_same_v<Type, QString>)
return typeId<QML, string>();
else if constexpr (std::is_same_v<Type, QDateTime>)
@@ -341,10 +339,11 @@ public:
private:
TypeId refreshTypedId(BaseCacheType &type,
::Utils::SmallStringView moduleName,
+ ModuleKind moduleKind,
::Utils::SmallStringView typeName) const
{
if (!type.moduleId)
- type.moduleId = m_projectStorage.moduleId(moduleName);
+ type.moduleId = m_projectStorage.moduleId(moduleName, moduleKind);
type.typeId = m_projectStorage.typeId(type.moduleId, typeName, Storage::Version{});
@@ -353,10 +352,11 @@ private:
TypeId refreshTypedIdWithoutTransaction(BaseCacheType &type,
::Utils::SmallStringView moduleName,
- ::Utils::SmallStringView typeName) const
+ ::Utils::SmallStringView typeName,
+ ModuleKind moduleKind) const
{
if (!type.moduleId)
- type.moduleId = m_projectStorage.fetchModuleIdUnguarded(moduleName);
+ type.moduleId = m_projectStorage.fetchModuleIdUnguarded(moduleName, moduleKind);
type.typeId = m_projectStorage.fetchTypeIdByModuleIdAndExportedName(type.moduleId, typeName);
@@ -371,26 +371,27 @@ private:
std::copy(std::begin(typeIds), std::end(typeIds), std::begin(m_typesWithoutProperties));
}
- template<const char *moduleName, const char *typeName>
+ template<const char *moduleName, const char *typeName, ModuleKind moduleKind = ModuleKind::QmlLibrary>
TypeId typeIdWithoutTransaction() const
{
- auto &type = std::get<CacheType<moduleName, typeName>>(m_types);
+ auto &type = std::get<CacheType<moduleName, moduleKind, typeName>>(m_types);
if (type.typeId)
return type.typeId;
- return refreshTypedIdWithoutTransaction(type, moduleName, typeName);
+ return refreshTypedIdWithoutTransaction(type, moduleName, typeName, moduleKind);
}
void updateTypeIdsWithoutProperties()
{
- setupTypeIdsWithoutProperties({typeIdWithoutTransaction<QML, BoolType>(),
- typeIdWithoutTransaction<QML, IntType>(),
- typeIdWithoutTransaction<QML_cppnative, UIntType>(),
- typeIdWithoutTransaction<QML, DoubleType>(),
- typeIdWithoutTransaction<QML_cppnative, FloatType>(),
- typeIdWithoutTransaction<QML, date>(),
- typeIdWithoutTransaction<QML, string>(),
- typeIdWithoutTransaction<QML, url>()});
+ setupTypeIdsWithoutProperties(
+ {typeIdWithoutTransaction<QML, BoolType>(),
+ typeIdWithoutTransaction<QML, IntType>(),
+ typeIdWithoutTransaction<QML, UIntType, ModuleKind::CppLibrary>(),
+ typeIdWithoutTransaction<QML, DoubleType>(),
+ typeIdWithoutTransaction<QML, FloatType, ModuleKind::CppLibrary>(),
+ typeIdWithoutTransaction<QML, date>(),
+ typeIdWithoutTransaction<QML, string>(),
+ typeIdWithoutTransaction<QML, url>()});
}
private:
diff --git a/src/plugins/qmldesigner/designercore/projectstorage/filesystem.cpp b/src/plugins/qmldesigner/designercore/projectstorage/filesystem.cpp
index 1376b2c3d9..d11190fdc7 100644
--- a/src/plugins/qmldesigner/designercore/projectstorage/filesystem.cpp
+++ b/src/plugins/qmldesigner/designercore/projectstorage/filesystem.cpp
@@ -11,6 +11,7 @@
#include <QDateTime>
#include <QDir>
+#include <QDirIterator>
#include <QFileInfo>
namespace QmlDesigner {
@@ -69,6 +70,18 @@ QString FileSystem::contentAsQString(const QString &filePath) const
return {};
}
+QStringList FileSystem::subdirectories(const QString &directoryPath) const
+{
+ QStringList directoryPaths;
+ directoryPaths.reserve(100);
+ QDirIterator directoryIterator{directoryPath, QDir::Dirs | QDir::NoDotAndDotDot};
+
+ while (directoryIterator.hasNext())
+ directoryPaths.push_back(directoryIterator.next());
+
+ return directoryPaths;
+}
+
void FileSystem::remove(const SourceIds &sourceIds)
{
for (SourceId sourceId : sourceIds)
diff --git a/src/plugins/qmldesigner/designercore/projectstorage/filesystem.h b/src/plugins/qmldesigner/designercore/projectstorage/filesystem.h
index 28754a8560..1c881741c6 100644
--- a/src/plugins/qmldesigner/designercore/projectstorage/filesystem.h
+++ b/src/plugins/qmldesigner/designercore/projectstorage/filesystem.h
@@ -31,6 +31,7 @@ public:
long long lastModified(SourceId sourceId) const override;
FileStatus fileStatus(SourceId sourceId) const override;
QString contentAsQString(const QString &filePath) const override;
+ QStringList subdirectories(const QString &directoryPath) const override;
void remove(const SourceIds &sourceIds) override;
diff --git a/src/plugins/qmldesigner/designercore/projectstorage/filesysteminterface.h b/src/plugins/qmldesigner/designercore/projectstorage/filesysteminterface.h
index 6a7c964fa6..ff7608c9a3 100644
--- a/src/plugins/qmldesigner/designercore/projectstorage/filesysteminterface.h
+++ b/src/plugins/qmldesigner/designercore/projectstorage/filesysteminterface.h
@@ -20,6 +20,7 @@ public:
virtual FileStatus fileStatus(SourceId sourceId) const = 0;
virtual void remove(const SourceIds &sourceIds) = 0;
virtual QString contentAsQString(const QString &filePath) const = 0;
+ virtual QStringList subdirectories(const QString &directoryPath) const = 0;
protected:
~FileSystemInterface() = default;
diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.cpp b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.cpp
index a7577d3ab7..2283b64945 100644
--- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.cpp
+++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.cpp
@@ -7,6 +7,25 @@
namespace QmlDesigner {
+enum class SpecialIdState { Unresolved = -1 };
+
+constexpr TypeId unresolvedTypeId = TypeId::createSpecialState(SpecialIdState::Unresolved);
+
+class UnresolvedTypeId : public TypeId
+{
+public:
+ constexpr UnresolvedTypeId()
+ : TypeId{TypeId::createSpecialState(SpecialIdState::Unresolved)}
+ {}
+
+ static constexpr UnresolvedTypeId create(DatabaseType idNumber)
+ {
+ UnresolvedTypeId id;
+ id.id = idNumber;
+ return id;
+ }
+};
+
struct ProjectStorage::Statements
{
Statements(Sqlite::Database &database)
@@ -17,9 +36,13 @@ struct ProjectStorage::Statements
Sqlite::ReadWriteStatement<1, 2> insertTypeStatement{
"INSERT OR IGNORE INTO types(sourceId, name) VALUES(?1, ?2) RETURNING typeId", database};
Sqlite::WriteStatement<5> updatePrototypeAndExtensionStatement{
- "UPDATE types SET prototypeId=?2, prototypeNameId=?3, extensionId=?4, extensionNameId=?5 "
- "WHERE typeId=?1 AND (prototypeId IS NOT ?2 OR extensionId IS NOT ?3 AND prototypeId "
- "IS NOT ?4 OR extensionNameId IS NOT ?5)",
+ "UPDATE types "
+ "SET prototypeId=?2, prototypeNameId=?3, extensionId=?4, extensionNameId=?5 "
+ "WHERE typeId=?1 AND ( "
+ " prototypeId IS NOT ?2 "
+ " OR extensionId IS NOT ?3 "
+ " OR prototypeId IS NOT ?4 "
+ " OR extensionNameId IS NOT ?5)",
database};
mutable Sqlite::ReadStatement<1, 1> selectTypeIdByExportedNameStatement{
"SELECT typeId FROM exportedTypeNames WHERE name=?1", database};
@@ -90,7 +113,19 @@ struct ProjectStorage::Statements
Sqlite::WriteStatement<2> updateTypeTraitStatement{
"UPDATE types SET traits = ?2 WHERE typeId=?1", database};
Sqlite::WriteStatement<2> updateTypeAnnotationTraitStatement{
- "UPDATE types SET annotationTraits = ?2 WHERE typeId=?1", database};
+ "WITH RECURSIVE "
+ " typeSelection(typeId) AS ("
+ " VALUES(?1) "
+ " UNION ALL "
+ " SELECT t.typeId "
+ " FROM types AS t JOIN typeSelection AS ts "
+ " WHERE prototypeId=ts.typeId "
+ " AND t.typeId NOT IN (SELECT typeId FROM typeAnnotations)) "
+ "UPDATE types AS t "
+ "SET annotationTraits = ?2 "
+ "FROM typeSelection ts "
+ "WHERE t.typeId=ts.typeId",
+ database};
Sqlite::ReadStatement<1, 2> selectNotUpdatedTypesInSourcesStatement{
"SELECT DISTINCT typeId FROM types WHERE (sourceId IN carray(?1) AND typeId NOT IN "
"carray(?2))",
@@ -235,14 +270,14 @@ struct ProjectStorage::Statements
database};
Sqlite::WriteStatement<1> deleteEnumerationDeclarationStatement{
"DELETE FROM enumerationDeclarations WHERE enumerationDeclarationId=?", database};
- mutable Sqlite::ReadStatement<1, 1> selectModuleIdByNameStatement{
- "SELECT moduleId FROM modules WHERE name=? LIMIT 1", database};
- mutable Sqlite::ReadWriteStatement<1, 1> insertModuleNameStatement{
- "INSERT INTO modules(name) VALUES(?1) RETURNING moduleId", database};
- mutable Sqlite::ReadStatement<1, 1> selectModuleNameStatement{
- "SELECT name FROM modules WHERE moduleId =?1", database};
- mutable Sqlite::ReadStatement<2> selectAllModulesStatement{"SELECT name, moduleId FROM modules",
- database};
+ mutable Sqlite::ReadStatement<1, 2> selectModuleIdByNameStatement{
+ "SELECT moduleId FROM modules WHERE kind=?1 AND name=?2 LIMIT 1", database};
+ mutable Sqlite::ReadWriteStatement<1, 2> insertModuleNameStatement{
+ "INSERT INTO modules(kind, name) VALUES(?1, ?2) RETURNING moduleId", database};
+ mutable Sqlite::ReadStatement<2, 1> selectModuleStatement{
+ "SELECT name, kind FROM modules WHERE moduleId =?1", database};
+ mutable Sqlite::ReadStatement<3> selectAllModulesStatement{
+ "SELECT name, kind, moduleId FROM modules", database};
mutable Sqlite::ReadStatement<1, 2> selectTypeIdBySourceIdAndNameStatement{
"SELECT typeId FROM types WHERE sourceId=?1 and name=?2", database};
mutable Sqlite::ReadStatement<1, 3> selectTypeIdByModuleIdsAndExportedNameStatement{
@@ -345,13 +380,51 @@ struct ProjectStorage::Statements
"SELECT name FROM propertyDeclarations WHERE propertyDeclarationId=?", database};
Sqlite::WriteStatement<2> updatePropertyDeclarationTypeStatement{
"UPDATE propertyDeclarations SET propertyTypeId=?2 WHERE propertyDeclarationId=?1", database};
- Sqlite::ReadWriteStatement<2, 1> updatePrototypeIdToNullStatement{
- "UPDATE types SET prototypeId=NULL WHERE prototypeId=?1 RETURNING "
- "typeId, prototypeNameId",
+ Sqlite::ReadWriteStatement<2, 2> updatePrototypeIdToTypeIdStatement{
+ "UPDATE types "
+ "SET prototypeId=?2 "
+ "WHERE prototypeId=?1 "
+ "RETURNING typeId, prototypeNameId",
+ database};
+ Sqlite::ReadWriteStatement<2, 2> updateExtensionIdToTypeIdStatement{
+ "UPDATE types "
+ "SET extensionId=?2 "
+ "WHERE extensionId=?1 "
+ "RETURNING typeId, extensionNameId",
+ database};
+ Sqlite::ReadStatement<2, 2> selectTypeIdAndPrototypeNameIdForPrototypeIdAndTypeNameStatement{
+ "SELECT typeId, prototypeNameId "
+ "FROM types "
+ "WHERE prototypeNameId IN ( "
+ " SELECT importedTypeNameId "
+ " FROM "
+ " importedTypeNames WHERE name=?1) "
+ " AND prototypeId=?2",
+ database};
+ Sqlite::ReadStatement<2, 2> selectTypeIdAndPrototypeNameIdForPrototypeIdAndSourceIdStatement{
+ "SELECT typeId , prototypeNameId "
+ "FROM types "
+ "WHERE prototypeId=?1 AND sourceId=?2",
+ database};
+ Sqlite::ReadStatement<2, 2> selectTypeIdAndExtensionNameIdForExtensionIdAndSourceIdStatement{
+ "SELECT typeId, extensionNameId "
+ "FROM types "
+ "WHERE extensionId=?1 AND sourceId=?2",
database};
- Sqlite::ReadWriteStatement<2, 1> updateExtensionIdToNullStatement{
- "UPDATE types SET extensionId=NULL WHERE extensionId=?1 RETURNING "
- "typeId, extensionNameId",
+ Sqlite::ReadWriteStatement<3, 3> updatePrototypeIdAndExtensionIdToTypeIdForSourceIdStatement{
+ "UPDATE types "
+ "SET prototypeId=?2, extensionId=?3 "
+ "WHERE sourceId=?1 "
+ "RETURNING typeId, prototypeNameId, extensionNameId",
+ database};
+ Sqlite::ReadStatement<2, 2> selectTypeIdForExtensionIdAndTypeNameStatement{
+ "SELECT typeId , prototypeNameId "
+ "FROM types "
+ "WHERE extensionNameId IN ( "
+ " SELECT importedTypeNameId "
+ " FROM importedTypeNames "
+ " WHERE name=?1) "
+ " AND extensionId=?2",
database};
Sqlite::WriteStatement<2> updateTypePrototypeStatement{
"UPDATE types SET prototypeId=?2 WHERE typeId=?1", database};
@@ -490,25 +563,31 @@ struct ProjectStorage::Statements
"DELETE FROM exportedTypeNames WHERE exportedTypeNameId=?", database};
Sqlite::WriteStatement<2> updateExportedTypeNameTypeIdStatement{
"UPDATE exportedTypeNames SET typeId=?2 WHERE exportedTypeNameId=?1", database};
- mutable Sqlite::ReadStatement<4, 1> selectProjectDatasForSourceIdsStatement{
- "SELECT projectSourceId, sourceId, moduleId, fileType FROM projectDatas WHERE "
- "projectSourceId IN carray(?1) ORDER BY projectSourceId, sourceId",
+ mutable Sqlite::ReadStatement<4, 1> selectDirectoryInfosForSourceIdsStatement{
+ "SELECT directorySourceId, sourceId, moduleId, fileType FROM directoryInfos WHERE "
+ "directorySourceId IN carray(?1) ORDER BY directorySourceId, sourceId",
database};
- Sqlite::WriteStatement<4> insertProjectDataStatement{
- "INSERT INTO projectDatas(projectSourceId, sourceId, "
+ Sqlite::WriteStatement<4> insertDirectoryInfoStatement{
+ "INSERT INTO directoryInfos(directorySourceId, sourceId, "
"moduleId, fileType) VALUES(?1, ?2, ?3, ?4)",
database};
- Sqlite::WriteStatement<2> deleteProjectDataStatement{
- "DELETE FROM projectDatas WHERE projectSourceId=?1 AND sourceId=?2", database};
- Sqlite::WriteStatement<4> updateProjectDataStatement{
- "UPDATE projectDatas SET moduleId=?3, fileType=?4 WHERE projectSourceId=?1 AND sourceId=?2",
+ Sqlite::WriteStatement<2> deleteDirectoryInfoStatement{
+ "DELETE FROM directoryInfos WHERE directorySourceId=?1 AND sourceId=?2", database};
+ Sqlite::WriteStatement<4> updateDirectoryInfoStatement{
+ "UPDATE directoryInfos SET moduleId=?3, fileType=?4 WHERE directorySourceId=?1 AND sourceId=?2",
+ database};
+ mutable Sqlite::ReadStatement<4, 1> selectDirectoryInfosForSourceIdStatement{
+ "SELECT directorySourceId, sourceId, moduleId, fileType FROM directoryInfos WHERE "
+ "directorySourceId=?1",
database};
- mutable Sqlite::ReadStatement<4, 1> selectProjectDatasForSourceIdStatement{
- "SELECT projectSourceId, sourceId, moduleId, fileType FROM projectDatas WHERE "
- "projectSourceId=?1",
+ mutable Sqlite::ReadStatement<4, 2> selectDirectoryInfosForSourceIdAndFileTypeStatement{
+ "SELECT directorySourceId, sourceId, moduleId, fileType FROM directoryInfos WHERE "
+ "directorySourceId=?1 AND fileType=?2",
database};
- mutable Sqlite::ReadStatement<4, 1> selectProjectDataForSourceIdStatement{
- "SELECT projectSourceId, sourceId, moduleId, fileType FROM projectDatas WHERE "
+ mutable Sqlite::ReadStatement<1, 2> selectDirectoryInfosSourceIdsForSourceIdAndFileTypeStatement{
+ "SELECT sourceId FROM directoryInfos WHERE directorySourceId=?1 AND fileType=?2", database};
+ mutable Sqlite::ReadStatement<4, 1> selectDirectoryInfoForSourceIdStatement{
+ "SELECT directorySourceId, sourceId, moduleId, fileType FROM directoryInfos WHERE "
"sourceId=?1 LIMIT 1",
database};
mutable Sqlite::ReadStatement<1, 1> selectTypeIdsForSourceIdsStatement{
@@ -601,6 +680,13 @@ struct ProjectStorage::Statements
"UPDATE types SET defaultPropertyId=NULL WHERE defaultPropertyId=?1", database};
mutable Sqlite::ReadStatement<3, 1> selectInfoTypeByTypeIdStatement{
"SELECT sourceId, traits, annotationTraits FROM types WHERE typeId=?", database};
+ mutable Sqlite::ReadStatement<1, 1> selectSourceIdByTypeIdStatement{
+ "SELECT sourceId FROM types WHERE typeId=?", database};
+ mutable Sqlite::ReadStatement<1, 1> selectPrototypeAnnotationTraitsByTypeIdStatement{
+ "SELECT annotationTraits "
+ "FROM types "
+ "WHERE typeId=(SELECT prototypeId FROM types WHERE typeId=?)",
+ database};
mutable Sqlite::ReadStatement<1, 1> selectDefaultPropertyDeclarationIdStatement{
"SELECT defaultPropertyId FROM types WHERE typeId=?", database};
mutable Sqlite::ReadStatement<1, 1> selectPrototypeIdsForTypeIdInOrderStatement{
@@ -639,17 +725,21 @@ struct ProjectStorage::Statements
database};
Sqlite::WriteStatement<1> deletePropertyEditorPathStatement{
"DELETE FROM propertyEditorPaths WHERE typeId=?1", database};
- mutable Sqlite::ReadStatement<4, 1> selectTypeAnnotationsForSourceIdsStatement{
- "SELECT typeId, iconPath, itemLibrary, hints FROM typeAnnotations WHERE "
+ mutable Sqlite::ReadStatement<5, 1> selectTypeAnnotationsForSourceIdsStatement{
+ "SELECT typeId, typeName, iconPath, itemLibrary, hints FROM typeAnnotations WHERE "
"sourceId IN carray(?1) ORDER BY typeId",
database};
- Sqlite::WriteStatement<6> insertTypeAnnotationStatement{
+ Sqlite::WriteStatement<7> insertTypeAnnotationStatement{
"INSERT INTO "
- " typeAnnotations(typeId, sourceId, directorySourceId, iconPath, itemLibrary, hints) "
- "VALUES(?1, ?2, ?3, ?4, ?5, ?6)",
+ " typeAnnotations(typeId, sourceId, directorySourceId, typeName, iconPath, itemLibrary, "
+ " hints) "
+ "VALUES(?1, ?2, ?3, ?4, ?5, ?6, ?7)",
+ database};
+ Sqlite::WriteStatement<5> updateTypeAnnotationStatement{
+ "UPDATE typeAnnotations "
+ "SET typeName=?2, iconPath=?3, itemLibrary=?4, hints=?5 "
+ "WHERE typeId=?1",
database};
- Sqlite::WriteStatement<4> updateTypeAnnotationStatement{
- "UPDATE typeAnnotations SET iconPath=?2, itemLibrary=?3, hints=?4 WHERE typeId=?1", database};
Sqlite::WriteStatement<1> deleteTypeAnnotationStatement{
"DELETE FROM typeAnnotations WHERE typeId=?1", database};
mutable Sqlite::ReadStatement<1, 1> selectTypeIconPathStatement{
@@ -663,22 +753,22 @@ struct ProjectStorage::Statements
"SELECT sourceId FROM typeAnnotations WHERE directorySourceId=?1 ORDER BY sourceId", database};
mutable Sqlite::ReadStatement<1, 0> selectTypeAnnotationDirectorySourceIdsStatement{
"SELECT DISTINCT directorySourceId FROM typeAnnotations ORDER BY directorySourceId", database};
- mutable Sqlite::ReadStatement<9> selectItemLibraryEntriesStatement{
- "SELECT typeId, i.value->>'$.name', i.value->>'$.iconPath', i.value->>'$.category', "
- " i.value->>'$.import', i.value->>'$.toolTip', i.value->>'$.properties', "
- " i.value->>'$.extraFilePaths', i.value->>'$.templatePath' "
+ mutable Sqlite::ReadStatement<10> selectItemLibraryEntriesStatement{
+ "SELECT typeId, typeName, i.value->>'$.name', i.value->>'$.iconPath', "
+ " i.value->>'$.category', i.value->>'$.import', i.value->>'$.toolTip', "
+ " i.value->>'$.properties', i.value->>'$.extraFilePaths', i.value->>'$.templatePath' "
"FROM typeAnnotations AS ta , json_each(ta.itemLibrary) AS i "
"WHERE ta.itemLibrary IS NOT NULL",
database};
- mutable Sqlite::ReadStatement<9, 1> selectItemLibraryEntriesByTypeIdStatement{
- "SELECT typeId, i.value->>'$.name', i.value->>'$.iconPath', i.value->>'$.category', "
- " i.value->>'$.import', i.value->>'$.toolTip', i.value->>'$.properties', "
- " i.value->>'$.extraFilePaths', i.value->>'$.templatePath' "
+ mutable Sqlite::ReadStatement<10, 1> selectItemLibraryEntriesByTypeIdStatement{
+ "SELECT typeId, typeName, i.value->>'$.name', i.value->>'$.iconPath', "
+ " i.value->>'$.category', i.value->>'$.import', i.value->>'$.toolTip', "
+ " i.value->>'$.properties', i.value->>'$.extraFilePaths', i.value->>'$.templatePath' "
"FROM typeAnnotations AS ta, json_each(ta.itemLibrary) AS i "
"WHERE typeId=?1 AND ta.itemLibrary IS NOT NULL",
database};
- mutable Sqlite::ReadStatement<9, 1> selectItemLibraryEntriesBySourceIdStatement{
- "SELECT typeId, i.value->>'$.name', i.value->>'$.iconPath', "
+ mutable Sqlite::ReadStatement<10, 1> selectItemLibraryEntriesBySourceIdStatement{
+ "SELECT typeId, typeName, i.value->>'$.name', i.value->>'$.iconPath', "
"i.value->>'$.category', "
" i.value->>'$.import', i.value->>'$.toolTip', i.value->>'$.properties', "
" i.value->>'$.extraFilePaths', i.value->>'$.templatePath' "
@@ -724,7 +814,7 @@ public:
createModuleExportedImportsTable(database, moduleIdColumn);
createDocumentImportsTable(database, moduleIdColumn);
createFileStatusesTable(database);
- createProjectDatasTable(database);
+ createDirectoryInfosTable(database);
createPropertyEditorPathsTable(database);
createTypeAnnotionsTable(database);
}
@@ -774,23 +864,23 @@ public:
auto &sourceIdColumn = typesTable.addColumn("sourceId", Sqlite::StrictColumnType::Integer);
auto &typesNameColumn = typesTable.addColumn("name", Sqlite::StrictColumnType::Text);
typesTable.addColumn("traits", Sqlite::StrictColumnType::Integer);
- auto &prototypeIdColumn = typesTable.addForeignKeyColumn("prototypeId",
- typesTable,
- Sqlite::ForeignKeyAction::NoAction,
- Sqlite::ForeignKeyAction::Restrict);
- typesTable.addColumn("prototypeNameId", Sqlite::StrictColumnType::Integer);
- auto &extensionIdColumn = typesTable.addForeignKeyColumn("extensionId",
- typesTable,
- Sqlite::ForeignKeyAction::NoAction,
- Sqlite::ForeignKeyAction::Restrict);
- typesTable.addColumn("extensionNameId", Sqlite::StrictColumnType::Integer);
+ auto &prototypeIdColumn = typesTable.addColumn("prototypeId",
+ Sqlite::StrictColumnType::Integer);
+ auto &prototypeNameIdColumn = typesTable.addColumn("prototypeNameId",
+ Sqlite::StrictColumnType::Integer);
+ auto &extensionIdColumn = typesTable.addColumn("extensionId",
+ Sqlite::StrictColumnType::Integer);
+ auto &extensionNameIdColumn = typesTable.addColumn("extensionNameId",
+ Sqlite::StrictColumnType::Integer);
auto &defaultPropertyIdColumn = typesTable.addColumn("defaultPropertyId",
Sqlite::StrictColumnType::Integer);
typesTable.addColumn("annotationTraits", Sqlite::StrictColumnType::Integer);
typesTable.addUniqueIndex({sourceIdColumn, typesNameColumn});
typesTable.addIndex({defaultPropertyIdColumn});
- typesTable.addIndex({prototypeIdColumn});
- typesTable.addIndex({extensionIdColumn});
+ typesTable.addIndex({prototypeIdColumn, sourceIdColumn});
+ typesTable.addIndex({extensionIdColumn, sourceIdColumn});
+ typesTable.addIndex({prototypeNameIdColumn});
+ typesTable.addIndex({extensionNameIdColumn});
typesTable.initialize(database);
@@ -942,9 +1032,10 @@ public:
auto &modelIdColumn = table.addColumn("moduleId",
Sqlite::StrictColumnType::Integer,
{Sqlite::PrimaryKey{}});
+ auto &kindColumn = table.addColumn("kind", Sqlite::StrictColumnType::Integer);
auto &nameColumn = table.addColumn("name", Sqlite::StrictColumnType::Text);
- table.addUniqueIndex({nameColumn});
+ table.addUniqueIndex({kindColumn, nameColumn});
table.initialize(database);
@@ -1041,20 +1132,21 @@ public:
table.initialize(database);
}
- void createProjectDatasTable(Database &database)
+ void createDirectoryInfosTable(Database &database)
{
Sqlite::StrictTable table;
table.setUseIfNotExists(true);
table.setUseWithoutRowId(true);
- table.setName("projectDatas");
- auto &projectSourceIdColumn = table.addColumn("projectSourceId",
+ table.setName("directoryInfos");
+ auto &directorySourceIdColumn = table.addColumn("directorySourceId",
Sqlite::StrictColumnType::Integer);
auto &sourceIdColumn = table.addColumn("sourceId", Sqlite::StrictColumnType::Integer);
table.addColumn("moduleId", Sqlite::StrictColumnType::Integer);
- table.addColumn("fileType", Sqlite::StrictColumnType::Integer);
+ auto &fileTypeColumn = table.addColumn("fileType", Sqlite::StrictColumnType::Integer);
- table.addPrimaryKeyContraint({projectSourceIdColumn, sourceIdColumn});
+ table.addPrimaryKeyContraint({directorySourceIdColumn, sourceIdColumn});
table.addUniqueIndex({sourceIdColumn});
+ table.addIndex({directorySourceIdColumn, fileTypeColumn});
table.initialize(database);
}
@@ -1086,7 +1178,7 @@ public:
auto &sourceIdColumn = table.addColumn("sourceId", Sqlite::StrictColumnType::Integer);
auto &directorySourceIdColumn = table.addColumn("directorySourceId",
Sqlite::StrictColumnType::Integer);
-
+ table.addColumn("typeName", Sqlite::StrictColumnType::Text);
table.addColumn("iconPath", Sqlite::StrictColumnType::Text);
table.addColumn("itemLibrary", Sqlite::StrictColumnType::Text);
table.addColumn("hints", Sqlite::StrictColumnType::Text);
@@ -1098,8 +1190,11 @@ public:
}
};
-ProjectStorage::ProjectStorage(Database &database, bool isInitialized)
+ProjectStorage::ProjectStorage(Database &database,
+ ProjectStorageErrorNotifierInterface &errorNotifier,
+ bool isInitialized)
: database{database}
+ , errorNotifier{&errorNotifier}
, exclusiveTransaction{database}
, initializer{std::make_unique<ProjectStorage::Initializer>(database, isInitialized)}
, moduleCache{ModuleStorageAdapter{*this}}
@@ -1143,7 +1238,9 @@ void ProjectStorage::synchronize(Storage::Synchronization::SynchronizationPackag
package.moduleDependencies,
package.updatedModuleDependencySourceIds,
package.moduleExportedImports,
- package.updatedModuleIds);
+ package.updatedModuleIds,
+ relinkablePrototypes,
+ relinkableExtensions);
synchronizeTypes(package.types,
updatedTypeIds,
insertedAliasPropertyDeclarations,
@@ -1174,7 +1271,7 @@ void ProjectStorage::synchronize(Storage::Synchronization::SynchronizationPackag
linkAliases(insertedAliasPropertyDeclarations, updatedAliasPropertyDeclarations);
- synchronizeProjectDatas(package.projectDatas, package.updatedProjectSourceIds);
+ synchronizeDirectoryInfos(package.directoryInfos, package.updatedDirectoryInfoSourceIds);
commonTypeCache_.resetTypeIds();
});
@@ -1191,7 +1288,24 @@ void ProjectStorage::synchronizeDocumentImports(Storage::Imports imports, Source
keyValue("source id", sourceId)};
Sqlite::withImmediateTransaction(database, [&] {
- synchronizeDocumentImports(imports, {sourceId}, Storage::Synchronization::ImportKind::Import);
+ AliasPropertyDeclarations relinkableAliasPropertyDeclarations;
+ PropertyDeclarations relinkablePropertyDeclarations;
+ Prototypes relinkablePrototypes;
+ Prototypes relinkableExtensions;
+ TypeIds deletedTypeIds;
+
+ synchronizeDocumentImports(imports,
+ {sourceId},
+ Storage::Synchronization::ImportKind::Import,
+ Relink::Yes,
+ relinkablePrototypes,
+ relinkableExtensions);
+
+ relink(relinkableAliasPropertyDeclarations,
+ relinkablePropertyDeclarations,
+ relinkablePrototypes,
+ relinkableExtensions,
+ deletedTypeIds);
});
}
@@ -1207,21 +1321,21 @@ void ProjectStorage::removeObserver(ProjectStorageObserver *observer)
observers.removeOne(observer);
}
-ModuleId ProjectStorage::moduleId(Utils::SmallStringView moduleName) const
+ModuleId ProjectStorage::moduleId(Utils::SmallStringView moduleName, Storage::ModuleKind kind) const
{
using NanotraceHR::keyValue;
NanotraceHR::Tracer tracer{"get module id"_t,
projectStorageCategory(),
keyValue("module name", moduleName)};
- auto moduleId = moduleCache.id(moduleName);
+ auto moduleId = moduleCache.id({moduleName, kind});
tracer.end(keyValue("module id", moduleId));
return moduleId;
}
-Utils::SmallString ProjectStorage::moduleName(ModuleId moduleId) const
+Storage::Module ProjectStorage::module(ModuleId moduleId) const
{
using NanotraceHR::keyValue;
NanotraceHR::Tracer tracer{"get module name"_t,
@@ -1231,11 +1345,12 @@ Utils::SmallString ProjectStorage::moduleName(ModuleId moduleId) const
if (!moduleId)
throw ModuleDoesNotExists{};
- auto moduleName = moduleCache.value(moduleId);
+ auto module = moduleCache.value(moduleId);
- tracer.end(keyValue("module name", moduleName));
+ tracer.end(keyValue("module name", module.name));
+ tracer.end(keyValue("module kind", module.kind));
- return moduleName;
+ return {module.name, module.kind};
}
TypeId ProjectStorage::typeId(ModuleId moduleId,
@@ -1570,6 +1685,7 @@ Storage::Info::ItemLibraryEntries ProjectStorage::itemLibraryEntries(TypeId type
Storage::Info::ItemLibraryEntries entries;
auto callback = [&](TypeId typeId_,
+ Utils::SmallStringView typeName,
Utils::SmallStringView name,
Utils::SmallStringView iconPath,
Utils::SmallStringView category,
@@ -1578,7 +1694,8 @@ Storage::Info::ItemLibraryEntries ProjectStorage::itemLibraryEntries(TypeId type
Utils::SmallStringView properties,
Utils::SmallStringView extraFilePaths,
Utils::SmallStringView templatePath) {
- auto &last = entries.emplace_back(typeId_, name, iconPath, category, import, toolTip, templatePath);
+ auto &last = entries.emplace_back(
+ typeId_, typeName, name, iconPath, category, import, toolTip, templatePath);
if (properties.size())
s->selectItemLibraryPropertiesStatement.readTo(last.properties, properties);
if (extraFilePaths.size())
@@ -1603,6 +1720,7 @@ Storage::Info::ItemLibraryEntries ProjectStorage::itemLibraryEntries(ImportId im
Storage::Info::ItemLibraryEntries entries;
auto callback = [&](TypeId typeId_,
+ Utils::SmallStringView typeName,
Utils::SmallStringView name,
Utils::SmallStringView iconPath,
Utils::SmallStringView category,
@@ -1611,7 +1729,8 @@ Storage::Info::ItemLibraryEntries ProjectStorage::itemLibraryEntries(ImportId im
Utils::SmallStringView properties,
Utils::SmallStringView extraFilePaths,
Utils::SmallStringView templatePath) {
- auto &last = entries.emplace_back(typeId_, name, iconPath, category, import, toolTip, templatePath);
+ auto &last = entries.emplace_back(
+ typeId_, typeName, name, iconPath, category, import, toolTip, templatePath);
if (properties.size())
s->selectItemLibraryPropertiesStatement.readTo(last.properties, properties);
if (extraFilePaths.size())
@@ -1636,6 +1755,7 @@ Storage::Info::ItemLibraryEntries ProjectStorage::itemLibraryEntries(SourceId so
Storage::Info::ItemLibraryEntries entries;
auto callback = [&](TypeId typeId,
+ Utils::SmallStringView typeName,
Utils::SmallStringView name,
Utils::SmallStringView iconPath,
Utils::SmallStringView category,
@@ -1644,7 +1764,8 @@ Storage::Info::ItemLibraryEntries ProjectStorage::itemLibraryEntries(SourceId so
Utils::SmallStringView properties,
Utils::SmallStringView extraFilePaths,
Utils::SmallStringView templatePath) {
- auto &last = entries.emplace_back(typeId, name, iconPath, category, import, toolTip, templatePath);
+ auto &last = entries.emplace_back(
+ typeId, typeName, name, iconPath, category, import, toolTip, templatePath);
if (properties.size())
s->selectItemLibraryPropertiesStatement.readTo(last.properties, properties);
if (extraFilePaths.size())
@@ -1667,6 +1788,7 @@ Storage::Info::ItemLibraryEntries ProjectStorage::allItemLibraryEntries() const
Storage::Info::ItemLibraryEntries entries;
auto callback = [&](TypeId typeId,
+ Utils::SmallStringView typeName,
Utils::SmallStringView name,
Utils::SmallStringView iconPath,
Utils::SmallStringView category,
@@ -1675,7 +1797,8 @@ Storage::Info::ItemLibraryEntries ProjectStorage::allItemLibraryEntries() const
Utils::SmallStringView properties,
Utils::SmallStringView extraFilePaths,
Utils::SmallStringView templatePath) {
- auto &last = entries.emplace_back(typeId, name, iconPath, category, import, toolTip, templatePath);
+ auto &last = entries.emplace_back(
+ typeId, typeName, name, iconPath, category, import, toolTip, templatePath);
if (properties.size())
s->selectItemLibraryPropertiesStatement.readTo(last.properties, properties);
if (extraFilePaths.size())
@@ -2082,53 +2205,87 @@ FileStatus ProjectStorage::fetchFileStatus(SourceId sourceId) const
return fileStatus;
}
-std::optional<Storage::Synchronization::ProjectData> ProjectStorage::fetchProjectData(SourceId sourceId) const
+std::optional<Storage::Synchronization::DirectoryInfo> ProjectStorage::fetchDirectoryInfo(SourceId sourceId) const
{
using NanotraceHR::keyValue;
- NanotraceHR::Tracer tracer{"fetch project data"_t,
+ NanotraceHR::Tracer tracer{"fetch directory info"_t,
projectStorageCategory(),
keyValue("source id", sourceId)};
- auto projectData = s->selectProjectDataForSourceIdStatement
- .optionalValueWithTransaction<Storage::Synchronization::ProjectData>(
+ auto directoryInfo = s->selectDirectoryInfoForSourceIdStatement
+ .optionalValueWithTransaction<Storage::Synchronization::DirectoryInfo>(
sourceId);
- tracer.end(keyValue("project data", projectData));
+ tracer.end(keyValue("directory info", directoryInfo));
+
+ return directoryInfo;
+}
+
+Storage::Synchronization::DirectoryInfos ProjectStorage::fetchDirectoryInfos(SourceId directorySourceId) const
+{
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"fetch directory infos by source id"_t,
+ projectStorageCategory(),
+ keyValue("source id", directorySourceId)};
+
+ auto directoryInfos = s->selectDirectoryInfosForSourceIdStatement
+ .valuesWithTransaction<Storage::Synchronization::DirectoryInfo, 1024>(
+ directorySourceId);
+
+ tracer.end(keyValue("directory infos", directoryInfos));
+
+ return directoryInfos;
+}
+
+Storage::Synchronization::DirectoryInfos ProjectStorage::fetchDirectoryInfos(
+ SourceId directorySourceId, Storage::Synchronization::FileType fileType) const
+{
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"fetch directory infos by source id and file type"_t,
+ projectStorageCategory(),
+ keyValue("source id", directorySourceId),
+ keyValue("file type", fileType)};
+
+ auto directoryInfos = s->selectDirectoryInfosForSourceIdAndFileTypeStatement
+ .valuesWithTransaction<Storage::Synchronization::DirectoryInfo, 16>(
+ directorySourceId, fileType);
- return projectData;
+ tracer.end(keyValue("directory infos", directoryInfos));
+
+ return directoryInfos;
}
-Storage::Synchronization::ProjectDatas ProjectStorage::fetchProjectDatas(SourceId projectSourceId) const
+Storage::Synchronization::DirectoryInfos ProjectStorage::fetchDirectoryInfos(
+ const SourceIds &directorySourceIds) const
{
using NanotraceHR::keyValue;
- NanotraceHR::Tracer tracer{"fetch project datas by source id"_t,
+ NanotraceHR::Tracer tracer{"fetch directory infos by source ids"_t,
projectStorageCategory(),
- keyValue("source id", projectSourceId)};
+ keyValue("source ids", directorySourceIds)};
- auto projectDatas = s->selectProjectDatasForSourceIdStatement
- .valuesWithTransaction<Storage::Synchronization::ProjectData, 1024>(
- projectSourceId);
+ auto directoryInfos = s->selectDirectoryInfosForSourceIdsStatement
+ .valuesWithTransaction<Storage::Synchronization::DirectoryInfo, 64>(
+ toIntegers(directorySourceIds));
- tracer.end(keyValue("project datas", projectDatas));
+ tracer.end(keyValue("directory infos", directoryInfos));
- return projectDatas;
+ return directoryInfos;
}
-Storage::Synchronization::ProjectDatas ProjectStorage::fetchProjectDatas(
- const SourceIds &projectSourceIds) const
+SmallSourceIds<32> ProjectStorage::fetchSubdirectorySourceIds(SourceId directorySourceId) const
{
using NanotraceHR::keyValue;
- NanotraceHR::Tracer tracer{"fetch project datas by source ids"_t,
+ NanotraceHR::Tracer tracer{"fetch subdirectory source ids"_t,
projectStorageCategory(),
- keyValue("source ids", projectSourceIds)};
+ keyValue("source id", directorySourceId)};
- auto projectDatas = s->selectProjectDatasForSourceIdsStatement
- .valuesWithTransaction<Storage::Synchronization::ProjectData, 64>(
- toIntegers(projectSourceIds));
+ auto sourceIds = s->selectDirectoryInfosSourceIdsForSourceIdAndFileTypeStatement
+ .valuesWithTransaction<SmallSourceIds<32>>(
+ directorySourceId, Storage::Synchronization::FileType::Directory);
- tracer.end(keyValue("project datas", projectDatas));
+ tracer.end(keyValue("source ids", sourceIds));
- return projectDatas;
+ return sourceIds;
}
void ProjectStorage::setPropertyEditorPathId(TypeId typeId, SourceId pathId)
@@ -2168,20 +2325,17 @@ void ProjectStorage::resetForTestsOnly()
moduleCache.clearForTestOnly();
}
-bool ProjectStorage::moduleNameLess(Utils::SmallStringView first, Utils::SmallStringView second) noexcept
-{
- return first < second;
-}
-
-ModuleId ProjectStorage::fetchModuleId(Utils::SmallStringView moduleName)
+ModuleId ProjectStorage::fetchModuleId(Utils::SmallStringView moduleName,
+ Storage::ModuleKind moduleKind)
{
using NanotraceHR::keyValue;
NanotraceHR::Tracer tracer{"fetch module id"_t,
projectStorageCategory(),
- keyValue("module name", moduleName)};
+ keyValue("module name", moduleName),
+ keyValue("module kind", moduleKind)};
auto moduleId = Sqlite::withDeferredTransaction(database, [&] {
- return fetchModuleIdUnguarded(moduleName);
+ return fetchModuleIdUnguarded(moduleName, moduleKind);
});
tracer.end(keyValue("module id", moduleId));
@@ -2189,26 +2343,26 @@ ModuleId ProjectStorage::fetchModuleId(Utils::SmallStringView moduleName)
return moduleId;
}
-Utils::PathString ProjectStorage::fetchModuleName(ModuleId id)
+Storage::Module ProjectStorage::fetchModule(ModuleId id)
{
using NanotraceHR::keyValue;
NanotraceHR::Tracer tracer{"fetch module name"_t,
projectStorageCategory(),
keyValue("module id", id)};
- auto moduleName = Sqlite::withDeferredTransaction(database,
- [&] { return fetchModuleNameUnguarded(id); });
+ auto module = Sqlite::withDeferredTransaction(database, [&] { return fetchModuleUnguarded(id); });
- tracer.end(keyValue("module name", moduleName));
+ tracer.end(keyValue("module name", module.name));
+ tracer.end(keyValue("module name", module.kind));
- return moduleName;
+ return module;
}
-ProjectStorage::Modules ProjectStorage::fetchAllModules() const
+ProjectStorage::ModuleCacheEntries ProjectStorage::fetchAllModules() const
{
NanotraceHR::Tracer tracer{"fetch all modules"_t, projectStorageCategory()};
- return s->selectAllModulesStatement.valuesWithTransaction<Module, 128>();
+ return s->selectAllModulesStatement.valuesWithTransaction<ModuleCacheEntry, 128>();
}
void ProjectStorage::callRefreshMetaInfoCallback(const TypeIds &deletedTypeIds)
@@ -2277,12 +2431,6 @@ void ProjectStorage::updateTypeIdInTypeAnnotations(Storage::Synchronization::Typ
annotation.typeName);
}
- for (auto &annotation : typeAnnotations) {
- if (!annotation.typeId)
- qWarning() << moduleName(annotation.moduleId).toQString()
- << annotation.typeName.toQString();
- }
-
typeAnnotations.erase(std::remove_if(typeAnnotations.begin(),
typeAnnotations.end(),
[](const auto &annotation) { return !annotation.typeId; }),
@@ -2311,7 +2459,6 @@ void ProjectStorage::synchronizeTypeAnnotations(Storage::Synchronization::TypeAn
if (!annotation.sourceId)
throw TypeAnnotationHasInvalidSourceId{};
- synchronizeTypeTraits(annotation.typeId, annotation.traits);
using NanotraceHR::keyValue;
NanotraceHR::Tracer tracer{"insert type annotations"_t,
@@ -2321,16 +2468,19 @@ void ProjectStorage::synchronizeTypeAnnotations(Storage::Synchronization::TypeAn
s->insertTypeAnnotationStatement.write(annotation.typeId,
annotation.sourceId,
annotation.directorySourceId,
+ annotation.typeName,
annotation.iconPath,
createEmptyAsNull(annotation.itemLibraryJson),
createEmptyAsNull(annotation.hintsJson));
+
+ synchronizeTypeTraits(annotation.typeId, annotation.traits);
};
auto update = [&](const TypeAnnotationView &annotationFromDatabase,
const TypeAnnotation &annotation) {
- synchronizeTypeTraits(annotation.typeId, annotation.traits);
- if (annotationFromDatabase.iconPath != annotation.iconPath
+ if (annotationFromDatabase.typeName != annotation.typeName
+ || annotationFromDatabase.iconPath != annotation.iconPath
|| annotationFromDatabase.itemLibraryJson != annotation.itemLibraryJson
|| annotationFromDatabase.hintsJson != annotation.hintsJson) {
using NanotraceHR::keyValue;
@@ -2341,24 +2491,33 @@ void ProjectStorage::synchronizeTypeAnnotations(Storage::Synchronization::TypeAn
keyValue("type annotation", annotation)};
s->updateTypeAnnotationStatement.write(annotation.typeId,
+ annotation.typeName,
annotation.iconPath,
createEmptyAsNull(annotation.itemLibraryJson),
createEmptyAsNull(annotation.hintsJson));
+
+ synchronizeTypeTraits(annotation.typeId, annotation.traits);
+
return Sqlite::UpdateChange::Update;
}
+ synchronizeTypeTraits(annotation.typeId, annotation.traits);
+
return Sqlite::UpdateChange::No;
};
auto remove = [&](const TypeAnnotationView &annotationFromDatabase) {
- synchronizeTypeTraits(annotationFromDatabase.typeId, Storage::TypeTraits{});
-
using NanotraceHR::keyValue;
NanotraceHR::Tracer tracer{"remove type annotations"_t,
projectStorageCategory(),
keyValue("type annotation", annotationFromDatabase)};
+ auto prototypeAnnotationTraits = s->selectPrototypeAnnotationTraitsByTypeIdStatement
+ .value<long long>(annotationFromDatabase.typeId);
s->deleteTypeAnnotationStatement.write(annotationFromDatabase.typeId);
+
+ s->updateTypeAnnotationTraitStatement.write(annotationFromDatabase.typeId,
+ prototypeAnnotationTraits);
};
Sqlite::insertUpdateDelete(range, typeAnnotations, compareKey, insert, update, remove);
@@ -2432,74 +2591,75 @@ void ProjectStorage::synchronizeTypes(Storage::Synchronization::Types &types,
syncDefaultProperties(types);
}
-void ProjectStorage::synchronizeProjectDatas(Storage::Synchronization::ProjectDatas &projectDatas,
- const SourceIds &updatedProjectSourceIds)
+void ProjectStorage::synchronizeDirectoryInfos(Storage::Synchronization::DirectoryInfos &directoryInfos,
+ const SourceIds &updatedDirectoryInfoSourceIds)
{
- NanotraceHR::Tracer tracer{"synchronize project datas"_t, projectStorageCategory()};
+ NanotraceHR::Tracer tracer{"synchronize directory infos"_t, projectStorageCategory()};
auto compareKey = [](auto &&first, auto &&second) {
- auto projectSourceIdDifference = first.projectSourceId - second.projectSourceId;
- if (projectSourceIdDifference != 0)
- return projectSourceIdDifference;
+ auto directorySourceIdDifference = first.directorySourceId - second.directorySourceId;
+ if (directorySourceIdDifference != 0)
+ return directorySourceIdDifference;
return first.sourceId - second.sourceId;
};
- std::sort(projectDatas.begin(), projectDatas.end(), [&](auto &&first, auto &&second) {
- return std::tie(first.projectSourceId, first.sourceId)
- < std::tie(second.projectSourceId, second.sourceId);
+ std::sort(directoryInfos.begin(), directoryInfos.end(), [&](auto &&first, auto &&second) {
+ return std::tie(first.directorySourceId, first.sourceId)
+ < std::tie(second.directorySourceId, second.sourceId);
});
- auto range = s->selectProjectDatasForSourceIdsStatement.range<Storage::Synchronization::ProjectData>(
- toIntegers(updatedProjectSourceIds));
+ auto range = s->selectDirectoryInfosForSourceIdsStatement.range<Storage::Synchronization::DirectoryInfo>(
+ toIntegers(updatedDirectoryInfoSourceIds));
- auto insert = [&](const Storage::Synchronization::ProjectData &projectData) {
+ auto insert = [&](const Storage::Synchronization::DirectoryInfo &directoryInfo) {
using NanotraceHR::keyValue;
- NanotraceHR::Tracer tracer{"insert project data"_t,
+ NanotraceHR::Tracer tracer{"insert directory info"_t,
projectStorageCategory(),
- keyValue("project data", projectData)};
+ keyValue("directory info", directoryInfo)};
- if (!projectData.projectSourceId)
- throw ProjectDataHasInvalidProjectSourceId{};
- if (!projectData.sourceId)
- throw ProjectDataHasInvalidSourceId{};
+ if (!directoryInfo.directorySourceId)
+ throw DirectoryInfoHasInvalidProjectSourceId{};
+ if (!directoryInfo.sourceId)
+ throw DirectoryInfoHasInvalidSourceId{};
- s->insertProjectDataStatement.write(projectData.projectSourceId,
- projectData.sourceId,
- projectData.moduleId,
- projectData.fileType);
+ s->insertDirectoryInfoStatement.write(directoryInfo.directorySourceId,
+ directoryInfo.sourceId,
+ directoryInfo.moduleId,
+ directoryInfo.fileType);
};
- auto update = [&](const Storage::Synchronization::ProjectData &projectDataFromDatabase,
- const Storage::Synchronization::ProjectData &projectData) {
- if (projectDataFromDatabase.fileType != projectData.fileType
- || !compareInvalidAreTrue(projectDataFromDatabase.moduleId, projectData.moduleId)) {
+ auto update = [&](const Storage::Synchronization::DirectoryInfo &directoryInfoFromDatabase,
+ const Storage::Synchronization::DirectoryInfo &directoryInfo) {
+ if (directoryInfoFromDatabase.fileType != directoryInfo.fileType
+ || !compareInvalidAreTrue(directoryInfoFromDatabase.moduleId, directoryInfo.moduleId)) {
using NanotraceHR::keyValue;
- NanotraceHR::Tracer tracer{"update project data"_t,
+ NanotraceHR::Tracer tracer{"update directory info"_t,
projectStorageCategory(),
- keyValue("project data", projectData),
- keyValue("project data from database", projectDataFromDatabase)};
-
- s->updateProjectDataStatement.write(projectData.projectSourceId,
- projectData.sourceId,
- projectData.moduleId,
- projectData.fileType);
+ keyValue("directory info", directoryInfo),
+ keyValue("directory info from database",
+ directoryInfoFromDatabase)};
+
+ s->updateDirectoryInfoStatement.write(directoryInfo.directorySourceId,
+ directoryInfo.sourceId,
+ directoryInfo.moduleId,
+ directoryInfo.fileType);
return Sqlite::UpdateChange::Update;
}
return Sqlite::UpdateChange::No;
};
- auto remove = [&](const Storage::Synchronization::ProjectData &projectData) {
+ auto remove = [&](const Storage::Synchronization::DirectoryInfo &directoryInfo) {
using NanotraceHR::keyValue;
- NanotraceHR::Tracer tracer{"remove project data"_t,
+ NanotraceHR::Tracer tracer{"remove directory info"_t,
projectStorageCategory(),
- keyValue("project data", projectData)};
+ keyValue("directory info", directoryInfo)};
- s->deleteProjectDataStatement.write(projectData.projectSourceId, projectData.sourceId);
+ s->deleteDirectoryInfoStatement.write(directoryInfo.directorySourceId, directoryInfo.sourceId);
};
- Sqlite::insertUpdateDelete(range, projectDatas, compareKey, insert, update, remove);
+ Sqlite::insertUpdateDelete(range, directoryInfos, compareKey, insert, update, remove);
}
void ProjectStorage::synchronizeFileStatuses(FileStatuses &fileStatuses,
@@ -2564,19 +2724,29 @@ void ProjectStorage::synchronizeImports(Storage::Imports &imports,
Storage::Imports &moduleDependencies,
const SourceIds &updatedModuleDependencySourceIds,
Storage::Synchronization::ModuleExportedImports &moduleExportedImports,
- const ModuleIds &updatedModuleIds)
+ const ModuleIds &updatedModuleIds,
+ Prototypes &relinkablePrototypes,
+ Prototypes &relinkableExtensions)
{
NanotraceHR::Tracer tracer{"synchronize imports"_t, projectStorageCategory()};
synchromizeModuleExportedImports(moduleExportedImports, updatedModuleIds);
NanotraceHR::Tracer importTracer{"synchronize qml document imports"_t, projectStorageCategory()};
- synchronizeDocumentImports(imports, updatedSourceIds, Storage::Synchronization::ImportKind::Import);
+ synchronizeDocumentImports(imports,
+ updatedSourceIds,
+ Storage::Synchronization::ImportKind::Import,
+ Relink::No,
+ relinkablePrototypes,
+ relinkableExtensions);
importTracer.end();
NanotraceHR::Tracer moduleDependenciesTracer{"synchronize module depdencies"_t,
projectStorageCategory()};
synchronizeDocumentImports(moduleDependencies,
updatedModuleDependencySourceIds,
- Storage::Synchronization::ImportKind::ModuleDependency);
+ Storage::Synchronization::ImportKind::ModuleDependency,
+ Relink::Yes,
+ relinkablePrototypes,
+ relinkableExtensions);
moduleDependenciesTracer.end();
}
@@ -2650,38 +2820,41 @@ void ProjectStorage::synchromizeModuleExportedImports(
Sqlite::insertUpdateDelete(range, moduleExportedImports, compareKey, insert, update, remove);
}
-ModuleId ProjectStorage::fetchModuleIdUnguarded(Utils::SmallStringView name) const
+ModuleId ProjectStorage::fetchModuleIdUnguarded(Utils::SmallStringView name,
+ Storage::ModuleKind kind) const
{
using NanotraceHR::keyValue;
NanotraceHR::Tracer tracer{"fetch module id ungarded"_t,
projectStorageCategory(),
- keyValue("module name", name)};
+ keyValue("module name", name),
+ keyValue("module kind", kind)};
- auto moduleId = s->selectModuleIdByNameStatement.value<ModuleId>(name);
+ auto moduleId = s->selectModuleIdByNameStatement.value<ModuleId>(kind, name);
if (!moduleId)
- moduleId = s->insertModuleNameStatement.value<ModuleId>(name);
+ moduleId = s->insertModuleNameStatement.value<ModuleId>(kind, name);
tracer.end(keyValue("module id", moduleId));
return moduleId;
}
-Utils::PathString ProjectStorage::fetchModuleNameUnguarded(ModuleId id) const
+Storage::Module ProjectStorage::fetchModuleUnguarded(ModuleId id) const
{
using NanotraceHR::keyValue;
- NanotraceHR::Tracer tracer{"fetch module name ungarded"_t,
+ NanotraceHR::Tracer tracer{"fetch module ungarded"_t,
projectStorageCategory(),
keyValue("module id", id)};
- auto moduleName = s->selectModuleNameStatement.value<Utils::PathString>(id);
+ auto module = s->selectModuleStatement.value<Storage::Module>(id);
- if (moduleName.empty())
+ if (!module)
throw ModuleDoesNotExists{};
- tracer.end(keyValue("module name", moduleName));
+ tracer.end(keyValue("module name", module.name));
+ tracer.end(keyValue("module name", module.kind));
- return moduleName;
+ return module;
}
void ProjectStorage::handleAliasPropertyDeclarationsWithPropertyType(
@@ -2741,10 +2914,29 @@ void ProjectStorage::handlePrototypes(TypeId prototypeId, Prototypes &relinkable
keyValue("relinkable prototypes", relinkablePrototypes)};
auto callback = [&](TypeId typeId, ImportedTypeNameId prototypeNameId) {
+ if (prototypeNameId)
+ relinkablePrototypes.emplace_back(typeId, prototypeNameId);
+ };
+
+ s->updatePrototypeIdToTypeIdStatement.readCallback(callback, prototypeId, unresolvedTypeId);
+}
+
+void ProjectStorage::handlePrototypesWithExportedTypeNameAndTypeId(
+ Utils::SmallStringView exportedTypeName, TypeId typeId, Prototypes &relinkablePrototypes)
+{
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"handle invalid prototypes"_t,
+ projectStorageCategory(),
+ keyValue("type id", exportedTypeName),
+ keyValue("relinkable prototypes", relinkablePrototypes)};
+
+ auto callback = [&](TypeId typeId, ImportedTypeNameId prototypeNameId) {
relinkablePrototypes.emplace_back(typeId, prototypeNameId);
};
- s->updatePrototypeIdToNullStatement.readCallback(callback, prototypeId);
+ s->selectTypeIdAndPrototypeNameIdForPrototypeIdAndTypeNameStatement.readCallback(callback,
+ exportedTypeName,
+ typeId);
}
void ProjectStorage::handleExtensions(TypeId extensionId, Prototypes &relinkableExtensions)
@@ -2756,10 +2948,27 @@ void ProjectStorage::handleExtensions(TypeId extensionId, Prototypes &relinkable
keyValue("relinkable extensions", relinkableExtensions)};
auto callback = [&](TypeId typeId, ImportedTypeNameId extensionNameId) {
+ if (extensionNameId)
+ relinkableExtensions.emplace_back(typeId, extensionNameId);
+ };
+
+ s->updateExtensionIdToTypeIdStatement.readCallback(callback, extensionId, unresolvedTypeId);
+}
+
+void ProjectStorage::handleExtensionsWithExportedTypeNameAndTypeId(
+ Utils::SmallStringView exportedTypeName, TypeId typeId, Prototypes &relinkableExtensions)
+{
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"handle invalid extensions"_t,
+ projectStorageCategory(),
+ keyValue("type id", exportedTypeName),
+ keyValue("relinkable extensions", relinkableExtensions)};
+
+ auto callback = [&](TypeId typeId, ImportedTypeNameId extensionNameId) {
relinkableExtensions.emplace_back(typeId, extensionNameId);
};
- s->updateExtensionIdToNullStatement.readCallback(callback, extensionId);
+ s->selectTypeIdForExtensionIdAndTypeNameStatement.readCallback(callback, exportedTypeName, typeId);
}
void ProjectStorage::deleteType(TypeId typeId,
@@ -2846,6 +3055,39 @@ void ProjectStorage::relinkPropertyDeclarations(PropertyDeclarations &relinkable
TypeCompare<PropertyDeclaration>{});
}
+template<typename Callable>
+void ProjectStorage::relinkPrototypes(Prototypes &relinkablePrototypes,
+ const TypeIds &deletedTypeIds,
+ Callable updateStatement)
+{
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"relink prototypes"_t,
+ projectStorageCategory(),
+ keyValue("relinkable prototypes", relinkablePrototypes),
+ keyValue("deleted type ids", deletedTypeIds)};
+
+ std::sort(relinkablePrototypes.begin(), relinkablePrototypes.end());
+ relinkablePrototypes.erase(std::unique(relinkablePrototypes.begin(), relinkablePrototypes.end()),
+ relinkablePrototypes.end());
+
+ Utils::set_greedy_difference(
+ relinkablePrototypes.cbegin(),
+ relinkablePrototypes.cend(),
+ deletedTypeIds.begin(),
+ deletedTypeIds.end(),
+ [&](const Prototype &prototype) {
+ TypeId prototypeId = fetchTypeId(prototype.prototypeNameId);
+
+ if (!prototypeId)
+ errorNotifier->typeNameCannotBeResolved(fetchImportedTypeName(prototype.prototypeNameId),
+ fetchTypeSourceId(prototype.typeId));
+
+ updateStatement(prototype.typeId, prototypeId);
+ checkForPrototypeChainCycle(prototype.typeId);
+ },
+ TypeCompare<Prototype>{});
+}
+
void ProjectStorage::deleteNotUpdatedTypes(const TypeIds &updatedTypeIds,
const SourceIds &updatedSourceIds,
const TypeIds &typeIdsToBeDeleted,
@@ -3060,6 +3302,9 @@ void ProjectStorage::synchronizeExportedTypes(const TypeIds &updatedTypeIds,
} catch (const Sqlite::ConstraintPreventsModification &) {
throw QmlDesigner::ExportedTypeCannotBeInserted{type.name};
}
+
+ handlePrototypesWithExportedTypeNameAndTypeId(type.name, unresolvedTypeId, relinkablePrototypes);
+ handleExtensionsWithExportedTypeNameAndTypeId(type.name, unresolvedTypeId, relinkableExtensions);
};
auto update = [&](const Storage::Synchronization::ExportedTypeView &view,
@@ -3095,6 +3340,7 @@ void ProjectStorage::synchronizeExportedTypes(const TypeIds &updatedTypeIds,
relinkableAliasPropertyDeclarations);
handlePrototypes(view.typeId, relinkablePrototypes);
handleExtensions(view.typeId, relinkableExtensions);
+
s->deleteExportedTypeNameStatement.write(view.exportedTypeNameId);
};
@@ -3410,11 +3656,90 @@ void ProjectStorage::resetRemovedAliasPropertyDeclarationsToNull(
PropertyCompare<AliasPropertyDeclaration>{});
}
+void ProjectStorage::handlePrototypesWithSourceIdAndPrototypeId(SourceId sourceId,
+ TypeId prototypeId,
+ Prototypes &relinkablePrototypes)
+{
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"handle prototypes with source id and prototype id"_t,
+ projectStorageCategory(),
+ keyValue("source id", sourceId),
+ keyValue("type id", prototypeId)};
+
+ auto callback = [&](TypeId typeId, ImportedTypeNameId prototypeNameId) {
+ if (prototypeNameId)
+ relinkablePrototypes.emplace_back(typeId, prototypeNameId);
+ };
+
+ s->selectTypeIdAndPrototypeNameIdForPrototypeIdAndSourceIdStatement.readCallback(callback,
+ prototypeId,
+ sourceId);
+}
+
+void ProjectStorage::handlePrototypesAndExtensionsWithSourceId(SourceId sourceId,
+ TypeId prototypeId,
+ TypeId extensionId,
+ Prototypes &relinkablePrototypes,
+ Prototypes &relinkableExtensions)
+{
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"handle prototypes with source id"_t,
+ projectStorageCategory(),
+ keyValue("source id", sourceId),
+ keyValue("prototype id", prototypeId),
+ keyValue("extension id", extensionId)};
+
+ auto callback =
+ [&](TypeId typeId, ImportedTypeNameId prototypeNameId, ImportedTypeNameId extensionNameId) {
+ if (prototypeNameId)
+ relinkablePrototypes.emplace_back(typeId, prototypeNameId);
+ if (extensionNameId)
+ relinkableExtensions.emplace_back(typeId, extensionNameId);
+ };
+
+ s->updatePrototypeIdAndExtensionIdToTypeIdForSourceIdStatement.readCallback(callback,
+ sourceId,
+ prototypeId,
+ extensionId);
+}
+
+void ProjectStorage::handleExtensionsWithSourceIdAndExtensionId(SourceId sourceId,
+ TypeId extensionId,
+ Prototypes &relinkableExtensions)
+{
+ using NanotraceHR::keyValue;
+ NanotraceHR::Tracer tracer{"handle prototypes with source id and prototype id"_t,
+ projectStorageCategory(),
+ keyValue("source id", sourceId),
+ keyValue("type id", extensionId)};
+
+ auto callback = [&](TypeId typeId, ImportedTypeNameId extensionNameId) {
+ if (extensionNameId)
+ relinkableExtensions.emplace_back(typeId, extensionNameId);
+ };
+
+ s->selectTypeIdAndExtensionNameIdForExtensionIdAndSourceIdStatement.readCallback(callback,
+ extensionId,
+ sourceId);
+}
+
ImportId ProjectStorage::insertDocumentImport(const Storage::Import &import,
Storage::Synchronization::ImportKind importKind,
ModuleId sourceModuleId,
- ImportId parentImportId)
+ ImportId parentImportId,
+ Relink relink,
+ Prototypes &relinkablePrototypes,
+ Prototypes &relinkableExtensions)
{
+ if (relink == Relink::Yes) {
+ handlePrototypesWithSourceIdAndPrototypeId(import.sourceId,
+ unresolvedTypeId,
+ relinkablePrototypes);
+ handleExtensionsWithSourceIdAndExtensionId(import.sourceId,
+ unresolvedTypeId,
+ relinkableExtensions);
+ }
+
if (import.version.minor) {
return s->insertDocumentImportWithVersionStatement.value<ImportId>(import.sourceId,
import.moduleId,
@@ -3441,7 +3766,10 @@ ImportId ProjectStorage::insertDocumentImport(const Storage::Import &import,
void ProjectStorage::synchronizeDocumentImports(Storage::Imports &imports,
const SourceIds &updatedSourceIds,
- Storage::Synchronization::ImportKind importKind)
+ Storage::Synchronization::ImportKind importKind,
+ Relink relink,
+ Prototypes &relinkablePrototypes,
+ Prototypes &relinkableExtensions)
{
std::sort(imports.begin(), imports.end(), [](auto &&first, auto &&second) {
return std::tie(first.sourceId, first.moduleId, first.version)
@@ -3478,7 +3806,13 @@ void ProjectStorage::synchronizeDocumentImports(Storage::Imports &imports,
keyValue("source id", import.sourceId),
keyValue("module id", import.moduleId)};
- auto importId = insertDocumentImport(import, importKind, import.moduleId, ImportId{});
+ auto importId = insertDocumentImport(import,
+ importKind,
+ import.moduleId,
+ ImportId{},
+ relink,
+ relinkablePrototypes,
+ relinkableExtensions);
auto callback = [&](ModuleId exportedModuleId, int majorVersion, int minorVersion) {
Storage::Import additionImport{exportedModuleId,
Storage::Version{majorVersion, minorVersion},
@@ -3498,7 +3832,10 @@ void ProjectStorage::synchronizeDocumentImports(Storage::Imports &imports,
auto indirectImportId = insertDocumentImport(additionImport,
exportedImportKind,
import.moduleId,
- importId);
+ importId,
+ relink,
+ relinkablePrototypes,
+ relinkableExtensions);
tracer.end(keyValue("import id", indirectImportId));
};
@@ -3525,6 +3862,13 @@ void ProjectStorage::synchronizeDocumentImports(Storage::Imports &imports,
s->deleteDocumentImportStatement.write(view.importId);
s->deleteDocumentImportsWithParentImportIdStatement.write(view.sourceId, view.importId);
+ if (relink == Relink::Yes) {
+ handlePrototypesAndExtensionsWithSourceId(view.sourceId,
+ unresolvedTypeId,
+ unresolvedTypeId,
+ relinkablePrototypes,
+ relinkableExtensions);
+ }
};
Sqlite::insertUpdateDelete(range, imports, compareKey, insert, update, remove);
@@ -3550,7 +3894,7 @@ Utils::PathString ProjectStorage::createJson(const Storage::Synchronization::Par
json.append("\"}");
} else {
json.append(R"(","tr":)");
- json.append(Utils::SmallString::number(to_underlying(parameter.traits)));
+ json.append(Utils::SmallString::number(Utils::to_underlying(parameter.traits)));
json.append("}");
}
}
@@ -4075,25 +4419,29 @@ void ProjectStorage::checkForAliasChainCycle(PropertyDeclarationId propertyDecla
}
std::pair<TypeId, ImportedTypeNameId> ProjectStorage::fetchImportedTypeNameIdAndTypeId(
- const Storage::Synchronization::ImportedTypeName &typeName, SourceId sourceId)
+ const Storage::Synchronization::ImportedTypeName &importedTypeName, SourceId sourceId)
{
using NanotraceHR::keyValue;
NanotraceHR::Tracer tracer{"fetch imported type name id and type id"_t,
projectStorageCategory(),
- keyValue("imported type name", typeName),
+ keyValue("imported type name", importedTypeName),
keyValue("source id", sourceId)};
TypeId typeId;
ImportedTypeNameId typeNameId;
- if (!std::visit([](auto &&typeName_) -> bool { return typeName_.name.isEmpty(); }, typeName)) {
- typeNameId = fetchImportedTypeNameId(typeName, sourceId);
+ auto typeName = std::visit([](auto &&importedTypeName) { return importedTypeName.name; },
+ importedTypeName);
+ if (!typeName.empty()) {
+ typeNameId = fetchImportedTypeNameId(importedTypeName, sourceId);
typeId = fetchTypeId(typeNameId);
tracer.end(keyValue("type id", typeId), keyValue("type name id", typeNameId));
- if (!typeId)
- throw TypeNameDoesNotExists{fetchImportedTypeName(typeNameId), sourceId};
+ if (!typeId) {
+ errorNotifier->typeNameCannotBeResolved(typeName, sourceId);
+ return {unresolvedTypeId, typeNameId};
+ }
}
return {typeId, typeNameId};
@@ -4242,6 +4590,11 @@ Utils::SmallString ProjectStorage::fetchImportedTypeName(ImportedTypeNameId type
return s->selectNameFromImportedTypeNamesStatement.value<Utils::SmallString>(typeNameId);
}
+SourceId ProjectStorage::fetchTypeSourceId(TypeId typeId) const
+{
+ return s->selectSourceIdByTypeIdStatement.value<SourceId>(typeId);
+}
+
TypeId ProjectStorage::fetchTypeId(ImportedTypeNameId typeNameId,
Storage::Synchronization::TypeNameKind kind) const
{
@@ -4253,9 +4606,10 @@ TypeId ProjectStorage::fetchTypeId(ImportedTypeNameId typeNameId,
TypeId typeId;
if (kind == Storage::Synchronization::TypeNameKind::Exported) {
- typeId = s->selectTypeIdForImportedTypeNameNamesStatement.value<TypeId>(typeNameId);
+ typeId = s->selectTypeIdForImportedTypeNameNamesStatement.value<UnresolvedTypeId>(typeNameId);
} else {
- typeId = s->selectTypeIdForQualifiedImportedTypeNameNamesStatement.value<TypeId>(typeNameId);
+ typeId = s->selectTypeIdForQualifiedImportedTypeNameNamesStatement.value<UnresolvedTypeId>(
+ typeNameId);
}
tracer.end(keyValue("type id", typeId));
diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h
index e7826f531b..54d9101596 100644
--- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h
+++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h
@@ -4,6 +4,7 @@
#pragma once
#include "commontypecache.h"
+#include "projectstorageerrornotifier.h"
#include "projectstorageexceptions.h"
#include "projectstorageinterface.h"
#include "projectstoragetypes.h"
@@ -38,21 +39,30 @@ class ProjectStorage final : public ProjectStorageInterface
using Database = Sqlite::Database;
friend Storage::Info::CommonTypeCache<ProjectStorageType>;
+ enum class Relink { No, Yes };
+
public:
- ProjectStorage(Database &database, bool isInitialized);
+ ProjectStorage(Database &database,
+ ProjectStorageErrorNotifierInterface &errorNotifier,
+ bool isInitialized);
~ProjectStorage();
void synchronize(Storage::Synchronization::SynchronizationPackage package) override;
void synchronizeDocumentImports(Storage::Imports imports, SourceId sourceId) override;
+ void setErrorNotifier(ProjectStorageErrorNotifierInterface &errorNotifier)
+ {
+ this->errorNotifier = &errorNotifier;
+ }
+
void addObserver(ProjectStorageObserver *observer) override;
void removeObserver(ProjectStorageObserver *observer) override;
- ModuleId moduleId(Utils::SmallStringView moduleName) const override;
+ ModuleId moduleId(Utils::SmallStringView moduleName, Storage::ModuleKind kind) const override;
- Utils::SmallString moduleName(ModuleId moduleId) const override;
+ Storage::Module module(ModuleId moduleId) const override;
TypeId typeId(ModuleId moduleId,
Utils::SmallStringView exportedTypeName,
@@ -116,7 +126,7 @@ public:
return commonTypeCache_;
}
- template<const char *moduleName, const char *typeName>
+ template<const char *moduleName, const char *typeName, Storage::ModuleKind moduleKind = Storage::ModuleKind::QmlLibrary>
TypeId commonTypeId() const
{
using NanotraceHR::keyValue;
@@ -125,7 +135,7 @@ public:
keyValue("module name", std::string_view{moduleName}),
keyValue("type name", std::string_view{typeName})};
- auto typeId = commonTypeCache_.typeId<moduleName, typeName>();
+ auto typeId = commonTypeCache_.typeId<moduleName, typeName, moduleKind>();
tracer.end(keyValue("type id", typeId));
@@ -229,11 +239,13 @@ public:
FileStatus fetchFileStatus(SourceId sourceId) const override;
- std::optional<Storage::Synchronization::ProjectData> fetchProjectData(SourceId sourceId) const override;
+ std::optional<Storage::Synchronization::DirectoryInfo> fetchDirectoryInfo(SourceId sourceId) const override;
- Storage::Synchronization::ProjectDatas fetchProjectDatas(SourceId projectSourceId) const override;
-
- Storage::Synchronization::ProjectDatas fetchProjectDatas(const SourceIds &projectSourceIds) const;
+ Storage::Synchronization::DirectoryInfos fetchDirectoryInfos(SourceId directorySourceId) const override;
+ Storage::Synchronization::DirectoryInfos fetchDirectoryInfos(
+ SourceId directorySourceId, Storage::Synchronization::FileType fileType) const override;
+ Storage::Synchronization::DirectoryInfos fetchDirectoryInfos(const SourceIds &directorySourceIds) const;
+ SmallSourceIds<32> fetchSubdirectorySourceIds(SourceId directorySourceId) const override;
void setPropertyEditorPathId(TypeId typeId, SourceId pathId);
@@ -244,50 +256,90 @@ public:
void resetForTestsOnly();
private:
+ struct ModuleView
+ {
+ ModuleView() = default;
+
+ ModuleView(Utils::SmallStringView name, Storage::ModuleKind kind)
+ : name{name}
+ , kind{kind}
+ {}
+
+ ModuleView(const Storage::Module &module)
+ : name{module.name}
+ , kind{module.kind}
+ {}
+
+ Utils::SmallStringView name;
+ Storage::ModuleKind kind;
+
+ friend bool operator<(ModuleView first, ModuleView second)
+ {
+ return std::tie(first.kind, first.name) < std::tie(second.kind, second.name);
+ }
+
+ friend bool operator==(const Storage::Module &first, ModuleView second)
+ {
+ return first.name == second.name && first.kind == second.kind;
+ }
+
+ friend bool operator==(ModuleView first, const Storage::Module &second)
+ {
+ return second == first;
+ }
+ };
+
class ModuleStorageAdapter
{
public:
- auto fetchId(const Utils::SmallStringView name) { return storage.fetchModuleId(name); }
+ auto fetchId(ModuleView module) { return storage.fetchModuleId(module.name, module.kind); }
- auto fetchValue(ModuleId id) { return storage.fetchModuleName(id); }
+ auto fetchValue(ModuleId id) { return storage.fetchModule(id); }
auto fetchAll() { return storage.fetchAllModules(); }
ProjectStorage &storage;
};
- class Module : public StorageCacheEntry<Utils::PathString, Utils::SmallStringView, ModuleId>
+ friend ModuleStorageAdapter;
+
+ static bool moduleNameLess(ModuleView first, ModuleView second) noexcept
{
- using Base = StorageCacheEntry<Utils::PathString, Utils::SmallStringView, ModuleId>;
+ return first < second;
+ }
+
+ class ModuleCacheEntry : public StorageCacheEntry<Storage::Module, ModuleView, ModuleId>
+ {
+ using Base = StorageCacheEntry<Storage::Module, ModuleView, ModuleId>;
public:
using Base::Base;
- friend bool operator==(const Module &first, const Module &second)
+ ModuleCacheEntry(Utils::SmallStringView name, Storage::ModuleKind kind, ModuleId moduleId)
+ : Base{{name, kind}, moduleId}
+ {}
+
+ friend bool operator==(const ModuleCacheEntry &first, const ModuleCacheEntry &second)
{
return &first == &second && first.value == second.value;
}
- };
-
- using Modules = std::vector<Module>;
- friend ModuleStorageAdapter;
+ friend bool operator==(const ModuleCacheEntry &first, ModuleView second)
+ {
+ return first.value.name == second.name && first.value.kind == second.kind;
+ }
+ };
- static bool moduleNameLess(Utils::SmallStringView first, Utils::SmallStringView second) noexcept;
+ using ModuleCacheEntries = std::vector<ModuleCacheEntry>;
- using ModuleCache = StorageCache<Utils::PathString,
- Utils::SmallStringView,
- ModuleId,
- ModuleStorageAdapter,
- NonLockingMutex,
- moduleNameLess,
- Module>;
+ using ModuleCache
+ = StorageCache<Storage::Module, ModuleView, ModuleId, ModuleStorageAdapter, NonLockingMutex, moduleNameLess, ModuleCacheEntry>;
- ModuleId fetchModuleId(Utils::SmallStringView moduleName);
+ ModuleId fetchModuleId(Utils::SmallStringView moduleName, Storage::ModuleKind moduleKind);
- Utils::PathString fetchModuleName(ModuleId id);
+ Storage::Module fetchModule(ModuleId id);
- Modules fetchAllModules() const;
+ ModuleCacheEntries fetchAllModules() const;
void callRefreshMetaInfoCallback(const TypeIds &deletedTypeIds);
@@ -398,6 +450,11 @@ private:
return first.typeId < second.typeId;
}
+ friend bool operator==(Prototype first, Prototype second)
+ {
+ return first.typeId == second.typeId;
+ }
+
template<typename String>
friend void convertToString(String &string, const Prototype &prototype)
{
@@ -461,10 +518,12 @@ private:
{
public:
TypeAnnotationView(TypeId typeId,
+ Utils::SmallStringView typeName,
Utils::SmallStringView iconPath,
Utils::SmallStringView itemLibraryJson,
Utils::SmallStringView hintsJson)
: typeId{typeId}
+ , typeName{typeName}
, iconPath{iconPath}
, itemLibraryJson{itemLibraryJson}
, hintsJson{hintsJson}
@@ -476,6 +535,7 @@ private:
using NanotraceHR::dictonary;
using NanotraceHR::keyValue;
auto dict = dictonary(keyValue("type id", typeAnnotationView.typeId),
+ keyValue("type name", typeAnnotationView.typeName),
keyValue("icon path", typeAnnotationView.iconPath),
keyValue("item library json", typeAnnotationView.itemLibraryJson),
keyValue("hints json", typeAnnotationView.hintsJson));
@@ -485,6 +545,7 @@ private:
public:
TypeId typeId;
+ Utils::SmallStringView typeName;
Utils::SmallStringView iconPath;
Utils::SmallStringView itemLibraryJson;
Utils::PathString hintsJson;
@@ -516,8 +577,8 @@ private:
Prototypes &relinkableExtensions,
const SourceIds &updatedSourceIds);
- void synchronizeProjectDatas(Storage::Synchronization::ProjectDatas &projectDatas,
- const SourceIds &updatedProjectSourceIds);
+ void synchronizeDirectoryInfos(Storage::Synchronization::DirectoryInfos &directoryInfos,
+ const SourceIds &updatedDirectoryInfoSourceIds);
void synchronizeFileStatuses(FileStatuses &fileStatuses, const SourceIds &updatedSourceIds);
@@ -526,15 +587,18 @@ private:
Storage::Imports &moduleDependencies,
const SourceIds &updatedModuleDependencySourceIds,
Storage::Synchronization::ModuleExportedImports &moduleExportedImports,
- const ModuleIds &updatedModuleIds);
+ const ModuleIds &updatedModuleIds,
+ Prototypes &relinkablePrototypes,
+ Prototypes &relinkableExtensions);
void synchromizeModuleExportedImports(
Storage::Synchronization::ModuleExportedImports &moduleExportedImports,
const ModuleIds &updatedModuleIds);
- ModuleId fetchModuleIdUnguarded(Utils::SmallStringView name) const override;
+ ModuleId fetchModuleIdUnguarded(Utils::SmallStringView name,
+ Storage::ModuleKind moduleKind) const override;
- Utils::PathString fetchModuleNameUnguarded(ModuleId id) const;
+ Storage::Module fetchModuleUnguarded(ModuleId id) const;
void handleAliasPropertyDeclarationsWithPropertyType(
TypeId typeId, AliasPropertyDeclarations &relinkableAliasPropertyDeclarations);
@@ -543,9 +607,14 @@ private:
PropertyDeclarations &relinkablePropertyDeclarations);
void handlePrototypes(TypeId prototypeId, Prototypes &relinkablePrototypes);
+ void handlePrototypesWithExportedTypeNameAndTypeId(Utils::SmallStringView exportedTypeName,
+ TypeId typeId,
+ Prototypes &relinkablePrototypes);
void handleExtensions(TypeId extensionId, Prototypes &relinkableExtensions);
-
+ void handleExtensionsWithExportedTypeNameAndTypeId(Utils::SmallStringView exportedTypeName,
+ TypeId typeId,
+ Prototypes &relinkableExtensions);
void deleteType(TypeId typeId,
AliasPropertyDeclarations &relinkableAliasPropertyDeclarations,
PropertyDeclarations &relinkablePropertyDeclarations,
@@ -561,32 +630,7 @@ private:
template<typename Callable>
void relinkPrototypes(Prototypes &relinkablePrototypes,
const TypeIds &deletedTypeIds,
- Callable updateStatement)
- {
- using NanotraceHR::keyValue;
- NanotraceHR::Tracer tracer{"relink prototypes"_t,
- projectStorageCategory(),
- keyValue("relinkable prototypes", relinkablePrototypes),
- keyValue("deleted type ids", deletedTypeIds)};
-
- std::sort(relinkablePrototypes.begin(), relinkablePrototypes.end());
-
- Utils::set_greedy_difference(
- relinkablePrototypes.cbegin(),
- relinkablePrototypes.cend(),
- deletedTypeIds.begin(),
- deletedTypeIds.end(),
- [&](const Prototype &prototype) {
- TypeId prototypeId = fetchTypeId(prototype.prototypeNameId);
-
- if (!prototypeId)
- throw TypeNameDoesNotExists{fetchImportedTypeName(prototype.prototypeNameId)};
-
- updateStatement(prototype.typeId, prototypeId);
- checkForPrototypeChainCycle(prototype.typeId);
- },
- TypeCompare<Prototype>{});
- }
+ Callable updateStatement);
void deleteNotUpdatedTypes(const TypeIds &updatedTypeIds,
const SourceIds &updatedSourceIds,
@@ -701,14 +745,32 @@ private:
Storage::Synchronization::Types &types,
AliasPropertyDeclarations &relinkableAliasPropertyDeclarations);
+ void handlePrototypesWithSourceIdAndPrototypeId(SourceId sourceId,
+ TypeId prototypeId,
+ Prototypes &relinkablePrototypes);
+ void handlePrototypesAndExtensionsWithSourceId(SourceId sourceId,
+ TypeId prototypeId,
+ TypeId extensionId,
+ Prototypes &relinkablePrototypes,
+ Prototypes &relinkableExtensions);
+ void handleExtensionsWithSourceIdAndExtensionId(SourceId sourceId,
+ TypeId extensionId,
+ Prototypes &relinkableExtensions);
+
ImportId insertDocumentImport(const Storage::Import &import,
Storage::Synchronization::ImportKind importKind,
ModuleId sourceModuleId,
- ImportId parentImportId);
+ ImportId parentImportId,
+ Relink forceRelink,
+ Prototypes &relinkablePrototypes,
+ Prototypes &relinkableExtensions);
void synchronizeDocumentImports(Storage::Imports &imports,
const SourceIds &updatedSourceIds,
- Storage::Synchronization::ImportKind importKind);
+ Storage::Synchronization::ImportKind importKind,
+ Relink forceRelink,
+ Prototypes &relinkablePrototypes,
+ Prototypes &relinkableExtensions);
static Utils::PathString createJson(const Storage::Synchronization::ParameterDeclarations &parameters);
@@ -854,6 +916,7 @@ private:
TypeId fetchTypeId(ImportedTypeNameId typeNameId) const;
Utils::SmallString fetchImportedTypeName(ImportedTypeNameId typeNameId) const;
+ SourceId fetchTypeSourceId(TypeId typeId) const;
TypeId fetchTypeId(ImportedTypeNameId typeNameId,
Storage::Synchronization::TypeNameKind kind) const;
@@ -920,6 +983,7 @@ private:
public:
Database &database;
+ ProjectStorageErrorNotifierInterface *errorNotifier = nullptr; // cannot be null
Sqlite::ExclusiveNonThrowingDestructorTransaction<Database> exclusiveTransaction;
std::unique_ptr<Initializer> initializer;
mutable ModuleCache moduleCache{ModuleStorageAdapter{*this}};
diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageerrornotifier.cpp b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageerrornotifier.cpp
new file mode 100644
index 0000000000..a4705f5eec
--- /dev/null
+++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageerrornotifier.cpp
@@ -0,0 +1,17 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "projectstorageerrornotifier.h"
+
+#include "sourcepathcache.h"
+
+namespace QmlDesigner {
+
+void ProjectStorageErrorNotifier::typeNameCannotBeResolved(Utils::SmallStringView typeName,
+ SourceId sourceId)
+{
+ qDebug() << "Missing type name: " << typeName
+ << " in file: " << m_pathCache.sourcePath(sourceId).toStringView();
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageerrornotifier.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageerrornotifier.h
new file mode 100644
index 0000000000..2695e93019
--- /dev/null
+++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageerrornotifier.h
@@ -0,0 +1,25 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "projectstorageerrornotifierinterface.h"
+
+#include <modelfwd.h>
+
+namespace QmlDesigner {
+
+class ProjectStorageErrorNotifier final : public ProjectStorageErrorNotifierInterface
+{
+public:
+ ProjectStorageErrorNotifier(PathCacheType &pathCache)
+ : m_pathCache{pathCache}
+ {}
+
+ void typeNameCannotBeResolved(Utils::SmallStringView typeName, SourceId souceId) override;
+
+private:
+ PathCacheType &m_pathCache;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageerrornotifierinterface.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageerrornotifierinterface.h
new file mode 100644
index 0000000000..8136c9d599
--- /dev/null
+++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageerrornotifierinterface.h
@@ -0,0 +1,27 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "projectstorageids.h"
+
+#include <utils/smallstringview.h>
+
+namespace QmlDesigner {
+
+class ProjectStorageErrorNotifierInterface
+{
+public:
+ ProjectStorageErrorNotifierInterface() = default;
+ ProjectStorageErrorNotifierInterface(ProjectStorageErrorNotifierInterface &&) = default;
+ ProjectStorageErrorNotifierInterface &operator=(ProjectStorageErrorNotifierInterface &&) = default;
+ ProjectStorageErrorNotifierInterface(const ProjectStorageErrorNotifierInterface &) = delete;
+ ProjectStorageErrorNotifierInterface &operator=(const ProjectStorageErrorNotifierInterface &) = delete;
+
+ virtual void typeNameCannotBeResolved(Utils::SmallStringView typeName, SourceId souceId) = 0;
+
+protected:
+ ~ProjectStorageErrorNotifierInterface() = default;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageexceptions.cpp b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageexceptions.cpp
index a5dc60c4fa..a86b78a785 100644
--- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageexceptions.cpp
+++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageexceptions.cpp
@@ -148,32 +148,32 @@ const char *CannotParseQmlDocumentFile::what() const noexcept
return "Cannot parse qml types file!";
}
-ProjectDataHasInvalidProjectSourceId::ProjectDataHasInvalidProjectSourceId()
+DirectoryInfoHasInvalidProjectSourceId::DirectoryInfoHasInvalidProjectSourceId()
{
- category().threadEvent("ProjectDataHasInvalidProjectSourceId"_t);
+ category().threadEvent("DirectoryInfoHasInvalidProjectSourceId"_t);
}
-const char *ProjectDataHasInvalidProjectSourceId::what() const noexcept
+const char *DirectoryInfoHasInvalidProjectSourceId::what() const noexcept
{
return "The project source id is invalid!";
}
-ProjectDataHasInvalidSourceId::ProjectDataHasInvalidSourceId()
+DirectoryInfoHasInvalidSourceId::DirectoryInfoHasInvalidSourceId()
{
- category().threadEvent("ProjectDataHasInvalidSourceId"_t);
+ category().threadEvent("DirectoryInfoHasInvalidSourceId"_t);
}
-const char *ProjectDataHasInvalidSourceId::what() const noexcept
+const char *DirectoryInfoHasInvalidSourceId::what() const noexcept
{
return "The source id is invalid!";
}
-ProjectDataHasInvalidModuleId::ProjectDataHasInvalidModuleId()
+DirectoryInfoHasInvalidModuleId::DirectoryInfoHasInvalidModuleId()
{
- category().threadEvent("ProjectDataHasInvalidModuleId"_t);
+ category().threadEvent("DirectoryInfoHasInvalidModuleId"_t);
}
-const char *ProjectDataHasInvalidModuleId::what() const noexcept
+const char *DirectoryInfoHasInvalidModuleId::what() const noexcept
{
return "The module id is invalid!";
}
diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageexceptions.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageexceptions.h
index d85f1f7f9e..f4f78f714b 100644
--- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageexceptions.h
+++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageexceptions.h
@@ -130,24 +130,24 @@ public:
const char *what() const noexcept override;
};
-class QMLDESIGNERCORE_EXPORT ProjectDataHasInvalidProjectSourceId : public ProjectStorageError
+class QMLDESIGNERCORE_EXPORT DirectoryInfoHasInvalidProjectSourceId : public ProjectStorageError
{
public:
- ProjectDataHasInvalidProjectSourceId();
+ DirectoryInfoHasInvalidProjectSourceId();
const char *what() const noexcept override;
};
-class QMLDESIGNERCORE_EXPORT ProjectDataHasInvalidSourceId : public ProjectStorageError
+class QMLDESIGNERCORE_EXPORT DirectoryInfoHasInvalidSourceId : public ProjectStorageError
{
public:
- ProjectDataHasInvalidSourceId();
+ DirectoryInfoHasInvalidSourceId();
const char *what() const noexcept override;
};
-class QMLDESIGNERCORE_EXPORT ProjectDataHasInvalidModuleId : public ProjectStorageError
+class QMLDESIGNERCORE_EXPORT DirectoryInfoHasInvalidModuleId : public ProjectStorageError
{
public:
- ProjectDataHasInvalidModuleId();
+ DirectoryInfoHasInvalidModuleId();
const char *what() const noexcept override;
};
diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinfotypes.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinfotypes.h
index 9f0c134ed3..1d630344ae 100644
--- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinfotypes.h
+++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinfotypes.h
@@ -7,6 +7,9 @@
#include <sqlite/sqlitevalue.h>
#include <utils/smallstring.h>
+#include <utils/utility.h>
+
+#include <QVarLengthArray>
#include <array>
#include <tuple>
@@ -15,12 +18,8 @@
namespace QmlDesigner {
-template<typename Enumeration>
-constexpr std::underlying_type_t<Enumeration> to_underlying(Enumeration enumeration) noexcept
-{
- static_assert(std::is_enum_v<Enumeration>, "to_underlying expect an enumeration");
- return static_cast<std::underlying_type_t<Enumeration>>(enumeration);
-}
+template<std::size_t size>
+using SmallPathStrings = QVarLengthArray<Utils::PathString, size>;
enum class FlagIs : unsigned int { False, Set, True };
@@ -42,6 +41,34 @@ void convertToString(String &string, const FlagIs &flagIs)
namespace QmlDesigner::Storage {
+enum class ModuleKind { QmlLibrary, CppLibrary, PathLibrary };
+
+struct Module
+{
+ Module() = default;
+
+ Module(Utils::SmallStringView name, Storage::ModuleKind kind)
+ : name{name}
+ , kind{kind}
+ {}
+
+ template<typename ModuleType>
+ Module(const ModuleType &module)
+ : name{module.name}
+ , kind{module.kind}
+ {}
+
+ Utils::PathString name;
+ Storage::ModuleKind kind = Storage::ModuleKind::QmlLibrary;
+
+ friend bool operator==(const Module &first, const Module &second)
+ {
+ return first.name == second.name && first.kind == second.kind;
+ }
+
+ explicit operator bool() const { return name.size(); }
+};
+
enum class PropertyDeclarationTraits : int {
None = 0,
IsReadOnly = 1 << 0,
@@ -352,6 +379,7 @@ using ToolTipString = Utils::BasicSmallString<94>;
struct ItemLibraryEntry
{
ItemLibraryEntry(TypeId typeId,
+ Utils::SmallStringView typeName,
Utils::SmallStringView name,
Utils::SmallStringView iconPath,
Utils::SmallStringView category,
@@ -359,6 +387,7 @@ struct ItemLibraryEntry
Utils::SmallStringView toolTip,
Utils::SmallStringView templatePath)
: typeId{typeId}
+ , typeName{typeName}
, name{name}
, iconPath{iconPath}
, category{category}
@@ -368,6 +397,7 @@ struct ItemLibraryEntry
{}
ItemLibraryEntry(TypeId typeId,
+ Utils::SmallStringView typeName,
Utils::SmallStringView name,
Utils::SmallStringView iconPath,
Utils::SmallStringView category,
@@ -375,6 +405,7 @@ struct ItemLibraryEntry
Utils::SmallStringView toolTip,
ItemLibraryProperties properties)
: typeId{typeId}
+ , typeName{typeName}
, name{name}
, iconPath{iconPath}
, category{category}
@@ -389,6 +420,7 @@ struct ItemLibraryEntry
using NanotraceHR::dictonary;
using NanotraceHR::keyValue;
auto dict = dictonary(keyValue("type id", entry.typeId),
+ keyValue("type name", entry.typeName),
keyValue("name", entry.name),
keyValue("icon path", entry.iconPath),
keyValue("category", entry.category),
@@ -402,6 +434,7 @@ struct ItemLibraryEntry
}
TypeId typeId;
+ Utils::SmallString typeName;
Utils::SmallString name;
Utils::PathString iconPath;
Utils::SmallString category;
diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinterface.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinterface.h
index 971e635517..4d840d2a5c 100644
--- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinterface.h
+++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinterface.h
@@ -31,8 +31,8 @@ public:
virtual void addObserver(ProjectStorageObserver *observer) = 0;
virtual void removeObserver(ProjectStorageObserver *observer) = 0;
- virtual ModuleId moduleId(::Utils::SmallStringView name) const = 0;
- virtual Utils::SmallString moduleName(ModuleId moduleId) const = 0;
+ virtual ModuleId moduleId(::Utils::SmallStringView name, Storage::ModuleKind kind) const = 0;
+ virtual QmlDesigner::Storage::Module module(ModuleId moduleId) const = 0;
virtual std::optional<Storage::Info::PropertyDeclaration>
propertyDeclaration(PropertyDeclarationId propertyDeclarationId) const = 0;
virtual TypeId typeId(ModuleId moduleId,
@@ -80,16 +80,20 @@ public:
virtual bool isBasedOn(TypeId, TypeId, TypeId, TypeId, TypeId, TypeId, TypeId, TypeId) const = 0;
virtual FileStatus fetchFileStatus(SourceId sourceId) const = 0;
- virtual Storage::Synchronization::ProjectDatas fetchProjectDatas(SourceId sourceId) const = 0;
- virtual std::optional<Storage::Synchronization::ProjectData> fetchProjectData(SourceId sourceId) const = 0;
+ virtual Storage::Synchronization::DirectoryInfos fetchDirectoryInfos(SourceId sourceId) const = 0;
+ virtual Storage::Synchronization::DirectoryInfos fetchDirectoryInfos(
+ SourceId directorySourceId, Storage::Synchronization::FileType) const
+ = 0;
+ virtual std::optional<Storage::Synchronization::DirectoryInfo> fetchDirectoryInfo(SourceId sourceId) const = 0;
+ virtual SmallSourceIds<32> fetchSubdirectorySourceIds(SourceId directorySourceId) const = 0;
virtual SourceId propertyEditorPathId(TypeId typeId) const = 0;
virtual const Storage::Info::CommonTypeCache<ProjectStorageType> &commonTypeCache() const = 0;
- template<const char *moduleName, const char *typeName>
+ template<const char *moduleName, const char *typeName, Storage::ModuleKind moduleKind = Storage::ModuleKind::QmlLibrary>
TypeId commonTypeId() const
{
- return commonTypeCache().template typeId<moduleName, typeName>();
+ return commonTypeCache().template typeId<moduleName, typeName, moduleKind>();
}
template<typename BuiltinType>
@@ -108,7 +112,7 @@ protected:
ProjectStorageInterface() = default;
~ProjectStorageInterface() = default;
- virtual ModuleId fetchModuleIdUnguarded(Utils::SmallStringView name) const = 0;
+ virtual ModuleId fetchModuleIdUnguarded(Utils::SmallStringView name, Storage::ModuleKind moduleKind) const = 0;
virtual TypeId fetchTypeIdByModuleIdAndExportedName(ModuleId moduleId, Utils::SmallStringView name) const = 0;
};
diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstoragetypes.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstoragetypes.h
index 8d810d94bd..1592628af5 100644
--- a/src/plugins/qmldesigner/designercore/projectstorage/projectstoragetypes.h
+++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstoragetypes.h
@@ -10,6 +10,7 @@
#include <nanotrace/nanotracehr.h>
#include <sqlite/sqlitevalue.h>
#include <utils/smallstring.h>
+#include <utils/utility.h>
#include <tuple>
#include <variant>
@@ -82,7 +83,7 @@ void convertToString(String &string, const TypeNameKind &kind)
}
}
-enum class FileType : char { QmlTypes, QmlDocument };
+enum class FileType : char { QmlTypes, QmlDocument, Directory };
template<typename String>
void convertToString(String &string, const FileType &type)
@@ -94,6 +95,9 @@ void convertToString(String &string, const FileType &type)
case FileType::QmlDocument:
convertToString(string, "QmlDocument");
break;
+ case FileType::Directory:
+ convertToString(string, "Directory");
+ break;
}
}
@@ -204,7 +208,7 @@ void convertToString(String &string, const IsAutoVersion &isAutoVersion)
constexpr bool operator<(IsAutoVersion first, IsAutoVersion second)
{
- return to_underlying(first) < to_underlying(second);
+ return Utils::to_underlying(first) < Utils::to_underlying(second);
}
class ModuleExportedImport
@@ -1160,44 +1164,44 @@ public:
using PropertyEditorQmlPaths = std::vector<class PropertyEditorQmlPath>;
-class ProjectData
+class DirectoryInfo
{
public:
- ProjectData(SourceId projectSourceId, SourceId sourceId, ModuleId moduleId, FileType fileType)
- : projectSourceId{projectSourceId}
+ DirectoryInfo(SourceId directorySourceId, SourceId sourceId, ModuleId moduleId, FileType fileType)
+ : directorySourceId{directorySourceId}
, sourceId{sourceId}
, moduleId{moduleId}
, fileType{fileType}
{}
- friend bool operator==(const ProjectData &first, const ProjectData &second)
+ friend bool operator==(const DirectoryInfo &first, const DirectoryInfo &second)
{
- return first.projectSourceId == second.projectSourceId && first.sourceId == second.sourceId
+ return first.directorySourceId == second.directorySourceId && first.sourceId == second.sourceId
&& first.moduleId.internalId() == second.moduleId.internalId()
&& first.fileType == second.fileType;
}
template<typename String>
- friend void convertToString(String &string, const ProjectData &projectData)
+ friend void convertToString(String &string, const DirectoryInfo &directoryInfo)
{
using NanotraceHR::dictonary;
using NanotraceHR::keyValue;
- auto dict = dictonary(keyValue("project source id", projectData.projectSourceId),
- keyValue("source id", projectData.sourceId),
- keyValue("module id", projectData.moduleId),
- keyValue("file type", projectData.fileType));
+ auto dict = dictonary(keyValue("project source id", directoryInfo.directorySourceId),
+ keyValue("source id", directoryInfo.sourceId),
+ keyValue("module id", directoryInfo.moduleId),
+ keyValue("file type", directoryInfo.fileType));
convertToString(string, dict);
}
public:
- SourceId projectSourceId;
+ SourceId directorySourceId;
SourceId sourceId;
ModuleId moduleId;
FileType fileType;
};
-using ProjectDatas = std::vector<ProjectData>;
+using DirectoryInfos = std::vector<DirectoryInfo>;
class TypeAnnotation
{
@@ -1291,9 +1295,9 @@ public:
, fileStatuses(std::move(fileStatuses))
{}
- SynchronizationPackage(SourceIds updatedProjectSourceIds, ProjectDatas projectDatas)
- : projectDatas(std::move(projectDatas))
- , updatedProjectSourceIds(std::move(updatedProjectSourceIds))
+ SynchronizationPackage(SourceIds updatedDirectoryInfoSourceIds, DirectoryInfos directoryInfos)
+ : directoryInfos(std::move(directoryInfos))
+ , updatedDirectoryInfoSourceIds(std::move(updatedDirectoryInfoSourceIds))
{}
public:
@@ -1302,8 +1306,8 @@ public:
SourceIds updatedSourceIds;
SourceIds updatedFileStatusSourceIds;
FileStatuses fileStatuses;
- ProjectDatas projectDatas;
- SourceIds updatedProjectSourceIds;
+ DirectoryInfos directoryInfos;
+ SourceIds updatedDirectoryInfoSourceIds;
Imports moduleDependencies;
SourceIds updatedModuleDependencySourceIds;
ModuleExportedImports moduleExportedImports;
diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.cpp b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.cpp
index 761d6371ef..a0e7bba3c5 100644
--- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.cpp
+++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.cpp
@@ -13,9 +13,9 @@
#include "sourcepathcache.h"
#include "typeannotationreader.h"
-#include <tracing/qmldesignertracing.h>
-
#include <sqlitedatabase.h>
+#include <tracing/qmldesignertracing.h>
+#include <utils/set_algorithm.h>
#include <QDirIterator>
#include <QRegularExpression>
@@ -102,6 +102,8 @@ ProjectStorageUpdater::Components createComponents(
}
for (const QmlDirParser::Component &qmlDirParserComponent : qmlDirParserComponents) {
+ if (qmlDirParserComponent.fileName.contains('/'))
+ continue;
components.push_back(ProjectStorageUpdater::Component{qmlDirParserComponent.fileName,
qmlDirParserComponent.typeName,
moduleId,
@@ -134,13 +136,13 @@ SourceIds filterNotUpdatedSourceIds(SourceIds updatedSourceIds, SourceIds notUpd
}
void addSourceIds(SourceIds &sourceIds,
- const Storage::Synchronization::ProjectDatas &projectDatas,
+ const Storage::Synchronization::DirectoryInfos &directoryInfos,
TracerLiteral message,
Tracer &tracer)
{
- for (const auto &projectData : projectDatas) {
- tracer.tick(message, keyValue("source id", projectData.sourceId));
- sourceIds.push_back(projectData.sourceId);
+ for (const auto &directoryInfo : directoryInfos) {
+ tracer.tick(message, keyValue("source id", directoryInfo.sourceId));
+ sourceIds.push_back(directoryInfo.sourceId);
}
}
@@ -164,8 +166,8 @@ void addDependencies(Storage::Imports &dependencies,
Tracer &tracer)
{
for (const QmlDirParser::Import &qmldirDependency : qmldirDependencies) {
- ModuleId moduleId = projectStorage.moduleId(Utils::PathString{qmldirDependency.module}
- + "-cppnative");
+ ModuleId moduleId = projectStorage.moduleId(Utils::PathString{qmldirDependency.module},
+ Storage::ModuleKind::CppLibrary);
auto &import = dependencies.emplace_back(moduleId, Storage::Version{}, sourceId);
tracer.tick(message, keyValue("import", import));
}
@@ -177,6 +179,7 @@ void addModuleExportedImport(Storage::Synchronization::ModuleExportedImports &im
Storage::Version version,
Storage::Synchronization::IsAutoVersion isAutoVersion,
std::string_view moduleName,
+ Storage::ModuleKind moduleKind,
std::string_view exportedModuleName)
{
NanotraceHR::Tracer tracer{"add module exported imports"_t,
@@ -186,16 +189,21 @@ void addModuleExportedImport(Storage::Synchronization::ModuleExportedImports &im
keyValue("version", version),
keyValue("is auto version", isAutoVersion),
keyValue("module name", moduleName),
+ keyValue("module kind", moduleKind),
keyValue("exported module name", exportedModuleName)};
imports.emplace_back(moduleId, exportedModuleId, version, isAutoVersion);
}
+bool isOptionalImport(QmlDirParser::Import::Flags flags)
+{
+ return flags & QmlDirParser::Import::Optional && !(flags & QmlDirParser::Import::OptionalDefault);
+}
+
void addModuleExportedImports(Storage::Synchronization::ModuleExportedImports &imports,
ModuleId moduleId,
ModuleId cppModuleId,
std::string_view moduleName,
- std::string_view cppModuleName,
const QList<QmlDirParser::Import> &qmldirImports,
ProjectStorageInterface &projectStorage)
{
@@ -205,24 +213,31 @@ void addModuleExportedImports(Storage::Synchronization::ModuleExportedImports &i
keyValue("module id", moduleId)};
for (const QmlDirParser::Import &qmldirImport : qmldirImports) {
+ if (isOptionalImport(qmldirImport.flags))
+ continue;
+
Utils::PathString exportedModuleName{qmldirImport.module};
- ModuleId exportedModuleId = projectStorage.moduleId(exportedModuleName);
+ using Storage::ModuleKind;
+ ModuleId exportedModuleId = projectStorage.moduleId(exportedModuleName,
+ ModuleKind::QmlLibrary);
addModuleExportedImport(imports,
moduleId,
exportedModuleId,
convertVersion(qmldirImport.version),
convertToIsAutoVersion(qmldirImport.flags),
moduleName,
+ ModuleKind::QmlLibrary,
exportedModuleName);
- exportedModuleName += "-cppnative";
- ModuleId exportedCppModuleId = projectStorage.moduleId(exportedModuleName);
+ ModuleId exportedCppModuleId = projectStorage.moduleId(exportedModuleName,
+ ModuleKind::CppLibrary);
addModuleExportedImport(imports,
cppModuleId,
exportedCppModuleId,
Storage::Version{},
Storage::Synchronization::IsAutoVersion::No,
- cppModuleName,
+ moduleName,
+ ModuleKind::CppLibrary,
exportedModuleName);
}
}
@@ -272,6 +287,8 @@ void ProjectStorageUpdater::update(QStringList directories,
try {
m_projectStorage.synchronize(std::move(package));
+ } catch (const TypeNameDoesNotExists &exception) {
+ qDebug() << "missing type: " << exception.what();
} catch (...) {
qWarning() << "Project storage could not been updated!";
}
@@ -289,7 +306,7 @@ void ProjectStorageUpdater::updateQmlTypes(const QStringList &qmlTypesPaths,
NanotraceHR::Tracer tracer{"update qmltypes file"_t, category()};
- ModuleId moduleId = m_projectStorage.moduleId("QML-cppnative");
+ ModuleId moduleId = m_projectStorage.moduleId("QML", Storage::ModuleKind::CppLibrary);
for (const QString &qmlTypesPath : qmlTypesPaths) {
SourceId sourceId = m_pathCache.sourceId(SourcePath{qmlTypesPath});
@@ -298,21 +315,19 @@ void ProjectStorageUpdater::updateQmlTypes(const QStringList &qmlTypesPaths,
keyValue("source id", sourceId),
keyValue("qml types path", qmlTypesPath));
- Storage::Synchronization::ProjectData projectData{sourceId,
- sourceId,
- moduleId,
- Storage::Synchronization::FileType::QmlTypes};
+ Storage::Synchronization::DirectoryInfo directoryInfo{
+ sourceId, sourceId, moduleId, Storage::Synchronization::FileType::QmlTypes};
- FileState state = parseTypeInfo(projectData,
+ FileState state = parseTypeInfo(directoryInfo,
Utils::PathString{qmlTypesPath},
package,
notUpdatedSourceIds);
if (state == FileState::Changed) {
- tracer.tick("append project data"_t, keyValue("project data", projectData));
- package.projectDatas.push_back(std::move(projectData));
+ tracer.tick("append project data"_t, keyValue("project data", directoryInfo));
+ package.directoryInfos.push_back(std::move(directoryInfo));
tracer.tick("append updated project source ids"_t, keyValue("source id", sourceId));
- package.updatedProjectSourceIds.push_back(sourceId);
+ package.updatedDirectoryInfoSourceIds.push_back(sourceId);
}
}
}
@@ -352,11 +367,11 @@ void ProjectStorageUpdater::updateDirectoryChanged(std::string_view directoryPat
package.updatedSourceIds.push_back(qmldirSourceId);
}
+ using Storage::ModuleKind;
Utils::PathString moduleName{parser.typeNamespace()};
- ModuleId moduleId = m_projectStorage.moduleId(moduleName);
- Utils::PathString cppModuleName = moduleName + "-cppnative";
- ModuleId cppModuleId = m_projectStorage.moduleId(cppModuleName);
- ModuleId pathModuleId = m_projectStorage.moduleId(directoryPath);
+ ModuleId moduleId = m_projectStorage.moduleId(moduleName, ModuleKind::QmlLibrary);
+ ModuleId cppModuleId = m_projectStorage.moduleId(moduleName, ModuleKind::CppLibrary);
+ ModuleId pathModuleId = m_projectStorage.moduleId(directoryPath, ModuleKind::PathLibrary);
auto imports = filterMultipleEntries(parser.imports());
@@ -364,16 +379,15 @@ void ProjectStorageUpdater::updateDirectoryChanged(std::string_view directoryPat
moduleId,
cppModuleId,
moduleName,
- cppModuleName,
imports,
m_projectStorage);
tracer.tick("append updated module id"_t, keyValue("module id", moduleId));
package.updatedModuleIds.push_back(moduleId);
- const auto qmlProjectDatas = m_projectStorage.fetchProjectDatas(directorySourceId);
- addSourceIds(package.updatedSourceIds, qmlProjectDatas, "append updated source id"_t, tracer);
+ const auto qmlDirectoryInfos = m_projectStorage.fetchDirectoryInfos(directorySourceId);
+ addSourceIds(package.updatedSourceIds, qmlDirectoryInfos, "append updated source id"_t, tracer);
addSourceIds(package.updatedFileStatusSourceIds,
- qmlProjectDatas,
+ qmlDirectoryInfos,
"append updated file status source id"_t,
tracer);
@@ -399,7 +413,7 @@ void ProjectStorageUpdater::updateDirectoryChanged(std::string_view directoryPat
watchedSourceIdsIds,
qmldirState);
tracer.tick("append updated project source id"_t, keyValue("module id", moduleId));
- package.updatedProjectSourceIds.push_back(directorySourceId);
+ package.updatedDirectoryInfoSourceIds.push_back(directorySourceId);
}
void ProjectStorageUpdater::updateDirectories(const QStringList &directories,
@@ -410,10 +424,111 @@ void ProjectStorageUpdater::updateDirectories(const QStringList &directories,
NanotraceHR::Tracer tracer{"update directories"_t, category()};
for (const QString &directory : directories)
- updateDirectory({directory}, package, notUpdatedSourceIds, watchedSourceIdsIds);
+ updateDirectory({directory}, {}, package, notUpdatedSourceIds, watchedSourceIdsIds);
+}
+
+void ProjectStorageUpdater::updateSubdirectories(const Utils::PathString &directoryPath,
+ SourceId directorySourceId,
+ FileState directoryState,
+ const SourceContextIds &subdirectoriesToIgnore,
+ Storage::Synchronization::SynchronizationPackage &package,
+ NotUpdatedSourceIds &notUpdatedSourceIds,
+ WatchedSourceIdsIds &watchedSourceIdsIds)
+{
+ struct Directory
+ {
+ Directory(Utils::SmallStringView path, SourceContextId sourceContextId, SourceId sourceId)
+ : path{path}
+ , sourceContextId{sourceContextId}
+ , sourceId{sourceId}
+ {}
+
+ bool operator<(const Directory &other) const
+ {
+ return sourceContextId < other.sourceContextId;
+ }
+
+ bool operator==(const Directory &other) const
+ {
+ return sourceContextId == other.sourceContextId;
+ }
+
+ Utils::PathString path;
+ SourceContextId sourceContextId;
+ SourceId sourceId;
+ };
+
+ struct Compare
+ {
+ bool operator()(const Directory &first, const Directory &second) const
+ {
+ return first.sourceContextId < second.sourceContextId;
+ }
+
+ bool operator()(const Directory &first, SourceContextId second) const
+ {
+ return first.sourceContextId < second;
+ }
+
+ bool operator()(SourceContextId first, const Directory &second) const
+ {
+ return first < second.sourceContextId;
+ }
+ };
+
+ using Directories = QVarLengthArray<Directory, 32>;
+
+ auto subdirectorySourceIds = m_projectStorage.fetchSubdirectorySourceIds(directorySourceId);
+ auto subdirectories = Utils::transform<Directories>(
+ subdirectorySourceIds, [&](SourceId sourceId) -> Directory {
+ auto sourceContextId = m_pathCache.sourceContextId(sourceId);
+ auto subdirectoryPath = m_pathCache.sourceContextPath(sourceContextId);
+ return {subdirectoryPath, sourceContextId, sourceId};
+ });
+
+ auto exisitingSubdirectoryPaths = m_fileSystem.subdirectories(directoryPath.toQString());
+ Directories existingSubdirecories;
+ for (const QString &subdirectory : exisitingSubdirectoryPaths) {
+ if (subdirectory.endsWith("/designer") || subdirectory.endsWith("/QtQuick/Scene2D")
+ || subdirectory.endsWith("/QtQuick/Scene3D"))
+ continue;
+ Utils::PathString subdirectoryPath = subdirectory;
+ auto [sourceContextId, sourceId] = m_pathCache.sourceContextAndSourceId(
+ SourcePath{subdirectoryPath + "/."});
+ subdirectories.emplace_back(subdirectoryPath, sourceContextId, sourceId);
+ existingSubdirecories.emplace_back(subdirectoryPath, sourceContextId, sourceId);
+ }
+
+ std::sort(subdirectories.begin(), subdirectories.end());
+ subdirectories.erase(std::unique(subdirectories.begin(), subdirectories.end()),
+ subdirectories.end());
+
+ std::set_difference(subdirectories.begin(),
+ subdirectories.end(),
+ subdirectoriesToIgnore.begin(),
+ subdirectoriesToIgnore.end(),
+ Utils::make_iterator([&](const Directory &subdirectory) {
+ updateDirectory(subdirectory.path,
+ subdirectoriesToIgnore,
+ package,
+ notUpdatedSourceIds,
+ watchedSourceIdsIds);
+ }),
+ Compare{});
+
+ if (directoryState == FileState::Changed) {
+ for (const auto &[subdirectoryPath, sourceContextId, subdirectorySourceId] :
+ existingSubdirecories) {
+ package.directoryInfos.emplace_back(directorySourceId,
+ subdirectorySourceId,
+ ModuleId{},
+ Storage::Synchronization::FileType::Directory);
+ }
+ }
}
void ProjectStorageUpdater::updateDirectory(const Utils::PathString &directoryPath,
+ const SourceContextIds &subdirectoriesToIgnore,
Storage::Synchronization::SynchronizationPackage &package,
NotUpdatedSourceIds &notUpdatedSourceIds,
WatchedSourceIdsIds &watchedSourceIdsIds)
@@ -451,10 +566,10 @@ void ProjectStorageUpdater::updateDirectory(const Utils::PathString &directoryPa
case FileState::NotChanged: {
tracer.tick("update directory not changed"_t);
- parseProjectDatas(m_projectStorage.fetchProjectDatas(directorySourceId),
- package,
- notUpdatedSourceIds,
- watchedSourceIdsIds);
+ parseDirectoryInfos(m_projectStorage.fetchDirectoryInfos(directorySourceId),
+ package,
+ notUpdatedSourceIds,
+ watchedSourceIdsIds);
break;
}
case FileState::NotExists: {
@@ -462,21 +577,29 @@ void ProjectStorageUpdater::updateDirectory(const Utils::PathString &directoryPa
package.updatedFileStatusSourceIds.push_back(directorySourceId);
package.updatedFileStatusSourceIds.push_back(qmldirSourceId);
- package.updatedProjectSourceIds.push_back(directorySourceId);
+ package.updatedDirectoryInfoSourceIds.push_back(directorySourceId);
package.updatedSourceIds.push_back(qmldirSourceId);
- auto qmlProjectDatas = m_projectStorage.fetchProjectDatas(directorySourceId);
- for (const Storage::Synchronization::ProjectData &projectData : qmlProjectDatas) {
- tracer.tick("append updated source id"_t, keyValue("source id", projectData.sourceId));
- package.updatedSourceIds.push_back(projectData.sourceId);
+ auto qmlDirectoryInfos = m_projectStorage.fetchDirectoryInfos(directorySourceId);
+ for (const Storage::Synchronization::DirectoryInfo &directoryInfo : qmlDirectoryInfos) {
+ tracer.tick("append updated source id"_t, keyValue("source id", directoryInfo.sourceId));
+ package.updatedSourceIds.push_back(directoryInfo.sourceId);
tracer.tick("append updated file status source id"_t,
- keyValue("source id", projectData.sourceId));
- package.updatedFileStatusSourceIds.push_back(projectData.sourceId);
+ keyValue("source id", directoryInfo.sourceId));
+ package.updatedFileStatusSourceIds.push_back(directoryInfo.sourceId);
}
break;
}
}
+ updateSubdirectories(directoryPath,
+ directorySourceId,
+ directoryState,
+ subdirectoriesToIgnore,
+ package,
+ notUpdatedSourceIds,
+ watchedSourceIdsIds);
+
tracer.end(keyValue("qmldir source path", qmldirSourcePath),
keyValue("directory source path", directorySourcePath),
keyValue("directory id", directoryId),
@@ -509,8 +632,12 @@ void ProjectStorageUpdater::updatePropertyEditorPaths(
auto state = fileState(directorySourceId, package, notUpdatedSourceIds);
- if (state == FileState::Changed)
- updatePropertyEditorPath(pathInfo.filePath(), package, directorySourceId);
+ if (state == FileState::Changed) {
+ updatePropertyEditorPath(pathInfo.filePath(),
+ package,
+ directorySourceId,
+ propertyEditorResourcesPath.size() + 1);
+ }
}
}
@@ -647,7 +774,8 @@ void ProjectStorageUpdater::updateTypeAnnotation(const QString &directoryPath,
void ProjectStorageUpdater::updatePropertyEditorPath(
const QString &directoryPath,
Storage::Synchronization::SynchronizationPackage &package,
- SourceId directorySourceId)
+ SourceId directorySourceId,
+ long long pathOffset)
{
NanotraceHR::Tracer tracer{"update property editor path"_t,
category(),
@@ -660,28 +788,31 @@ void ProjectStorageUpdater::updatePropertyEditorPath(
auto dir = QDir{directoryPath};
const auto fileInfos = dir.entryInfoList({"*Pane.qml", "*Specifics.qml"}, QDir::Files);
for (const auto &fileInfo : fileInfos)
- updatePropertyEditorFilePath(fileInfo.filePath(), package, directorySourceId);
+ updatePropertyEditorFilePath(fileInfo.filePath(), package, directorySourceId, pathOffset);
}
void ProjectStorageUpdater::updatePropertyEditorFilePath(
const QString &path,
Storage::Synchronization::SynchronizationPackage &package,
- SourceId directorySourceId)
+ SourceId directorySourceId,
+ long long pathOffset)
{
NanotraceHR::Tracer tracer{"update property editor file path"_t,
category(),
keyValue("directory path", path),
keyValue("directory source id", directorySourceId)};
- QRegularExpression regex{R"xo(.+\/(\w+)\/(\w+)(Specifics|Pane).qml)xo"};
- auto match = regex.match(path);
+ QRegularExpression regex{R"xo((.+)\/(\w+)(Specifics|Pane).qml)xo"};
+ auto match = regex.match(QStringView{path}.mid(pathOffset));
QString oldModuleName;
ModuleId moduleId;
if (match.hasMatch()) {
- auto moduleName = match.capturedView(1);
+ auto moduleName = match.capturedView(1).toString();
+ moduleName.replace('/', '.');
if (oldModuleName != moduleName) {
- oldModuleName = moduleName.toString();
- moduleId = m_projectStorage.moduleId(Utils::SmallString{moduleName});
+ oldModuleName = moduleName;
+ moduleId = m_projectStorage.moduleId(Utils::SmallString{moduleName},
+ Storage::ModuleKind::QmlLibrary);
}
Storage::TypeNameString typeName{match.capturedView(2)};
SourceId pathId = m_pathCache.sourceId(SourcePath{path});
@@ -770,7 +901,11 @@ void ProjectStorageUpdater::pathsWithIdsChanged(const std::vector<IdPaths> &chan
for (auto sourceContextId : directorySourceContextIds) {
Utils::PathString directory = m_pathCache.sourceContextPath(sourceContextId);
- updateDirectory(directory, package, notUpdatedSourceIds, watchedSourceIds);
+ updateDirectory(directory,
+ directorySourceContextIds,
+ package,
+ notUpdatedSourceIds,
+ watchedSourceIds);
}
for (SourceId sourceId : filterUniqueSourceIds(qmlDocumentSourceIds)) {
@@ -782,9 +917,9 @@ void ProjectStorageUpdater::pathsWithIdsChanged(const std::vector<IdPaths> &chan
for (SourceId sourceId : filterUniqueSourceIds(std::move(qmltypesSourceIds))) {
if (!contains(directorySourceContextIds, m_pathCache.sourceContextId(sourceId))) {
auto qmltypesPath = m_pathCache.sourcePath(sourceId);
- auto projectData = m_projectStorage.fetchProjectData(sourceId);
- if (projectData)
- parseTypeInfo(*projectData, qmltypesPath, package, notUpdatedSourceIds);
+ auto directoryInfo = m_projectStorage.fetchDirectoryInfo(sourceId);
+ if (directoryInfo)
+ parseTypeInfo(*directoryInfo, qmltypesPath, package, notUpdatedSourceIds);
}
}
} catch (const QmlDesigner::CannotParseQmlTypesFile &) {
@@ -849,41 +984,44 @@ void ProjectStorageUpdater::parseTypeInfos(const QStringList &typeInfos,
tracer.tick("append module dependenct source source id"_t, keyValue("source id", sourceId));
package.updatedModuleDependencySourceIds.push_back(sourceId);
- auto projectData = package.projectDatas.emplace_back(
+ const auto &directoryInfo = package.directoryInfos.emplace_back(
directorySourceId, sourceId, moduleId, Storage::Synchronization::FileType::QmlTypes);
tracer.tick("append project data"_t, keyValue("source id", sourceId));
- parseTypeInfo(projectData, qmltypesPath, package, notUpdatedSourceIds);
+ parseTypeInfo(directoryInfo, qmltypesPath, package, notUpdatedSourceIds);
}
}
-void ProjectStorageUpdater::parseProjectDatas(const Storage::Synchronization::ProjectDatas &projectDatas,
- Storage::Synchronization::SynchronizationPackage &package,
- NotUpdatedSourceIds &notUpdatedSourceIds,
- WatchedSourceIdsIds &watchedSourceIds)
+void ProjectStorageUpdater::parseDirectoryInfos(
+ const Storage::Synchronization::DirectoryInfos &directoryInfos,
+ Storage::Synchronization::SynchronizationPackage &package,
+ NotUpdatedSourceIds &notUpdatedSourceIds,
+ WatchedSourceIdsIds &watchedSourceIds)
{
NanotraceHR::Tracer tracer{"parse project datas"_t, category()};
- for (const Storage::Synchronization::ProjectData &projectData : projectDatas) {
- switch (projectData.fileType) {
+ for (const Storage::Synchronization::DirectoryInfo &directoryInfo : directoryInfos) {
+ switch (directoryInfo.fileType) {
case Storage::Synchronization::FileType::QmlTypes: {
- watchedSourceIds.qmltypesSourceIds.push_back(projectData.sourceId);
+ watchedSourceIds.qmltypesSourceIds.push_back(directoryInfo.sourceId);
- auto qmltypesPath = m_pathCache.sourcePath(projectData.sourceId);
- parseTypeInfo(projectData, qmltypesPath, package, notUpdatedSourceIds);
+ auto qmltypesPath = m_pathCache.sourcePath(directoryInfo.sourceId);
+ parseTypeInfo(directoryInfo, qmltypesPath, package, notUpdatedSourceIds);
break;
}
case Storage::Synchronization::FileType::QmlDocument: {
- watchedSourceIds.qmlSourceIds.push_back(projectData.sourceId);
+ watchedSourceIds.qmlSourceIds.push_back(directoryInfo.sourceId);
- parseQmlComponent(projectData.sourceId, package, notUpdatedSourceIds);
+ parseQmlComponent(directoryInfo.sourceId, package, notUpdatedSourceIds);
break;
}
+ case Storage::Synchronization::FileType::Directory:
+ break;
}
}
}
-auto ProjectStorageUpdater::parseTypeInfo(const Storage::Synchronization::ProjectData &projectData,
+auto ProjectStorageUpdater::parseTypeInfo(const Storage::Synchronization::DirectoryInfo &directoryInfo,
Utils::SmallStringView qmltypesPath,
Storage::Synchronization::SynchronizationPackage &package,
NotUpdatedSourceIds &notUpdatedSourceIds) -> FileState
@@ -892,19 +1030,19 @@ auto ProjectStorageUpdater::parseTypeInfo(const Storage::Synchronization::Projec
category(),
keyValue("qmltypes path", qmltypesPath)};
- auto state = fileState(projectData.sourceId, package, notUpdatedSourceIds);
+ auto state = fileState(directoryInfo.sourceId, package, notUpdatedSourceIds);
switch (state) {
case FileState::Changed: {
- tracer.tick("append updated source ids"_t, keyValue("source id", projectData.sourceId));
- package.updatedSourceIds.push_back(projectData.sourceId);
+ tracer.tick("append updated source ids"_t, keyValue("source id", directoryInfo.sourceId));
+ package.updatedSourceIds.push_back(directoryInfo.sourceId);
const auto content = m_fileSystem.contentAsQString(QString{qmltypesPath});
- m_qmlTypesParser.parse(content, package.imports, package.types, projectData);
+ m_qmlTypesParser.parse(content, package.imports, package.types, directoryInfo);
break;
}
case FileState::NotChanged: {
- tracer.tick("append not updated source ids"_t, keyValue("source id", projectData.sourceId));
- notUpdatedSourceIds.sourceIds.push_back(projectData.sourceId);
+ tracer.tick("append not updated source ids"_t, keyValue("source id", directoryInfo.sourceId));
+ notUpdatedSourceIds.sourceIds.push_back(directoryInfo.sourceId);
break;
}
case FileState::NotExists:
@@ -951,9 +1089,9 @@ void ProjectStorageUpdater::parseQmlComponent(Utils::SmallStringView relativeFil
tracer.tick("append not updated source id"_t, keyValue("source id", sourceId));
notUpdatedSourceIds.sourceIds.emplace_back(sourceId);
- const auto &projectData = package.projectDatas.emplace_back(
+ const auto &directoryInfo = package.directoryInfos.emplace_back(
directorySourceId, sourceId, ModuleId{}, Storage::Synchronization::FileType::QmlDocument);
- tracer.tick("append project data"_t, keyValue("project data", projectData));
+ tracer.tick("append project data"_t, keyValue("project data", directoryInfo));
return;
}
@@ -967,9 +1105,9 @@ void ProjectStorageUpdater::parseQmlComponent(Utils::SmallStringView relativeFil
break;
}
- const auto &projectData = package.projectDatas.emplace_back(
+ const auto &directoryInfo = package.directoryInfos.emplace_back(
directorySourceId, sourceId, ModuleId{}, Storage::Synchronization::FileType::QmlDocument);
- tracer.tick("append project data"_t, keyValue("project data", projectData));
+ tracer.tick("append project data"_t, keyValue("project data", directoryInfo));
tracer.tick("append updated source id"_t, keyValue("source id", sourceId));
package.updatedSourceIds.push_back(sourceId);
diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.h
index 640969fe99..baecbd6b11 100644
--- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.h
+++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.h
@@ -143,9 +143,17 @@ private:
WatchedSourceIdsIds &watchedSourceIdsIds);
void updateDirectory(const Utils::PathString &directory,
+ const SourceContextIds &subdirecoriesToIgnore,
Storage::Synchronization::SynchronizationPackage &package,
NotUpdatedSourceIds &notUpdatedSourceIds,
WatchedSourceIdsIds &watchedSourceIdsIds);
+ void updateSubdirectories(const Utils::PathString &directory,
+ SourceId directorySourceId,
+ FileState directoryFileState,
+ const SourceContextIds &subdirecoriesToIgnore,
+ Storage::Synchronization::SynchronizationPackage &package,
+ NotUpdatedSourceIds &notUpdatedSourceIds,
+ WatchedSourceIdsIds &watchedSourceIdsIds);
void updateDirectoryChanged(std::string_view directoryPath,
FileState qmldirState,
SourcePath qmldirSourcePath,
@@ -177,10 +185,12 @@ private:
Storage::Synchronization::SynchronizationPackage &package);
void updatePropertyEditorPath(const QString &path,
Storage::Synchronization::SynchronizationPackage &package,
- SourceId directorySourceId);
+ SourceId directorySourceId,
+ long long pathOffset);
void updatePropertyEditorFilePath(const QString &filePath,
Storage::Synchronization::SynchronizationPackage &package,
- SourceId directorySourceId);
+ SourceId directorySourceId,
+ long long pathOffset);
void parseTypeInfos(const QStringList &typeInfos,
const QList<QmlDirParser::Import> &qmldirDependencies,
const QList<QmlDirParser::Import> &qmldirImports,
@@ -190,11 +200,11 @@ private:
Storage::Synchronization::SynchronizationPackage &package,
NotUpdatedSourceIds &notUpdatedSourceIds,
WatchedSourceIdsIds &watchedSourceIdsIds);
- void parseProjectDatas(const Storage::Synchronization::ProjectDatas &projectDatas,
- Storage::Synchronization::SynchronizationPackage &package,
- NotUpdatedSourceIds &notUpdatedSourceIds,
- WatchedSourceIdsIds &watchedSourceIdsIds);
- FileState parseTypeInfo(const Storage::Synchronization::ProjectData &projectData,
+ void parseDirectoryInfos(const Storage::Synchronization::DirectoryInfos &directoryInfos,
+ Storage::Synchronization::SynchronizationPackage &package,
+ NotUpdatedSourceIds &notUpdatedSourceIds,
+ WatchedSourceIdsIds &watchedSourceIdsIds);
+ FileState parseTypeInfo(const Storage::Synchronization::DirectoryInfo &directoryInfo,
Utils::SmallStringView qmltypesPath,
Storage::Synchronization::SynchronizationPackage &package,
NotUpdatedSourceIds &notUpdatedSourceIds);
diff --git a/src/plugins/qmldesigner/designercore/projectstorage/qmldocumentparser.cpp b/src/plugins/qmldesigner/designercore/projectstorage/qmldocumentparser.cpp
index 27efa8d530..4338da62ce 100644
--- a/src/plugins/qmldesigner/designercore/projectstorage/qmldocumentparser.cpp
+++ b/src/plugins/qmldesigner/designercore/projectstorage/qmldocumentparser.cpp
@@ -66,23 +66,32 @@ Storage::Import createImport(const QmlDom::Import &qmlImport,
Utils::SmallStringView directoryPath,
QmlDocumentParser::ProjectStorage &storage)
{
+ using Storage::ModuleKind;
using QmlUriKind = QQmlJS::Dom::QmlUri::Kind;
auto &&uri = qmlImport.uri;
- if (uri.kind() == QmlUriKind::RelativePath) {
+ switch (uri.kind()) {
+ case QmlUriKind::AbsolutePath:
+ case QmlUriKind::DirectoryUrl: {
+ auto moduleId = storage.moduleId(Utils::PathString{uri.toString()}, ModuleKind::PathLibrary);
+ return Storage::Import(moduleId, convertVersion(qmlImport.version), sourceId);
+ }
+ case QmlUriKind::RelativePath: {
auto path = createNormalizedPath(directoryPath, uri.localPath());
- auto moduleId = storage.moduleId(createNormalizedPath(directoryPath, uri.localPath()));
+ auto moduleId = storage.moduleId(createNormalizedPath(directoryPath, uri.localPath()),
+ ModuleKind::PathLibrary);
return Storage::Import(moduleId, Storage::Version{}, sourceId);
}
-
- if (uri.kind() == QmlUriKind::ModuleUri) {
- auto moduleId = storage.moduleId(Utils::PathString{uri.moduleUri()});
+ case QmlUriKind::ModuleUri: {
+ auto moduleId = storage.moduleId(Utils::PathString{uri.moduleUri()}, ModuleKind::QmlLibrary);
return Storage::Import(moduleId, convertVersion(qmlImport.version), sourceId);
}
+ case QmlUriKind::Invalid:
+ return Storage::Import{};
+ }
- auto moduleId = storage.moduleId(Utils::PathString{uri.toString()});
- return Storage::Import(moduleId, convertVersion(qmlImport.version), sourceId);
+ return Storage::Import{};
}
QualifiedImports createQualifiedImports(const QList<QmlDom::Import> &qmlImports,
@@ -122,11 +131,13 @@ void addImports(Storage::Imports &imports,
}
}
- auto localDirectoryModuleId = storage.moduleId(directoryPath);
+ using Storage::ModuleKind;
+
+ auto localDirectoryModuleId = storage.moduleId(directoryPath, ModuleKind::PathLibrary);
imports.emplace_back(localDirectoryModuleId, Storage::Version{}, sourceId);
++importCount;
- auto qmlModuleId = storage.moduleId("QML");
+ auto qmlModuleId = storage.moduleId("QML", ModuleKind::QmlLibrary);
imports.emplace_back(qmlModuleId, Storage::Version{}, sourceId);
++importCount;
diff --git a/src/plugins/qmldesigner/designercore/projectstorage/qmltypesparser.cpp b/src/plugins/qmldesigner/designercore/projectstorage/qmltypesparser.cpp
index 104338e514..b3ec4f0024 100644
--- a/src/plugins/qmldesigner/designercore/projectstorage/qmltypesparser.cpp
+++ b/src/plugins/qmldesigner/designercore/projectstorage/qmltypesparser.cpp
@@ -28,7 +28,7 @@ namespace QmlDesigner {
constexpr auto category = ProjectStorageTracing::projectStorageUpdaterCategory;
using NanotraceHR::keyValue;
using Tracer = ProjectStorageTracing::Category::TracerType;
-
+using Storage::ModuleKind;
namespace QmlDom = QQmlJS::Dom;
namespace {
@@ -71,8 +71,7 @@ const Storage::Import &appendImports(Storage::Imports &imports,
});
Utils::PathString moduleName{QStringView(dependency.begin(), spaceFound)};
- moduleName.append("-cppnative");
- ModuleId cppModuleId = storage.moduleId(moduleName);
+ ModuleId cppModuleId = storage.moduleId(moduleName, ModuleKind::CppLibrary);
return imports.emplace_back(cppModuleId, Storage::Version{}, sourceId);
}
@@ -98,7 +97,8 @@ void addImports(Storage::Imports &imports,
const auto &import = imports.emplace_back(cppModuleId, Storage::Version{}, sourceId);
tracer.tick("append import"_t, keyValue("import", import));
- if (ModuleId qmlCppModuleId = storage.moduleId("QML-cppnative"); cppModuleId != qmlCppModuleId) {
+ if (ModuleId qmlCppModuleId = storage.moduleId("QML", ModuleKind::CppLibrary);
+ cppModuleId != qmlCppModuleId) {
const auto &import = imports.emplace_back(qmlCppModuleId, Storage::Version{}, sourceId);
tracer.tick("append import"_t, keyValue("import", import));
}
@@ -145,7 +145,8 @@ Storage::Synchronization::ExportedTypes createExports(const QList<QQmlJSScope::E
for (const QQmlJSScope::Export &qmlExport : qmlExports) {
TypeNameString exportedTypeName{qmlExport.type()};
- exportedTypes.emplace_back(storage.moduleId(Utils::SmallString{qmlExport.package()}),
+ exportedTypes.emplace_back(storage.moduleId(Utils::SmallString{qmlExport.package()},
+ ModuleKind::QmlLibrary),
std::move(exportedTypeName),
createVersion(qmlExport.version()));
}
@@ -469,16 +470,16 @@ void addType(Storage::Synchronization::Types &types,
using namespace Qt::StringLiterals;
constexpr auto skipLists = std::make_tuple(
- std::pair{"QtQuick.Templates-cppnative"sv, std::array{"QQuickItem"_L1}});
+ std::pair{std::pair{"QtQuick.Templates"_sv, ModuleKind::CppLibrary}, std::array{"QQuickItem"_L1}});
-Utils::span<const QLatin1StringView> getSkipList(std::string_view moduleName)
+Utils::span<const QLatin1StringView> getSkipList(const Storage::Module &module)
{
static constexpr Utils::span<const QLatin1StringView> emptySkipList;
auto currentSkipList = emptySkipList;
std::apply(
[&](const auto &entry) {
- if (entry.first == moduleName)
+ if (entry.first.first == module.name && entry.first.second == module.kind)
currentSkipList = entry.second;
},
skipLists);
@@ -494,7 +495,7 @@ bool skipType(const QQmlJSExportedScope &object, Utils::span<const QLatin1String
}
void addTypes(Storage::Synchronization::Types &types,
- const Storage::Synchronization::ProjectData &projectData,
+ const Storage::Synchronization::DirectoryInfo &directoryInfo,
const QList<QQmlJSExportedScope> &objects,
QmlTypesParser::ProjectStorage &storage,
const ComponentWithoutNamespaces &componentNameWithoutNamespaces)
@@ -502,15 +503,15 @@ void addTypes(Storage::Synchronization::Types &types,
NanotraceHR::Tracer tracer{"add types"_t, category()};
types.reserve(Utils::usize(objects) + types.size());
- const auto skipList = getSkipList(storage.moduleName(projectData.moduleId));
+ const auto skipList = getSkipList(storage.module(directoryInfo.moduleId));
for (const auto &object : objects) {
if (skipType(object, skipList))
continue;
addType(types,
- projectData.sourceId,
- projectData.moduleId,
+ directoryInfo.sourceId,
+ directoryInfo.moduleId,
object,
storage,
componentNameWithoutNamespaces);
@@ -522,7 +523,7 @@ void addTypes(Storage::Synchronization::Types &types,
void QmlTypesParser::parse(const QString &sourceContent,
Storage::Imports &imports,
Storage::Synchronization::Types &types,
- const Storage::Synchronization::ProjectData &projectData)
+ const Storage::Synchronization::DirectoryInfo &directoryInfo)
{
NanotraceHR::Tracer tracer{"qmltypes parser parse"_t, category()};
@@ -535,8 +536,8 @@ void QmlTypesParser::parse(const QString &sourceContent,
auto componentNameWithoutNamespaces = createComponentNameWithoutNamespaces(components);
- addImports(imports, projectData.sourceId, dependencies, m_storage, projectData.moduleId);
- addTypes(types, projectData, components, m_storage, componentNameWithoutNamespaces);
+ addImports(imports, directoryInfo.sourceId, dependencies, m_storage, directoryInfo.moduleId);
+ addTypes(types, directoryInfo, components, m_storage, componentNameWithoutNamespaces);
}
#else
@@ -544,7 +545,7 @@ void QmlTypesParser::parse(const QString &sourceContent,
void QmlTypesParser::parse([[maybe_unused]] const QString &sourceContent,
[[maybe_unused]] Storage::Imports &imports,
[[maybe_unused]] Storage::Synchronization::Types &types,
- [[maybe_unused]] const Storage::Synchronization::ProjectData &projectData)
+ [[maybe_unused]] const Storage::Synchronization::DirectoryInfo &directoryInfo)
{}
#endif
diff --git a/src/plugins/qmldesigner/designercore/projectstorage/qmltypesparser.h b/src/plugins/qmldesigner/designercore/projectstorage/qmltypesparser.h
index 4a6427501b..c73a429f91 100644
--- a/src/plugins/qmldesigner/designercore/projectstorage/qmltypesparser.h
+++ b/src/plugins/qmldesigner/designercore/projectstorage/qmltypesparser.h
@@ -31,7 +31,7 @@ public:
void parse(const QString &sourceContent,
Storage::Imports &imports,
Storage::Synchronization::Types &types,
- const Storage::Synchronization::ProjectData &projectData) override;
+ const Storage::Synchronization::DirectoryInfo &directoryInfo) override;
private:
#ifdef QDS_BUILD_QMLPARSER
diff --git a/src/plugins/qmldesigner/designercore/projectstorage/qmltypesparserinterface.h b/src/plugins/qmldesigner/designercore/projectstorage/qmltypesparserinterface.h
index cdc7cd54d7..c0880cf5c6 100644
--- a/src/plugins/qmldesigner/designercore/projectstorage/qmltypesparserinterface.h
+++ b/src/plugins/qmldesigner/designercore/projectstorage/qmltypesparserinterface.h
@@ -15,7 +15,7 @@ public:
virtual void parse(const QString &sourceContent,
Storage::Imports &imports,
Storage::Synchronization::Types &types,
- const Storage::Synchronization::ProjectData &projectData)
+ const Storage::Synchronization::DirectoryInfo &directoryInfo)
= 0;
protected:
diff --git a/src/plugins/qmldesigner/designercore/projectstorage/sourcepath.h b/src/plugins/qmldesigner/designercore/projectstorage/sourcepath.h
index b655c5cc34..fa550a4d52 100644
--- a/src/plugins/qmldesigner/designercore/projectstorage/sourcepath.h
+++ b/src/plugins/qmldesigner/designercore/projectstorage/sourcepath.h
@@ -7,6 +7,8 @@
#include <utils/smallstring.h>
+#include <QVarLengthArray>
+
namespace QmlDesigner {
class SourcePath : public Utils::PathString
@@ -128,5 +130,6 @@ private:
};
using SourcePaths = std::vector<SourcePath>;
-
+template<std::size_t size>
+using SmallSourcePaths = QVarLengthArray<SourcePath, size>;
} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/designercore/projectstorage/storagecache.h b/src/plugins/qmldesigner/designercore/projectstorage/storagecache.h
index 85c6147d2c..32ecb1c3f7 100644
--- a/src/plugins/qmldesigner/designercore/projectstorage/storagecache.h
+++ b/src/plugins/qmldesigner/designercore/projectstorage/storagecache.h
@@ -313,7 +313,7 @@ private:
return entries.end();
}
- auto value = *found;
+ const auto &value = *found;
if (value == view) {
return found;
diff --git a/src/plugins/qmldesigner/designercore/projectstorage/typeannotationreader.cpp b/src/plugins/qmldesigner/designercore/projectstorage/typeannotationreader.cpp
index 67a63542bc..71eba94966 100644
--- a/src/plugins/qmldesigner/designercore/projectstorage/typeannotationreader.cpp
+++ b/src/plugins/qmldesigner/designercore/projectstorage/typeannotationreader.cpp
@@ -178,8 +178,15 @@ TypeAnnotationReader::ParserSate TypeAnnotationReader::readDocument(const QStrin
TypeAnnotationReader::ParserSate TypeAnnotationReader::readMetaInfoRootElement(const QString &name)
{
if (name == typeElementName) {
- m_typeAnnotations.emplace_back(m_sourceId, m_directorySourceId);
+ auto &annotation = m_typeAnnotations.emplace_back(m_sourceId, m_directorySourceId);
+ annotation.traits.canBeDroppedInFormEditor = FlagIs::True;
+ annotation.traits.canBeDroppedInNavigator = FlagIs::True;
+ annotation.traits.isMovable = FlagIs::True;
+ annotation.traits.isResizable = FlagIs::True;
+ annotation.traits.hasFormEditorItem = FlagIs::True;
+ annotation.traits.visibleInLibrary = FlagIs::True;
m_itemLibraryEntries = json::array();
+
return ParsingType;
} else {
addErrorInvalidType(name);
@@ -258,7 +265,8 @@ void TypeAnnotationReader::readTypeProperty(QStringView name, const QVariant &va
auto [moduleName, typeName] = decomposeTypePath(fullTypeName);
m_typeAnnotations.back().typeName = typeName;
- m_typeAnnotations.back().moduleId = m_projectStorage.moduleId(moduleName);
+ m_typeAnnotations.back().moduleId = m_projectStorage.moduleId(moduleName,
+ ModuleKind::QmlLibrary);
} else if (name == "icon"_L1) {
m_typeAnnotations.back().iconPath = absoluteFilePathForDocument(value.toString());
@@ -304,7 +312,7 @@ QString deEscape(const QString &value)
QVariant deEscapeVariant(const QVariant &value)
{
- if (value.typeId() == QVariant::String)
+ if (value.typeId() == QMetaType::QString)
return deEscape(value.toString());
return value;
}
@@ -459,9 +467,9 @@ using json = nlohmann::json;
out = json::array({});
out.push_back(property.name);
out.push_back(property.type);
- if (property.value.type() == QVariant::String)
+ if (property.value.typeId() == QMetaType::QString)
out.push_back(Utils::PathString{property.value.toString()});
- else if (property.value.type() == QVariant::Int || property.value.type() == QVariant::LongLong)
+ else if (property.value.typeId() == QMetaType::Int || property.value.typeId() == QMetaType::LongLong)
out.push_back(property.value.toLongLong());
else
out.push_back(property.value.toDouble());
diff --git a/src/plugins/qmldesigner/designercore/tracing/qmldesignertracing.cpp b/src/plugins/qmldesigner/designercore/tracing/qmldesignertracing.cpp
index b5798b713d..cbe7b0ec38 100644
--- a/src/plugins/qmldesigner/designercore/tracing/qmldesignertracing.cpp
+++ b/src/plugins/qmldesigner/designercore/tracing/qmldesignertracing.cpp
@@ -88,4 +88,13 @@ Category &projectStorageUpdaterCategory()
} // namespace ProjectStorageTracing
+namespace MetaInfoTracing {
+Category &category()
+{
+ thread_local Category category_{"meta info"_t, Tracing::eventQueueWithStringArguments(), category};
+
+ return category_;
+}
+} // namespace MetaInfoTracing
+
} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/designercore/tracing/qmldesignertracing.h b/src/plugins/qmldesigner/designercore/tracing/qmldesignertracing.h
index 3a33834c70..899ceb6cd2 100644
--- a/src/plugins/qmldesigner/designercore/tracing/qmldesignertracing.h
+++ b/src/plugins/qmldesigner/designercore/tracing/qmldesignertracing.h
@@ -62,4 +62,20 @@ using Category = NanotraceHR::StringViewWithStringArgumentsCategory<projectStora
[[gnu::pure]] Category &projectStorageUpdaterCategory();
} // namespace ProjectStorageTracing
+
+namespace MetaInfoTracing {
+constexpr NanotraceHR::Tracing tracingStatus()
+{
+#ifdef ENABLE_METAINFO_TRACING
+ return NanotraceHR::Tracing::IsEnabled;
+#else
+ return NanotraceHR::Tracing::IsDisabled;
+#endif
+}
+
+using Category = NanotraceHR::StringViewWithStringArgumentsCategory<tracingStatus()>;
+
+[[gnu::pure]] Category &category();
+
+} // namespace MetaInfoTracing
} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/designercore/uniquename.cpp b/src/plugins/qmldesigner/designercore/uniquename.cpp
new file mode 100644
index 0000000000..d7506164db
--- /dev/null
+++ b/src/plugins/qmldesigner/designercore/uniquename.cpp
@@ -0,0 +1,165 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "uniquename.h"
+
+#include <utils/span.h>
+
+#include <QFileInfo>
+#include <QRegularExpression>
+
+namespace QmlDesigner::UniqueName {
+
+using namespace Qt::Literals;
+
+constexpr QLatin1StringView keywords[] {
+ "anchors"_L1, "as"_L1, "baseState"_L1,
+ "border"_L1, "bottom"_L1, "break"_L1,
+ "case"_L1, "catch"_L1, "clip"_L1,
+ "color"_L1, "continue"_L1, "data"_L1,
+ "debugger"_L1, "default"_L1, "delete"_L1,
+ "do"_L1, "else"_L1, "enabled"_L1,
+ "finally"_L1, "flow"_L1, "focus"_L1,
+ "font"_L1, "for"_L1, "function"_L1,
+ "height"_L1, "if"_L1, "import"_L1,
+ "in"_L1, "instanceof"_L1, "item"_L1,
+ "layer"_L1, "left"_L1, "margin"_L1,
+ "new"_L1, "opacity"_L1, "padding"_L1,
+ "parent"_L1, "print"_L1, "rect"_L1,
+ "return"_L1, "right"_L1, "scale"_L1,
+ "shaderInfo"_L1, "source"_L1, "sprite"_L1,
+ "spriteSequence"_L1, "state"_L1, "switch"_L1,
+ "text"_L1, "this"_L1, "throw"_L1,
+ "top"_L1, "try"_L1, "typeof"_L1,
+ "var"_L1, "visible"_L1, "void"_L1,
+ "while"_L1, "with"_L1, "x"_L1,
+ "y"_L1
+};
+
+namespace {
+
+QString toCamelCase(const QString &input)
+{
+ QString result = input.at(0).toLower();
+ bool capitalizeNext = false;
+
+ for (const QChar &c : Utils::span{input}.subspan(1)) {
+ bool isValidChar = c.isLetterOrNumber() || c == '_';
+ if (isValidChar)
+ result += capitalizeNext ? c.toUpper() : c;
+
+ capitalizeNext = !isValidChar;
+ }
+
+ return result;
+}
+
+} // namespace
+
+/**
+ * @brief Generates a unique name based on the provided name.
+ *
+ * This method iteratively generates a name by appending suffixes until a unique name is found.
+ * The uniqueness of the generated name is determined by the provided predicate function.
+ *
+ * @param name The original name to be made unique.
+ * @param predicate A function that checks if a name exists. Returns true if the name exists,
+ * false if name is unique.
+ * @return A unique name derived from the provided name.
+ */
+QString generate(const QString &name, std::function<bool(const QString &)> predicate)
+{
+ if (!predicate(name))
+ return name;
+
+ // match prefix and number (including zero padding) parts
+ static QRegularExpression rgx("(\\D*?)(\\d+)$");
+ QRegularExpressionMatch match = rgx.match(name);
+
+ QString prefix;
+ int number = 0;
+ int padding = 0;
+
+ if (match.hasMatch()) {
+ // Split the name into prefix and number
+ prefix = match.captured(1);
+ QString numberStr = match.captured(2);
+ number = numberStr.toInt();
+ padding = numberStr.size();
+ } else {
+ prefix = name;
+ }
+
+ QString nameTemplate = "%1%2";
+ QString newName;
+ do {
+ newName = nameTemplate.arg(prefix).arg(++number, padding, 10, QChar('0'));
+ } while (predicate(newName));
+
+ return newName;
+}
+
+/**
+ * @brief Generates a unique path based on the provided path. If the path belongs to a file, the
+ * filename or if it's a directory, the directory name will be adjusted to ensure uniqueness.
+ *
+ * This method appends a numerical suffix (or increment it if it exists) to the filename or
+ * directory name if necessary to make it unique.
+ *
+ * @param path The original path to be made unique.
+ * @return A unique path derived from the provided path.
+ */
+QString generatePath(const QString &path)
+{
+ // Remove the trailing slash if it exists (otherwise QFileInfo::path() returns empty)
+ QString adjustedPath = path;
+ if (adjustedPath.endsWith('/'))
+ adjustedPath.chop(1);
+
+ QFileInfo fileInfo = QFileInfo(adjustedPath);
+ QString baseName = fileInfo.baseName();
+ QString suffix = fileInfo.completeSuffix();
+ if (!suffix.isEmpty())
+ suffix.prepend('.');
+
+ QString parentDir = fileInfo.path();
+ QString pathTemplate = parentDir + "/%1" + suffix;
+
+ QString uniqueBaseName = UniqueName::generate(baseName, [&] (const QString &currName) {
+ return QFileInfo::exists(pathTemplate.arg(currName));
+ });
+
+ return pathTemplate.arg(uniqueBaseName);
+}
+
+/**
+ * @brief Generates a unique ID based on the provided id
+ *
+ * This works similar to get() with additional restrictions:
+ * - Removes non-Latin1 characters
+ * - Removes spaces
+ * - Ensures the first letter is lowercase
+ * - Converts spaces to camel case
+ * - Prepends an underscore if id starts with a number or is a reserved word
+ *
+ * @param id The original id to be made unique.
+ * @return A unique Id (when predicate() returns false)
+ */
+QString generateId(const QString &id, std::function<bool(const QString &)> predicate)
+{
+ // remove non word (non A-Z, a-z, 0-9) or space characters
+ QString newId = id.trimmed();
+
+ newId = toCamelCase(newId);
+
+ // prepend _ if starts with a digit or invalid id (such as reserved words)
+ if (newId.at(0).isDigit() || std::binary_search(std::begin(keywords), std::end(keywords), newId))
+ newId.prepend('_');
+
+ if (!predicate)
+ return newId;
+
+ return UniqueName::generate(newId, predicate);
+}
+
+} // namespace QmlDesigner::UniqueName
diff --git a/src/plugins/qmldesigner/designercore/uniquename.h b/src/plugins/qmldesigner/designercore/uniquename.h
new file mode 100644
index 0000000000..85927c4514
--- /dev/null
+++ b/src/plugins/qmldesigner/designercore/uniquename.h
@@ -0,0 +1,17 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <qmldesignercorelib_exports.h>
+
+#include <QString>
+
+namespace QmlDesigner::UniqueName {
+
+QString generate(const QString &name, std::function<bool(const QString &)> predicate);
+QString generatePath(const QString &path);
+QMLDESIGNERCORE_EXPORT QString generateId(const QString &id,
+ std::function<bool(const QString &)> predicate = {});
+
+} // namespace QmlDesigner::UniqueName
diff --git a/src/plugins/qmldesigner/designmodecontext.cpp b/src/plugins/qmldesigner/designmodecontext.cpp
index 43fb0d9d75..796261935a 100644
--- a/src/plugins/qmldesigner/designmodecontext.cpp
+++ b/src/plugins/qmldesigner/designmodecontext.cpp
@@ -3,7 +3,6 @@
#include "designmodecontext.h"
#include "assetslibrarywidget.h"
-#include "collectionwidget.h"
#include "designmodewidget.h"
#include "edit3dwidget.h"
#include "formeditorwidget.h"
@@ -98,15 +97,4 @@ void TextEditorContext::contextHelp(const HelpCallback &callback) const
qobject_cast<TextEditorWidget *>(m_widget)->contextHelp(callback);
}
-CollectionEditorContext::CollectionEditorContext(QWidget *widget)
- : IContext(widget)
-{
- setWidget(widget);
- setContext(Core::Context(Constants::C_QMLCOLLECTIONEDITOR, Constants::C_QT_QUICK_TOOLS_MENU));
-}
-
-void CollectionEditorContext::contextHelp(const HelpCallback &callback) const
-{
- qobject_cast<CollectionWidget *>(m_widget)->contextHelp(callback);
-}
} // namespace QmlDesigner::Internal
diff --git a/src/plugins/qmldesigner/designmodecontext.h b/src/plugins/qmldesigner/designmodecontext.h
index 12f0113d97..1d146deb7d 100644
--- a/src/plugins/qmldesigner/designmodecontext.h
+++ b/src/plugins/qmldesigner/designmodecontext.h
@@ -73,14 +73,5 @@ public:
TextEditorContext(QWidget *widget);
void contextHelp(const Core::IContext::HelpCallback &callback) const override;
};
-
-class CollectionEditorContext : public Core::IContext
-{
- Q_OBJECT
-
-public:
- CollectionEditorContext(QWidget *widget);
- void contextHelp(const Core::IContext::HelpCallback &callback) const override;
-};
} // namespace Internal
} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/designmodewidget.h b/src/plugins/qmldesigner/designmodewidget.h
index 464994b7e3..881335da75 100644
--- a/src/plugins/qmldesigner/designmodewidget.h
+++ b/src/plugins/qmldesigner/designmodewidget.h
@@ -11,7 +11,6 @@
#include <QWidget>
#include <QMainWindow>
-#include <QScopedPointer>
#include <advanceddockingsystem/dockmanager.h>
#include <annotationeditor/globalannotationeditor.h>
diff --git a/src/plugins/qmldesigner/documentwarningwidget.cpp b/src/plugins/qmldesigner/documentwarningwidget.cpp
index e1b8346a66..a1cc12d825 100644
--- a/src/plugins/qmldesigner/documentwarningwidget.cpp
+++ b/src/plugins/qmldesigner/documentwarningwidget.cpp
@@ -149,8 +149,8 @@ bool DocumentWarningWidget::eventFilter(QObject *object, QEvent *event)
void DocumentWarningWidget::showEvent(QShowEvent *event)
{
- const QColor backgroundColor = Utils::creatorTheme()->color(Utils::Theme::DScontrolBackground);
- const QColor outlineColor = Utils::creatorTheme()->color(Utils::Theme::DScontrolOutline);
+ const QColor backgroundColor = Utils::creatorColor(Utils::Theme::DScontrolBackground);
+ const QColor outlineColor = Utils::creatorColor(Utils::Theme::DScontrolOutline);
QPalette pal = palette();
pal.setColor(QPalette::ToolTipBase, backgroundColor);
pal.setColor(QPalette::ToolTipText, outlineColor);
diff --git a/src/plugins/qmldesigner/qmldesignerconstants.h b/src/plugins/qmldesigner/qmldesignerconstants.h
index fb691097f7..28f3ed6097 100644
--- a/src/plugins/qmldesigner/qmldesignerconstants.h
+++ b/src/plugins/qmldesigner/qmldesignerconstants.h
@@ -19,7 +19,6 @@ inline constexpr char C_QMLNAVIGATOR[] = "QmlDesigner::Navigator";
inline constexpr char C_QMLTEXTEDITOR[] = "QmlDesigner::TextEditor";
inline constexpr char C_QMLMATERIALBROWSER[] = "QmlDesigner::MaterialBrowser";
inline constexpr char C_QMLASSETSLIBRARY[] = "QmlDesigner::AssetsLibrary";
-inline constexpr char C_QMLCOLLECTIONEDITOR[] = "QmlDesigner::CollectionEditor";
// Special context for preview menu, shared b/w designer and text editor
inline constexpr char C_QT_QUICK_TOOLS_MENU[] = "QmlDesigner::ToolsMenu";
@@ -51,6 +50,7 @@ inline constexpr char EDIT3D_EDIT_CAMERA[] = "QmlDesigner.Editor3D.EditCameraTog
inline constexpr char EDIT3D_ORIENTATION[] = "QmlDesigner.Editor3D.OrientationToggle";
inline constexpr char EDIT3D_EDIT_LIGHT[] = "QmlDesigner.Editor3D.EditLightToggle";
inline constexpr char EDIT3D_EDIT_SHOW_GRID[] = "QmlDesigner.Editor3D.ToggleGrid";
+inline constexpr char EDIT3D_EDIT_SHOW_LOOKAT[] = "QmlDesigner.Editor3D.ToggleLookAt";
inline constexpr char EDIT3D_EDIT_SELECT_BACKGROUND_COLOR[]
= "QmlDesigner.Editor3D.SelectBackgroundColor";
inline constexpr char EDIT3D_EDIT_SELECT_GRID_COLOR[] = "QmlDesigner.Editor3D.SelectGridColor";
@@ -78,8 +78,14 @@ inline constexpr char EDIT3D_SNAP_CONFIG[] = "QmlDesigner.Editor3D.SnapConfig";
inline constexpr char EDIT3D_CAMERA_SPEED_CONFIG[] = "QmlDesigner.Editor3D.CameraSpeedConfig";
inline constexpr char QML_DESIGNER_SUBFOLDER[] = "/designer/";
-inline constexpr char COMPONENT_BUNDLES_TYPE[] = "ComponentBundles";
-inline constexpr char GENERATED_COMPONENTS_FOLDER[] = "GeneratedComponents";
+inline constexpr char BUNDLE_JSON_FILENAME[] = "bundle.json";
+inline constexpr char COMPONENT_BUNDLES_TYPE[] = "Bundles";
+inline constexpr char COMPONENT_BUNDLES_MATERIAL_BUNDLE_TYPE[] = "Materials";
+inline constexpr char COMPONENT_BUNDLES_EFFECT_BUNDLE_TYPE[] = "Effects";
+inline constexpr char COMPONENT_BUNDLES_USER_MATERIAL_BUNDLE_TYPE[] = "UserMaterials";
+inline constexpr char COMPONENT_BUNDLES_USER_EFFECT_BUNDLE_TYPE[] = "UserEffects";
+inline constexpr char COMPONENT_BUNDLES_USER_3D_BUNDLE_TYPE[] = "User3D";
+inline constexpr char GENERATED_COMPONENTS_FOLDER[] = "Generated";
inline constexpr char COMPONENT_BUNDLES_ASSET_REF_FILE[] = "_asset_ref.json";
inline constexpr char OLD_QUICK_3D_ASSETS_FOLDER[] = "Quick3DAssets";
inline constexpr char QUICK_3D_COMPONENTS_FOLDER[] = "QtQuick3D";
@@ -90,7 +96,10 @@ inline constexpr char QUICK_3D_ASSET_IMPORT_DATA_SOURCE_KEY[] = "source_scene";
inline constexpr char OLD_ASSET_IMPORT_FOLDER[] = "asset_imports";
inline constexpr char OLD_EFFECTS_IMPORT_FOLDER[] = "/asset_imports/Effects";
inline constexpr char OLD_EFFECTS_FOLDER[] = "Effects";
-inline constexpr char COMPOSED_EFFECTS_TYPE[] = "ComposedEffects";
+inline constexpr char OLD_COMPONENT_BUNDLES_TYPE[] = "ComponentBundles";
+inline constexpr char OLD_COMPONENT_BUNDLES_MATERIAL_BUNDLE_TYPE[] = "MaterialBundle";
+inline constexpr char OLD_COMPONENT_BUNDLES_EFFECT_BUNDLE_TYPE[] = "EffectBundle";
+inline constexpr char COMPOSED_EFFECTS_TYPE[] = "Effects";
inline constexpr char MATERIAL_LIB_ID[] = "__materialLibrary__";
inline constexpr char MIME_TYPE_ITEM_LIBRARY_INFO[]
@@ -98,7 +107,7 @@ inline constexpr char MIME_TYPE_ITEM_LIBRARY_INFO[]
inline constexpr char MIME_TYPE_ASSETS[] = "application/vnd.qtdesignstudio.assets";
inline constexpr char MIME_TYPE_MATERIAL[] = "application/vnd.qtdesignstudio.material";
inline constexpr char MIME_TYPE_TEXTURE[] = "application/vnd.qtdesignstudio.texture";
-inline constexpr char MIME_TYPE_BUNDLE_EFFECT[] = "application/vnd.qtdesignstudio.bundleeffect";
+inline constexpr char MIME_TYPE_BUNDLE_ITEM[] = "application/vnd.qtdesignstudio.bundleitem";
inline constexpr char MIME_TYPE_BUNDLE_MATERIAL[] = "application/vnd.qtdesignstudio.bundlematerial";
inline constexpr char MIME_TYPE_BUNDLE_TEXTURE[] = "application/vnd.qtdesignstudio.bundletexture";
inline constexpr char MIME_TYPE_ASSET_IMAGE[] = "application/vnd.qtdesignstudio.asset.image";
@@ -178,7 +187,6 @@ inline constexpr char OBJECT_NAME_EFFECT_COMPOSER[] = "QQuickWidgetEffectCompose
inline constexpr char OBJECT_NAME_MATERIAL_BROWSER[] = "QQuickWidgetMaterialBrowser";
inline constexpr char OBJECT_NAME_MATERIAL_EDITOR[] = "QQuickWidgetMaterialEditor";
inline constexpr char OBJECT_NAME_PROPERTY_EDITOR[] = "QQuickWidgetPropertyEditor";
-inline constexpr char OBJECT_NAME_COLLECTION_EDITOR[] = "QQuickWidgetQDSCollectionEditor";
inline constexpr char OBJECT_NAME_STATES_EDITOR[] = "QQuickWidgetStatesEditor";
inline constexpr char OBJECT_NAME_TEXTURE_EDITOR[] = "QQuickWidgetTextureEditor";
inline constexpr char OBJECT_NAME_TOP_TOOLBAR[] = "QQuickWidgetTopToolbar";
diff --git a/src/plugins/qmldesigner/qmldesignerexternaldependencies.cpp b/src/plugins/qmldesigner/qmldesignerexternaldependencies.cpp
index 321d95197f..97b2b46e7d 100644
--- a/src/plugins/qmldesigner/qmldesignerexternaldependencies.cpp
+++ b/src/plugins/qmldesigner/qmldesignerexternaldependencies.cpp
@@ -56,6 +56,11 @@ QUrl ExternalDependencies::projectUrl() const
return {};
}
+QString ExternalDependencies::projectName() const
+{
+ return QmlDesignerPlugin::instance()->documentManager().currentProjectName();
+}
+
QString ExternalDependencies::currentProjectDirPath() const
{
return QmlDesignerPlugin::instance()->documentManager().currentProjectDirPath().toString();
diff --git a/src/plugins/qmldesigner/qmldesignerexternaldependencies.h b/src/plugins/qmldesigner/qmldesignerexternaldependencies.h
index b4908c2383..6a49e4b551 100644
--- a/src/plugins/qmldesigner/qmldesignerexternaldependencies.h
+++ b/src/plugins/qmldesigner/qmldesignerexternaldependencies.h
@@ -21,6 +21,7 @@ public:
QString qmlPuppetFallbackDirectory() const override;
QString defaultPuppetToplevelBuildDirectory() const override;
QUrl projectUrl() const override;
+ QString projectName() const override;
QString currentProjectDirPath() const override;
QUrl currentResourcePath() const override;
void parseItemLibraryDescriptions() override;
diff --git a/src/plugins/qmldesigner/qmldesignerplugin.cpp b/src/plugins/qmldesigner/qmldesignerplugin.cpp
index 601cf96681..9b6fb50425 100644
--- a/src/plugins/qmldesigner/qmldesignerplugin.cpp
+++ b/src/plugins/qmldesigner/qmldesignerplugin.cpp
@@ -4,7 +4,6 @@
#include "qmldesignerplugin.h"
#include "qmldesignertr.h"
-#include "collectioneditor/collectionview.h"
#include "coreplugin/iwizardfactory.h"
#include "designmodecontext.h"
#include "designmodewidget.h"
@@ -298,7 +297,6 @@ bool QmlDesignerPlugin::initialize(const QStringList & /*arguments*/, QString *e
//TODO Move registering those types out of the property editor, since they are used also in the states editor
Quick2PropertyEditorView::registerQmlTypes();
- CollectionView::registerDeclarativeType();
StudioQuickWidget::registerDeclarativeType();
QmlDesignerBase::WindowManager::registerDeclarativeType();
@@ -392,7 +390,6 @@ void QmlDesignerPlugin::integrateIntoQtCreator(QWidget *modeWidget)
Core::Context qmlDesignerNavigatorContext(Constants::C_QMLNAVIGATOR);
Core::Context qmlDesignerMaterialBrowserContext(Constants::C_QMLMATERIALBROWSER);
Core::Context qmlDesignerAssetsLibraryContext(Constants::C_QMLASSETSLIBRARY);
- Core::Context qmlDesignerCollectionEditorContext(Constants::C_QMLCOLLECTIONEDITOR);
context->context().add(qmlDesignerMainContext);
context->context().add(qmlDesignerFormEditorContext);
@@ -400,7 +397,6 @@ void QmlDesignerPlugin::integrateIntoQtCreator(QWidget *modeWidget)
context->context().add(qmlDesignerNavigatorContext);
context->context().add(qmlDesignerMaterialBrowserContext);
context->context().add(qmlDesignerAssetsLibraryContext);
- context->context().add(qmlDesignerCollectionEditorContext);
context->context().add(ProjectExplorer::Constants::QMLJS_LANGUAGE_ID);
d->shortCutManager.registerActions(qmlDesignerMainContext, qmlDesignerFormEditorContext,
@@ -625,12 +621,14 @@ void QmlDesignerPlugin::enforceDelayedInitialize()
return;
// adding default path to item library plugins
- const QString postfix = Utils::HostOsInfo::isMacHost() ? QString("/QmlDesigner")
- : QString("/qmldesigner");
- const QStringList pluginPaths = Utils::transform(ExtensionSystem::PluginManager::pluginPaths(),
- [postfix](const QString &p) {
- return QString(p + postfix);
- });
+ const QString postfix = Utils::HostOsInfo::isMacHost()
+ ? QString("QmlDesigner")
+ : QString("qmldesigner");
+ const QStringList pluginPaths =
+ Utils::transform(ExtensionSystem::PluginManager::pluginPaths(),
+ [postfix](const Utils::FilePath &p) {
+ return (p / postfix).toFSPathString();
+ });
#ifndef QDS_USE_PROJECTSTORAGE
MetaInfo::initializeGlobal(pluginPaths, d->externalDependencies);
diff --git a/src/plugins/qmldesigner/qmldesignerprojectmanager.cpp b/src/plugins/qmldesigner/qmldesignerprojectmanager.cpp
index 9602bf050f..730a557d12 100644
--- a/src/plugins/qmldesigner/qmldesignerprojectmanager.cpp
+++ b/src/plugins/qmldesigner/qmldesignerprojectmanager.cpp
@@ -12,15 +12,16 @@
#include <projectstorage/filesystem.h>
#include <projectstorage/nonlockingmutex.h>
#include <projectstorage/projectstorage.h>
+#include <projectstorage/projectstorageerrornotifier.h>
#include <projectstorage/projectstoragepathwatcher.h>
#include <projectstorage/projectstorageupdater.h>
#include <projectstorage/qmldocumentparser.h>
#include <projectstorage/qmltypesparser.h>
#include <projectstorage/sourcepathcache.h>
-#include <sqlitedatabase.h>
#include <qmlprojectmanager/qmlproject.h>
#include <qtsupport/baseqtversion.h>
#include <qtsupport/qtkitaspect.h>
+#include <sqlitedatabase.h>
#include <asynchronousexplicitimagecache.h>
#include <asynchronousimagecache.h>
@@ -181,7 +182,8 @@ public:
pathCache.sourceId(SourcePath{project->projectDirectory().toString() + "/."}).internalId())}
{}
Sqlite::Database database;
- ProjectStorage storage{database, database.isInitialized()};
+ ProjectStorageErrorNotifier errorNotifier{pathCache};
+ ProjectStorage storage{database, errorNotifier, database.isInitialized()};
PathCacheType pathCache{storage};
FileSystem fileSystem{pathCache};
FileStatusCache fileStatusCache{fileSystem};
@@ -238,30 +240,34 @@ QmlDesignerProjectManager::QmlDesignerProjectManager(ExternalDependenciesInterfa
, m_externalDependencies{externalDependencies}
{
auto editorManager = ::Core::EditorManager::instance();
- QObject::connect(editorManager, &::Core::EditorManager::editorOpened, [&](auto *editor) {
+ QObject::connect(editorManager, &::Core::EditorManager::editorOpened, &dummy, [&](auto *editor) {
editorOpened(editor);
});
- QObject::connect(editorManager, &::Core::EditorManager::currentEditorChanged, [&](auto *editor) {
- currentEditorChanged(editor);
- });
- QObject::connect(editorManager, &::Core::EditorManager::editorsClosed, [&](const auto &editors) {
- editorsClosed(editors);
- });
+ QObject::connect(editorManager,
+ &::Core::EditorManager::currentEditorChanged,
+ &dummy,
+ [&](auto *editor) { currentEditorChanged(editor); });
+ QObject::connect(editorManager,
+ &::Core::EditorManager::editorsClosed,
+ &dummy,
+ [&](const auto &editors) { editorsClosed(editors); });
auto sessionManager = ::ProjectExplorer::ProjectManager::instance();
QObject::connect(sessionManager,
&::ProjectExplorer::ProjectManager::projectAdded,
+ &dummy,
[&](auto *project) { projectAdded(project); });
QObject::connect(sessionManager,
&::ProjectExplorer::ProjectManager::aboutToRemoveProject,
+ &dummy,
[&](auto *project) { aboutToRemoveProject(project); });
QObject::connect(sessionManager,
&::ProjectExplorer::ProjectManager::projectRemoved,
+ &dummy,
[&](auto *project) { projectRemoved(project); });
- QObject::connect(&m_previewImageCacheData->timer,
- &QTimer::timeout,
- this,
- &QmlDesignerProjectManager::generatePreview);
+ QObject::connect(&m_previewImageCacheData->timer, &QTimer::timeout, &dummy, [&]() {
+ generatePreview();
+ });
}
QmlDesignerProjectManager::~QmlDesignerProjectManager() = default;
@@ -337,81 +343,32 @@ Utils::FilePath qmlPath(::ProjectExplorer::Target *target)
return {};
}
-template<typename... Path>
-bool skipDirectoriesWith(const QStringView directoryPath, const Path &...paths)
-{
- return (directoryPath.contains(paths) || ...);
-}
-
-template<typename... Path>
-bool skipDirectoriesEndsWith(const QStringView directoryPath, const Path &...paths)
-{
- return (directoryPath.endsWith(paths) || ...);
-}
-
-bool skipPath(const QString &directoryPath)
-{
- return skipDirectoriesWith(directoryPath,
- u"QtApplicationManager",
- u"QtInterfaceFramework",
- u"QtOpcUa",
- u"Qt3D",
- u"Scene2D",
- u"Scene3D",
- u"QtWayland",
- u"Qt5Compat",
- u"QtCharts",
- u"QtLocation",
- u"QtPositioning",
- u"MaterialEditor",
- u"QtTextToSpeech",
- u"QtWebEngine",
- u"Qt/labs",
- u"QtDataVisualization")
- || skipDirectoriesEndsWith(directoryPath, u"designer");
-}
-
-void collectQmldirPaths(const QString &path, QStringList &qmldirPaths)
-{
- QDirIterator dirIterator{path, QDir::Dirs | QDir::NoDotAndDotDot, QDirIterator::Subdirectories};
-
- QString rootQmldirPath = path + "/qmldir";
- if (!skipPath(path) && QFileInfo::exists(rootQmldirPath))
- qmldirPaths.push_back(path);
-
- while (dirIterator.hasNext()) {
- auto directoryPath = dirIterator.next();
-
- QString qmldirPath = directoryPath + "/qmldir";
- if (!skipPath(directoryPath) && QFileInfo::exists(qmldirPath))
- qmldirPaths.push_back(directoryPath);
- }
-}
-
[[maybe_unused]] void projectQmldirPaths(::ProjectExplorer::Target *target, QStringList &qmldirPaths)
{
::QmlProjectManager::QmlBuildSystem *buildSystem = getQmlBuildSystem(target);
const Utils::FilePath projectDirectoryPath = buildSystem->canonicalProjectDir();
- const QStringList importPaths = buildSystem->importPaths();
- const QDir projectDirectory(projectDirectoryPath.toString());
- for (const QString &importPath : importPaths)
- collectQmldirPaths(importPath, qmldirPaths);
+ qmldirPaths.push_back(projectDirectoryPath.path());
}
[[maybe_unused]] void qtQmldirPaths(::ProjectExplorer::Target *target, QStringList &qmldirPaths)
{
- if constexpr (useProjectStorage())
- collectQmldirPaths(qmlPath(target).toString(), qmldirPaths);
+ if constexpr (useProjectStorage()) {
+ auto qmlRootPath = qmlPath(target).toString();
+ qmldirPaths.push_back(qmlRootPath + "/QtQml");
+ qmldirPaths.push_back(qmlRootPath + "/QtQuick");
+ qmldirPaths.push_back(qmlRootPath + "/QtQuick3D");
+ qmldirPaths.push_back(qmlRootPath + "/Qt5Compat");
+ }
}
[[maybe_unused]] void qtQmldirPathsForLiteDesigner(QStringList &qmldirPaths)
{
if constexpr (useProjectStorage()) {
auto qmlRootPath = QLibraryInfo::path(QLibraryInfo::QmlImportsPath);
- collectQmldirPaths(qmlRootPath + "/QtQml", qmldirPaths);
- collectQmldirPaths(qmlRootPath + "/QtQuick", qmldirPaths);
+ qmldirPaths.push_back(qmlRootPath + "/QtQml");
+ qmldirPaths.push_back(qmlRootPath + "/QtQuick");
}
}
@@ -567,12 +524,12 @@ QmlDesignerProjectManager::ImageCacheData *QmlDesignerProjectManager::imageCache
m_imageCacheData->nodeInstanceCollector.setTarget(project->activeTarget());
QObject::connect(project,
&ProjectExplorer::Project::activeTargetChanged,
- this,
+ &dummy,
setTargetInImageCache);
}
QObject::connect(ProjectExplorer::ProjectManager::instance(),
&ProjectExplorer::ProjectManager::startupProjectChanged,
- this,
+ &dummy,
[=](ProjectExplorer::Project *project) {
setTargetInImageCache(activeTarget(project));
});
diff --git a/src/plugins/qmldesigner/qmldesignerprojectmanager.h b/src/plugins/qmldesigner/qmldesignerprojectmanager.h
index bd45bf16c9..1e5cec2e3e 100644
--- a/src/plugins/qmldesigner/qmldesignerprojectmanager.h
+++ b/src/plugins/qmldesigner/qmldesignerprojectmanager.h
@@ -28,10 +28,8 @@ namespace QmlDesigner {
class ExternalDependenciesInterface;
-class QmlDesignerProjectManager : public QObject
+class QmlDesignerProjectManager
{
- Q_OBJECT
-
class QmlDesignerProjectManagerProjectData;
class PreviewImageCacheData;
class ImageCacheData;
@@ -70,5 +68,6 @@ private:
std::unique_ptr<PreviewImageCacheData> m_previewImageCacheData;
std::unique_ptr<QmlDesignerProjectManagerProjectData> m_projectData;
ExternalDependenciesInterface &m_externalDependencies;
+ QObject dummy;
};
} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewplugin.cpp b/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewplugin.cpp
index d5036c939c..34cd4338ac 100644
--- a/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewplugin.cpp
+++ b/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewplugin.cpp
@@ -165,7 +165,7 @@ void QmlPreviewWidgetPlugin::setLanguageLocale(const QString &locale)
QObject *QmlPreviewWidgetPlugin::getPreviewPlugin()
{
- const QVector<ExtensionSystem::PluginSpec *> &specs = ExtensionSystem::PluginManager::plugins();
+ const ExtensionSystem::PluginSpecs &specs = ExtensionSystem::PluginManager::plugins();
const auto pluginIt = std::find_if(specs.cbegin(), specs.cend(),
[](const ExtensionSystem::PluginSpec *p) {
return p->name() == "QmlPreview";
diff --git a/src/plugins/qmldesigner/qtquickplugin/images/frame-animation-16px.png b/src/plugins/qmldesigner/qtquickplugin/images/frame-animation-16px.png
new file mode 100644
index 0000000000..5171fdd79d
--- /dev/null
+++ b/src/plugins/qmldesigner/qtquickplugin/images/frame-animation-16px.png
Binary files differ
diff --git a/src/plugins/qmldesigner/qtquickplugin/images/frame-animation-24px.png b/src/plugins/qmldesigner/qtquickplugin/images/frame-animation-24px.png
new file mode 100644
index 0000000000..2fba147700
--- /dev/null
+++ b/src/plugins/qmldesigner/qtquickplugin/images/frame-animation-24px.png
Binary files differ
diff --git a/src/plugins/qmldesigner/qtquickplugin/images/frame-animation-24px@2x.png b/src/plugins/qmldesigner/qtquickplugin/images/frame-animation-24px@2x.png
new file mode 100644
index 0000000000..170c113bbd
--- /dev/null
+++ b/src/plugins/qmldesigner/qtquickplugin/images/frame-animation-24px@2x.png
Binary files differ
diff --git a/src/plugins/qmldesigner/qtquickplugin/qtquickplugin.qrc b/src/plugins/qmldesigner/qtquickplugin/qtquickplugin.qrc
index 34838797d4..c2a1ff5929 100644
--- a/src/plugins/qmldesigner/qtquickplugin/qtquickplugin.qrc
+++ b/src/plugins/qmldesigner/qtquickplugin/qtquickplugin.qrc
@@ -127,5 +127,8 @@
<file>images/extended-view3d-16px.png</file>
<file>images/extended-view3d-24px.png</file>
<file>images/extended-view3d-24px@2x.png</file>
+ <file>images/frame-animation-16px.png</file>
+ <file>images/frame-animation-24px.png</file>
+ <file>images/frame-animation-24px@2x.png</file>
</qresource>
</RCC>
diff --git a/src/plugins/qmldesigner/qtquickplugin/quick.metainfo b/src/plugins/qmldesigner/qtquickplugin/quick.metainfo
index 46cb42b60d..63d390dded 100644
--- a/src/plugins/qmldesigner/qtquickplugin/quick.metainfo
+++ b/src/plugins/qmldesigner/qtquickplugin/quick.metainfo
@@ -549,6 +549,26 @@ MetaInfo {
}
Type {
+ name: "QtQuick.FrameAnimation"
+ icon: ":/qtquickplugin/images/frame-animation-16px.png"
+
+ Hints {
+ visibleInNavigator: true
+ canBeDroppedInNavigator: true
+ canBeDroppedInFormEditor: false
+ canBeContainer: false
+ }
+
+ ItemLibraryEntry {
+ name: "Frame Animation"
+ category: "d.Qt Quick - Animation"
+ libraryIcon: ":/qtquickplugin/images/frame-animation-24px.png"
+ version: "2.0"
+ toolTip: qsTr("Triggers a handler at every animation frame update.")
+ }
+ }
+
+ Type {
name: "QtQml.Timer"
icon: ":/qtquickplugin/images/timer-16px.png"
diff --git a/src/plugins/qmldesigner/settingspage.cpp b/src/plugins/qmldesigner/settingspage.cpp
index 17c75a0285..df17f005a2 100644
--- a/src/plugins/qmldesigner/settingspage.cpp
+++ b/src/plugins/qmldesigner/settingspage.cpp
@@ -39,7 +39,8 @@ namespace Internal {
static QStringList puppetModes()
{
- static QStringList puppetModeList{"", "all", "editormode", "rendermode", "previewmode", "bakelightsmode"};
+ static QStringList puppetModeList{"", "all", "editormode", "rendermode", "previewmode",
+ "bakelightsmode", "import3dmode"};
return puppetModeList;
}
diff --git a/src/plugins/qmldesigner/shortcutmanager.cpp b/src/plugins/qmldesigner/shortcutmanager.cpp
index 833466a2aa..661ff3f271 100644
--- a/src/plugins/qmldesigner/shortcutmanager.cpp
+++ b/src/plugins/qmldesigner/shortcutmanager.cpp
@@ -232,12 +232,10 @@ void ShortCutManager::registerActions(const Core::Context &qmlDesignerMainContex
connect(Core::ICore::instance(), &Core::ICore::contextChanged, this, [&](const Core::Context &context) {
isMatBrowserActive = context.contains(Constants::C_QMLMATERIALBROWSER);
isAssetsLibraryActive = context.contains(Constants::C_QMLASSETSLIBRARY);
- isCollectionEditorActive = context.contains(Constants::C_QMLCOLLECTIONEDITOR);
if (!context.contains(Constants::C_QMLFORMEDITOR) && !context.contains(Constants::C_QMLEDITOR3D)
&& !context.contains(Constants::C_QMLNAVIGATOR)) {
- m_deleteAction.setEnabled(isMatBrowserActive || isAssetsLibraryActive
- || isCollectionEditorActive);
+ m_deleteAction.setEnabled(isMatBrowserActive || isAssetsLibraryActive);
m_cutAction.setEnabled(false);
m_copyAction.setEnabled(false);
m_pasteAction.setEnabled(false);
@@ -294,8 +292,6 @@ void ShortCutManager::deleteSelected()
actionManager.view()->emitCustomNotification("delete_selected_material");
else if (isAssetsLibraryActive)
actionManager.view()->emitCustomNotification("delete_selected_assets");
- else if (isCollectionEditorActive)
- actionManager.view()->emitCustomNotification("delete_selected_collection");
else if (currentDesignDocument())
currentDesignDocument()->deleteSelected();
}
diff --git a/src/plugins/qmldesigner/shortcutmanager.h b/src/plugins/qmldesigner/shortcutmanager.h
index 8714bb5fbc..70b019217c 100644
--- a/src/plugins/qmldesigner/shortcutmanager.h
+++ b/src/plugins/qmldesigner/shortcutmanager.h
@@ -64,7 +64,6 @@ private:
bool isMatBrowserActive = false;
bool isAssetsLibraryActive = false;
- bool isCollectionEditorActive = false;
};
} // namespace QmlDesigner
diff --git a/src/plugins/qmldesignerbase/QmlDesignerBase.json.in b/src/plugins/qmldesignerbase/QmlDesignerBase.json.in
index 27c62aeef6..a02264b63f 100644
--- a/src/plugins/qmldesignerbase/QmlDesignerBase.json.in
+++ b/src/plugins/qmldesignerbase/QmlDesignerBase.json.in
@@ -14,6 +14,6 @@
],
"Category" : "Qt Quick",
"Description" : "Provides support code for the qml designer and co..",
- "Url" : "http://www.qt.io",
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/qmldesignerbase/studio/studiostyle.cpp b/src/plugins/qmldesignerbase/studio/studiostyle.cpp
index 6924e40f7c..8bb92ccc88 100644
--- a/src/plugins/qmldesignerbase/studio/studiostyle.cpp
+++ b/src/plugins/qmldesignerbase/studio/studiostyle.cpp
@@ -29,7 +29,7 @@ inline QColor studioTextColor(bool enabled,
: Theme::DStextColor)
: Theme::DStextColorDisabled;
- return creatorTheme()->color(themePenColorId);
+ return creatorColor(themePenColorId);
}
inline QColor studioButtonBgColor(bool enabled,
@@ -51,7 +51,7 @@ inline QColor studioButtonBgColor(bool enabled,
)
: Theme::DScontrolBackgroundDisabled;
- return creatorTheme()->color(themePenColorId);
+ return creatorColor(themePenColorId);
}
inline QColor studioButtonOutlineColor(bool enabled,
@@ -67,7 +67,7 @@ inline QColor studioButtonOutlineColor(bool enabled,
: Theme::DScontrolOutline)
: Theme::DScontrolOutlineDisabled;
- return creatorTheme()->color(themePenColorId);
+ return creatorColor(themePenColorId);
}
inline bool anyParentsFocused(const QWidget *widget)
@@ -187,7 +187,7 @@ void StudioStyle::drawPrimitive(
case PE_FrameMenu:
case PE_PanelMenu:
if (isQmlEditorMenu(widget))
- painter->fillRect(option->rect, creatorTheme()->color(Theme::DSsubPanelBackground));
+ painter->fillRect(option->rect, creatorColor(Theme::DSsubPanelBackground));
else
Super::drawPrimitive(element, option, painter, widget);
break;
@@ -241,7 +241,7 @@ void StudioStyle::drawPrimitive(
}
// The separator color is currently the same as toolbar bg
- painter->fillRect(colorRect, creatorTheme()->color(Theme::DStoolbarBackground));
+ painter->fillRect(colorRect, creatorColor(Theme::DStoolbarBackground));
} break;
default: {
@@ -282,7 +282,7 @@ void StudioStyle::drawControl(
QStyleOptionMenuItem item = *mbi;
if (isActive) {
- painter->fillRect(item.rect, creatorTheme()->color(Theme::DSinteraction));
+ painter->fillRect(item.rect, creatorColor(Theme::DSinteraction));
}
forwardX += startMargin;
@@ -294,7 +294,7 @@ void StudioStyle::drawControl(
item.rect.right() - additionalMargin,
commonHeight);
- painter->setPen(creatorTheme()->color(Theme::DSstateSeparatorColor));
+ painter->setPen(creatorColor(Theme::DSstateSeparatorColor));
painter->drawLine(separatorLine);
item.text.clear();
painter->restore();
@@ -458,7 +458,7 @@ void StudioStyle::drawComplexControl(
: Theme::DSpopupBackground // Idle
: Theme::DSpopupBackground; // Disabled
- QColor frameColor = creatorTheme()->color(themeframeColor);
+ QColor frameColor = creatorColor(themeframeColor);
if ((option->subControls & SC_SliderGroove) && groove.isValid()) {
Theme::Color themeBgPlusColor = enabled
@@ -494,9 +494,9 @@ void StudioStyle::drawComplexControl(
painter->save();
painter->setPen(Qt::NoPen);
- painter->setBrush(creatorTheme()->color(themeBgPlusColor));
+ painter->setBrush(creatorColor(themeBgPlusColor));
painter->drawRoundedRect(plusRect, borderRadius, borderRadius);
- painter->setBrush(creatorTheme()->color(themeBgMinusColor));
+ painter->setBrush(creatorColor(themeBgMinusColor));
painter->drawRoundedRect(minusRect, borderRadius, borderRadius);
painter->restore();
}
@@ -509,7 +509,7 @@ void StudioStyle::drawComplexControl(
: Theme::DScontrolBackgroundDisabled;
painter->setBrush(Qt::NoBrush);
- painter->setPen(creatorTheme()->color(tickPen));
+ painter->setPen(creatorColor(tickPen));
int tickSize = proxy()->pixelMetric(PM_SliderTickmarkOffset, option, widget);
int available = proxy()->pixelMetric(PM_SliderSpaceAvailable, slider, widget);
int interval = slider->tickInterval;
@@ -577,7 +577,7 @@ void StudioStyle::drawComplexControl(
int halfSliderThickness = horizontal
? handle.width() / 2
: handle.height() / 2;
- painter->setBrush(creatorTheme()->color(handleColor));
+ painter->setBrush(creatorColor(handleColor));
painter->setPen(Qt::NoPen);
painter->drawRoundedRect(handle,
halfSliderThickness,
@@ -733,7 +733,7 @@ void StudioStyle::drawComplexControl(
bool enabled = scrollBar->state & QStyle::State_Enabled;
bool hovered = enabled && scrollBar->state & QStyle::State_MouseOver;
- QColor buttonColor = creatorTheme()->color(hovered ? Theme::DSscrollBarHandle
+ QColor buttonColor = creatorColor(hovered ? Theme::DSscrollBarHandle
: Theme::DSscrollBarHandle_idle);
QColor gradientStartColor = buttonColor.lighter(118);
QColor gradientStopColor = buttonColor;
@@ -753,12 +753,12 @@ void StudioStyle::drawComplexControl(
painter->save();
painter->setPen(Qt::NoPen);
if (hasTransientStyle) {
- QColor brushColor(creatorTheme()->color(Theme::DSscrollBarTrack));
+ QColor brushColor(creatorColor(Theme::DSscrollBarTrack));
brushColor.setAlpha(0.3 * 255);
painter->setBrush(QBrush(brushColor));
painter->drawRoundedRect(scrollBarGroove, 4, 4);
} else {
- painter->setBrush(QBrush(creatorTheme()->color(Theme::DSscrollBarTrack)));
+ painter->setBrush(QBrush(creatorColor(Theme::DSscrollBarTrack)));
painter->drawRect(rect);
}
painter->restore();
diff --git a/src/plugins/qmldesignerbase/studio/studiostyle_p.cpp b/src/plugins/qmldesignerbase/studio/studiostyle_p.cpp
index c1e1dece05..adfd08c6ef 100644
--- a/src/plugins/qmldesignerbase/studio/studiostyle_p.cpp
+++ b/src/plugins/qmldesignerbase/studio/studiostyle_p.cpp
@@ -31,7 +31,7 @@ inline QColor studioTextColor(bool enabled, bool active, bool checked)
: Theme::DStextColor)
: Theme::DStextColorDisabled;
- return creatorTheme()->color(themePenColorId);
+ return creatorColor(themePenColorId);
}
}; // namespace
@@ -39,7 +39,7 @@ StudioStylePrivate::StudioStylePrivate(StudioStyle *q)
: QObject(q)
, q(q)
{
- auto color = [](Theme::Color c) { return creatorTheme()->color(c); };
+ auto color = [](Theme::Color c) { return creatorColor(c); };
{
stdPalette.setColorGroup(QPalette::Disabled, // group
diff --git a/src/plugins/qmldesignerlite/QmlDesignerLite.json.in b/src/plugins/qmldesignerlite/QmlDesignerLite.json.in
index 75410dfd58..80944b3087 100644
--- a/src/plugins/qmldesignerlite/QmlDesignerLite.json.in
+++ b/src/plugins/qmldesignerlite/QmlDesignerLite.json.in
@@ -15,7 +15,7 @@
"Category" : "Qt Quick",
"Description" : "Qml Designer Lite",
"LongDescription": "Qml Designer Lite is a lightweight version of Qt Design Studio, providing a subset of the features of the full Qt Design Studio.",
- "Url" : "http://www.qt.io",
+ "Url" : "https://www.qt.io",
"Experimental": true,
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/qmljseditor/QmlJSEditor.json.in b/src/plugins/qmljseditor/QmlJSEditor.json.in
index c05d4efa80..f12625d6bf 100644
--- a/src/plugins/qmljseditor/QmlJSEditor.json.in
+++ b/src/plugins/qmljseditor/QmlJSEditor.json.in
@@ -13,7 +13,8 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Qt Quick",
- "Description" : "Editor for QML and JavaScript.",
- "Url" : "http://www.qt.io",
+ "Description" : "Edit QML and JavaScript files",
+ "LongDescription" : [],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/qmljseditor/qmljscomponentfromobjectdef.cpp b/src/plugins/qmljseditor/qmljscomponentfromobjectdef.cpp
index 134baa5827..ce6473b988 100644
--- a/src/plugins/qmljseditor/qmljscomponentfromobjectdef.cpp
+++ b/src/plugins/qmljseditor/qmljscomponentfromobjectdef.cpp
@@ -187,10 +187,7 @@ public:
for (const QString &property : std::as_const(result))
replacement += property + QLatin1String(": ") + propertyReader.readAstValue(property) + QLatin1Char('\n');
- Utils::ChangeSet changes;
- changes.replace(start, end, replacement);
- currentFile->setChangeSet(changes);
- currentFile->apply();
+ currentFile->apply(ChangeSet::makeReplace(start, end, replacement));
Core::IVersionControl *versionControl = Core::VcsManager::findVersionControlForDirectory(
path);
diff --git a/src/plugins/qmljseditor/qmljseditingsettingspage.cpp b/src/plugins/qmljseditor/qmljseditingsettingspage.cpp
index f12d1ea57a..644c0d1d50 100644
--- a/src/plugins/qmljseditor/qmljseditingsettingspage.cpp
+++ b/src/plugins/qmljseditor/qmljseditingsettingspage.cpp
@@ -21,6 +21,7 @@
#include <QCheckBox>
#include <QComboBox>
+#include <QGroupBox>
#include <QLabel>
#include <QLineEdit>
#include <QMenu>
@@ -104,7 +105,7 @@ void QmlJsEditingSettings::fromSettings(QtcSettings *settings)
= settings->value(AUTO_FORMAT_ONLY_CURRENT_PROJECT, QVariant(false)).toBool();
m_foldAuxData = settings->value(FOLD_AUX_DATA, QVariant(true)).toBool();
m_uiQmlOpenMode = settings->value(UIQML_OPEN_MODE, "").toString();
- m_qmllsSettings.useQmlls = settings->value(USE_QMLLS, QVariant(false)).toBool();
+ m_qmllsSettings.useQmlls = settings->value(USE_QMLLS, QVariant(true)).toBool();
m_qmllsSettings.useLatestQmlls = settings->value(USE_LATEST_QMLLS, QVariant(false)).toBool();
m_qmllsSettings.disableBuiltinCodemodel
= settings->value(DISABLE_BUILTIN_CODEMODEL, QVariant(false)).toBool();
@@ -396,7 +397,7 @@ public:
uiQmlOpenComboBox->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
uiQmlOpenComboBox->setSizeAdjustPolicy(QComboBox::QComboBox::AdjustToContents);
- useQmlls = new QCheckBox(Tr::tr("Enable QML Language Server (EXPERIMENTAL!)"));
+ useQmlls = new QCheckBox(Tr::tr("Enable QML Language Server"));
useQmlls->setChecked(s.qmllsSettings().useQmlls);
disableBuiltInCodemodel = new QCheckBox(
Tr::tr("Use QML Language Server advanced features (renaming, find usages and co.) "
diff --git a/src/plugins/qmljseditor/qmljsquickfixes.cpp b/src/plugins/qmljseditor/qmljsquickfixes.cpp
index 126361451e..5bb077e814 100644
--- a/src/plugins/qmljseditor/qmljsquickfixes.cpp
+++ b/src/plugins/qmljseditor/qmljsquickfixes.cpp
@@ -70,8 +70,7 @@ public:
changes.insert(currentFile->startOf(_objectInitializer->rbraceToken),
QLatin1String("\n"));
- currentFile->setChangeSet(changes);
- currentFile->apply();
+ currentFile->apply(changes);
}
};
@@ -117,11 +116,9 @@ public:
const QmlJSRefactoringChanges &,
const QString &) override
{
- Utils::ChangeSet changes;
- const int insertLoc = _message.location.begin() - _message.location.startColumn + 1;
- changes.insert(insertLoc, QString::fromLatin1("// %1\n").arg(_message.suppressionString()));
- currentFile->setChangeSet(changes);
- currentFile->apply();
+ currentFile->apply(Utils::ChangeSet::makeInsert(
+ _message.location.begin() - _message.location.startColumn + 1,
+ QString::fromLatin1("// %1\n").arg(_message.suppressionString())));
}
};
diff --git a/src/plugins/qmljseditor/qmljswrapinloader.cpp b/src/plugins/qmljseditor/qmljswrapinloader.cpp
index bf5fd44d4a..5f6b474ad6 100644
--- a/src/plugins/qmljseditor/qmljswrapinloader.cpp
+++ b/src/plugins/qmljseditor/qmljswrapinloader.cpp
@@ -146,8 +146,7 @@ public:
" id: %2\n"
" sourceComponent: %1\n"
"}\n").arg(componentId, loaderId));
- currentFile->setChangeSet(changes);
- currentFile->apply();
+ currentFile->apply(changes);
}
};
diff --git a/src/plugins/qmljseditor/qmloutlinemodel.cpp b/src/plugins/qmljseditor/qmloutlinemodel.cpp
index 456789f645..9f15680774 100644
--- a/src/plugins/qmljseditor/qmloutlinemodel.cpp
+++ b/src/plugins/qmljseditor/qmloutlinemodel.cpp
@@ -897,10 +897,8 @@ void QmlOutlineModel::reparentNodes(QmlOutlineItem *targetItem, int row, QList<Q
changedRanges << range;
}
- QmlJSRefactoringChanges refactoring(ModelManagerInterface::instance(), m_semanticInfo.snapshot);
- TextEditor::RefactoringFilePtr file = refactoring.file(m_semanticInfo.document->fileName());
- file->setChangeSet(changeSet);
- file->apply();
+ QmlJSRefactoringChanges(ModelManagerInterface::instance(), m_semanticInfo.snapshot)
+ .file(m_semanticInfo.document->fileName())->apply(changeSet);
}
void QmlOutlineModel::moveObjectMember(AST::Node *toMove,
diff --git a/src/plugins/qmljstools/QmlJSTools.json.in b/src/plugins/qmljstools/QmlJSTools.json.in
index 0edfb8a7bf..9e80e06c79 100644
--- a/src/plugins/qmljstools/QmlJSTools.json.in
+++ b/src/plugins/qmljstools/QmlJSTools.json.in
@@ -14,7 +14,7 @@
],
"Category" : "Qt Quick",
"Description" : "Tools for analyzing Qml/JS code.",
- "Url" : "http://www.qt.io",
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES},
"Mimetypes" : [
diff --git a/src/plugins/qmljstools/qmljsfunctionfilter.cpp b/src/plugins/qmljstools/qmljsfunctionfilter.cpp
index 0453d11b9c..c6f8dfa4fd 100644
--- a/src/plugins/qmljstools/qmljsfunctionfilter.cpp
+++ b/src/plugins/qmljstools/qmljsfunctionfilter.cpp
@@ -5,8 +5,6 @@
#include "qmljslocatordata.h"
#include "qmljstoolstr.h"
-#include <extensionsystem/pluginmanager.h>
-
#include <utils/algorithm.h>
#include <utils/async.h>
@@ -81,7 +79,6 @@ LocatorMatcherTasks QmlJSFunctionsFilter::matchers()
Storage<LocatorStorage> storage;
const auto onSetup = [storage, entries = m_data->entries()](Async<void> &async) {
- async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
async.setConcurrentCallData(matches, *storage, entries);
};
diff --git a/src/plugins/qmlpreview/QmlPreview.json.in b/src/plugins/qmlpreview/QmlPreview.json.in
index d053c4040b..76aec1c762 100644
--- a/src/plugins/qmlpreview/QmlPreview.json.in
+++ b/src/plugins/qmlpreview/QmlPreview.json.in
@@ -13,7 +13,8 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Qt Quick",
- "Description" : "Qml Preview Plugin.",
- "Url" : "http://www.qt.io",
+ "Description" : "Preview QML files and Qt Quick applications",
+ "LongDescription" : [],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/qmlpreview/tests/qmlpreviewplugin_test.cpp b/src/plugins/qmlpreview/tests/qmlpreviewplugin_test.cpp
index d041afbffd..0b005d538f 100644
--- a/src/plugins/qmlpreview/tests/qmlpreviewplugin_test.cpp
+++ b/src/plugins/qmlpreview/tests/qmlpreviewplugin_test.cpp
@@ -29,7 +29,7 @@ private slots:
static ExtensionSystem::IPlugin *getPlugin()
{
- const QVector<ExtensionSystem::PluginSpec *> plugins = ExtensionSystem::PluginManager::plugins();
+ const ExtensionSystem::PluginSpecs plugins = ExtensionSystem::PluginManager::plugins();
auto it = std::find_if(plugins.begin(), plugins.end(), [](ExtensionSystem::PluginSpec *spec) {
return spec->name() == "QmlPreview";
});
diff --git a/src/plugins/qmlprofiler/QmlProfiler.json.in b/src/plugins/qmlprofiler/QmlProfiler.json.in
index 45fdb6e8ff..3d34fb9d55 100644
--- a/src/plugins/qmlprofiler/QmlProfiler.json.in
+++ b/src/plugins/qmlprofiler/QmlProfiler.json.in
@@ -13,7 +13,10 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Qt Quick",
- "Description" : "Qml Profiler Plugin.",
- "Url" : "http://www.qt.io",
+ "Description" : "Profile Qt Quick applications",
+ "LongDescription" : [
+ "Find causes for typical performance problems, such as slowness and unresponsive, stuttering user interfaces."
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/qmlprofiler/flamegraphview.cpp b/src/plugins/qmlprofiler/flamegraphview.cpp
index ad26f2156a..eae418fa25 100644
--- a/src/plugins/qmlprofiler/flamegraphview.cpp
+++ b/src/plugins/qmlprofiler/flamegraphview.cpp
@@ -31,7 +31,7 @@ FlameGraphView::FlameGraphView(QmlProfilerModelManager *manager, QWidget *parent
m_content->rootContext()->setContextProperty(QStringLiteral("flameGraphModel"), m_model);
m_content->setSource(QUrl(QStringLiteral(
"qrc:/qt/qml/QtCreator/QmlProfiler/QmlProfilerFlameGraphView.qml")));
- m_content->setClearColor(Utils::creatorTheme()->color(Utils::Theme::Timeline_BackgroundColor1));
+ m_content->setClearColor(Utils::creatorColor(Utils::Theme::Timeline_BackgroundColor1));
m_content->setResizeMode(QQuickWidget::SizeRootObjectToView);
m_content->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
diff --git a/src/plugins/qmlprofiler/qmlprofilerbindingloopsrenderpass.cpp b/src/plugins/qmlprofiler/qmlprofilerbindingloopsrenderpass.cpp
index 0db02375ae..7cea73b254 100644
--- a/src/plugins/qmlprofiler/qmlprofilerbindingloopsrenderpass.cpp
+++ b/src/plugins/qmlprofiler/qmlprofilerbindingloopsrenderpass.cpp
@@ -285,7 +285,7 @@ BindingLoopMaterialShader::BindingLoopMaterialShader()
static QColor bindingLoopsColor()
{
- return Utils::creatorTheme()->color(Utils::Theme::Timeline_HighlightColor);
+ return Utils::creatorColor(Utils::Theme::Timeline_HighlightColor);
}
bool BindingLoopMaterialShader::updateUniformData(RenderState &state, QSGMaterial *, QSGMaterial *)
diff --git a/src/plugins/qmlprofiler/qmlprofilerstatisticsmodel.cpp b/src/plugins/qmlprofiler/qmlprofilerstatisticsmodel.cpp
index 4d3a73d4dc..1d202a64a6 100644
--- a/src/plugins/qmlprofiler/qmlprofilerstatisticsmodel.cpp
+++ b/src/plugins/qmlprofiler/qmlprofilerstatisticsmodel.cpp
@@ -201,7 +201,7 @@ QVariant QmlProfilerStatisticsModel::dataForMainEntry(const QModelIndex &index,
case TypeIdRole:
return s_mainEntryTypeId;
case Qt::ForegroundRole:
- return Utils::creatorTheme()->color(Utils::Theme::Timeline_TextColor);
+ return Utils::creatorColor(Utils::Theme::Timeline_TextColor);
case SortRole:
switch (index.column()) {
case MainTimeInPercent:
@@ -279,8 +279,8 @@ QVariant QmlProfilerStatisticsModel::data(const QModelIndex &index, int role) co
}
case Qt::ForegroundRole:
return (stats.recursive > 0 || m_notes.contains(typeIndex))
- ? Utils::creatorTheme()->color(Utils::Theme::Timeline_HighlightColor)
- : Utils::creatorTheme()->color(Utils::Theme::Timeline_TextColor);
+ ? Utils::creatorColor(Utils::Theme::Timeline_HighlightColor)
+ : Utils::creatorColor(Utils::Theme::Timeline_TextColor);
case SortRole:
switch (index.column()) {
case MainLocation:
@@ -547,7 +547,7 @@ QVariant QmlProfilerStatisticsRelativesModel::dataForMainEntry(qint64 totalDurat
case TypeIdRole:
return QmlProfilerStatisticsModel::s_mainEntryTypeId;
case Qt::ForegroundRole:
- return Utils::creatorTheme()->color(Utils::Theme::Timeline_TextColor);
+ return Utils::creatorColor(Utils::Theme::Timeline_TextColor);
case SortRole:
if (column == RelativeTotalTime)
return totalDuration;
@@ -598,8 +598,8 @@ QVariant QmlProfilerStatisticsRelativesModel::data(const QModelIndex &index, int
return stats.isRecursive ? Tr::tr("called recursively") : QString();
case Qt::ForegroundRole:
return stats.isRecursive
- ? Utils::creatorTheme()->color(Utils::Theme::Timeline_HighlightColor)
- : Utils::creatorTheme()->color(Utils::Theme::Timeline_TextColor);
+ ? Utils::creatorColor(Utils::Theme::Timeline_HighlightColor)
+ : Utils::creatorColor(Utils::Theme::Timeline_TextColor);
case SortRole:
switch (index.column()) {
case RelativeLocation:
diff --git a/src/plugins/qmlprofiler/tests/localqmlprofilerrunner_test.cpp b/src/plugins/qmlprofiler/tests/localqmlprofilerrunner_test.cpp
index 7799c96028..a417775dbf 100644
--- a/src/plugins/qmlprofiler/tests/localqmlprofilerrunner_test.cpp
+++ b/src/plugins/qmlprofiler/tests/localqmlprofilerrunner_test.cpp
@@ -43,7 +43,7 @@ void LocalQmlProfilerRunnerTest::testRunner()
serverUrl.setPath("invalid");
runControl = new RunControl(ProjectExplorer::Constants::QML_PROFILER_RUN_MODE);
- runControl->setCommandLine({"\\-/|\\-/", {}});
+ runControl->setCommandLine(CommandLine{"\\-/|\\-/"});
profiler = new LocalQmlProfilerSupport(runControl, serverUrl);
@@ -109,7 +109,7 @@ void LocalQmlProfilerRunnerTest::testRunner()
serverUrl.clear();
serverUrl = Utils::urlFromLocalHostAndFreePort();
runControl = new RunControl(ProjectExplorer::Constants::QML_PROFILER_RUN_MODE);
- runControl->setCommandLine({app, {}});
+ runControl->setCommandLine(CommandLine{app});
profiler = new LocalQmlProfilerSupport(runControl, serverUrl);
connectRunner();
runControl->initiateStart();
diff --git a/src/plugins/qmlprojectmanager/.clang-format b/src/plugins/qmlprojectmanager/.clang-format
new file mode 100644
index 0000000000..366f82f76f
--- /dev/null
+++ b/src/plugins/qmlprojectmanager/.clang-format
@@ -0,0 +1,50 @@
+Language: Cpp
+AccessModifierOffset: -4
+AlignEscapedNewlines: DontAlign
+AllowShortFunctionsOnASingleLine: Inline
+AlwaysBreakTemplateDeclarations: true # use with clang 19
+BinPackArguments: false
+BinPackParameters: false
+BraceWrapping:
+ AfterClass: true
+ AfterFunction: true
+ AfterStruct: true
+ SplitEmptyFunction: false
+ SplitEmptyRecord: false
+ SplitEmptyNamespace: false
+BreakBeforeBinaryOperators: All
+BreakBeforeBraces: Custom
+BreakConstructorInitializers: BeforeComma
+BreakInheritanceList: AfterComma
+# BreakTemplateDeclarations: Yes # use with clang 19
+ColumnLimit: 100
+IncludeCategories:
+ - Regex: 'Q.*'
+ Priority: 8
+ CaseSensitive: true
+IndentPPDirectives: AfterHash
+IndentWidth: 4
+KeepEmptyLinesAtTheStartOfBlocks: false
+# Do not add QT_BEGIN_NAMESPACE/QT_END_NAMESPACE as this will indent lines in between.
+ObjCBlockIndentWidth: 4
+PPIndentWidth: 2
+PackConstructorInitializers: Never
+PenaltyBreakAssignment: 500
+PenaltyBreakBeforeFirstCallParameter: 150
+PenaltyBreakComment: 500
+PenaltyBreakFirstLessLess: 400
+PenaltyBreakString: 600
+PenaltyExcessCharacter: 7
+PenaltyReturnTypeOnItsOwnLine: 300
+QualifierAlignment: Custom
+QualifierOrder: ['friend', 'inline', 'static', 'constexpr', 'const', 'type']
+ReferenceAlignment: Right
+ReflowComments: false
+SeparateDefinitionBlocks: Always
+SortUsingDeclarations: Lexicographic
+SpaceAfterCStyleCast: true
+SpaceAfterTemplateKeyword: false
+SpaceBeforeParens: ControlStatementsExceptControlMacros
+SpacesInContainerLiterals: false
+StatementAttributeLikeMacros: [emit]
+TabWidth: 4
diff --git a/src/plugins/qmlprojectmanager/QmlProjectManager.json.in b/src/plugins/qmlprojectmanager/QmlProjectManager.json.in
index 5df33015f4..18388275f3 100644
--- a/src/plugins/qmlprojectmanager/QmlProjectManager.json.in
+++ b/src/plugins/qmlprojectmanager/QmlProjectManager.json.in
@@ -14,7 +14,7 @@
],
"Category" : "Qt Quick",
"Description" : "Qt Quick support",
- "Url" : "http://www.qt.io",
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES},
"Mimetypes" : [
diff --git a/src/plugins/qmlprojectmanager/buildsystem/projectitem/converters.cpp b/src/plugins/qmlprojectmanager/buildsystem/projectitem/converters.cpp
index 95cc859af5..82ba1fc2e4 100644
--- a/src/plugins/qmlprojectmanager/buildsystem/projectitem/converters.cpp
+++ b/src/plugins/qmlprojectmanager/buildsystem/projectitem/converters.cpp
@@ -14,7 +14,12 @@ const static QStringList imageFilesFilter{QStringLiteral("*.jpeg"),
QStringLiteral("*.png"),
QStringLiteral("*.svg"),
QStringLiteral("*.hdr"),
- QStringLiteral("*.ktx")};
+ QStringLiteral("*.ktx"),
+ QStringLiteral("*.bmp"),
+ QStringLiteral("*.ttf"),
+ QStringLiteral("*.tiff"),
+ QStringLiteral("*.webp"),
+ QStringLiteral("*.gif")};
QString jsonValueToString(const QJsonValue &val, int indentationLevel, bool indented);
diff --git a/src/plugins/qmlprojectmanager/buildsystem/projectitem/qmlprojectitem.cpp b/src/plugins/qmlprojectmanager/buildsystem/projectitem/qmlprojectitem.cpp
index eb6e6de177..5229d486ce 100644
--- a/src/plugins/qmlprojectmanager/buildsystem/projectitem/qmlprojectitem.cpp
+++ b/src/plugins/qmlprojectmanager/buildsystem/projectitem/qmlprojectitem.cpp
@@ -79,7 +79,7 @@ void QmlProjectItem::setupFileFilters()
connect(fileFilterItem.get(),
&FileFilterItem::filesChanged,
this,
- &QmlProjectItem::qmlFilesChanged);
+ &QmlProjectItem::filesChanged);
#endif
m_content.push_back(std::move(fileFilterItem));
};
@@ -105,10 +105,7 @@ void QmlProjectItem::setupFileFilters()
fileFilterItem->setDefaultDirectory(m_projectFile.parentDir().toString());
fileFilterItem->setDirectory(groupDir.toString());
#ifndef TESTS_ENABLED_QMLPROJECTITEM
- connect(fileFilterItem.get(),
- &FileFilterItem::filesChanged,
- this,
- &QmlProjectItem::qmlFilesChanged);
+ connect(fileFilterItem.get(), &FileFilterItem::filesChanged, this, &QmlProjectItem::filesChanged);
#endif
m_content.push_back(std::move(fileFilterItem));
};
diff --git a/src/plugins/qmlprojectmanager/buildsystem/projectitem/qmlprojectitem.h b/src/plugins/qmlprojectmanager/buildsystem/projectitem/qmlprojectitem.h
index 7d57ad2e60..5d0b520f14 100644
--- a/src/plugins/qmlprojectmanager/buildsystem/projectitem/qmlprojectitem.h
+++ b/src/plugins/qmlprojectmanager/buildsystem/projectitem/qmlprojectitem.h
@@ -92,7 +92,7 @@ public:
void setEnableCMakeGeneration(bool enable);
signals:
- void qmlFilesChanged(const QSet<QString> &, const QSet<QString> &);
+ void filesChanged(const QSet<QString> &, const QSet<QString> &);
private:
typedef QSharedPointer<QmlProjectItem> ShrdPtrQPI;
diff --git a/src/plugins/qmlprojectmanager/buildsystem/qmlbuildsystem.cpp b/src/plugins/qmlprojectmanager/buildsystem/qmlbuildsystem.cpp
index 00e501d6f8..97b540ad7e 100644
--- a/src/plugins/qmlprojectmanager/buildsystem/qmlbuildsystem.cpp
+++ b/src/plugins/qmlprojectmanager/buildsystem/qmlbuildsystem.cpp
@@ -6,8 +6,9 @@
#include "../qmlprojectconstants.h"
#include "../qmlprojectmanagertr.h"
#include "../qmlproject.h"
+#include "projectitem/qmlprojectitem.h"
+#include "projectnode/qmlprojectnodes.h"
-#include <QtCore5Compat/qtextcodec.h>
#include <qmljs/qmljsmodelmanagerinterface.h>
#include <coreplugin/actionmanager/actioncontainer.h>
@@ -30,18 +31,18 @@
#include <projectexplorer/kitaspects.h>
#include <projectexplorer/kitmanager.h>
#include <projectexplorer/projectexplorerconstants.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
-#include "projectexplorer/projectmanager.h"
-#include "projectitem/qmlprojectitem.h"
-#include "projectnode/qmlprojectnodes.h"
-
-#include "utils/algorithm.h"
-#include "utils/qtcassert.h"
+#include <utils/algorithm.h>
+#include <utils/filepath.h>
+#include <utils/filesystemwatcher.h>
+#include <utils/qtcassert.h>
-#include "texteditor/textdocument.h"
+#include <texteditor/textdocument.h>
#include <QAction>
+#include <QtCore5Compat/qtextcodec.h>
using namespace ProjectExplorer;
namespace QmlProjectManager {
@@ -141,8 +142,6 @@ void QmlBuildSystem::registerMenuButtons()
//wip:
bool QmlBuildSystem::updateProjectFile()
{
- qDebug() << "debug#1-mainfilepath" << mainFilePath();
-
QFile file(mainFilePath().fileName().append("project-test"));
if (!file.open(QIODevice::ReadWrite | QIODevice::Truncate)) {
qCritical() << "Cannot open Qml Project file for editing!";
@@ -220,17 +219,56 @@ void QmlBuildSystem::refresh(RefreshOptions options)
void QmlBuildSystem::initProjectItem()
{
m_projectItem.reset(new QmlProjectItem{projectFilePath()});
- connect(m_projectItem.get(),
- &QmlProjectItem::qmlFilesChanged,
- this,
- &QmlBuildSystem::refreshFiles);
- connect(m_projectItem.get(),
- &QmlProjectItem::qmlFilesChanged,
+ connect(m_projectItem.data(), &QmlProjectItem::filesChanged, this, &QmlBuildSystem::refreshFiles);
+ connect(m_projectItem.data(),
+ &QmlProjectItem::filesChanged,
m_cmakeGen,
&GenerateCmake::CMakeGenerator::update);
m_cmakeGen->setEnabled(m_projectItem->enableCMakeGeneration());
+
+ initMcuProjectItems();
+}
+
+void QmlBuildSystem::initMcuProjectItems()
+{
+ m_mcuProjectItems.clear();
+ m_mcuProjectFilesWatcher.clear();
+
+ Utils::FilePath projectDir = projectFilePath().parentDir();
+ // traverse the project dir and find all other mcu projects (.qmlproject files) in the project tree
+ // and add them to the m_mcuProjectItems vector
+ QDirIterator it(projectDir.toFSPathString(), QDir::Files, QDirIterator::Subdirectories);
+ while (it.hasNext()) {
+ it.next();
+ if (it.fileInfo().suffix() == "qmlproject" && it.filePath() != projectFilePath().toString()) {
+ auto qmlProjectItem = QSharedPointer<QmlProjectItem>(
+ new QmlProjectItem{Utils::FilePath::fromString(it.filePath())});
+
+ m_mcuProjectItems.append(qmlProjectItem);
+ connect(qmlProjectItem.data(),
+ &QmlProjectItem::filesChanged,
+ this,
+ &QmlBuildSystem::refreshFiles);
+ connect(qmlProjectItem.data(),
+ &QmlProjectItem::filesChanged,
+ m_cmakeGen,
+ &GenerateCmake::CMakeGenerator::update);
+
+ m_mcuProjectFilesWatcher.addFile(it.filePath(),
+ Utils::FileSystemWatcher::WatchModifiedDate);
+
+ connect(&m_mcuProjectFilesWatcher,
+ &Utils::FileSystemWatcher::fileChanged,
+ this,
+ [this](const QString &file) {
+ Q_UNUSED(file)
+ initMcuProjectItems();
+ refresh(RefreshOptions::Files);
+ });
+ }
+ }
}
void QmlBuildSystem::parseProjectFiles()
@@ -239,7 +277,6 @@ void QmlBuildSystem::parseProjectFiles()
modelManager->updateSourceFiles(m_projectItem->files(), true);
}
-
const QString mainFileName = m_projectItem->mainFile();
if (!mainFileName.isEmpty()) {
Utils::FilePath mainFilePath = canonicalProjectDir().resolvePath(mainFileName);
@@ -265,6 +302,16 @@ void QmlBuildSystem::generateProjectTree()
: FileNode::fileTypeForFileName(file);
newRoot->addNestedNode(std::make_unique<FileNode>(file, fileType));
}
+
+ for (const auto &mcuProjectItem : m_mcuProjectItems) {
+ for (const auto &file : mcuProjectItem->files()) {
+ // newRoot->addNestedNode(std::make_unique<FileNode>(file, FileType::Project));
+ const FileType fileType = (file == projectFilePath())
+ ? FileType::Project
+ : FileNode::fileTypeForFileName(file);
+ newRoot->addNestedNode(std::make_unique<FileNode>(file, fileType));
+ }
+ }
newRoot->addNestedNode(std::make_unique<FileNode>(projectFilePath(), FileType::Project));
setRootProjectNode(std::move(newRoot));
@@ -534,7 +581,7 @@ void QmlBuildSystem::refreshFiles(const QSet<QString> & /*added*/, const QSet<QS
QVariant QmlBuildSystem::additionalData(Utils::Id id) const
{
if (id == Constants::customFileSelectorsData)
- return customFileSelectors();
+ return fileSelectors();
if (id == Constants::supportedLanguagesData)
return supportedLanguages();
if (id == Constants::primaryLanguageData)
@@ -547,8 +594,6 @@ QVariant QmlBuildSystem::additionalData(Utils::Id id) const
return qt6Project();
if (id == Constants::mainFilePath)
return mainFilePath().toString();
- if (id == Constants::customImportPaths)
- return customImportPaths();
if (id == Constants::canonicalProjectDir)
return canonicalProjectDir().toString();
return {};
@@ -632,12 +677,7 @@ Utils::EnvironmentItems QmlBuildSystem::environment() const
return m_projectItem->environment();
}
-QStringList QmlBuildSystem::customImportPaths() const
-{
- return m_projectItem->importPaths();
-}
-
-QStringList QmlBuildSystem::customFileSelectors() const
+QStringList QmlBuildSystem::fileSelectors() const
{
return m_projectItem->fileSelectors();
}
@@ -682,7 +722,7 @@ QStringList QmlBuildSystem::importPaths() const
return m_projectItem->importPaths();
}
-QStringList QmlBuildSystem::absoluteImportPaths()
+QStringList QmlBuildSystem::absoluteImportPaths() const
{
return Utils::transform<QStringList>(m_projectItem->importPaths(), [&](const QString &importPath) {
Utils::FilePath filePath = Utils::FilePath::fromString(importPath);
@@ -692,11 +732,6 @@ QStringList QmlBuildSystem::absoluteImportPaths()
});
}
-Utils::FilePaths QmlBuildSystem::files() const
-{
- return m_projectItem->files();
-}
-
QString QmlBuildSystem::versionQt() const
{
return m_projectItem->versionQt();
diff --git a/src/plugins/qmlprojectmanager/buildsystem/qmlbuildsystem.h b/src/plugins/qmlprojectmanager/buildsystem/qmlbuildsystem.h
index e3f9b99adb..d91f60cdd1 100644
--- a/src/plugins/qmlprojectmanager/buildsystem/qmlbuildsystem.h
+++ b/src/plugins/qmlprojectmanager/buildsystem/qmlbuildsystem.h
@@ -6,7 +6,9 @@
#pragma once
#include "../qmlprojectmanager_global.h"
+
#include <projectexplorer/buildsystem.h>
+#include <utils/filesystemwatcher.h>
#include "qmlprojectmanager/cmakegen/cmakegenerator.h"
@@ -72,9 +74,8 @@ public:
Utils::EnvironmentItems environment() const;
QStringList importPaths() const;
- QStringList absoluteImportPaths();
- QStringList customImportPaths() const;
- QStringList customFileSelectors() const;
+ QStringList absoluteImportPaths() const;
+ QStringList fileSelectors() const;
bool multilanguageSupport() const;
QStringList supportedLanguages() const;
@@ -91,7 +92,6 @@ public:
QStringList shaderToolArgs() const;
QStringList shaderToolFiles() const;
- Utils::FilePaths files() const;
QString versionQt() const;
QString versionQtQuick() const;
@@ -117,10 +117,15 @@ private:
const Utils::FilePath &mainFilePath,
const QString &oldFile);
+ // this is the main project item
QSharedPointer<QmlProjectItem> m_projectItem;
+ // these are the mcu project items which can be found in the project tree
+ QList<QSharedPointer<QmlProjectItem>> m_mcuProjectItems;
+ Utils::FileSystemWatcher m_mcuProjectFilesWatcher;
bool m_blockFilesUpdate = false;
void initProjectItem();
+ void initMcuProjectItems();
void parseProjectFiles();
void generateProjectTree();
diff --git a/src/plugins/qmlprojectmanager/cmakegen/cmakegenerator.cpp b/src/plugins/qmlprojectmanager/cmakegen/cmakegenerator.cpp
index 659c544ebd..9ae576ec35 100644
--- a/src/plugins/qmlprojectmanager/cmakegen/cmakegenerator.cpp
+++ b/src/plugins/qmlprojectmanager/cmakegen/cmakegenerator.cpp
@@ -17,8 +17,9 @@
#include "coreplugin/actionmanager/actioncontainer.h"
#include <QDirIterator>
-#include <QRegularExpression>
+#include <QFileInfo>
#include <QMenu>
+#include <QRegularExpression>
#include <set>
@@ -87,8 +88,8 @@ void CMakeGenerator::updateMenuAction()
CMakeGenerator::CMakeGenerator(QmlBuildSystem *bs, QObject *parent)
: QObject(parent)
- , m_root(std::make_shared<Node>())
, m_buildSystem(bs)
+ , m_root(std::make_shared<Node>())
{}
const QmlProject *CMakeGenerator::qmlProject() const
@@ -170,6 +171,9 @@ void CMakeGenerator::update(const QSet<QString> &added, const QSet<QString> &rem
std::set<NodePtr> dirtyModules;
for (const QString &add : added) {
const Utils::FilePath path = Utils::FilePath::fromString(add);
+ if (ignore(path.parentDir()))
+ continue;
+
if (auto node = findOrCreateNode(m_root, path.parentDir())) {
insertFile(node, path);
if (auto module = findModuleFor(node))
@@ -208,10 +212,28 @@ bool CMakeGenerator::isResource(const Utils::FilePath &path) const
return suffixes.contains(path.suffix(), Qt::CaseInsensitive);
}
-bool CMakeGenerator::ignoreFile(const Utils::FilePath &path) const
+bool CMakeGenerator::ignore(const Utils::FilePath &path) const
{
- static const QStringList suffixes = { "hints" };
- return suffixes.contains(path.suffix(), Qt::CaseInsensitive);
+ if (path.isFile()) {
+ static const QStringList suffixes = { "hints" };
+ return suffixes.contains(path.suffix(), Qt::CaseInsensitive);
+ } else if (path.isDir()) {
+ if (!m_root->dir.exists())
+ return true;
+
+ static const QStringList fileNames = { "CMakeCache.txt", "build.ninja" };
+
+ Utils::FilePath dir = path;
+ while (dir.isChildOf(m_root->dir)) {
+ for (const QString& fileName : fileNames) {
+ Utils::FilePath checkFile = dir.pathAppended(fileName);
+ if (checkFile.exists())
+ return true;
+ }
+ dir = dir.parentDir();
+ }
+ }
+ return false;
}
void CMakeGenerator::createCMakeFiles(const NodePtr &node) const
@@ -308,8 +330,7 @@ NodePtr CMakeGenerator::findOrCreateNode(NodePtr &node, const Utils::FilePath &p
};
const Utils::FilePath relative = path.relativeChildPath(node->dir);
- const QChar separator = relative.pathComponentSeparator();
- const QList<QStringView> components = relative.pathView().split(separator);
+ const QList<QStringView> components = relative.pathView().split('/');
NodePtr lastNode = node;
for (const auto &comp : components) {
@@ -445,6 +466,9 @@ void CMakeGenerator::parseNodeTree(NodePtr &generatorNode,
{
for (const auto *childNode : folderNode->nodes()) {
if (const auto *subFolderNode = childNode->asFolderNode()) {
+ if (ignore(subFolderNode->filePath()))
+ continue;
+
NodePtr childGeneratorNode = std::make_shared<Node>();
childGeneratorNode->parent = generatorNode;
childGeneratorNode->dir = subFolderNode->filePath();
@@ -499,7 +523,10 @@ void CMakeGenerator::compareWithFileSystem(const NodePtr &node) const
while (iter.hasNext()) {
auto next = Utils::FilePath::fromString(iter.next());
- if (isResource(next) && !findFile(next) && !ignoreFile(next))
+ if (ignore(next.parentDir()))
+ continue;
+
+ if (isResource(next) && !findFile(next) && !ignore(next))
files.push_back(next);
}
diff --git a/src/plugins/qmlprojectmanager/cmakegen/cmakegenerator.h b/src/plugins/qmlprojectmanager/cmakegen/cmakegenerator.h
index 6077166d5b..3af3405879 100644
--- a/src/plugins/qmlprojectmanager/cmakegen/cmakegenerator.h
+++ b/src/plugins/qmlprojectmanager/cmakegen/cmakegenerator.h
@@ -47,7 +47,7 @@ public:
private:
bool isQml(const Utils::FilePath &path) const;
bool isResource(const Utils::FilePath &path) const;
- bool ignoreFile(const Utils::FilePath &path) const;
+ bool ignore(const Utils::FilePath &path) const;
void createCMakeFiles(const NodePtr &node) const;
void createSourceFiles() const;
@@ -70,12 +70,12 @@ private:
void compareWithFileSystem(const NodePtr &node) const;
bool m_enabled = false;
+ QmlBuildSystem *m_buildSystem = nullptr;
CMakeWriter::Ptr m_writer = {};
QString m_projectName = {};
NodePtr m_root = {};
QStringList m_moduleNames = {};
- QmlBuildSystem *m_buildSystem = nullptr;
};
} // namespace GenerateCmake
diff --git a/src/plugins/qmlprojectmanager/cmakegen/templates/qmlcomponents.tpl b/src/plugins/qmlprojectmanager/cmakegen/templates/qmlcomponents.tpl
index a9f20243a6..5640b85844 100644
--- a/src/plugins/qmlprojectmanager/cmakegen/templates/qmlcomponents.tpl
+++ b/src/plugins/qmlprojectmanager/cmakegen/templates/qmlcomponents.tpl
@@ -3,6 +3,7 @@
message("Building designer components.")
+set(QT_QDS_COMPONENTS_NOWARN on)
set(QT_QML_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/qml")
include(FetchContent)
@@ -17,6 +18,7 @@ FetchContent_Populate(ds)
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE
QuickStudioComponentsplugin
+ QuickStudioDesignEffectsplugin
QuickStudioEffectsplugin
QuickStudioApplicationplugin
FlowViewplugin
diff --git a/src/plugins/qmlprojectmanager/qmlmainfileaspect.cpp b/src/plugins/qmlprojectmanager/qmlmainfileaspect.cpp
index 7a35d9f15e..983b712633 100644
--- a/src/plugins/qmlprojectmanager/qmlmainfileaspect.cpp
+++ b/src/plugins/qmlprojectmanager/qmlmainfileaspect.cpp
@@ -56,7 +56,7 @@ QmlMainFileAspect::~QmlMainFileAspect()
delete m_fileListCombo;
}
-void QmlMainFileAspect::addToLayout(Layouting::LayoutItem &parent)
+void QmlMainFileAspect::addToLayout(Layouting::Layout &parent)
{
QTC_ASSERT(!m_fileListCombo, delete m_fileListCombo);
m_fileListCombo = new QComboBox;
diff --git a/src/plugins/qmlprojectmanager/qmlmainfileaspect.h b/src/plugins/qmlprojectmanager/qmlmainfileaspect.h
index 4a64055b6c..fd875dde79 100644
--- a/src/plugins/qmlprojectmanager/qmlmainfileaspect.h
+++ b/src/plugins/qmlprojectmanager/qmlmainfileaspect.h
@@ -42,7 +42,7 @@ public:
Utils::FilePath currentFile;
};
- void addToLayout(Layouting::LayoutItem &parent) final;
+ void addToLayout(Layouting::Layout &parent) final;
void toMap(Utils::Store &map) const final;
void fromMap(const Utils::Store &map) final;
diff --git a/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp b/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp
index ae16f09fc2..e6028e1d7d 100644
--- a/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp
+++ b/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp
@@ -21,7 +21,7 @@ namespace QmlProjectManager {
static bool isMultilanguagePresent()
{
- const QVector<ExtensionSystem::PluginSpec *> &specs = ExtensionSystem::PluginManager::plugins();
+ const ExtensionSystem::PluginSpecs &specs = ExtensionSystem::PluginManager::plugins();
return std::find_if(specs.cbegin(), specs.cend(),
[](ExtensionSystem::PluginSpec *spec) {
return spec->name() == "MultiLanguage";
@@ -41,7 +41,7 @@ static FilePath getMultilanguageDatabaseFilePath(ProjectExplorer::Target *target
static QObject *getPreviewPlugin()
{
- const QVector<ExtensionSystem::PluginSpec *> &specs = ExtensionSystem::PluginManager::plugins();
+ const ExtensionSystem::PluginSpecs &specs = ExtensionSystem::PluginManager::plugins();
const auto pluginIt = std::find_if(specs.cbegin(), specs.cend(),
[](const ExtensionSystem::PluginSpec *p) {
return p->name() == "QmlPreview";
diff --git a/src/plugins/qmlprojectmanager/qmlprojectconstants.h b/src/plugins/qmlprojectmanager/qmlprojectconstants.h
index a937979954..ad77948105 100644
--- a/src/plugins/qmlprojectmanager/qmlprojectconstants.h
+++ b/src/plugins/qmlprojectmanager/qmlprojectconstants.h
@@ -13,7 +13,6 @@ const char customQtForMCUs[] = "CustomQtForMCUs";
const char customQt6Project[] = "CustomQt6Project";
const char mainFilePath[] = "MainFilePath";
-const char customImportPaths[] = "CustomImportPaths";
const char canonicalProjectDir[] ="CanonicalProjectDir";
const char enviromentLaunchedQDS[] = "QTC_LAUNCHED_QDS";
diff --git a/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp b/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp
index 5742b945a8..503738eba5 100644
--- a/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp
+++ b/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp
@@ -97,12 +97,12 @@ QmlProjectRunConfiguration::QmlProjectRunConfiguration(Target *target, Id id)
// arguments from .qmlproject file
const QmlBuildSystem *bs = qobject_cast<QmlBuildSystem *>(target->buildSystem());
- for (const QString &importPath : bs->customImportPaths()) {
+ for (const QString &importPath : bs->absoluteImportPaths()) {
cmd.addArg("-I");
- cmd.addArg(bs->targetDirectory().pathAppended(importPath).path());
+ cmd.addArg(importPath);
}
- for (const QString &fileSelector : bs->customFileSelectors()) {
+ for (const QString &fileSelector : bs->fileSelectors()) {
cmd.addArg("-S");
cmd.addArg(fileSelector);
}
diff --git a/src/plugins/qnx/Qnx.json.in b/src/plugins/qnx/Qnx.json.in
index 0340d3b7dc..dc266e0ded 100644
--- a/src/plugins/qnx/Qnx.json.in
+++ b/src/plugins/qnx/Qnx.json.in
@@ -13,7 +13,13 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Device Support",
- "Description" : "Adds support for QNX to Qt Creator.",
- "Url" : "http://www.blackberry.com",
+ "Description" : "Develop for QNX Neutrino devices",
+ "LongDescription" : [
+ "Connect devices with USB or WLAN to run, debug, and analyze applications built for them.",
+ "You also need:",
+ "- Qt for QNX",
+ "- QNX Software Development Platform (SDP)"
+ ],
+ "Url" : "https://www.blackberry.com",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/qnx/slog2inforunner.cpp b/src/plugins/qnx/slog2inforunner.cpp
index e717e56e33..b7ed28ffd2 100644
--- a/src/plugins/qnx/slog2inforunner.cpp
+++ b/src/plugins/qnx/slog2inforunner.cpp
@@ -35,7 +35,7 @@ void Slog2InfoRunner::start()
QTC_CHECK(!m_taskTreeRunner.isRunning());
const auto onTestSetup = [this](Process &process) {
- process.setCommand({device()->filePath("slog2info"), {}});
+ process.setCommand(CommandLine{device()->filePath("slog2info")});
};
const auto onTestDone = [this](DoneWith result) {
if (result == DoneWith::Success) {
diff --git a/src/plugins/qtapplicationmanager/QtApplicationManagerIntegration.json.in b/src/plugins/qtapplicationmanager/QtApplicationManagerIntegration.json.in
index 75e2ddeaf2..e097973692 100644
--- a/src/plugins/qtapplicationmanager/QtApplicationManagerIntegration.json.in
+++ b/src/plugins/qtapplicationmanager/QtApplicationManagerIntegration.json.in
@@ -15,8 +15,12 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Device Support",
- "Description" : "Support for QtApplicationManager on a variety of different target platforms.",
- "Url" : "http://www.qt.io",
+ "Description" : "Run applications in Qt Application Manager",
+ "LongDescription" : [
+ "You also need:",
+ "- Qt Application Manager"
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES},
"Mimetypes" : [
diff --git a/src/plugins/qtapplicationmanager/appmanagerplugin.cpp b/src/plugins/qtapplicationmanager/appmanagerplugin.cpp
index f100f53b34..3afd7705b4 100644
--- a/src/plugins/qtapplicationmanager/appmanagerplugin.cpp
+++ b/src/plugins/qtapplicationmanager/appmanagerplugin.cpp
@@ -36,6 +36,7 @@ class AppManagerPlugin final : public ExtensionSystem::IPlugin
setupAppManagerRunWorker();
setupAppManagerDebugWorker();
setupAppManagerQmlToolingWorker();
+ setupAppManagerPerfProfilerWorker();
}
};
diff --git a/src/plugins/qtapplicationmanager/appmanagerruncontrol.cpp b/src/plugins/qtapplicationmanager/appmanagerruncontrol.cpp
index bfcda6e287..ac4b0c26c1 100644
--- a/src/plugins/qtapplicationmanager/appmanagerruncontrol.cpp
+++ b/src/plugins/qtapplicationmanager/appmanagerruncontrol.cpp
@@ -15,6 +15,8 @@
#include <debugger/debuggerruncontrol.h>
#include <debugger/debuggerkitaspect.h>
+#include <perfprofiler/perfprofilerconstants.h>
+
#include <projectexplorer/buildconfiguration.h>
#include <projectexplorer/buildsystem.h>
#include <projectexplorer/buildtargetinfo.h>
@@ -99,15 +101,10 @@ public:
setId(AppManager::Constants::DEBUG_LAUNCHER_ID);
setEssential(true);
- connect(&m_launcher, &Process::started, this, &RunWorker::reportStarted);
- connect(&m_launcher, &Process::done, this, &RunWorker::reportStopped);
-
- connect(&m_launcher, &Process::readyReadStandardOutput, this, [this] {
- appendMessage(m_launcher.readAllStandardOutput(), StdOutFormat);
- });
- connect(&m_launcher, &Process::readyReadStandardError, this, [this] {
- appendMessage(m_launcher.readAllStandardError(), StdErrFormat);
- });
+ if (usePerf) {
+ suppressDefaultStdOutHandling();
+ runControl->setProperty("PerfProcess", QVariant::fromValue(process()));
+ }
m_portsGatherer = new Debugger::DebugServerPortsGatherer(runControl);
m_portsGatherer->setUseGdbServer(useGdbServer || usePerf);
@@ -123,7 +120,6 @@ public:
if (auto envAspect = runControl->aspectData<EnvironmentAspect>())
envVars = envAspect->environment.toStringList();
-// const int perfPort = m_portsGatherer->gdbServer().port();
const int gdbServerPort = m_portsGatherer->gdbServer().port();
const int qmlServerPort = m_portsGatherer->qmlServer().port();
@@ -147,14 +143,10 @@ public:
}
cmd.addArg(debugArgs.join(' '));
}
- //FIXME UNTESTED CODE
if (m_usePerf) {
- Store settingsData = runControl->settingsData("Analyzer.Perf.Settings");
- QVariant perfRecordArgs = settingsData.value("Analyzer.Perf.RecordArguments");
- QString args = Utils::transform(perfRecordArgs.toStringList(), [](QString arg) {
- return arg.replace(',', ",,");
- }).join(',');
- cmd.addArg(QString("perf record %1 -o - --").arg(args));
+ const Store perfArgs = runControl->settingsData(PerfProfiler::Constants::PerfSettingsId);
+ const QString recordArgs = perfArgs[PerfProfiler::Constants::PerfRecordArgsId].toString();
+ cmd.addArg(QString("perf record %1 -o - --").arg(recordArgs));
}
cmd.addArg("-eio");
@@ -185,7 +177,6 @@ private:
bool m_useGdbServer;
bool m_useQmlServer;
QmlDebug::QmlDebugServicesPreset m_qmlServices;
- Process m_launcher;
};
@@ -300,6 +291,25 @@ private:
RunWorker *m_worker = nullptr;
};
+// AppManagerDevicePerfProfilerSupport
+
+class AppManagerPerfProfilerSupport final : public RunWorker
+{
+public:
+ explicit AppManagerPerfProfilerSupport(RunControl *runControl)
+ : RunWorker(runControl)
+ {
+ setId("AppManagerPerfProfilerSupport");
+
+ m_profilee = new AppManInferiorRunner(runControl, true, false, false,
+ QmlDebug::NoQmlDebugServices);
+ addStartDependency(m_profilee);
+ addStopDependency(m_profilee);
+ }
+
+private:
+ AppManInferiorRunner *m_profilee = nullptr;
+};
// Factories
@@ -338,6 +348,17 @@ public:
}
};
+class AppManagerPerfProfilerWorkerFactory final : public RunWorkerFactory
+{
+public:
+ AppManagerPerfProfilerWorkerFactory()
+ {
+ setProduct<AppManagerPerfProfilerSupport>();
+ addSupportedRunMode("PerfRecorder");
+ addSupportedRunConfig(Constants::RUNANDDEBUGCONFIGURATION_ID);
+ }
+};
+
void setupAppManagerRunWorker()
{
static AppManagerRunWorkerFactory theAppManagerRunWorkerFactory;
@@ -353,4 +374,9 @@ void setupAppManagerQmlToolingWorker()
static AppManagerQmlToolingWorkerFactory theAppManagerQmlToolingWorkerFactory;
}
+void setupAppManagerPerfProfilerWorker()
+{
+ static AppManagerPerfProfilerWorkerFactory theAppManagerPerfProfilerWorkerFactory;
+}
+
} // AppManager::Internal
diff --git a/src/plugins/qtapplicationmanager/appmanagerruncontrol.h b/src/plugins/qtapplicationmanager/appmanagerruncontrol.h
index b98f462c29..3530d4d10b 100644
--- a/src/plugins/qtapplicationmanager/appmanagerruncontrol.h
+++ b/src/plugins/qtapplicationmanager/appmanagerruncontrol.h
@@ -10,5 +10,6 @@ namespace AppManager::Internal {
void setupAppManagerRunWorker();
void setupAppManagerDebugWorker();
void setupAppManagerQmlToolingWorker();
+void setupAppManagerPerfProfilerWorker();
} // AppManager::Internal
diff --git a/src/plugins/qtsupport/QtSupport.json.in b/src/plugins/qtsupport/QtSupport.json.in
index 0a39a762ce..af3bed662a 100644
--- a/src/plugins/qtsupport/QtSupport.json.in
+++ b/src/plugins/qtsupport/QtSupport.json.in
@@ -14,7 +14,7 @@
],
"Category" : "Build Systems",
"Description" : "Provides support code for build systems.",
- "Url" : "http://www.qt.io",
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES},
"Mimetypes" : [
diff --git a/src/plugins/qtsupport/externaleditors.cpp b/src/plugins/qtsupport/externaleditors.cpp
index 4009025b8d..371623f6cf 100644
--- a/src/plugins/qtsupport/externaleditors.cpp
+++ b/src/plugins/qtsupport/externaleditors.cpp
@@ -263,7 +263,7 @@ public:
explicit ExternalDesignerFactory(QObject *guard)
{
setId("Qt.Designer");
- setDisplayName(::Core::Tr::tr("Qt Designer"));
+ setDisplayName(::Core::Tr::tr("Qt Widgets Designer"));
setMimeTypes({Utils::Constants::FORM_MIMETYPE});
setEditorStarter([guard](const FilePath &filePath, QString *errorMessage) {
@@ -276,7 +276,7 @@ public:
if (HostOsInfo::isMacHost())
return startEditorProcess(data, errorMessage);
- /* Qt Designer on the remaining platforms: Uses Designer's own
+ /* Qt Widgets Designer on the remaining platforms: Uses Designer's own
* Tcp-based communication mechanism to ensure all files are opened
* in one instance (per version). */
@@ -288,7 +288,8 @@ public:
qDebug() << Q_FUNC_INFO << "\nWriting to socket:" << data.binary << filePath;
QTcpSocket *socket = it.value();
if (!socket->write(filePath.toString().toUtf8() + '\n')) {
- *errorMessage = Tr::tr("Qt Designer is not responding (%1).").arg(socket->errorString());
+ *errorMessage = Tr::tr("Qt Widgets Designer is not responding (%1).")
+ .arg(socket->errorString());
return false;
}
return true;
diff --git a/src/plugins/qtsupport/gettingstartedwelcomepage.cpp b/src/plugins/qtsupport/gettingstartedwelcomepage.cpp
index f02a433fbe..1240c9bbd3 100644
--- a/src/plugins/qtsupport/gettingstartedwelcomepage.cpp
+++ b/src/plugins/qtsupport/gettingstartedwelcomepage.cpp
@@ -279,7 +279,7 @@ public:
using namespace Layouting;
Row titleRow {
- customMargin({0, 0, ExVPaddingGapXl, 0}),
+ customMargins(0, 0, ExVPaddingGapXl, 0),
spacing(ExVPaddingGapXl),
};
@@ -317,7 +317,7 @@ public:
titleRow,
gridView,
spacing(ExVPaddingGapXl),
- customMargin({ExVPaddingGapXl, ExVPaddingGapXl, 0, 0}),
+ customMargins(ExVPaddingGapXl, ExVPaddingGapXl, 0, 0),
}.attachTo(this);
connect(&m_exampleDelegate, &ExampleDelegate::tagClicked,
diff --git a/src/plugins/qtsupport/qtbuildaspects.cpp b/src/plugins/qtsupport/qtbuildaspects.cpp
index 2f6ee19220..2f7d966ba6 100644
--- a/src/plugins/qtsupport/qtbuildaspects.cpp
+++ b/src/plugins/qtsupport/qtbuildaspects.cpp
@@ -30,12 +30,12 @@ QmlDebuggingAspect::QmlDebuggingAspect(AspectContainer *container)
setValue(buildPropertiesSettings().qmlDebugging());
}
-void QmlDebuggingAspect::addToLayout(Layouting::LayoutItem &parent)
+void QmlDebuggingAspect::addToLayout(Layouting::Layout &parent)
{
SelectionAspect::addToLayout(parent);
const auto warningLabel = createSubWidget<InfoLabel>(QString(), InfoLabel::Warning);
warningLabel->setElideMode(Qt::ElideNone);
- parent.addRow({{}, warningLabel});
+ parent.addRow({Layouting::empty, warningLabel});
const auto changeHandler = [this, warningLabel] {
QString warningText;
QTC_ASSERT(m_buildConfig, return);
@@ -78,13 +78,13 @@ void QtQuickCompilerAspect::setBuildConfiguration(const BuildConfiguration *buil
m_buildConfig = buildConfig;
}
-void QtQuickCompilerAspect::addToLayout(Layouting::LayoutItem &parent)
+void QtQuickCompilerAspect::addToLayout(Layouting::Layout &parent)
{
SelectionAspect::addToLayout(parent);
const auto warningLabel = createSubWidget<InfoLabel>(QString(), InfoLabel::Warning);
warningLabel->setElideMode(Qt::ElideNone);
warningLabel->setVisible(false);
- parent.addRow({{}, warningLabel});
+ parent.addRow({Layouting::empty, warningLabel});
const auto changeHandler = [this, warningLabel] {
QString warningText;
QTC_ASSERT(m_buildConfig, return);
diff --git a/src/plugins/qtsupport/qtbuildaspects.h b/src/plugins/qtsupport/qtbuildaspects.h
index 873daf000d..e112e6a60b 100644
--- a/src/plugins/qtsupport/qtbuildaspects.h
+++ b/src/plugins/qtsupport/qtbuildaspects.h
@@ -21,7 +21,7 @@ public:
void setBuildConfiguration(const ProjectExplorer::BuildConfiguration *newBuildConfig);
private:
- void addToLayout(Layouting::LayoutItem &parent) override;
+ void addToLayout(Layouting::Layout &parent) override;
const ProjectExplorer::BuildConfiguration *m_buildConfig = nullptr;
};
@@ -36,7 +36,7 @@ public:
void setBuildConfiguration(const ProjectExplorer::BuildConfiguration *newBuildConfig);
private:
- void addToLayout(Layouting::LayoutItem &parent) override;
+ void addToLayout(Layouting::Layout &parent) override;
const ProjectExplorer::BuildConfiguration *m_buildConfig = nullptr;
};
diff --git a/src/plugins/qtsupport/qtcreator_tutorials.xml b/src/plugins/qtsupport/qtcreator_tutorials.xml
index af65745443..b17b0def6a 100644
--- a/src/plugins/qtsupport/qtcreator_tutorials.xml
+++ b/src/plugins/qtsupport/qtcreator_tutorials.xml
@@ -16,14 +16,14 @@
</tutorial>
<tutorial imageUrl=":qtsupport/images/icons/tutorialicon.png" difficulty="" docUrl="qthelp://org.qt-project.qtcreator/doc/creator-writing-program.html" projectPath="" name="Creating a Qt Widget-Based Application">
<description><![CDATA[Using Qt Creator to create a small Qt application, Text Finder.]]></description>
- <tags>qt creator,qt designer,widgets,c++,text,help</tags>
+ <tags>qt creator,qt widgets designer,widgets,c++,text,help</tags>
<meta>
<entry name="category">Help</entry>
</meta>
</tutorial>
<tutorial imageUrl=":qtsupport/images/icons/tutorialicon.png" difficulty="" docUrl="qthelp://org.qt-project.qtdoc/qtwidgets/qtwidgets-tutorials-notepad-example.html" projectPath="" name="Getting Started Programming with Qt Widgets">
<description><![CDATA[Developing Qt applications using C++ and the Qt Widgets module.]]></description>
- <tags>qt,qt creator,qt designer,widgets,c++,help</tags>
+ <tags>qt,qt creator,qt widgets designer,widgets,c++,help</tags>
<meta>
<entry name="category">Help</entry>
</meta>
@@ -66,7 +66,7 @@
</tutorial>
<tutorial imageUrl=":qtsupport/images/icons/youtubeLnVjI0I7cKs.webp" difficulty="" projectPath="" name="Qt Creator - Design and Edit Modes" isVideo="true" videoUrl="https://www.youtube.com/watch?v=LnVjI0I7cKs" videoLength="4:34">
<description><![CDATA[Developing a Qt Widgets application.]]></description>
- <tags>qt creator,learning,qt designer,coding,2023</tags>
+ <tags>qt creator,learning,qt widgets designer,coding,2023</tags>
<meta>
<entry name="category">Learning</entry>
</meta>
@@ -276,9 +276,9 @@
<entry name="category">Talk</entry>
</meta>
</tutorial>
- <tutorial imageUrl=":qtsupport/images/icons/youtubeB0X5FOev9Lw.webp" difficulty="" projectPath="" name="Qt Designer tutorial: Integrate custom widgets" isVideo="true" videoUrl="https://youtu.be/B0X5FOev9Lw" videoLength="27:07">
- <description><![CDATA[Integrating custom widgets into Qt Designer.]]></description>
- <tags>qt designer,widgets,ui,talk,2019</tags>
+ <tutorial imageUrl=":qtsupport/images/icons/youtubeB0X5FOev9Lw.webp" difficulty="" projectPath="" name="Qt Widgets Designer tutorial: Integrate custom widgets" isVideo="true" videoUrl="https://youtu.be/B0X5FOev9Lw" videoLength="27:07">
+ <description><![CDATA[Integrating custom widgets into Qt Widgets Designer.]]></description>
+ <tags>qt widgets designer,widgets,ui,talk,2019</tags>
<meta>
<entry name="category">Talk</entry>
</meta>
diff --git a/src/plugins/qtsupport/qtkitaspect.cpp b/src/plugins/qtsupport/qtkitaspect.cpp
index 8059c1059a..18a18b6ae4 100644
--- a/src/plugins/qtsupport/qtkitaspect.cpp
+++ b/src/plugins/qtsupport/qtkitaspect.cpp
@@ -62,7 +62,7 @@ public:
private:
void makeReadOnly() final { m_combo->setEnabled(false); }
- void addToLayoutImpl(Layouting::LayoutItem &parent) override
+ void addToLayoutImpl(Layouting::Layout &parent) override
{
addMutableAction(m_combo);
parent.addItem(m_combo);
diff --git a/src/plugins/qtsupport/qtparser.cpp b/src/plugins/qtsupport/qtparser.cpp
index 387af762a3..93fd16d90a 100644
--- a/src/plugins/qtsupport/qtparser.cpp
+++ b/src/plugins/qtsupport/qtparser.cpp
@@ -45,7 +45,7 @@ Utils::OutputLineParser::Result QtParser::handleLine(const QString &line, Utils:
LinkSpecs linkSpecs;
const Utils::FilePath file
= absoluteFilePath(Utils::FilePath::fromUserInput(match.captured("file")));
- addLinkSpecForAbsoluteFilePath(linkSpecs, file, lineno, match, "file");
+ addLinkSpecForAbsoluteFilePath(linkSpecs, file, lineno, -1, match, "file");
CompileTask task(type, match.captured("description").trimmed(), file, lineno);
task.column = match.captured("column").toInt();
scheduleTask(task, 1);
@@ -62,7 +62,7 @@ Utils::OutputLineParser::Result QtParser::handleLine(const QString &line, Utils:
message.prepend(": ").prepend(fileName);
} else if (fileName.endsWith(".ui")) {
filePath = absoluteFilePath(Utils::FilePath::fromUserInput(fileName));
- addLinkSpecForAbsoluteFilePath(linkSpecs, filePath, -1, match, "file");
+ addLinkSpecForAbsoluteFilePath(linkSpecs, filePath, -1, -1, match, "file");
} else {
isUicMessage = false;
}
@@ -79,7 +79,7 @@ Utils::OutputLineParser::Result QtParser::handleLine(const QString &line, Utils:
LinkSpecs linkSpecs;
const Utils::FilePath file
= absoluteFilePath(Utils::FilePath::fromUserInput(match.captured("file")));
- addLinkSpecForAbsoluteFilePath(linkSpecs, file, 0, match, "file");
+ addLinkSpecForAbsoluteFilePath(linkSpecs, file, -1, -1, match, "file");
CompileTask task(type, match.captured("description"), file);
scheduleTask(task, 1);
return {Status::Done, linkSpecs};
@@ -95,7 +95,7 @@ Utils::OutputLineParser::Result QtParser::handleLine(const QString &line, Utils:
if (!ok)
lineno = -1;
LinkSpecs linkSpecs;
- addLinkSpecForAbsoluteFilePath(linkSpecs, file, lineno, match, "file");
+ addLinkSpecForAbsoluteFilePath(linkSpecs, file, lineno, -1, match, "file");
CompileTask task(type, match.captured("description"), file, lineno,
match.captured("column").toInt());
scheduleTask(task, 1);
diff --git a/src/plugins/qtsupport/qttestparser.cpp b/src/plugins/qtsupport/qttestparser.cpp
index 93030f20e6..a19929b754 100644
--- a/src/plugins/qtsupport/qttestparser.cpp
+++ b/src/plugins/qtsupport/qttestparser.cpp
@@ -44,8 +44,8 @@ OutputLineParser::Result QtTestParser::handleLine(const QString &line, OutputFor
m_currentTask.file = absoluteFilePath(FilePath::fromString(
QDir::fromNativeSeparators(match.captured("file"))));
m_currentTask.line = match.captured("line").toInt();
- addLinkSpecForAbsoluteFilePath(linkSpecs, m_currentTask.file, m_currentTask.line, match,
- "file");
+ addLinkSpecForAbsoluteFilePath(
+ linkSpecs, m_currentTask.file, m_currentTask.line, m_currentTask.column, match, "file");
emitCurrentTask();
return {Status::Done, linkSpecs};
}
diff --git a/src/plugins/qtsupport/qtversionmanager.cpp b/src/plugins/qtsupport/qtversionmanager.cpp
index c269eca3cc..2a309b67b2 100644
--- a/src/plugins/qtsupport/qtversionmanager.cpp
+++ b/src/plugins/qtsupport/qtversionmanager.cpp
@@ -230,7 +230,7 @@ bool QtVersionManagerImpl::restoreQtVersions()
if (!key.view().startsWith(keyPrefix))
continue;
bool ok;
- int count = key.toByteArray().mid(keyPrefix.count()).toInt(&ok);
+ int count = key.toByteArray().mid(keyPrefix.size()).toInt(&ok);
if (!ok || count < 0)
continue;
@@ -304,7 +304,7 @@ void QtVersionManagerImpl::updateFromInstaller(bool emitSignal)
if (!key.view().startsWith(keyPrefix))
continue;
bool ok;
- int count = key.toByteArray().mid(keyPrefix.count()).toInt(&ok);
+ int count = key.toByteArray().mid(keyPrefix.size()).toInt(&ok);
if (!ok || count < 0)
continue;
diff --git a/src/plugins/qtsupport/translationwizardpage.cpp b/src/plugins/qtsupport/translationwizardpage.cpp
index bde480edd0..214996792f 100644
--- a/src/plugins/qtsupport/translationwizardpage.cpp
+++ b/src/plugins/qtsupport/translationwizardpage.cpp
@@ -76,7 +76,7 @@ TranslationWizardPage::TranslationWizardPage(const QString &enabledExpr, bool si
auto localeStrings = transform<QList<LocalePair>>(allLocales,
[](const QLocale &l) {
const QString displayName = QLocale::languageToString(l.language()).append(" (")
- .append(QLocale::countryToString(l.country())).append(')');
+ .append(QLocale::territoryToString(l.territory())).append(')');
const QString tsFileBaseName = l.name();
return qMakePair(displayName, tsFileBaseName);
});
diff --git a/src/plugins/remotelinux/RemoteLinux.json.in b/src/plugins/remotelinux/RemoteLinux.json.in
index 2345b5acab..084f01a20a 100644
--- a/src/plugins/remotelinux/RemoteLinux.json.in
+++ b/src/plugins/remotelinux/RemoteLinux.json.in
@@ -13,7 +13,12 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Device Support",
- "Description" : "Support for deployment to and execution on a remote Linux host.",
- "Url" : "http://www.qt.io",
+ "Description" : "Develop applications for embedded Linux devices",
+ "LongDescription" : [
+ "Connect devices with USB or over a network to run, debug, and analyze applications built for them.",
+ "You also need:",
+ "- A toolchain for embedded development"
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/remotelinux/filesystemaccess_test.cpp b/src/plugins/remotelinux/filesystemaccess_test.cpp
index 0f2e5bd937..b9e77f79e9 100644
--- a/src/plugins/remotelinux/filesystemaccess_test.cpp
+++ b/src/plugins/remotelinux/filesystemaccess_test.cpp
@@ -188,7 +188,7 @@ void FileSystemAccessTest::testWorkingDirectory()
const FilePath dir = baseFilePath() / "testdir with space and 'various' \"quotes\" here";
QVERIFY(dir.ensureWritableDir());
Process proc;
- proc.setCommand({"pwd", {}});
+ proc.setCommand(CommandLine{"pwd"});
proc.setWorkingDirectory(dir);
proc.start();
QVERIFY(proc.waitForFinished());
diff --git a/src/plugins/remotelinux/linuxdevice.cpp b/src/plugins/remotelinux/linuxdevice.cpp
index 8d3b46eb1b..c971c8caa2 100644
--- a/src/plugins/remotelinux/linuxdevice.cpp
+++ b/src/plugins/remotelinux/linuxdevice.cpp
@@ -15,8 +15,6 @@
#include <coreplugin/icore.h>
#include <coreplugin/messagemanager.h>
-#include <extensionsystem/pluginmanager.h>
-
#include <projectexplorer/devicesupport/devicemanager.h>
#include <projectexplorer/devicesupport/filetransfer.h>
#include <projectexplorer/devicesupport/filetransferinterface.h>
@@ -367,8 +365,9 @@ Environment LinuxDevicePrivate::getEnvironment()
return m_environmentCache.value();
Process getEnvProc;
- getEnvProc.setCommand({q->filePath("env"), {}});
- getEnvProc.runBlocking();
+ getEnvProc.setCommand(CommandLine{q->filePath("env")});
+ using namespace std::chrono;
+ getEnvProc.runBlocking(5s);
const QString remoteOutput = getEnvProc.cleanedStdOut();
m_environmentCache = Environment(remoteOutput.split('\n', Qt::SkipEmptyParts), q->osType());
@@ -472,12 +471,10 @@ qint64 SshProcessInterface::processId() const
ProcessResult SshProcessInterface::runInShell(const CommandLine &command, const QByteArray &data)
{
Process process;
- CommandLine cmd = {d->m_device->filePath("/bin/sh"), {"-c"}};
QString tmp;
ProcessArgs::addArg(&tmp, command.executable().path());
ProcessArgs::addArgs(&tmp, command.arguments());
- cmd.addArg(tmp);
- process.setCommand(cmd);
+ process.setCommand({d->m_device->filePath("/bin/sh"), {"-c", tmp}});
process.setWriteData(data);
using namespace std::chrono_literals;
process.runBlocking(2s);
@@ -526,7 +523,7 @@ void SshProcessInterface::handleSendControlSignal(ControlSignal controlSignal)
QTC_ASSERT(pid, return); // TODO: try sending a signal based on process name
const QString args = QString::fromLatin1("-%1 -%2")
.arg(controlSignalToInt(controlSignal)).arg(pid);
- const CommandLine command = { "kill", args, CommandLine::Raw };
+ const CommandLine command{"kill", args, CommandLine::Raw};
// Killing by using the pid as process group didn't work
// Fallback to killing the pid directly
@@ -534,7 +531,7 @@ void SshProcessInterface::handleSendControlSignal(ControlSignal controlSignal)
if (runInShell(command, {}) != ProcessResult::FinishedWithSuccess) {
const QString args = QString::fromLatin1("-%1 %2")
.arg(controlSignalToInt(controlSignal)).arg(pid);
- const CommandLine command = { "kill" , args, CommandLine::Raw };
+ const CommandLine command{"kill" , args, CommandLine::Raw};
runInShell(command, {});
}
}
@@ -1041,7 +1038,7 @@ LinuxDevice::LinuxDevice()
// specify the shell executable.
const QString shell = env.hasChanges() ? env.value_or("SHELL", "/bin/sh") : QString();
- proc->setCommand({filePath(shell), {}});
+ proc->setCommand(CommandLine{filePath(shell)});
proc->setTerminalMode(TerminalMode::Run);
proc->setEnvironment(env);
proc->setWorkingDirectory(workingDir);
@@ -1235,25 +1232,20 @@ RunResult LinuxDevicePrivate::runInShell(const CommandLine &cmd, const QByteArra
void LinuxDevicePrivate::announceConnectionAttempt()
{
- const auto announce = [id = announceId(), name = q->displayName()] {
- Core::ICore::infoBar()->addInfo(
- InfoBarEntry(id,
- Tr::tr("Establishing initial connection to device \"%1\". "
- "This might take a moment.")
- .arg(name)));
+ const QString message = Tr::tr("Establishing initial connection to device \"%1\". "
+ "This might take a moment.").arg(q->displayName());
+ qCDebug(linuxDeviceLog) << message;
+ if (isMainThread()) {
+ Core::ICore::infoBar()->addInfo(InfoBarEntry(announceId(), message));
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); // Yes, twice.
- };
- if (QThread::currentThread() == qApp->thread())
- announce();
- else
- QMetaObject::invokeMethod(Core::ICore::infoBar(), announce, Qt::BlockingQueuedConnection);
+ }
}
void LinuxDevicePrivate::unannounceConnectionAttempt()
{
- QMetaObject::invokeMethod(Core::ICore::infoBar(),
- [id = announceId()] { Core::ICore::infoBar()->removeInfo(id); });
+ if (isMainThread())
+ Core::ICore::infoBar()->removeInfo(announceId());
}
bool LinuxDevicePrivate::checkDisconnectedWithWarning()
@@ -1489,7 +1481,7 @@ private:
if (file.m_targetPermissions == FilePermissions::ForceExecutable)
batchData += "chmod 1775 " + target + '\n';
}
- process().setCommand({sftpBinary, fullConnectionOptions() << "-b" << "-" << host()});
+ process().setCommand({sftpBinary, {fullConnectionOptions(), "-b", "-", host()}});
process().setWriteData(batchData);
process().start();
}
@@ -1531,7 +1523,8 @@ private:
const QString sshCmdLine = ProcessArgs::joinArgs(
QStringList{SshSettings::sshFilePath().toUserOutput()}
<< fullConnectionOptions(), OsTypeLinux);
- QStringList options{"-e", sshCmdLine, m_setup.m_rsyncFlags};
+ QStringList options{"-e", sshCmdLine};
+ options << ProcessArgs::splitArgs(m_setup.m_rsyncFlags, HostOsInfo::hostOs());
if (!m_batches.isEmpty()) { // NormalRun
const auto batchIt = m_batches.begin();
@@ -1621,8 +1614,6 @@ private:
const auto onCreateDirSetup = [iteratorParentDirs](Async<expected_str<void>> &async) {
async.setConcurrentCallData(createDir, *iteratorParentDirs);
- if (Utils::isMainThread())
- async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
};
const auto onCreateDirDone = [this,
@@ -1640,8 +1631,6 @@ private:
const auto onCopySetup = [iterator](Async<expected_str<void>> &async) {
async.setConcurrentCallData(copyFile, *iterator);
- if (Utils::isMainThread())
- async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
};
const auto onCopyDone = [this, iterator, counterStorage](
diff --git a/src/plugins/remotelinux/linuxdevicetester.cpp b/src/plugins/remotelinux/linuxdevicetester.cpp
index d6ab3a9745..be9cb71686 100644
--- a/src/plugins/remotelinux/linuxdevicetester.cpp
+++ b/src/plugins/remotelinux/linuxdevicetester.cpp
@@ -7,8 +7,6 @@
#include "remotelinuxtr.h"
#include "utils/async.h"
-#include <extensionsystem/pluginmanager.h>
-
#include <projectexplorer/devicesupport/deviceusedportsgatherer.h>
#include <projectexplorer/devicesupport/filetransfer.h>
#include <projectexplorer/projectexplorerconstants.h>
@@ -102,7 +100,6 @@ GroupItem GenericLinuxDeviceTesterPrivate::connectionTask() const
const auto onSetup = [this](Async<bool> &task) {
emit q->progressMessage(Tr::tr("Connecting to device..."));
task.setConcurrentCallData([device = m_device] { return device->tryToConnect(); });
- task.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
};
const auto onDone = [this](const Async<bool> &task) {
const bool success = task.isResultAvailable() && task.result();
diff --git a/src/plugins/remotelinux/makeinstallstep.cpp b/src/plugins/remotelinux/makeinstallstep.cpp
index 38edbb6791..8b678bea96 100644
--- a/src/plugins/remotelinux/makeinstallstep.cpp
+++ b/src/plugins/remotelinux/makeinstallstep.cpp
@@ -245,7 +245,7 @@ void MakeInstallStep::updateArgsFromAspect()
void MakeInstallStep::updateFullCommandLine()
{
- CommandLine cmd{makeExecutable(), userArguments(), CommandLine::Raw};
+ const CommandLine cmd{makeExecutable(), userArguments(), CommandLine::Raw};
m_fullCommand.setValue(cmd.toUserOutput());
}
diff --git a/src/plugins/remotelinux/publickeydeploymentdialog.cpp b/src/plugins/remotelinux/publickeydeploymentdialog.cpp
index d3ade682cb..b0da7f2b1d 100644
--- a/src/plugins/remotelinux/publickeydeploymentdialog.cpp
+++ b/src/plugins/remotelinux/publickeydeploymentdialog.cpp
@@ -112,7 +112,7 @@ PublicKeyDeploymentDialog::~PublicKeyDeploymentDialog()
void PublicKeyDeploymentDialog::handleDeploymentDone(bool succeeded, const QString &errorMessage)
{
QString buttonText = succeeded ? Tr::tr("Deployment finished successfully.") : errorMessage;
- const QString textColor = creatorTheme()->color(
+ const QString textColor = creatorColor(
succeeded ? Theme::TextColorNormal : Theme::TextColorError).name();
setLabelText(QString::fromLatin1("<font color=\"%1\">%2</font>")
.arg(textColor, buttonText.replace("\n", "<br/>")));
diff --git a/src/plugins/remotelinux/sshdevicewizard.cpp b/src/plugins/remotelinux/sshdevicewizard.cpp
index d73e146957..2cf001cd17 100644
--- a/src/plugins/remotelinux/sshdevicewizard.cpp
+++ b/src/plugins/remotelinux/sshdevicewizard.cpp
@@ -19,6 +19,7 @@
#include <utils/utilsicons.h>
#include <QLabel>
+#include <QLayout>
#include <QPushButton>
#include <QSpinBox>
diff --git a/src/plugins/remotelinux/sshkeycreationdialog.cpp b/src/plugins/remotelinux/sshkeycreationdialog.cpp
index 03c6f7a543..a94a56cf61 100644
--- a/src/plugins/remotelinux/sshkeycreationdialog.cpp
+++ b/src/plugins/remotelinux/sshkeycreationdialog.cpp
@@ -110,11 +110,11 @@ void SshKeyCreationDialog::generateKeys()
const QString keyTypeString = QLatin1String(m_rsa->isChecked() ? "rsa": "ecdsa");
QApplication::setOverrideCursor(Qt::BusyCursor);
Process keygen;
- const QStringList args{"-t", keyTypeString, "-b", m_comboBox->currentText(),
- "-N", QString(), "-f", privateKeyFilePath().path()};
- QString errorMsg;
- keygen.setCommand({SshSettings::keygenFilePath(), args});
+ keygen.setCommand({SshSettings::keygenFilePath(),
+ {"-t", keyTypeString, "-b", m_comboBox->currentText(), "-N", QString(), "-f",
+ privateKeyFilePath().path()}});
keygen.start();
+ QString errorMsg;
if (!keygen.waitForFinished())
errorMsg = keygen.errorString();
else if (keygen.exitCode() != 0)
diff --git a/src/plugins/resourceeditor/ResourceEditor.json.in b/src/plugins/resourceeditor/ResourceEditor.json.in
index be3ace268a..349c8b0c96 100644
--- a/src/plugins/resourceeditor/ResourceEditor.json.in
+++ b/src/plugins/resourceeditor/ResourceEditor.json.in
@@ -13,8 +13,11 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Qt Creator",
- "Description" : "Editor for qrc files.",
- "Url" : "http://www.qt.io",
+ "Description" : "Edit Qt Resource System (.qrc) files",
+ "LongDescription" : [
+ "Store files in the application's executable with the platform-independent Qt Resource System."
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES},
"Mimetypes" : [
diff --git a/src/plugins/resourceeditor/qrceditor/resourcefile.cpp b/src/plugins/resourceeditor/qrceditor/resourcefile.cpp
index 38ee38fedf..bfc68e89d1 100644
--- a/src/plugins/resourceeditor/qrceditor/resourcefile.cpp
+++ b/src/plugins/resourceeditor/qrceditor/resourcefile.cpp
@@ -795,7 +795,7 @@ QVariant ResourceModel::data(const QModelIndex &index, int role) const
// File node
Q_ASSERT(file);
if (!file->exists())
- result = Utils::creatorTheme()->color(Utils::Theme::TextColorError);
+ result = Utils::creatorColor(Utils::Theme::TextColorError);
}
break;
default:
diff --git a/src/plugins/rustls/CMakeLists.txt b/src/plugins/rustls/CMakeLists.txt
new file mode 100644
index 0000000000..17184ddf13
--- /dev/null
+++ b/src/plugins/rustls/CMakeLists.txt
@@ -0,0 +1,4 @@
+add_qtc_lua_plugin(rustls
+ SOURCES rustls/rustls.lua
+ rustls/init.lua
+)
diff --git a/src/plugins/rustls/rustls.qbs b/src/plugins/rustls/rustls.qbs
new file mode 100644
index 0000000000..f629705f09
--- /dev/null
+++ b/src/plugins/rustls/rustls.qbs
@@ -0,0 +1,8 @@
+QtcLuaPlugin {
+ name: "rustls"
+
+ luafiles: [
+ "init.lua",
+ "rustls.lua",
+ ]
+}
diff --git a/src/plugins/rustls/rustls/init.lua b/src/plugins/rustls/rustls/init.lua
new file mode 100644
index 0000000000..474c368f3a
--- /dev/null
+++ b/src/plugins/rustls/rustls/init.lua
@@ -0,0 +1,200 @@
+-- Copyright (C) 2024 The Qt Company Ltd.
+-- SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+local LSP = require('LSP')
+local mm = require('MessageManager')
+local Utils = require('Utils')
+local Process = require('Process')
+local S = require('Settings')
+local Gui = require('Gui')
+local a = require('async')
+local fetch = require('Fetch').fetch
+local Install = require('Install')
+
+Settings = {}
+
+local function createCommand()
+ local cmd = { Settings.binary.expandedValue:nativePath() }
+ return cmd
+end
+
+local function filter(tbl, callback)
+ for i = #tbl, 1, -1 do
+ if not callback(tbl[i]) then
+ table.remove(tbl, i)
+ end
+ end
+end
+
+local function installOrUpdateServer()
+ local data = a.wait(fetch({
+ url = "https://api.github.com/repos/rust-lang/rust-analyzer/releases?per_page=2",
+ convertToTable = true,
+ headers = {
+ Accept = "application/vnd.github.v3+json",
+ ["X-GitHub-Api-Version"] = "2022-11-28"
+ }
+ }))
+
+ if type(data) == "table" and #data > 1 then
+ local r = data[1]
+ if r.prerelease then
+ r = data[2]
+ end
+ local lspPkgInfo = Install.packageInfo("rust-analyzer")
+ if not lspPkgInfo or lspPkgInfo.version ~= r.tag_name then
+ local osTr = { mac = "apple-darwin", windows = "pc-windows-msvc", linux = "unknown-linux-gnu" }
+ local archTr = { unknown = "", x86 = "", x86_64 = "x86_64", itanium = "", arm = "", arm64 = "aarch64" }
+ local extTr = { mac = "gz", windows = "zip", linux = "gz" }
+ local os = osTr[Utils.HostOsInfo.os]
+ local arch = archTr[Utils.HostOsInfo.architecture]
+ local ext = extTr[Utils.HostOsInfo.os]
+
+ local expectedFileName = "rust-analyzer-" .. arch .. "-" .. os .. "." .. ext
+
+ filter(r.assets, function(asset)
+ return string.find(asset.name, expectedFileName, 1, true) == 1
+ end)
+
+ if #r.assets == 0 then
+ print("No assets found for this platform")
+ return
+ end
+ local res, err = a.wait(Install.install(
+ "Do you want to install the rust-analyzer?", {
+ name = "rust-analyzer",
+ url = r.assets[1].browser_download_url,
+ version = r.tag_name
+ }))
+
+ if not res then
+ mm.writeFlashing("Failed to install rust-analyzer: " .. err)
+ return
+ end
+
+ lspPkgInfo = Install.packageInfo("rust-analyzer")
+ print("Installed:", lspPkgInfo.name, "version:", lspPkgInfo.version, "at", lspPkgInfo.path)
+ end
+
+ local binary = "rust-analyzer"
+ if Utils.HostOsInfo.isWindowsHost() then
+ binary = "rust-analyzer.exe"
+ end
+
+ Settings.binary:setValue(lspPkgInfo.path:resolvePath(binary))
+ Settings:apply()
+ return
+ end
+
+ if type(data) == "string" then
+ print("Failed to fetch:", data)
+ else
+ print("No rust-analyzer release found.")
+ end
+end
+IsTryingToInstall = false
+
+local function setupClient()
+ Client = LSP.Client.create({
+ name = 'Rust Language Server',
+ cmd = createCommand,
+ transport = 'stdio',
+ languageFilter = {
+ patterns = { '*.rs' },
+ mimeTypes = { 'text/rust' }
+ },
+ settings = Settings,
+ startBehavior = "RequiresProject",
+ onStartFailed = function()
+ a.sync(function()
+ if IsTryingToInstall == true then
+ return
+ end
+ IsTryingToInstall = true
+ installOrUpdateServer()
+ IsTryingToInstall = false
+ end)()
+ end
+ })
+end
+
+local function using(tbl)
+ local result = _G
+ for k, v in pairs(tbl) do result[k] = v end
+ return result
+end
+local function layoutSettings()
+ --- "using namespace Gui"
+ local _ENV = using(Gui)
+
+ local layout = Form {
+ Settings.binary, br,
+ Row {
+ PushButton {
+ text = "Try to install Rust language server",
+ onClicked = function() a.sync(installOrUpdateServer)() end,
+ br,
+ },
+ st
+ }
+ }
+
+ return layout
+end
+
+local function binaryFromPkg()
+ local lspPkgInfo = Install.packageInfo("rust-analyzer")
+ if lspPkgInfo then
+ local binary = "rust-analyzer"
+ if Utils.HostOsInfo.isWindowsHost() then
+ binary = "rust-analyzer.exe"
+ end
+ local binaryPath = lspPkgInfo.path:resolvePath(binary)
+ if binaryPath:isExecutableFile() == true then
+ return binaryPath
+ end
+ end
+
+ return nil
+end
+
+local function findBinary()
+ local binary = binaryFromPkg()
+ if binary then
+ return binary
+ end
+
+ -- Search for the binary in the PATH
+ local serverPath = Utils.FilePath.fromUserInput("rust-analyzer")
+ local absolute = a.wait(serverPath:searchInPath()):resolveSymlinks()
+ if absolute:isExecutableFile() == true then
+ return absolute
+ end
+ return serverPath
+end
+local function setupAspect()
+ ---@class Settings: AspectContainer
+ Settings = S.AspectContainer.create({
+ autoApply = false,
+ layouter = layoutSettings,
+ });
+
+ Settings.binary = S.FilePathAspect.create({
+ settingsKey = "Rustls.Binary",
+ displayName = "Binary",
+ labelText = "Binary:",
+ toolTip = "The path to the rust analyzer binary.",
+ expectedKind = S.Kind.ExistingCommand,
+ defaultPath = findBinary(),
+ })
+
+ return Settings
+end
+
+local function setup(parameters)
+ setupAspect()
+ setupClient()
+end
+
+return {
+ setup = function() a.sync(setup)() end,
+}
diff --git a/src/plugins/rustls/rustls/rustls.lua b/src/plugins/rustls/rustls/rustls.lua
new file mode 100644
index 0000000000..ae2a5c6eb4
--- /dev/null
+++ b/src/plugins/rustls/rustls/rustls.lua
@@ -0,0 +1,24 @@
+-- Copyright (C) 2024 The Qt Company Ltd.
+-- SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+return {
+ Name = "RustLanguageServer",
+ Version = "1.0.0",
+ CompatVersion = "1.0.0",
+ Vendor = "The Qt Company",
+ Category = "Language Client",
+ Description = "The Rust Language Server",
+ Experimental = false,
+ DisabledByDefault = false,
+ LongDescription = [[
+This plugin provides the Rust Language Server.
+It will try to install it if it is not found.
+ ]],
+ Dependencies = {
+ { Name = "Core", Version = "13.0.82", Required = true },
+ { Name = "Lua", Version = "13.0.82", Required = true },
+ { Name = "LuaLanguageClient", Version = "13.0.82", Required = true }
+ },
+ setup = function()
+ require 'init'.setup()
+ end,
+} --[[@as QtcPlugin]]
diff --git a/src/plugins/saferenderer/SafeRenderer.json.in b/src/plugins/saferenderer/SafeRenderer.json.in
index 0f569e539c..3b313c7cff 100644
--- a/src/plugins/saferenderer/SafeRenderer.json.in
+++ b/src/plugins/saferenderer/SafeRenderer.json.in
@@ -14,7 +14,13 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Device Support",
- "Description" : "Helper plugin for Qt Safe Renderer projects.",
+ "Description" : "Render safety-critical items in functional safety systems",
+ "LongDescription" : [
+ "Qt Safe Renderer separates the safety-critical rendering from the other parts of the system, so it can render safety-critical UI elements even if there are failures in the main UI. Therefore, you can use Qt in a system requiring certification without changing the Qt libraries.",
+ "You also need:",
+ "- Qt Safe Renderer",
+ "- A Qt version supported by the Qt Safe Renderer"
+ ],
"Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES},
diff --git a/src/plugins/screenrecorder/ScreenRecorder.json.in b/src/plugins/screenrecorder/ScreenRecorder.json.in
index 671147ff0d..f982ee3d83 100644
--- a/src/plugins/screenrecorder/ScreenRecorder.json.in
+++ b/src/plugins/screenrecorder/ScreenRecorder.json.in
@@ -14,7 +14,8 @@
],
"DisabledByDefault" : true,
"SoftLoadable" : true,
- "Description" : "Screen recording.",
- "Url" : "http://www.qt.io",
+ "Description" : "Record screens",
+ "LongDescription" : [],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/screenrecorder/cropandtrim.cpp b/src/plugins/screenrecorder/cropandtrim.cpp
index 4f65469e62..14f4c7fb4a 100644
--- a/src/plugins/screenrecorder/cropandtrim.cpp
+++ b/src/plugins/screenrecorder/cropandtrim.cpp
@@ -324,7 +324,7 @@ CropWidget::CropWidget(QWidget *parent)
saveImageButton,
copyImageToClipboardButton,
},
- noMargin(),
+ noMargin,
}.attachTo(this);
connect(m_xSpinBox, &QSpinBox::valueChanged, this, &CropWidget::onSpinBoxChanged);
@@ -526,19 +526,19 @@ TrimWidget::TrimWidget(const ClipInfo &clip, QWidget *parent)
using namespace Layouting;
Column {
- Row { m_frameSlider, m_currentTime, "/", m_clipDuration },
+ Row { m_frameSlider, m_currentTime, QString("/"), m_clipDuration },
Group {
title(Tr::tr("Trimming")),
Row {
m_trimStart.button, m_trimStart.timeLabel,
Space(20),
m_trimEnd.button, m_trimEnd.timeLabel,
- Stretch(), Space(20),
+ st, Space(20),
Tr::tr("Range:"), m_trimRange,
m_trimResetButton,
},
},
- noMargin(),
+ noMargin,
}.attachTo(this);
connect(m_frameSlider, &QSlider::valueChanged, this, [this] {
@@ -652,7 +652,7 @@ CropAndTrimDialog::CropAndTrimDialog(const ClipInfo &clip, QWidget *parent)
using namespace Layouting;
Column {
Group {
- title("Cropping"),
+ title(Tr::tr("Cropping")),
Column { m_cropWidget },
},
Space(16),
@@ -698,7 +698,7 @@ void CropAndTrimDialog::startFrameFetch()
if (m_nextFetchFrame == -1)
return;
- const CommandLine cl = {
+ const CommandLine cl{
Internal::settings().ffmpegTool(),
{
"-v", "error",
@@ -760,7 +760,7 @@ CropAndTrimWidget::CropAndTrimWidget(QWidget *parent)
Row {
m_button,
m_cropSizeWarningIcon,
- noMargin(), spacing(0),
+ noMargin, spacing(0),
}.attachTo(this);
connect(m_button, &QPushButton::clicked, this, [this] {
diff --git a/src/plugins/screenrecorder/export.cpp b/src/plugins/screenrecorder/export.cpp
index 15f38993ad..00daed94a8 100644
--- a/src/plugins/screenrecorder/export.cpp
+++ b/src/plugins/screenrecorder/export.cpp
@@ -144,7 +144,7 @@ ExportWidget::ExportWidget(QWidget *parent)
exportButton->setText(Tr::tr("Export..."));
using namespace Layouting;
- Row { st, new StyledSeparator, exportButton, noMargin(), spacing(0) }.attachTo(this);
+ Row { st, new StyledSeparator, exportButton, noMargin, spacing(0) }.attachTo(this);
connect(exportButton, &QToolButton::clicked, this, [this] {
FilePathAspect &lastDir = Internal::settings().exportLastDirectory;
diff --git a/src/plugins/screenrecorder/ffmpegutils.cpp b/src/plugins/screenrecorder/ffmpegutils.cpp
index fa0a6f45ae..9f1abb8ec8 100644
--- a/src/plugins/screenrecorder/ffmpegutils.cpp
+++ b/src/plugins/screenrecorder/ffmpegutils.cpp
@@ -152,7 +152,7 @@ static QVersionNumber parseVersionNumber(const QByteArray &toolOutput)
QVersionNumber toolVersion()
{
Process proc;
- const CommandLine cl = {
+ const CommandLine cl{
Internal::settings().ffprobeTool(),
{
"-v", "quiet",
@@ -201,7 +201,7 @@ static ClipInfo parseClipInfo(const QByteArray &toolOutput)
ClipInfo clipInfo(const FilePath &path)
{
Process proc;
- const CommandLine cl = {
+ const CommandLine cl{
Internal::settings().ffprobeTool(),
{
"-v", "quiet",
diff --git a/src/plugins/screenrecorder/record.cpp b/src/plugins/screenrecorder/record.cpp
index aba61ed2eb..70e1d5e275 100644
--- a/src/plugins/screenrecorder/record.cpp
+++ b/src/plugins/screenrecorder/record.cpp
@@ -21,6 +21,7 @@
#include <QAction>
#include <QDialogButtonBox>
#include <QGuiApplication>
+#include <QLayout>
#include <QLoggingCategory>
#include <QMessageBox>
#include <QScreen>
@@ -224,7 +225,7 @@ RecordWidget::RecordWidget(const FilePath &recordFile, QWidget *parent)
st,
progressLabel,
Space(6),
- noMargin(), spacing(0),
+ noMargin, spacing(0),
}.attachTo(this);
connect(settingsButton, &QToolButton::clicked, this, [this] {
diff --git a/src/plugins/screenrecorder/screenrecorderplugin.cpp b/src/plugins/screenrecorder/screenrecorderplugin.cpp
index 9e42b2979f..d7f93066e6 100644
--- a/src/plugins/screenrecorder/screenrecorderplugin.cpp
+++ b/src/plugins/screenrecorder/screenrecorderplugin.cpp
@@ -29,6 +29,7 @@
#include <coreplugin/icore.h>
#include <QDialog>
+#include <QLayout>
using namespace Utils;
using namespace Core;
@@ -56,7 +57,7 @@ public:
Column {
m_recordWidget,
Row { m_cropAndTrimStatusWidget, m_exportWidget },
- noMargin(), spacing(0),
+ noMargin, spacing(0),
}.attachTo(this);
auto setLowerRowEndabled = [this] (bool enabled) {
@@ -86,7 +87,7 @@ public:
});
m_spinner = new SpinnerSolution::Spinner(SpinnerSolution::SpinnerSize::Medium, this);
- m_spinner->setColor(creatorTheme()->color(Theme::IconsBaseColor));
+ m_spinner->setColor(creatorColor(Theme::IconsBaseColor));
m_spinner->hide();
layout()->setSizeConstraint(QLayout::SetFixedSize);
diff --git a/src/plugins/scxmleditor/ScxmlEditor.json.in b/src/plugins/scxmleditor/ScxmlEditor.json.in
index 3b4351f78b..2c1c7ad9ea 100644
--- a/src/plugins/scxmleditor/ScxmlEditor.json.in
+++ b/src/plugins/scxmleditor/ScxmlEditor.json.in
@@ -13,8 +13,11 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Modeling",
- "Description" : "Visual Editor for SCXML (State Chart XML) files.",
- "Url" : "http://www.qt.io",
+ "Description" : "Create SCXML (State Chart XML) files",
+ "LongDescription" : [
+ "Visualize how a system reacts to events. State charts define the states that the system can be in, and how the system can move from one state to another."
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES},
"Mimetypes" : [
diff --git a/src/plugins/scxmleditor/common/shapestoolbox.cpp b/src/plugins/scxmleditor/common/shapestoolbox.cpp
index 82def88379..3fa15c562b 100644
--- a/src/plugins/scxmleditor/common/shapestoolbox.cpp
+++ b/src/plugins/scxmleditor/common/shapestoolbox.cpp
@@ -33,7 +33,7 @@ ShapesToolbox::ShapesToolbox(QWidget *parent)
Column {
spacing(0),
scrollArea,
- noMargin,
+ noMargin
}.attachTo(this);
}
diff --git a/src/plugins/scxmleditor/common/stateview.cpp b/src/plugins/scxmleditor/common/stateview.cpp
index 517f53bd3e..c31dd4f850 100644
--- a/src/plugins/scxmleditor/common/stateview.cpp
+++ b/src/plugins/scxmleditor/common/stateview.cpp
@@ -33,7 +33,7 @@ StateView::StateView(StateItem *state, QWidget *parent)
using namespace Layouting;
Row {
- PushButton{ text("Back"), onClicked([this] { closeView(); }, this) },
+ PushButton{ text(QString("Back")), onClicked([this] { closeView(); }, this) },
stateNameLabel,
noMargin
}.attachTo(titleBar);
diff --git a/src/plugins/scxmleditor/plugin_interface/scattributeitemdelegate.cpp b/src/plugins/scxmleditor/plugin_interface/scattributeitemdelegate.cpp
index fa735e5d68..0121ddf191 100644
--- a/src/plugins/scxmleditor/plugin_interface/scattributeitemdelegate.cpp
+++ b/src/plugins/scxmleditor/plugin_interface/scattributeitemdelegate.cpp
@@ -19,12 +19,12 @@ QWidget *SCAttributeItemDelegate::createEditor(QWidget *parent, const QStyleOpti
Q_UNUSED(option)
switch (index.data(DataTypeRole).toInt()) {
- case QVariant::StringList: {
+ case QMetaType::QStringList: {
auto combo = new QComboBox(parent);
combo->setFocusPolicy(Qt::StrongFocus);
return combo;
}
- case QVariant::String: {
+ case QMetaType::QString: {
if (index.column() == 0) {
auto edit = new QLineEdit(parent);
edit->setFocusPolicy(Qt::StrongFocus);
@@ -52,7 +52,7 @@ void SCAttributeItemDelegate::updateEditorGeometry(QWidget *editor, const QStyle
void SCAttributeItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
switch (index.data(DataTypeRole).toInt()) {
- case QVariant::StringList: {
+ case QMetaType::QStringList: {
auto combo = qobject_cast<QComboBox*>(editor);
if (combo) {
combo->clear();
diff --git a/src/plugins/scxmleditor/plugin_interface/scattributeitemmodel.cpp b/src/plugins/scxmleditor/plugin_interface/scattributeitemmodel.cpp
index 454af2930f..c759bcca73 100644
--- a/src/plugins/scxmleditor/plugin_interface/scattributeitemmodel.cpp
+++ b/src/plugins/scxmleditor/plugin_interface/scattributeitemmodel.cpp
@@ -84,7 +84,7 @@ QVariant SCAttributeItemModel::data(const QModelIndex &index, int role) const
}
} else {
if (bEditable) {
- if (m_tag->tagType() > MetadataItem && m_tag->info()->attributes[index.row()].datatype == QVariant::StringList)
+ if (m_tag->tagType() > MetadataItem && m_tag->info()->attributes[index.row()].datatype == QMetaType::QStringList)
return QString::fromLatin1(m_tag->info()->attributes[index.row()].value).split(";");
else
return m_tag->attribute(index.row());
@@ -100,11 +100,11 @@ QVariant SCAttributeItemModel::data(const QModelIndex &index, int role) const
break;
case DataTypeRole: {
if (m_tag->tagType() == Metadata || m_tag->tagType() == MetadataItem)
- return (int)QVariant::String;
+ return (int)QMetaType::QString;
else if (index.column() == 1 && m_tag->info()->n_attributes > 0)
return m_tag->info()->attributes[index.row()].datatype;
else
- return QVariant::Invalid;
+ return {};
}
case DataRole: {
if (m_tag->info()->n_attributes > 0)
diff --git a/src/plugins/scxmleditor/plugin_interface/scxmltypes.h b/src/plugins/scxmleditor/plugin_interface/scxmltypes.h
index 62fe00528a..0224fb8cb2 100644
--- a/src/plugins/scxmleditor/plugin_interface/scxmltypes.h
+++ b/src/plugins/scxmleditor/plugin_interface/scxmltypes.h
@@ -65,119 +65,119 @@ struct scxmltag_type_t
// Define tag-attributes
const scxmltag_attribute_t scxml_scxml_attributes[] = {
- {"initial", nullptr, false, false, QVariant::String},
- {"name", nullptr, false, true, QVariant::String},
- {"xmlns", "http://www.w3.org/2005/07/scxml", true, false, QVariant::String},
- {"version", "1.0", true, false, QVariant::String},
- {"datamodel", nullptr, false, true, QVariant::String},
- {"binding", "early;late", false, true, QVariant::StringList}
+ {"initial", nullptr, false, false, QMetaType::QString},
+ {"name", nullptr, false, true, QMetaType::QString},
+ {"xmlns", "http://www.w3.org/2005/07/scxml", true, false, QMetaType::QString},
+ {"version", "1.0", true, false, QMetaType::QString},
+ {"datamodel", nullptr, false, true, QMetaType::QString},
+ {"binding", "early;late", false, true, QMetaType::QStringList}
};
const scxmltag_attribute_t scxml_state_attributes[] = {
- {"id", nullptr, false, true, QVariant::String},
- {"initial", nullptr, false, false, QVariant::String}
+ {"id", nullptr, false, true, QMetaType::QString},
+ {"initial", nullptr, false, false, QMetaType::QString}
};
const scxmltag_attribute_t scxml_parallel_attributes[] = {
- {"id", nullptr, false, true, QVariant::String}
+ {"id", nullptr, false, true, QMetaType::QString}
};
const scxmltag_attribute_t scxml_transition_attributes[] = {
- {"event", nullptr, false, true, QVariant::String},
- {"cond", nullptr, false, true, QVariant::String},
- {"target", nullptr, false, true, QVariant::String},
- {"type", "internal;external", false, true, QVariant::StringList}
+ {"event", nullptr, false, true, QMetaType::QString},
+ {"cond", nullptr, false, true, QMetaType::QString},
+ {"target", nullptr, false, true, QMetaType::QString},
+ {"type", "internal;external", false, true, QMetaType::QStringList}
};
const scxmltag_attribute_t scxml_initialtransition_attributes[] = {
- {"target", nullptr, false, false, QVariant::String}
+ {"target", nullptr, false, false, QMetaType::QString}
};
const scxmltag_attribute_t scxml_final_attributes[] = {
- {"id", nullptr, false, true, QVariant::String}
+ {"id", nullptr, false, true, QMetaType::QString}
};
const scxmltag_attribute_t scxml_history_attributes[] = {
- {"id", nullptr, false, true, QVariant::String},
- {"type", "shallow;deep", false, true, QVariant::StringList}
+ {"id", nullptr, false, true, QMetaType::QString},
+ {"type", "shallow;deep", false, true, QMetaType::QStringList}
};
const scxmltag_attribute_t scxml_raise_attributes[] = {
- {"event", nullptr, true, true, QVariant::String}
+ {"event", nullptr, true, true, QMetaType::QString}
};
const scxmltag_attribute_t scxml_if_attributes[] = {
- {"cond", nullptr, true, true, QVariant::String},
+ {"cond", nullptr, true, true, QMetaType::QString},
};
const scxmltag_attribute_t scxml_elseif_attributes[] = {
- {"cond", nullptr, true, true, QVariant::String}
+ {"cond", nullptr, true, true, QMetaType::QString}
};
const scxmltag_attribute_t scxml_foreach_attributes[] = {
- {"array", nullptr, true, true, QVariant::String},
- {"item", nullptr, true, true, QVariant::String},
- {"index", nullptr, false, true, QVariant::String}
+ {"array", nullptr, true, true, QMetaType::QString},
+ {"item", nullptr, true, true, QMetaType::QString},
+ {"index", nullptr, false, true, QMetaType::QString}
};
const scxmltag_attribute_t scxml_log_attributes[] = {
- {"label", "", false, true, QVariant::String},
- {"expr", nullptr, false, true, QVariant::String}
+ {"label", "", false, true, QMetaType::QString},
+ {"expr", nullptr, false, true, QMetaType::QString}
};
const scxmltag_attribute_t scxml_data_attributes[] = {
- {"id", nullptr, true, true, QVariant::String},
- {"src", nullptr, false, true, QVariant::String},
- {"expr", nullptr, false, true, QVariant::String}
+ {"id", nullptr, true, true, QMetaType::QString},
+ {"src", nullptr, false, true, QMetaType::QString},
+ {"expr", nullptr, false, true, QMetaType::QString}
};
const scxmltag_attribute_t scxml_assign_attributes[] = {
- {"location", nullptr, true, true, QVariant::String},
- {"expr", nullptr, false, true, QVariant::String}
+ {"location", nullptr, true, true, QMetaType::QString},
+ {"expr", nullptr, false, true, QMetaType::QString}
};
const scxmltag_attribute_t scxml_content_attributes[] = {
- {"expr", nullptr, false, true, QVariant::String}
+ {"expr", nullptr, false, true, QMetaType::QString}
};
const scxmltag_attribute_t scxml_param_attributes[] = {
- {"name", nullptr, true, true, QVariant::String},
- {"expr", nullptr, false, true, QVariant::String},
- {"location", nullptr, false, true, QVariant::String}
+ {"name", nullptr, true, true, QMetaType::QString},
+ {"expr", nullptr, false, true, QMetaType::QString},
+ {"location", nullptr, false, true, QMetaType::QString}
};
const scxmltag_attribute_t scxml_script_attributes[] = {
- {"src", nullptr, false, true, QVariant::String}
+ {"src", nullptr, false, true, QMetaType::QString}
};
const scxmltag_attribute_t scxml_send_attributes[] = {
- {"event", nullptr, false, true, QVariant::String},
- {"eventexpr", nullptr, false, true, QVariant::String},
- {"target", nullptr, false, true, QVariant::String},
- {"targetexpr", nullptr, false, true, QVariant::String},
- {"type", nullptr, false, true, QVariant::String},
- {"typeexpr", nullptr, false, true, QVariant::String},
- {"id", nullptr, false, true, QVariant::String},
- {"idlocation", nullptr, false, true, QVariant::String},
- {"delay", nullptr, false, true, QVariant::String},
- {"delayexpr", nullptr, false, true, QVariant::String},
- {"namelist", nullptr, false, true, QVariant::String}
+ {"event", nullptr, false, true, QMetaType::QString},
+ {"eventexpr", nullptr, false, true, QMetaType::QString},
+ {"target", nullptr, false, true, QMetaType::QString},
+ {"targetexpr", nullptr, false, true, QMetaType::QString},
+ {"type", nullptr, false, true, QMetaType::QString},
+ {"typeexpr", nullptr, false, true, QMetaType::QString},
+ {"id", nullptr, false, true, QMetaType::QString},
+ {"idlocation", nullptr, false, true, QMetaType::QString},
+ {"delay", nullptr, false, true, QMetaType::QString},
+ {"delayexpr", nullptr, false, true, QMetaType::QString},
+ {"namelist", nullptr, false, true, QMetaType::QString}
};
const scxmltag_attribute_t scxml_cancel_attributes[] = {
- {"sendid", nullptr, false, true, QVariant::String},
- {"sendidexpr", nullptr, false, true, QVariant::String}
+ {"sendid", nullptr, false, true, QMetaType::QString},
+ {"sendidexpr", nullptr, false, true, QMetaType::QString}
};
const scxmltag_attribute_t scxml_invoke_attributes[] = {
- {"type", nullptr, false, true, QVariant::String},
- {"typeexpr", nullptr, false, true, QVariant::String},
- {"src", nullptr, false, true, QVariant::String},
- {"srcexpr", nullptr, false, true, QVariant::String},
- {"id", nullptr, false, true, QVariant::String},
- {"idlocation", nullptr, false, true, QVariant::String},
- {"namelist", nullptr, false, true, QVariant::String},
- {"autoforward", ";true;false", false, true, QVariant::StringList}
+ {"type", nullptr, false, true, QMetaType::QString},
+ {"typeexpr", nullptr, false, true, QMetaType::QString},
+ {"src", nullptr, false, true, QMetaType::QString},
+ {"srcexpr", nullptr, false, true, QMetaType::QString},
+ {"id", nullptr, false, true, QMetaType::QString},
+ {"idlocation", nullptr, false, true, QMetaType::QString},
+ {"namelist", nullptr, false, true, QMetaType::QString},
+ {"autoforward", ";true;false", false, true, QMetaType::QStringList}
};
const scxmltag_type_t scxml_unknown = {
diff --git a/src/plugins/serialterminal/SerialTerminal.json.in b/src/plugins/serialterminal/SerialTerminal.json.in
index 78ac127ab4..4cbefbaa20 100644
--- a/src/plugins/serialterminal/SerialTerminal.json.in
+++ b/src/plugins/serialterminal/SerialTerminal.json.in
@@ -15,6 +15,6 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Description" : "Serial Port Terminal",
- "Url" : "http://www.qt.io",
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/silversearcher/SilverSearcher.json.in b/src/plugins/silversearcher/SilverSearcher.json.in
index bfaf553635..8d6aacee69 100644
--- a/src/plugins/silversearcher/SilverSearcher.json.in
+++ b/src/plugins/silversearcher/SilverSearcher.json.in
@@ -13,7 +13,12 @@
"",
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
- "Description" : "Adds possibility to use SilverSearcher tool as an alternative mechanism of 'find in files'",
- "Url" : "http://www.qt.io",
+ "Description" : "Search with Silver Searcher",
+ "LongDescription" : [
+ "Use the Silver Searcher tool to find in files.",
+ "You also need:",
+ "- Silver Searcher"
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/silversearcher/findinfilessilversearcher.cpp b/src/plugins/silversearcher/findinfilessilversearcher.cpp
index cb290e046d..57a2683428 100644
--- a/src/plugins/silversearcher/findinfilessilversearcher.cpp
+++ b/src/plugins/silversearcher/findinfilessilversearcher.cpp
@@ -128,14 +128,15 @@ public:
QString toolTip() const final { return {}; }
QWidget *widget() const final { return m_widget; }
- void readSettings(QtcSettings *settings) final
+ void readSettings(const Store &s) final
{
- m_searchOptionsLineEdit->setText(settings->value(s_searchOptionsString).toString());
+ m_searchOptionsLineEdit->setText(s.value(s_searchOptionsString).toString());
}
- void writeSettings(QtcSettings *settings) const final
+ void writeSettings(Store &s) const final
{
- settings->setValue(s_searchOptionsString, m_searchOptionsLineEdit->text());
+ if (!m_searchOptionsLineEdit->text().isEmpty())
+ s.insert(s_searchOptionsString, m_searchOptionsLineEdit->text());
}
SearchExecutor searchExecutor() const final
diff --git a/src/plugins/squish/Squish.json.in b/src/plugins/squish/Squish.json.in
index faa56b6837..3b52e09fba 100644
--- a/src/plugins/squish/Squish.json.in
+++ b/src/plugins/squish/Squish.json.in
@@ -13,8 +13,13 @@
"",
"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."
],
-"Description" : "Squish plugin. Provides integration of Squish.",
-"Url" : "http://www.qt.io",
+"Description" : "Test applications with the Squish automated GUI testing framework",
+"LongDescription" : [
+ "Map AUTs (application under test) and run Squish test suites and cases.",
+ "You also need:",
+ "- Access to a Squish server"
+],
+"Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES},
"Mimetypes" : [
diff --git a/src/plugins/squish/squishoutputpane.cpp b/src/plugins/squish/squishoutputpane.cpp
index adb73b6b7e..7db24f4eef 100644
--- a/src/plugins/squish/squishoutputpane.cpp
+++ b/src/plugins/squish/squishoutputpane.cpp
@@ -44,8 +44,8 @@ SquishOutputPane::SquishOutputPane()
m_outputWidget->setLayout(outputLayout);
QPalette pal;
- pal.setColor(QPalette::Window, Utils::creatorTheme()->color(Utils::Theme::InfoBarBackground));
- pal.setColor(QPalette::WindowText, Utils::creatorTheme()->color(Utils::Theme::InfoBarText));
+ pal.setColor(QPalette::Window, Utils::creatorColor(Utils::Theme::InfoBarBackground));
+ pal.setColor(QPalette::WindowText, Utils::creatorColor(Utils::Theme::InfoBarText));
m_summaryWidget = new QFrame;
m_summaryWidget->setPalette(pal);
diff --git a/src/plugins/squish/squishperspective.cpp b/src/plugins/squish/squishperspective.cpp
index 1b762c71a4..4eb63cbb7e 100644
--- a/src/plugins/squish/squishperspective.cpp
+++ b/src/plugins/squish/squishperspective.cpp
@@ -57,9 +57,9 @@ static QIcon iconForType(IconType type)
static QString customStyleSheet(bool extended)
{
- static const QString red = Utils::creatorTheme()->color(
+ static const QString red = Utils::creatorColor(
Utils::Theme::ProgressBarColorError).name();
- static const QString green = Utils::creatorTheme()->color(
+ static const QString green = Utils::creatorColor(
Utils::Theme::ProgressBarColorFinished).name();
if (!extended)
return "QProgressBar {text-align:left; border:0px}";
diff --git a/src/plugins/squish/squishserverprocess.cpp b/src/plugins/squish/squishserverprocess.cpp
index 9e4b974143..3af7bf308a 100644
--- a/src/plugins/squish/squishserverprocess.cpp
+++ b/src/plugins/squish/squishserverprocess.cpp
@@ -26,9 +26,8 @@ void SquishServerProcess::stop()
{
if (m_process.state() != QProcess::NotRunning && m_serverPort > 0) {
Utils::Process serverKiller;
- QStringList args;
- args << "--stop" << "--port" << QString::number(m_serverPort);
- serverKiller.setCommand({m_process.commandLine().executable(), args});
+ serverKiller.setCommand({m_process.commandLine().executable(),
+ {"--stop", "--port", QString::number(m_serverPort)}});
serverKiller.setEnvironment(m_process.environment());
serverKiller.start();
if (!serverKiller.waitForFinished()) {
diff --git a/src/plugins/squish/squishsettings.cpp b/src/plugins/squish/squishsettings.cpp
index a8e0518a3d..1b8c799eda 100644
--- a/src/plugins/squish/squishsettings.cpp
+++ b/src/plugins/squish/squishsettings.cpp
@@ -387,10 +387,10 @@ SquishServerSettingsWidget::SquishServerSettingsWidget(QWidget *parent)
using namespace Layouting;
Form grid {
&m_applicationsView, br,
- &m_serverSettings.autTimeout, br,
- &m_serverSettings.responseTimeout, br,
- &m_serverSettings.postMortemWaitTime, br,
- &m_serverSettings.animatedCursor, br,
+ m_serverSettings.autTimeout, br,
+ m_serverSettings.responseTimeout, br,
+ m_serverSettings.postMortemWaitTime, br,
+ m_serverSettings.animatedCursor, br,
};
Column buttonCol {
add,
diff --git a/src/plugins/squish/squishtools.cpp b/src/plugins/squish/squishtools.cpp
index f6ef2e67e0..68dabd6449 100644
--- a/src/plugins/squish/squishtools.cpp
+++ b/src/plugins/squish/squishtools.cpp
@@ -554,8 +554,8 @@ void SquishTools::startSquishServer(Request request)
m_perspective.updateStatus(Tr::tr("Running test case"));
}
- const QStringList arguments = serverArgumentsFromSettings();
- m_serverProcess.start({toolsSettings.serverPath, arguments}, squishEnvironment());
+ m_serverProcess.start({toolsSettings.serverPath, serverArgumentsFromSettings()},
+ squishEnvironment());
}
void SquishTools::stopSquishServer()
@@ -569,13 +569,10 @@ void SquishTools::startSquishRunner()
if (!isValidToStartRunner() || !setupRunnerPath())
return;
- const QStringList args = runnerArgumentsFromSettings();
-
if (m_request == RecordTestRequested)
m_closeRunnerOnEndRecord = true;
- Utils::CommandLine cmdLine = {toolsSettings.runnerPath, args};
- setupAndStartSquishRunnerProcess(cmdLine);
+ setupAndStartSquishRunnerProcess({toolsSettings.runnerPath, runnerArgumentsFromSettings()});
}
void SquishTools::setupAndStartRecorder()
@@ -604,7 +601,7 @@ void SquishTools::setupAndStartRecorder()
m_secondaryRunner = new SquishRunnerProcess(this);
m_secondaryRunner->setupProcess(SquishRunnerProcess::Record);
- const CommandLine cmd = {toolsSettings.runnerPath, args};
+ const CommandLine cmd{toolsSettings.runnerPath, args};
connect(m_secondaryRunner, &SquishRunnerProcess::recorderDone,
this, &SquishTools::onRecorderFinished);
qCDebug(LOG) << "Recorder starting:" << cmd.toUserOutput();
@@ -629,7 +626,7 @@ void SquishTools::setupAndStartInspector()
m_secondaryRunner = new SquishRunnerProcess(this);
m_secondaryRunner->setupProcess(SquishRunnerProcess::Inspect);
- const CommandLine cmd = {toolsSettings.runnerPath, args};
+ const CommandLine cmd{toolsSettings.runnerPath, args};
connect(m_secondaryRunner, &SquishRunnerProcess::logOutputReceived,
this, &SquishTools::logOutputReceived);
connect(m_secondaryRunner, &SquishRunnerProcess::objectPicked,
@@ -676,8 +673,8 @@ void SquishTools::executeRunnerQuery()
if (!isValidToStartRunner() || !setupRunnerPath())
return;
- QStringList arguments = { "--port", QString::number(m_serverProcess.port()) };
- Utils::CommandLine cmdLine = {toolsSettings.runnerPath, arguments};
+ CommandLine cmdLine{toolsSettings.runnerPath,
+ {"--port", QString::number(m_serverProcess.port())}};
switch (m_query) {
case ServerInfo:
cmdLine.addArg("--info");
@@ -690,7 +687,7 @@ void SquishTools::executeRunnerQuery()
case SetGlobalScriptDirs:
cmdLine.addArg("--config");
cmdLine.addArg("setGlobalScriptDirs");
- cmdLine.addArgs(m_queryParameter, Utils::CommandLine::Raw);
+ cmdLine.addArgs(m_queryParameter, CommandLine::Raw);
break;
default:
QTC_ASSERT(false, return);
@@ -1106,10 +1103,9 @@ void SquishTools::interruptRunner()
{
qCDebug(LOG) << "Interrupting runner";
QTC_ASSERT(m_primaryRunner, return);
- qint64 processId = m_primaryRunner->processId();
- const CommandLine cmd(toolsSettings.processComPath, {QString::number(processId), "break"});
+ const qint64 processId = m_primaryRunner->processId();
Process process;
- process.setCommand(cmd);
+ process.setCommand({toolsSettings.processComPath, {QString::number(processId), "break"}});
process.start();
process.waitForFinished();
}
@@ -1122,10 +1118,9 @@ void SquishTools::terminateRunner()
m_perspective.updateStatus(Tr::tr("User stop initiated."));
// should we terminate the AUT instead of the runner?!?
QTC_ASSERT(m_primaryRunner, return);
- qint64 processId = m_primaryRunner->processId();
- const CommandLine cmd(toolsSettings.processComPath, {QString::number(processId), "terminate"});
+ const qint64 processId = m_primaryRunner->processId();
Process process;
- process.setCommand(cmd);
+ process.setCommand({toolsSettings.processComPath, {QString::number(processId), "terminate"}});
process.start();
process.waitForFinished();
}
@@ -1263,7 +1258,7 @@ bool SquishTools::setupRunnerPath()
return true;
}
-void SquishTools::setupAndStartSquishRunnerProcess(const Utils::CommandLine &cmdLine)
+void SquishTools::setupAndStartSquishRunnerProcess(const CommandLine &cmdLine)
{
QTC_ASSERT(m_primaryRunner, return);
// avoid crashes on fast re-usage of Process
diff --git a/src/plugins/squish/squishwizardpages.cpp b/src/plugins/squish/squishwizardpages.cpp
index 0ee6dab6b2..707421c413 100644
--- a/src/plugins/squish/squishwizardpages.cpp
+++ b/src/plugins/squish/squishwizardpages.cpp
@@ -319,7 +319,7 @@ bool SquishFileGenerator::setup(const QVariant &data, QString *errorMessage)
if (data.isNull())
return false;
- if (data.typeId() != QVariant::Map) {
+ if (data.typeId() != QMetaType::QVariantMap) {
*errorMessage = Tr::tr("Key is not an object.");
return false;
}
diff --git a/src/plugins/squish/testresult.cpp b/src/plugins/squish/testresult.cpp
index 7e229d50e0..38b9ca1d9a 100644
--- a/src/plugins/squish/testresult.cpp
+++ b/src/plugins/squish/testresult.cpp
@@ -46,29 +46,27 @@ QString TestResult::typeToString(Result::Type type)
QColor TestResult::colorForType(Result::Type type)
{
- Utils::Theme *creatorTheme = Utils::creatorTheme();
-
switch (type) {
case Result::Start:
case Result::Log:
case Result::Detail:
case Result::End:
- return creatorTheme->color(Utils::Theme::OutputPanes_StdOutTextColor);
+ return creatorColor(Utils::Theme::OutputPanes_StdOutTextColor);
case Result::Pass:
- return creatorTheme->color(Utils::Theme::OutputPanes_TestPassTextColor);
+ return creatorColor(Utils::Theme::OutputPanes_TestPassTextColor);
case Result::Fail:
case Result::Error:
- return creatorTheme->color(Utils::Theme::OutputPanes_TestFailTextColor);
+ return creatorColor(Utils::Theme::OutputPanes_TestFailTextColor);
case Result::ExpectedFail:
- return creatorTheme->color(Utils::Theme::OutputPanes_TestXFailTextColor);
+ return creatorColor(Utils::Theme::OutputPanes_TestXFailTextColor);
case Result::UnexpectedPass:
- return creatorTheme->color(Utils::Theme::OutputPanes_TestXPassTextColor);
+ return creatorColor(Utils::Theme::OutputPanes_TestXPassTextColor);
case Result::Warn:
- return creatorTheme->color(Utils::Theme::OutputPanes_TestWarnTextColor);
+ return creatorColor(Utils::Theme::OutputPanes_TestWarnTextColor);
case Result::Fatal:
- return creatorTheme->color(Utils::Theme::OutputPanes_TestFatalTextColor);
+ return creatorColor(Utils::Theme::OutputPanes_TestFatalTextColor);
}
- return creatorTheme->color(Utils::Theme::OutputPanes_StdOutTextColor);
+ return creatorColor(Utils::Theme::OutputPanes_StdOutTextColor);
}
} // namespace Internal
diff --git a/src/plugins/studiowelcome/StudioWelcome.json.in b/src/plugins/studiowelcome/StudioWelcome.json.in
index 9ee3c25172..5ab72606cf 100644
--- a/src/plugins/studiowelcome/StudioWelcome.json.in
+++ b/src/plugins/studiowelcome/StudioWelcome.json.in
@@ -14,6 +14,6 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Description" : "Qt Design Studio Welcome Page.",
- "Url" : "http://www.qt.io",
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/studiowelcome/studiowelcomeplugin.cpp b/src/plugins/studiowelcome/studiowelcomeplugin.cpp
index d526a9c436..6cb94efb35 100644
--- a/src/plugins/studiowelcome/studiowelcomeplugin.cpp
+++ b/src/plugins/studiowelcome/studiowelcomeplugin.cpp
@@ -338,30 +338,27 @@ public:
if (exampleVersion.isEmpty())
return true;
- const QStringList exampleVersionParts = exampleVersion.split('.');
- const QStringList qdsVersionParts = QCoreApplication::applicationVersion().split('.');
+ // Split versions into parts (major, minor, patch)
+ QStringList qdsVersionParts = QCoreApplication::applicationVersion().split('.');
+ QStringList exampleVersionParts = exampleVersion.split('.');
- QList<int> exampleVerInts;
- QList<int> qdsVerInts;
- for (const QString &part : exampleVersionParts)
- exampleVerInts.append(part.toInt());
+ // Fill missing parts with zeros
+ while (qdsVersionParts.size() < 3)
+ qdsVersionParts.append("0");
- for (const QString &part : qdsVersionParts)
- qdsVerInts.append(part.toInt());
+ while (exampleVersionParts.size() < 3)
+ exampleVersionParts.append("0");
- // pad zeros so both lists are same size
- while (qdsVerInts.size() < exampleVerInts.size())
- qdsVerInts.append(0);
+ int qdsMajor = qdsVersionParts.at(0).toInt();
+ int qdsMinor = qdsVersionParts.at(1).toInt();
+ int qdsPatch = qdsVersionParts.at(2).toInt();
- while (exampleVerInts.size() < qdsVerInts.size())
- exampleVerInts.append(0);
+ int exMajor = exampleVersionParts.at(0).toInt();
+ int exMinor = exampleVersionParts.at(1).toInt();
+ int exPatch = exampleVersionParts.at(2).toInt();
- for (int i = 0; i < qdsVerInts.size(); ++i) {
- if (exampleVerInts[i] < qdsVerInts[i])
- return false;
- }
-
- return true;
+ return QT_VERSION_CHECK(exMajor, exMinor, exPatch)
+ <= QT_VERSION_CHECK(qdsMajor, qdsMinor, qdsPatch);
}
public slots:
@@ -587,6 +584,9 @@ static bool forceDownLoad()
static bool showSplashScreen()
{
+ // some error dialog is maybe open, be silent to avoid focus problems (macOS had some)
+ if (Core::ICore::mainWindow() != Core::ICore::dialogParent())
+ return false;
const Key lastQDSVersionEntry = "QML/Designer/lastQDSVersion";
QtcSettings *settings = Core::ICore::settings();
diff --git a/src/plugins/subversion/Subversion.json.in b/src/plugins/subversion/Subversion.json.in
index 604ba2648a..0c2f55bd30 100644
--- a/src/plugins/subversion/Subversion.json.in
+++ b/src/plugins/subversion/Subversion.json.in
@@ -13,8 +13,12 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Version Control",
- "Description" : "Subversion integration.",
- "Url" : "http://www.qt.io",
+ "Description" : "Access the Subversion version control system",
+ "LongDescription" : [
+ "You also need:",
+ "- Subversion"
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES},
"Mimetypes" : [
diff --git a/src/plugins/subversion/subversionclient.cpp b/src/plugins/subversion/subversionclient.cpp
index e69809143d..1dfb19ed86 100644
--- a/src/plugins/subversion/subversionclient.cpp
+++ b/src/plugins/subversion/subversionclient.cpp
@@ -115,6 +115,7 @@ CommandLine &operator<<(Utils::CommandLine &command, SubversionClient::AddAuthOp
QString SubversionClient::synchronousTopic(const FilePath &repository) const
{
+ // TODO: Looks unused
QStringList args;
QString svnVersionBinary = vcsBinary(repository).toString();
diff --git a/src/plugins/subversion/subversionplugin.cpp b/src/plugins/subversion/subversionplugin.cpp
index b28224a866..36a5455a62 100644
--- a/src/plugins/subversion/subversionplugin.cpp
+++ b/src/plugins/subversion/subversionplugin.cpp
@@ -1067,7 +1067,7 @@ bool SubversionPluginPrivate::isVcsFileOrDirectory(const FilePath &filePath) con
bool SubversionPluginPrivate::isConfigured() const
{
- const FilePath binary = settings().binaryPath();
+ const FilePath binary = settings().binaryPath.effectiveBinary();
if (binary.isEmpty())
return false;
QFileInfo fi = binary.toFileInfo();
diff --git a/src/plugins/subversion/subversionsettings.cpp b/src/plugins/subversion/subversionsettings.cpp
index 284472938f..1bea22072c 100644
--- a/src/plugins/subversion/subversionsettings.cpp
+++ b/src/plugins/subversion/subversionsettings.cpp
@@ -76,7 +76,7 @@ SubversionSettings::SubversionSettings()
Group {
title(Tr::tr("Authentication")),
- useAuthentication.groupChecker(),
+ groupChecker(useAuthentication.groupChecker()),
Form {
userName, br,
password,
diff --git a/src/plugins/tellajoke/tellajoke.qbs b/src/plugins/tellajoke/tellajoke.qbs
new file mode 100644
index 0000000000..d2efc1a64e
--- /dev/null
+++ b/src/plugins/tellajoke/tellajoke.qbs
@@ -0,0 +1,5 @@
+QtcLuaPlugin {
+ name: "tellajoke"
+
+ luafiles: "tellajoke.lua"
+}
diff --git a/src/plugins/terminal/Terminal.json.in b/src/plugins/terminal/Terminal.json.in
index 8c631dd651..e976947407 100644
--- a/src/plugins/terminal/Terminal.json.in
+++ b/src/plugins/terminal/Terminal.json.in
@@ -12,7 +12,10 @@
"",
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
- "Description" : "Terminal window.",
- "Url" : "http://www.qt.io",
+ "Description" : "Open a terminal as an output view",
+ "LongDescription" : [
+ "Instead of using an external terminal."
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/terminal/shellintegration.cpp b/src/plugins/terminal/shellintegration.cpp
index 5dfd450e68..1303f92288 100644
--- a/src/plugins/terminal/shellintegration.cpp
+++ b/src/plugins/terminal/shellintegration.cpp
@@ -133,7 +133,7 @@ void ShellIntegration::onOsc(int cmd, std::string_view str, bool initial, bool f
qCDebug(integrationLog) << "OSC 133:" << data;
} else if (cmd == 633 && command.length() == 1) {
if (command[0] == 'E') {
- CommandLine cmdLine = CommandLine::fromUserInput(data.toString());
+ const CommandLine cmdLine = CommandLine::fromUserInput(data.toString());
emit commandChanged(cmdLine);
} else if (command[0] == 'D') {
emit commandChanged({});
@@ -174,12 +174,10 @@ void ShellIntegration::prepareProcess(Utils::Process &process)
m_tempDir.filePath(filesToCopy.bash.rcFile.fileName()));
rcPath.copyFile(tmpRc);
- CommandLine newCmd = {cmd.executable(), {"--init-file", tmpRc.nativePath()}};
-
if (cmd.arguments() == "-l")
env.set("VSCODE_SHELL_LOGIN", "1");
- cmd = newCmd;
+ cmd = {cmd.executable(), {"--init-file", tmpRc.nativePath()}};
} else if (cmd.executable().baseName() == "zsh") {
for (const FileToCopy &file : filesToCopy.zsh.files) {
const auto copyResult = file.source.copyFile(
diff --git a/src/plugins/terminal/shellmodel.cpp b/src/plugins/terminal/shellmodel.cpp
index 4368efee97..dd45515b09 100644
--- a/src/plugins/terminal/shellmodel.cpp
+++ b/src/plugins/terminal/shellmodel.cpp
@@ -168,7 +168,7 @@ QList<ShellModelItem> ShellModel::remote() const
ProjectExplorer::DeviceManager::instance()->forEachDevice(
[&result](const std::shared_ptr<const ProjectExplorer::IDevice> &device) {
if (device->type() != ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE)
- result << ShellModelItem{device->displayName(), {{device->rootPath(), {}}}};
+ result << ShellModelItem{device->displayName(), {CommandLine{device->rootPath()}}};
});
return result;
diff --git a/src/plugins/terminal/terminalpane.cpp b/src/plugins/terminal/terminalpane.cpp
index 1648638713..8b6699ef77 100644
--- a/src/plugins/terminal/terminalpane.cpp
+++ b/src/plugins/terminal/terminalpane.cpp
@@ -115,7 +115,7 @@ void TerminalPane::openTerminal(const OpenTerminalParameters &parameters)
if (!shell.isExecutableFile())
parametersCopy.workingDirectory.reset();
else
- parametersCopy.shellCommand = CommandLine{shell, {}};
+ parametersCopy.shellCommand = CommandLine{shell};
}
const auto terminalWidget = new TerminalWidget(&m_tabWidget, parametersCopy);
diff --git a/src/plugins/terminal/terminalsettings.cpp b/src/plugins/terminal/terminalsettings.cpp
index 62e0c209c1..4e754dc7dd 100644
--- a/src/plugins/terminal/terminalsettings.cpp
+++ b/src/plugins/terminal/terminalsettings.cpp
@@ -527,37 +527,35 @@ TerminalSettings::TerminalSettings()
enableMouseTracking.setToolTip(Tr::tr("Enables mouse tracking in the terminal."));
enableMouseTracking.setDefaultValue(true);
- Theme *theme = Utils::creatorTheme();
+ setupColor(this, foregroundColor, "Foreground", creatorColor(Theme::TerminalForeground));
+ setupColor(this, backgroundColor, "Background", creatorColor(Theme::TerminalBackground));
+ setupColor(this, selectionColor, "Selection", creatorColor(Theme::TerminalSelection));
- setupColor(this, foregroundColor, "Foreground", theme->color(Theme::TerminalForeground));
- setupColor(this, backgroundColor, "Background", theme->color(Theme::TerminalBackground));
- setupColor(this, selectionColor, "Selection", theme->color(Theme::TerminalSelection));
+ setupColor(this, findMatchColor, "Find matches", creatorColor(Theme::TerminalFindMatch));
- setupColor(this, findMatchColor, "Find matches", theme->color(Theme::TerminalFindMatch));
+ setupColor(this, colors[0], "0", creatorColor(Theme::TerminalAnsi0), "black");
+ setupColor(this, colors[8], "8", creatorColor(Theme::TerminalAnsi8), "bright black");
- setupColor(this, colors[0], "0", theme->color(Theme::TerminalAnsi0), "black");
- setupColor(this, colors[8], "8", theme->color(Theme::TerminalAnsi8), "bright black");
+ setupColor(this, colors[1], "1", creatorColor(Theme::TerminalAnsi1), "red");
+ setupColor(this, colors[9], "9", creatorColor(Theme::TerminalAnsi9), "bright red");
- setupColor(this, colors[1], "1", theme->color(Theme::TerminalAnsi1), "red");
- setupColor(this, colors[9], "9", theme->color(Theme::TerminalAnsi9), "bright red");
+ setupColor(this, colors[2], "2", creatorColor(Theme::TerminalAnsi2), "green");
+ setupColor(this, colors[10], "10", creatorColor(Theme::TerminalAnsi10), "bright green");
- setupColor(this, colors[2], "2", theme->color(Theme::TerminalAnsi2), "green");
- setupColor(this, colors[10], "10", theme->color(Theme::TerminalAnsi10), "bright green");
+ setupColor(this, colors[3], "3", creatorColor(Theme::TerminalAnsi3), "yellow");
+ setupColor(this, colors[11], "11", creatorColor(Theme::TerminalAnsi11), "bright yellow");
- setupColor(this, colors[3], "3", theme->color(Theme::TerminalAnsi3), "yellow");
- setupColor(this, colors[11], "11", theme->color(Theme::TerminalAnsi11), "bright yellow");
+ setupColor(this, colors[4], "4", creatorColor(Theme::TerminalAnsi4), "blue");
+ setupColor(this, colors[12], "12", creatorColor(Theme::TerminalAnsi12), "bright blue");
- setupColor(this, colors[4], "4", theme->color(Theme::TerminalAnsi4), "blue");
- setupColor(this, colors[12], "12", theme->color(Theme::TerminalAnsi12), "bright blue");
+ setupColor(this, colors[5], "5", creatorColor(Theme::TerminalAnsi5), "magenta");
+ setupColor(this, colors[13], "13", creatorColor(Theme::TerminalAnsi13), "bright magenta");
- setupColor(this, colors[5], "5", theme->color(Theme::TerminalAnsi5), "magenta");
- setupColor(this, colors[13], "13", theme->color(Theme::TerminalAnsi13), "bright magenta");
+ setupColor(this, colors[6], "6", creatorColor(Theme::TerminalAnsi6), "cyan");
+ setupColor(this, colors[14], "14", creatorColor(Theme::TerminalAnsi14), "bright cyan");
- setupColor(this, colors[6], "6", theme->color(Theme::TerminalAnsi6), "cyan");
- setupColor(this, colors[14], "14", theme->color(Theme::TerminalAnsi14), "bright cyan");
-
- setupColor(this, colors[7], "7", theme->color(Theme::TerminalAnsi7), "white");
- setupColor(this, colors[15], "15", theme->color(Theme::TerminalAnsi15), "bright white");
+ setupColor(this, colors[7], "7", creatorColor(Theme::TerminalAnsi7), "white");
+ setupColor(this, colors[15], "15", creatorColor(Theme::TerminalAnsi15), "bright white");
setLayouter([this] {
using namespace Layouting;
diff --git a/src/plugins/terminal/terminalwidget.cpp b/src/plugins/terminal/terminalwidget.cpp
index a314d0babe..c6c97ae9bd 100644
--- a/src/plugins/terminal/terminalwidget.cpp
+++ b/src/plugins/terminal/terminalwidget.cpp
@@ -83,7 +83,7 @@ void TerminalWidget::setupPty()
{
m_process = std::make_unique<Process>();
- CommandLine shellCommand = m_openParameters.shellCommand.value_or(
+ const CommandLine shellCommand = m_openParameters.shellCommand.value_or(
CommandLine{settings().shell(), settings().shellArguments(), CommandLine::Raw});
if (shellCommand.executable().isRootPath()) {
diff --git a/src/plugins/texteditor/TextEditor.json.in b/src/plugins/texteditor/TextEditor.json.in
index f2b6fbbcf5..42e32325ed 100644
--- a/src/plugins/texteditor/TextEditor.json.in
+++ b/src/plugins/texteditor/TextEditor.json.in
@@ -14,7 +14,7 @@
],
"Category" : "Core",
"Description" : "Text editor framework and the implementation of the basic text editor.",
- "Url" : "http://www.qt.io",
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES},
"Mimetypes" : [
"<?xml version='1.0'?>",
diff --git a/src/plugins/texteditor/basefilefind.cpp b/src/plugins/texteditor/basefilefind.cpp
index ce6d2f4999..d18a75531b 100644
--- a/src/plugins/texteditor/basefilefind.cpp
+++ b/src/plugins/texteditor/basefilefind.cpp
@@ -139,8 +139,8 @@ public:
QString title() const override { return Tr::tr("Internal"); }
QString toolTip() const override { return {}; }
QWidget *widget() const override { return m_widget; }
- void readSettings(QtcSettings * /*settings*/) override {}
- void writeSettings(QtcSettings * /*settings*/) const override {}
+ void readSettings(const Store &) override {}
+ void writeSettings(Store &) const override {}
SearchExecutor searchExecutor() const override
{
return [](const FileFindParameters &parameters) {
@@ -442,47 +442,57 @@ FilePath BaseFileFind::searchDir() const
return d->m_searchDir;
}
-void BaseFileFind::writeCommonSettings(QtcSettings *settings)
+void BaseFileFind::writeCommonSettings(
+ Store &s, const QString &defaultFilter, const QString &defaultExclusionFilter) const
{
const auto fromNativeSeparators = [](const QStringList &files) -> QStringList {
return Utils::transform(files, &QDir::fromNativeSeparators);
};
- settings->setValue("filters", fromNativeSeparators(d->m_filterStrings.stringList()));
- if (d->m_filterCombo)
- settings->setValue("currentFilter",
- QDir::fromNativeSeparators(d->m_filterCombo->currentText()));
- settings->setValue("exclusionFilters", fromNativeSeparators(d->m_exclusionStrings.stringList()));
- if (d->m_exclusionCombo)
- settings->setValue("currentExclusionFilter",
- QDir::fromNativeSeparators(d->m_exclusionCombo->currentText()));
+ const QStringList filterStrings = fromNativeSeparators(d->m_filterStrings.stringList());
+ if (filterStrings.size() != 1 || filterStrings.first() != defaultFilter)
+ s.insert("filters", filterStrings);
+ const QString currentFilter = d->m_filterCombo
+ ? QDir::fromNativeSeparators(d->m_filterCombo->currentText())
+ : d->m_filterSetting;
+ if (currentFilter != defaultFilter)
+ s.insert("currentFilter", currentFilter);
+ const QStringList exclusionFilters = fromNativeSeparators(d->m_exclusionStrings.stringList());
+ if (exclusionFilters.size() != 1 || exclusionFilters.first() != defaultExclusionFilter)
+ s.insert("exclusionFilters", exclusionFilters);
+ const QString currentExclusionFilter = d->m_exclusionCombo ? QDir::fromNativeSeparators(
+ d->m_exclusionCombo->currentText())
+ : d->m_exclusionSetting;
+ if (currentExclusionFilter != defaultExclusionFilter)
+ s.insert("currentExclusionFilter", currentExclusionFilter);
for (const SearchEngine *searchEngine : std::as_const(d->m_searchEngines))
- searchEngine->writeSettings(settings);
- settings->setValue("currentSearchEngineIndex", d->m_currentSearchEngineIndex);
+ searchEngine->writeSettings(s);
+ if (d->m_currentSearchEngineIndex != 0)
+ s.insert("currentSearchEngineIndex", d->m_currentSearchEngineIndex);
}
-void BaseFileFind::readCommonSettings(QtcSettings *settings, const QString &defaultFilter,
- const QString &defaultExclusionFilter)
+void BaseFileFind::readCommonSettings(
+ const Store &s, const QString &defaultFilter, const QString &defaultExclusionFilter)
{
const auto toNativeSeparators = [](const QStringList &files) -> QStringList {
return Utils::transform(files, &QDir::toNativeSeparators);
};
- const QStringList filterSetting = settings->value("filters").toStringList();
+ const QStringList filterSetting = s.value("filters").toStringList();
const QStringList filters = filterSetting.isEmpty() ? QStringList(defaultFilter)
: filterSetting;
- const QVariant currentFilter = settings->value("currentFilter");
+ const QVariant currentFilter = s.value("currentFilter");
d->m_filterSetting = currentFilter.isValid() ? currentFilter.toString()
: filters.first();
d->m_filterStrings.setStringList(toNativeSeparators(filters));
if (d->m_filterCombo)
syncComboWithSettings(d->m_filterCombo, d->m_filterSetting);
- QStringList exclusionFilters = settings->value("exclusionFilters").toStringList();
+ QStringList exclusionFilters = s.value("exclusionFilters").toStringList();
if (!exclusionFilters.contains(defaultExclusionFilter))
exclusionFilters << defaultExclusionFilter;
- const QVariant currentExclusionFilter = settings->value("currentExclusionFilter");
+ const QVariant currentExclusionFilter = s.value("currentExclusionFilter");
d->m_exclusionSetting = currentExclusionFilter.isValid() ? currentExclusionFilter.toString()
: exclusionFilters.first();
d->m_exclusionStrings.setStringList(toNativeSeparators(exclusionFilters));
@@ -490,8 +500,8 @@ void BaseFileFind::readCommonSettings(QtcSettings *settings, const QString &defa
syncComboWithSettings(d->m_exclusionCombo, d->m_exclusionSetting);
for (SearchEngine* searchEngine : std::as_const(d->m_searchEngines))
- searchEngine->readSettings(settings);
- const int currentSearchEngineIndex = settings->value("currentSearchEngineIndex", 0).toInt();
+ searchEngine->readSettings(s);
+ const int currentSearchEngineIndex = s.value("currentSearchEngineIndex", 0).toInt();
syncSearchEngineCombo(currentSearchEngineIndex);
}
@@ -594,8 +604,7 @@ FilePaths BaseFileFind::replaceAll(const QString &text, const SearchResultItems
item.mainRange().end.column + 1);
changeSet.replace(start, end, replacement);
}
- file->setChangeSet(changeSet);
- file->apply();
+ file->apply(changeSet);
}
return changes.keys();
diff --git a/src/plugins/texteditor/basefilefind.h b/src/plugins/texteditor/basefilefind.h
index 47adae4b86..de4286c31b 100644
--- a/src/plugins/texteditor/basefilefind.h
+++ b/src/plugins/texteditor/basefilefind.h
@@ -69,8 +69,8 @@ public:
virtual QString title() const = 0;
virtual QString toolTip() const = 0; // add %1 placeholder where the find flags should be put
virtual QWidget *widget() const = 0;
- virtual void readSettings(Utils::QtcSettings *settings) = 0;
- virtual void writeSettings(Utils::QtcSettings *settings) const = 0;
+ virtual void readSettings(const Utils::Store &s) = 0;
+ virtual void writeSettings(Utils::Store &settings) const = 0;
virtual SearchExecutor searchExecutor() const = 0;
virtual EditorOpener editorOpener() const { return {}; }
bool isEnabled() const;
@@ -108,8 +108,10 @@ protected:
virtual QString toolTip() const = 0; // see Core::SearchResultWindow::startNewSearch,
// add %1 placeholder where the find flags should be put
- void writeCommonSettings(Utils::QtcSettings *settings);
- void readCommonSettings(Utils::QtcSettings *settings, const QString &defaultFilter, const QString &defaultExclusionFilter);
+ void writeCommonSettings(
+ Utils::Store &s, const QString &defaultFilter, const QString &defaultExclusionFilter) const;
+ void readCommonSettings(
+ const Utils::Store &s, const QString &defaultFilter, const QString &defaultExclusionFilter);
QList<QPair<QWidget *, QWidget *>> createPatternWidgets();
QStringList fileNameFilters() const;
QStringList fileExclusionFilters() const;
diff --git a/src/plugins/texteditor/bookmarkmanager.cpp b/src/plugins/texteditor/bookmarkmanager.cpp
index 6dcb4de6cc..a61cc58736 100644
--- a/src/plugins/texteditor/bookmarkmanager.cpp
+++ b/src/plugins/texteditor/bookmarkmanager.cpp
@@ -876,6 +876,7 @@ void BookmarkManager::edit()
auto layout = new QFormLayout(&dlg);
auto noteEdit = new QLineEdit(b->note());
noteEdit->setMinimumWidth(300);
+ noteEdit->setFocus();
auto lineNumberSpinbox = new QSpinBox;
lineNumberSpinbox->setRange(1, INT_MAX);
lineNumberSpinbox->setValue(b->lineNumber());
diff --git a/src/plugins/texteditor/completionsettingspage.cpp b/src/plugins/texteditor/completionsettingspage.cpp
index aba8646714..03f45404c6 100644
--- a/src/plugins/texteditor/completionsettingspage.cpp
+++ b/src/plugins/texteditor/completionsettingspage.cpp
@@ -214,7 +214,7 @@ CompletionSettingsPageWidget::CompletionSettingsPageWidget(CompletionSettingsPag
}
},
Group {
- title(Tr::tr("&Automatically insert matching characters")),
+ title(Tr::tr("&Automatically Insert Matching Characters")),
Row {
Column {
m_insertBrackets,
diff --git a/src/plugins/texteditor/displaysettingspage.cpp b/src/plugins/texteditor/displaysettingspage.cpp
index b5435ed5e9..fe914b27ce 100644
--- a/src/plugins/texteditor/displaysettingspage.cpp
+++ b/src/plugins/texteditor/displaysettingspage.cpp
@@ -108,7 +108,7 @@ public:
rightAligned->setChecked(true);
betweenLines = new QRadioButton(Tr::tr("Between lines"));
- displayAnnotations = new QGroupBox(Tr::tr("Line annotations")),
+ displayAnnotations = new QGroupBox(Tr::tr("Line Annotations")),
displayAnnotations->setCheckable(true);
using namespace Layouting;
diff --git a/src/plugins/texteditor/findincurrentfile.cpp b/src/plugins/texteditor/findincurrentfile.cpp
index 7d3ad84997..45121f72d8 100644
--- a/src/plugins/texteditor/findincurrentfile.cpp
+++ b/src/plugins/texteditor/findincurrentfile.cpp
@@ -27,8 +27,8 @@ private:
QString id() const final;
QString displayName() const final;
bool isEnabled() const final;
- void writeSettings(Utils::QtcSettings *settings) final;
- void readSettings(Utils::QtcSettings *settings) final;
+ Utils::Store save() const final;
+ void restore(const Utils::Store &s) final;
QString label() const final;
QString toolTip() const final;
@@ -37,6 +37,9 @@ private:
void handleFileChange(Core::IEditor *editor);
QPointer<Core::IDocument> m_currentDocument;
+
+ // deprecated
+ QByteArray settingsKey() const final;
};
FindInCurrentFile::FindInCurrentFile()
@@ -97,18 +100,24 @@ void FindInCurrentFile::handleFileChange(Core::IEditor *editor)
}
}
-void FindInCurrentFile::writeSettings(QtcSettings *settings)
+const char kDefaultInclusion[] = "*";
+const char kDefaultExclusion[] = "";
+
+Store FindInCurrentFile::save() const
+{
+ Store s;
+ writeCommonSettings(s, kDefaultInclusion, kDefaultExclusion);
+ return s;
+}
+
+void FindInCurrentFile::restore(const Store &s)
{
- settings->beginGroup("FindInCurrentFile");
- writeCommonSettings(settings);
- settings->endGroup();
+ readCommonSettings(s, kDefaultInclusion, kDefaultExclusion);
}
-void FindInCurrentFile::readSettings(QtcSettings *settings)
+QByteArray FindInCurrentFile::settingsKey() const
{
- settings->beginGroup("FindInCurrentFile");
- readCommonSettings(settings, "*", "");
- settings->endGroup();
+ return "FindInCurrentFile";
}
void setupFindInCurrentFile()
diff --git a/src/plugins/texteditor/findinfiles.cpp b/src/plugins/texteditor/findinfiles.cpp
index 7348c600e2..9dc68cd653 100644
--- a/src/plugins/texteditor/findinfiles.cpp
+++ b/src/plugins/texteditor/findinfiles.cpp
@@ -188,18 +188,24 @@ QWidget *FindInFiles::createConfigWidget()
return m_configWidget;
}
-void FindInFiles::writeSettings(QtcSettings *settings)
+const char kDefaultInclusion[] = "*.cpp,*.h";
+const char kDefaultExclusion[] = "*/.git/*,*/.cvs/*,*/.svn/*,*.autosave,*/build/*";
+
+Store FindInFiles::save() const
+{
+ Store s;
+ writeCommonSettings(s, kDefaultInclusion, kDefaultExclusion);
+ return s;
+}
+
+void FindInFiles::restore(const Utils::Store &s)
{
- settings->beginGroup("FindInFiles");
- writeCommonSettings(settings);
- settings->endGroup();
+ readCommonSettings(s, kDefaultInclusion, kDefaultExclusion);
}
-void FindInFiles::readSettings(QtcSettings *settings)
+QByteArray FindInFiles::settingsKey() const
{
- settings->beginGroup("FindInFiles");
- readCommonSettings(settings, "*.cpp,*.h", "*/.git/*,*/.cvs/*,*/.svn/*,*.autosave,*/build/*");
- settings->endGroup();
+ return "FindInFiles";
}
void FindInFiles::setBaseDirectory(const FilePath &directory)
diff --git a/src/plugins/texteditor/findinfiles.h b/src/plugins/texteditor/findinfiles.h
index c7803a16ed..b3bd7b7095 100644
--- a/src/plugins/texteditor/findinfiles.h
+++ b/src/plugins/texteditor/findinfiles.h
@@ -30,8 +30,8 @@ public:
QString id() const override;
QString displayName() const override;
QWidget *createConfigWidget() override;
- void writeSettings(Utils::QtcSettings *settings) override;
- void readSettings(Utils::QtcSettings *settings) override;
+ Utils::Store save() const override;
+ void restore(const Utils::Store &s) override;
bool isValid() const override;
void setDirectory(const Utils::FilePath &directory);
@@ -39,6 +39,9 @@ public:
static void findOnFileSystem(const QString &path);
static FindInFiles *instance();
+ // deprecated
+ QByteArray settingsKey() const override;
+
protected:
QString label() const override;
QString toolTip() const override;
diff --git a/src/plugins/texteditor/findinopenfiles.cpp b/src/plugins/texteditor/findinopenfiles.cpp
index dcd505cbe1..deeb66e9f5 100644
--- a/src/plugins/texteditor/findinopenfiles.cpp
+++ b/src/plugins/texteditor/findinopenfiles.cpp
@@ -25,14 +25,17 @@ private:
QString id() const final;
QString displayName() const final;
bool isEnabled() const final;
- void writeSettings(Utils::QtcSettings *settings) final;
- void readSettings(Utils::QtcSettings *settings) final;
+ Utils::Store save() const final;
+ void restore(const Utils::Store &s) final;
QString label() const final;
QString toolTip() const final;
FileContainerProvider fileContainerProvider() const final;
void updateEnabledState() { emit enabledChanged(isEnabled()); }
+
+ // deprecated
+ QByteArray settingsKey() const final;
};
FindInOpenFiles::FindInOpenFiles()
@@ -90,21 +93,25 @@ bool FindInOpenFiles::isEnabled() const
return Core::DocumentModel::entryCount() > 0;
}
-void FindInOpenFiles::writeSettings(QtcSettings *settings)
+const char kDefaultInclusion[] = "*";
+const char kDefaultExclusion[] = "";
+
+Store FindInOpenFiles::save() const
{
- settings->beginGroup("FindInOpenFiles");
- writeCommonSettings(settings);
- settings->endGroup();
+ Store s;
+ writeCommonSettings(s, kDefaultInclusion, kDefaultExclusion);
+ return s;
}
-void FindInOpenFiles::readSettings(QtcSettings *settings)
+void FindInOpenFiles::restore(const Store &s)
{
- settings->beginGroup("FindInOpenFiles");
- readCommonSettings(settings, "*", "");
- settings->endGroup();
+ readCommonSettings(s, kDefaultInclusion, kDefaultExclusion);
}
-
+QByteArray FindInOpenFiles::settingsKey() const
+{
+ return "FindInOpenFiles";
+}
void setupFindInOpenFiles()
{
diff --git a/src/plugins/texteditor/fontsettings.cpp b/src/plugins/texteditor/fontsettings.cpp
index e74a37c0d3..44b179d01c 100644
--- a/src/plugins/texteditor/fontsettings.cpp
+++ b/src/plugins/texteditor/fontsettings.cpp
@@ -491,8 +491,7 @@ static QString defaultFontFamily()
return QLatin1String("Menlo");
const QString sourceCodePro(g_sourceCodePro);
- const QFontDatabase dataBase;
- if (dataBase.hasFamily(sourceCodePro))
+ if (QFontDatabase::hasFamily(sourceCodePro))
return sourceCodePro;
if (Utils::HostOsInfo::isAnyUnixHost())
diff --git a/src/plugins/texteditor/fontsettingspage.cpp b/src/plugins/texteditor/fontsettingspage.cpp
index bdd899b7e0..f528496dcf 100644
--- a/src/plugins/texteditor/fontsettingspage.cpp
+++ b/src/plugins/texteditor/fontsettingspage.cpp
@@ -458,15 +458,14 @@ void FontSettingsPageWidget::updateFontZoom(const FontSettings &fontSettings)
QList<int> FontSettingsPageWidget::pointSizesForSelectedFont() const
{
- QFontDatabase db;
const QString familyName = m_fontComboBox->currentFont().family();
- QList<int> sizeLst = db.pointSizes(familyName);
+ QList<int> sizeLst = QFontDatabase::pointSizes(familyName);
if (!sizeLst.isEmpty())
return sizeLst;
- QStringList styles = db.styles(familyName);
+ QStringList styles = QFontDatabase::styles(familyName);
if (!styles.isEmpty())
- sizeLst = db.pointSizes(familyName, styles.first());
+ sizeLst = QFontDatabase::pointSizes(familyName, styles.first());
if (sizeLst.isEmpty())
sizeLst = QFontDatabase::standardSizes();
diff --git a/src/plugins/texteditor/refactoringchanges.cpp b/src/plugins/texteditor/refactoringchanges.cpp
index ebd487cc37..c8688f2fba 100644
--- a/src/plugins/texteditor/refactoringchanges.cpp
+++ b/src/plugins/texteditor/refactoringchanges.cpp
@@ -214,6 +214,9 @@ void RefactoringFile::setOpenEditor(bool activate, int pos)
bool RefactoringFile::apply()
{
+ if (m_changes.isEmpty())
+ return true;
+
// test file permissions
if (!m_filePath.isWritableFile()) {
ReadOnlyFilesDialog roDialog(m_filePath, ICore::dialogParent());
@@ -287,6 +290,12 @@ bool RefactoringFile::apply()
return result;
}
+bool RefactoringFile::apply(const Utils::ChangeSet &changeSet)
+{
+ setChangeSet(changeSet);
+ return apply();
+}
+
void RefactoringFile::setupFormattingRanges(const QList<ChangeSet::EditOp> &replaceList)
{
QTextDocument * const doc = m_editor ? m_editor->document() : m_document;
@@ -359,7 +368,7 @@ void RefactoringFile::doFormatting()
Utils::sort(m_formattingCursors, [](const auto &tc1, const auto &tc2) {
return tc1.first.selectionStart() < tc2.first.selectionStart();
});
- static const QString clangFormatLineRemovalBlocker("// QTC_TEMP");
+ static const QString clangFormatLineRemovalBlocker("");
for (auto &[formattingCursor, _] : m_formattingCursors) {
const QTextBlock firstBlock = document->findBlock(formattingCursor.selectionStart());
const QTextBlock lastBlock = document->findBlock(formattingCursor.selectionEnd());
diff --git a/src/plugins/texteditor/refactoringchanges.h b/src/plugins/texteditor/refactoringchanges.h
index 913c731df2..3e043c4f6b 100644
--- a/src/plugins/texteditor/refactoringchanges.h
+++ b/src/plugins/texteditor/refactoringchanges.h
@@ -60,6 +60,7 @@ public:
void setChangeSet(const Utils::ChangeSet &changeSet);
void setOpenEditor(bool activate = false, int pos = -1);
bool apply();
+ bool apply(const Utils::ChangeSet &changeSet);
bool create(const QString &contents, bool reindent, bool openInEditor);
protected:
diff --git a/src/plugins/texteditor/textdocument.cpp b/src/plugins/texteditor/textdocument.cpp
index 9d8133912c..7fcfe5c86c 100644
--- a/src/plugins/texteditor/textdocument.cpp
+++ b/src/plugins/texteditor/textdocument.cpp
@@ -522,10 +522,7 @@ bool TextDocument::applyChangeSet(const ChangeSet &changeSet)
{
if (changeSet.isEmpty())
return true;
- PlainRefactoringFileFactory changes;
- const RefactoringFilePtr file = changes.file(filePath());
- file->setChangeSet(changeSet);
- return file->apply();
+ return PlainRefactoringFileFactory().file(filePath())->apply(changeSet);
}
// the blocks list must be sorted
diff --git a/src/plugins/texteditor/texteditor.cpp b/src/plugins/texteditor/texteditor.cpp
index 92636e63cd..03164abb36 100644
--- a/src/plugins/texteditor/texteditor.cpp
+++ b/src/plugins/texteditor/texteditor.cpp
@@ -354,6 +354,7 @@ public:
BaseTextEditorPrivate() = default;
TextEditorFactoryPrivate *m_origin = nullptr;
+ QByteArray m_savedNavigationState;
};
class HoverHandlerRunner
@@ -772,7 +773,6 @@ public:
QSharedPointer<TextDocument> m_document;
QList<QMetaObject::Connection> m_documentConnections;
QByteArray m_tempState;
- QByteArray m_tempNavigationState;
bool m_parenthesesMatchingEnabled = false;
QTimer m_parenthesesMatchingTimer;
@@ -1020,6 +1020,8 @@ void TextEditorWidgetFind::selectAll(const QString &txt, FindFlags findFlags)
if (future.resultCount() <= 0)
return;
const SearchResultItems &results = future.result();
+ if (results.isEmpty())
+ return;
const auto cursorForResult = [this](const SearchResultItem &item) {
return selectRange(m_editor->document(), item.mainRange());
};
@@ -1314,15 +1316,15 @@ TextEditorWidget::TextEditorWidget(QWidget *parent)
{
// "Needed", as the creation below triggers ChildEvents that are
// passed to this object's event() which uses 'd'.
- d = nullptr;
- d = new TextEditorWidgetPrivate(this);
-
+ d = std::make_unique<Internal::TextEditorWidgetPrivate>(this);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
setLayoutDirection(Qt::LeftToRight);
viewport()->setMouseTracking(true);
setFrameStyle(QFrame::NoFrame);
}
+TextEditorWidget::~TextEditorWidget() = default;
+
void TextEditorWidget::setTextDocument(const QSharedPointer<TextDocument> &doc)
{
d->setDocument(doc);
@@ -1501,12 +1503,6 @@ void TextEditorWidgetPrivate::setDocument(const QSharedPointer<TextDocument> &do
setupFromDefinition(currentDefinition());
}
-TextEditorWidget::~TextEditorWidget()
-{
- delete d;
- d = nullptr;
-}
-
void TextEditorWidget::print(QPrinter *printer)
{
const bool oldFullPage = printer->fullPage();
@@ -6391,7 +6387,7 @@ void TextEditorWidgetPrivate::paintRevisionMarker(QPainter &painter,
void TextEditorWidget::extraAreaPaintEvent(QPaintEvent *e)
{
- ExtraAreaPaintEventData data(this, d);
+ ExtraAreaPaintEventData data(this, d.get());
QTC_ASSERT(data.documentLayout, return);
QPainter painter(d->m_extraArea);
@@ -6498,7 +6494,7 @@ void TextEditorWidgetPrivate::slotUpdateRequest(const QRect &r, int dy)
void TextEditorWidgetPrivate::saveCurrentCursorPositionForNavigation()
{
m_lastCursorChangeWasInteresting = true;
- m_tempNavigationState = q->saveState();
+ emit q->saveCurrentStateForNavigationHistory();
}
void TextEditorWidgetPrivate::updateCurrentLineHighlight()
@@ -6554,8 +6550,7 @@ void TextEditorWidget::slotCursorPositionChanged()
<< "indent:" << BaseTextDocumentLayout::userData(textCursor().block())->foldingIndent();
#endif
if (!d->m_contentsChanged && d->m_lastCursorChangeWasInteresting) {
- if (EditorManager::currentEditor() && EditorManager::currentEditor()->widget() == this)
- EditorManager::addCurrentPositionToNavigationHistory(d->m_tempNavigationState);
+ emit addSavedStateToNavigationHistory();
d->m_lastCursorChangeWasInteresting = false;
} else if (d->m_contentsChanged) {
d->saveCurrentCursorPositionForNavigation();
@@ -6861,7 +6856,6 @@ void TextEditorWidget::mouseReleaseEvent(QMouseEvent *e)
{
const Qt::MouseButton button = e->button();
if (d->m_linkPressed && d->isMouseNavigationEvent(e) && button == Qt::LeftButton) {
- EditorManager::addCurrentPositionToNavigationHistory();
bool inNextSplit = ((e->modifiers() & Qt::AltModifier) && !alwaysOpenLinksInNextSplit())
|| (alwaysOpenLinksInNextSplit() && !(e->modifiers() & Qt::AltModifier));
@@ -7589,7 +7583,7 @@ bool TextEditorWidget::openLink(const Utils::Link &link, bool inNextSplit)
}
if (!inNextSplit && textDocument()->filePath() == link.targetFilePath) {
- EditorManager::addCurrentPositionToNavigationHistory();
+ emit addCurrentStateToNavigationHistory();
gotoLine(link.targetLine, link.targetColumn, true, true);
setFocus();
return true;
@@ -9059,7 +9053,7 @@ QMimeData *TextEditorWidget::createMimeDataFromSelection(bool withHtml) const
} else {
const int startPosition = current.position() - selectionStart
- removedCount;
- int endPosition = startPosition + current.text().count();
+ int endPosition = startPosition + current.text().size();
if (current != last)
endPosition++;
removedCount += endPosition - startPosition;
@@ -9605,6 +9599,23 @@ void BaseTextEditor::select(int toPos)
editorWidget()->setTextCursor(tc);
}
+void BaseTextEditor::saveCurrentStateForNavigationHistory()
+{
+ d->m_savedNavigationState = saveState();
+}
+
+void BaseTextEditor::addSavedStateToNavigationHistory()
+{
+ if (EditorManager::currentEditor() == this)
+ EditorManager::addCurrentPositionToNavigationHistory(d->m_savedNavigationState);
+}
+
+void BaseTextEditor::addCurrentStateToNavigationHistory()
+{
+ if (EditorManager::currentEditor() == this)
+ EditorManager::addCurrentPositionToNavigationHistory();
+}
+
void TextEditorWidgetPrivate::updateCursorPosition()
{
m_contextHelpItem = HelpItem();
@@ -10004,7 +10015,7 @@ void TextEditorWidget::setupGenericHighlighter()
setLineSeparatorsAllowed(true);
connect(textDocument(), &IDocument::filePathChanged,
- d, &TextEditorWidgetPrivate::reconfigure);
+ d.get(), &TextEditorWidgetPrivate::reconfigure);
}
//
@@ -10236,6 +10247,21 @@ BaseTextEditor *TextEditorFactoryPrivate::createEditorHelper(const TextDocumentP
[editor](EditorManager::OpenEditorFlags flags) {
EditorManager::activateEditor(editor, flags);
});
+ QObject::connect(
+ textEditorWidget,
+ &TextEditorWidget::saveCurrentStateForNavigationHistory,
+ editor,
+ &BaseTextEditor::saveCurrentStateForNavigationHistory);
+ QObject::connect(
+ textEditorWidget,
+ &TextEditorWidget::addSavedStateToNavigationHistory,
+ editor,
+ &BaseTextEditor::addSavedStateToNavigationHistory);
+ QObject::connect(
+ textEditorWidget,
+ &TextEditorWidget::addCurrentStateToNavigationHistory,
+ editor,
+ &BaseTextEditor::addCurrentStateToNavigationHistory);
if (m_useGenericHighlighter)
textEditorWidget->setupGenericHighlighter();
diff --git a/src/plugins/texteditor/texteditor.h b/src/plugins/texteditor/texteditor.h
index 78dc0e0b82..7fc56e8733 100644
--- a/src/plugins/texteditor/texteditor.h
+++ b/src/plugins/texteditor/texteditor.h
@@ -169,6 +169,11 @@ public:
private:
friend class TextEditorFactory;
friend class Internal::TextEditorFactoryPrivate;
+
+ void saveCurrentStateForNavigationHistory();
+ void addSavedStateToNavigationHistory();
+ void addCurrentStateToNavigationHistory();
+
Internal::BaseTextEditorPrivate *d;
};
@@ -532,6 +537,11 @@ signals:
void requestCallHierarchy(const QTextCursor &cursor);
void toolbarOutlineChanged(QWidget *newOutline);
+ // used by the IEditor
+ void saveCurrentStateForNavigationHistory();
+ void addSavedStateToNavigationHistory();
+ void addCurrentStateToNavigationHistory();
+
protected:
QTextBlock blockForVisibleRow(int row) const;
QTextBlock blockForVerticalOffset(int offset) const;
@@ -657,7 +667,7 @@ protected:
virtual void slotCodeStyleSettingsChanged(const QVariant &); // Used in CppEditor
private:
- Internal::TextEditorWidgetPrivate *d;
+ std::unique_ptr<Internal::TextEditorWidgetPrivate> d;
friend class BaseTextEditor;
friend class TextEditorFactory;
friend class Internal::TextEditorFactoryPrivate;
diff --git a/src/plugins/texteditor/textmark.cpp b/src/plugins/texteditor/textmark.cpp
index 1739cf61b9..459d0ca27c 100644
--- a/src/plugins/texteditor/textmark.cpp
+++ b/src/plugins/texteditor/textmark.cpp
@@ -371,7 +371,7 @@ bool TextMark::addToolTipContent(QLayout *target) const
QColor TextMark::annotationColor() const
{
if (m_color.has_value())
- return Utils::creatorTheme()->color(*m_color).toHsl();
+ return Utils::creatorColor(*m_color).toHsl();
return {};
}
diff --git a/src/plugins/todo/Todo.json.in b/src/plugins/todo/Todo.json.in
index 2b6227492e..8574d8a780 100644
--- a/src/plugins/todo/Todo.json.in
+++ b/src/plugins/todo/Todo.json.in
@@ -13,7 +13,10 @@
"",
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
- "Description" : "Adds pane that lists all TODO, FIXME, etc. entries in comments.",
- "Url" : "http://www.qt.io",
+ "Description" : "List keywords from files",
+ "LongDescription" : [
+ "List the BUG, FIXME, NOTE, TODO, and WARNING keywords from the current file, from all project files, or from a subproject."
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/todo/keyword.cpp b/src/plugins/todo/keyword.cpp
index 8a505b1239..a178bb92e1 100644
--- a/src/plugins/todo/keyword.cpp
+++ b/src/plugins/todo/keyword.cpp
@@ -8,7 +8,7 @@
namespace Todo {
namespace Internal {
-Keyword::Keyword() : color(Utils::creatorTheme()->color(Utils::Theme::TextColorNormal))
+Keyword::Keyword() : color(Utils::creatorColor(Utils::Theme::TextColorNormal))
{
}
diff --git a/src/plugins/todo/settings.cpp b/src/plugins/todo/settings.cpp
index 68f6a3d709..dc534f98c8 100644
--- a/src/plugins/todo/settings.cpp
+++ b/src/plugins/todo/settings.cpp
@@ -99,7 +99,6 @@ void Settings::load()
void Settings::setDefault()
{
scanningScope = ScanningScopeCurrentFile;
- Utils::Theme *theme = Utils::creatorTheme();
keywords.clear();
@@ -107,32 +106,32 @@ void Settings::setDefault()
keyword.name = "TODO";
keyword.iconType = IconType::Todo;
- keyword.color = theme->color(Utils::Theme::OutputPanes_NormalMessageTextColor);
+ keyword.color = creatorColor(Utils::Theme::OutputPanes_NormalMessageTextColor);
keywords.append(keyword);
keyword.name = R"(\todo)";
keyword.iconType = IconType::Todo;
- keyword.color = theme->color(Utils::Theme::OutputPanes_NormalMessageTextColor);
+ keyword.color = creatorColor(Utils::Theme::OutputPanes_NormalMessageTextColor);
keywords.append(keyword);
keyword.name = "NOTE";
keyword.iconType = IconType::Info;
- keyword.color = theme->color(Utils::Theme::OutputPanes_NormalMessageTextColor);
+ keyword.color = creatorColor(Utils::Theme::OutputPanes_NormalMessageTextColor);
keywords.append(keyword);
keyword.name = "FIXME";
keyword.iconType = IconType::Error;
- keyword.color = theme->color(Utils::Theme::OutputPanes_ErrorMessageTextColor);
+ keyword.color = creatorColor(Utils::Theme::OutputPanes_ErrorMessageTextColor);
keywords.append(keyword);
keyword.name = "BUG";
keyword.iconType = IconType::Bug;
- keyword.color = theme->color(Utils::Theme::OutputPanes_ErrorMessageTextColor);
+ keyword.color = creatorColor(Utils::Theme::OutputPanes_ErrorMessageTextColor);
keywords.append(keyword);
keyword.name = "WARNING";
keyword.iconType = IconType::Warning;
- keyword.color = theme->color(Utils::Theme::OutputPanes_WarningMessageTextColor);
+ keyword.color = creatorColor(Utils::Theme::OutputPanes_WarningMessageTextColor);
keywords.append(keyword);
keywordsEdited = false;
@@ -212,7 +211,7 @@ OptionsDialog::OptionsDialog()
}
},
Group {
- title(Tr::tr("Scanning scope")),
+ title(Tr::tr("Scanning Scope")),
Column {
m_scanInProjectRadioButton,
m_scanInCurrentFileRadioButton,
diff --git a/src/plugins/updateinfo/UpdateInfo.json.in b/src/plugins/updateinfo/UpdateInfo.json.in
index b7f15e449b..6e4b699bfb 100644
--- a/src/plugins/updateinfo/UpdateInfo.json.in
+++ b/src/plugins/updateinfo/UpdateInfo.json.in
@@ -13,7 +13,8 @@
"",
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
- "Description" : "Displays Update-Infos for Qt Installer Framework-based Updaters.",
- "Url" : "http://www.qt.io",
+ "Description" : "View information from Qt Installer Framework-based updaters",
+ "LongDescription" : [],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/valgrind/Valgrind.json.in b/src/plugins/valgrind/Valgrind.json.in
index 1f270b1218..ff35fa0577 100644
--- a/src/plugins/valgrind/Valgrind.json.in
+++ b/src/plugins/valgrind/Valgrind.json.in
@@ -13,7 +13,12 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Code Analyzer",
- "Description" : "Valgrind Plugin.",
- "Url" : "http://www.qt.io",
+ "Description" : "Analyze code with Valgrind's Tool Suite",
+ "LongDescription" : [
+ "Detect problems in memory management with Memcheck and find cache misses in the code with Callgrind.",
+ "You also need:",
+ "- Valgrind's Tool Suite"
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/valgrind/valgrindmemcheckparsertest.cpp b/src/plugins/valgrind/valgrindmemcheckparsertest.cpp
index 44cac3712c..90d1f27ca0 100644
--- a/src/plugins/valgrind/valgrindmemcheckparsertest.cpp
+++ b/src/plugins/valgrind/valgrindmemcheckparsertest.cpp
@@ -187,9 +187,9 @@ void ValgrindMemcheckParserTest::initTest(const QString &testfile, const QString
QVERIFY2(fileInfo.isExecutable(), qPrintable(fakeValgrind));
QVERIFY2(!fileInfo.isDir(), qPrintable(fakeValgrind));
- const QStringList args = {QString("--xml-socket=127.0.0.1:%1").arg(m_server->serverPort()),
- "-i", testfile};
- m_process->setCommand({FilePath::fromString(fakeValgrind), args + otherArgs});
+ m_process->setCommand({FilePath::fromString(fakeValgrind),
+ {QString("--xml-socket=127.0.0.1:%1").arg(m_server->serverPort()),
+ "-i", testfile, otherArgs}});
m_process->start();
using namespace std::chrono_literals;
@@ -545,7 +545,7 @@ void ValgrindMemcheckParserTest::testRealValgrind()
debuggee.command.setExecutable(FilePath::fromString(executable));
debuggee.environment = sysEnv;
ValgrindProcess runner;
- runner.setValgrindCommand({"valgrind", {}});
+ runner.setValgrindCommand(CommandLine{"valgrind"});
runner.setDebuggee(debuggee);
RunnerDumper dumper(&runner);
runner.runBlocking();
diff --git a/src/plugins/valgrind/valgrindsettings.cpp b/src/plugins/valgrind/valgrindsettings.cpp
index b520f32892..47f4597374 100644
--- a/src/plugins/valgrind/valgrindsettings.cpp
+++ b/src/plugins/valgrind/valgrindsettings.cpp
@@ -116,7 +116,7 @@ SuppressionAspect::~SuppressionAspect()
delete d;
}
-void SuppressionAspect::addToLayout(Layouting::LayoutItem &parent)
+void SuppressionAspect::addToLayout(Layouting::Layout &parent)
{
QTC_CHECK(!d->addEntry);
QTC_CHECK(!d->removeEntry);
diff --git a/src/plugins/valgrind/valgrindsettings.h b/src/plugins/valgrind/valgrindsettings.h
index 93866bae86..43a2b94592 100644
--- a/src/plugins/valgrind/valgrindsettings.h
+++ b/src/plugins/valgrind/valgrindsettings.h
@@ -20,7 +20,7 @@ public:
SuppressionAspect(Utils::AspectContainer *container, bool global);
~SuppressionAspect() final;
- void addToLayout(Layouting::LayoutItem &parent) final;
+ void addToLayout(Layouting::Layout &parent) final;
void fromMap(const Utils::Store &map) final;
void toMap(Utils::Store &map) const final;
diff --git a/src/plugins/valgrind/valgrindtestrunnertest.cpp b/src/plugins/valgrind/valgrindtestrunnertest.cpp
index 7d53efca33..863265f8d0 100644
--- a/src/plugins/valgrind/valgrindtestrunnertest.cpp
+++ b/src/plugins/valgrind/valgrindtestrunnertest.cpp
@@ -93,11 +93,8 @@ QString ValgrindTestRunnerTest::runTestBinary(const QString &binary, const QStri
debuggee.command.setExecutable(Utils::FilePath::fromString(binPath));
debuggee.environment = Utils::Environment::systemEnvironment();
- CommandLine valgrind{"valgrind", {"--num-callers=50", "--track-origins=yes"}};
- valgrind.addArgs(vArgs);
-
m_runner->setLocalServerAddress(QHostAddress::LocalHost);
- m_runner->setValgrindCommand(valgrind);
+ m_runner->setValgrindCommand({"valgrind", {"--num-callers=50", "--track-origins=yes", vArgs}});
m_runner->setDebuggee(debuggee);
m_runner->runBlocking();
return binPath;
diff --git a/src/plugins/valgrind/xmlprotocol/parser.cpp b/src/plugins/valgrind/xmlprotocol/parser.cpp
index 2bab91fa71..550a021a87 100644
--- a/src/plugins/valgrind/xmlprotocol/parser.cpp
+++ b/src/plugins/valgrind/xmlprotocol/parser.cpp
@@ -10,8 +10,6 @@
#include "suppression.h"
#include "../valgrindtr.h"
-#include <extensionsystem/pluginmanager.h>
-
#include <utils/async.h>
#include <utils/expected.h>
#include <utils/futuresynchronizer.h>
@@ -684,7 +682,7 @@ public:
if (!m_watcher)
return;
m_thread->cancel();
- ExtensionSystem::PluginManager::futureSynchronizer()->addFuture(m_watcher->future());
+ Utils::futureSynchronizer()->addFuture(m_watcher->future());
}
void start()
diff --git a/src/plugins/vcpkg/Vcpkg.json.in b/src/plugins/vcpkg/Vcpkg.json.in
index a24d835d91..64dc6a9acc 100644
--- a/src/plugins/vcpkg/Vcpkg.json.in
+++ b/src/plugins/vcpkg/Vcpkg.json.in
@@ -14,8 +14,9 @@
],
"Experimental" : true,
"SoftLoadable" : true,
- "Description" : "vcpkg integration.",
- "Url" : "http://www.qt.io",
+ "Description" : "Get and manage libraries from the vcpkg package manager",
+ "LongDescription" : [],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES},
"Mimetypes" : [
diff --git a/src/plugins/vcsbase/VcsBase.json.in b/src/plugins/vcsbase/VcsBase.json.in
index 87e343ff45..a1717a9138 100644
--- a/src/plugins/vcsbase/VcsBase.json.in
+++ b/src/plugins/vcsbase/VcsBase.json.in
@@ -13,7 +13,11 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Version Control",
- "Description" : "Version Control System Base Plugin.",
- "Url" : "http://www.qt.io",
+ "Description" : "Provides the technical basis for version control system (VCS) extensions",
+ "LongDescription" : [
+ "You also need:",
+ "- An extension for a VCS tool, such as Git, and the tool"
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/vcsbase/commonvcssettings.cpp b/src/plugins/vcsbase/commonvcssettings.cpp
index 6c6db16c3e..1dbbb130dc 100644
--- a/src/plugins/vcsbase/commonvcssettings.cpp
+++ b/src/plugins/vcsbase/commonvcssettings.cpp
@@ -92,9 +92,9 @@ CommonVcsSettings::CommonVcsSettings()
empty,
PushButton {
text(Tr::tr("Reset VCS Cache")),
- tooltip(Tr::tr("Reset information about which "
- "version control system handles which directory.")),
- onClicked(&VcsManager::clearVersionControlCache)
+ Layouting::toolTip(Tr::tr("Reset information about which "
+ "version control system handles which directory.")),
+ onClicked(&VcsManager::clearVersionControlCache, (QObject *)nullptr)
}
}
};
diff --git a/src/plugins/vcsbase/submiteditorwidget.cpp b/src/plugins/vcsbase/submiteditorwidget.cpp
index b8f3d2a151..73d8358656 100644
--- a/src/plugins/vcsbase/submiteditorwidget.cpp
+++ b/src/plugins/vcsbase/submiteditorwidget.cpp
@@ -585,8 +585,7 @@ void SubmitEditorWidget::verifyDescription()
}
auto fontColor = [](Utils::Theme::Color color) {
- return QString("<font color=\"%1\">")
- .arg(Utils::creatorTheme()->color(color).name());
+ return QString("<font color=\"%1\">").arg(Utils::creatorColor(color).name());
};
const QString hint = fontColor(Utils::Theme::OutputPanes_TestWarnTextColor);
const QString warning = fontColor(Utils::Theme::TextColorError);
diff --git a/src/plugins/vcsbase/submitfilemodel.cpp b/src/plugins/vcsbase/submitfilemodel.cpp
index 02a9b6e359..a07538076d 100644
--- a/src/plugins/vcsbase/submitfilemodel.cpp
+++ b/src/plugins/vcsbase/submitfilemodel.cpp
@@ -45,7 +45,7 @@ static QBrush fileStatusTextForeground(SubmitFileModel::FileStatusHint statusHin
statusTextColor = Theme::VcsBase_FileUnmerged_TextColor;
break;
}
- return QBrush(Utils::creatorTheme()->color(statusTextColor));
+ return QBrush(Utils::creatorColor(statusTextColor));
}
static QList<QStandardItem *> createFileRow(const FilePath &repositoryRoot,
diff --git a/src/plugins/vcsbase/vcsbaseclient.cpp b/src/plugins/vcsbase/vcsbaseclient.cpp
index 6a36d628bd..1ce9e6d5a8 100644
--- a/src/plugins/vcsbase/vcsbaseclient.cpp
+++ b/src/plugins/vcsbase/vcsbaseclient.cpp
@@ -125,7 +125,7 @@ QStringList VcsBaseClientImpl::splitLines(const QString &s)
QString VcsBaseClientImpl::stripLastNewline(const QString &in)
{
if (in.endsWith('\n'))
- return in.left(in.count() - 1);
+ return in.left(in.size() - 1);
return in;
}
diff --git a/src/plugins/vcsbase/vcsbasediffeditorcontroller.cpp b/src/plugins/vcsbase/vcsbasediffeditorcontroller.cpp
index 4ba6baba34..8ab4a7946f 100644
--- a/src/plugins/vcsbase/vcsbasediffeditorcontroller.cpp
+++ b/src/plugins/vcsbase/vcsbasediffeditorcontroller.cpp
@@ -3,11 +3,8 @@
#include "vcsbasediffeditorcontroller.h"
-#include <extensionsystem/pluginmanager.h>
-
#include <utils/async.h>
#include <utils/environment.h>
-#include <utils/futuresynchronizer.h>
#include <utils/qtcprocess.h>
using namespace DiffEditor;
@@ -41,7 +38,6 @@ VcsBaseDiffEditorController::~VcsBaseDiffEditorController()
GroupItem VcsBaseDiffEditorController::postProcessTask(const Storage<QString> &inputStorage)
{
const auto onSetup = [inputStorage](Async<QList<FileData>> &async) {
- async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
async.setConcurrentCallData(&DiffUtils::readPatchWithPromise, *inputStorage);
};
const auto onDone = [this](const Async<QList<FileData>> &async, DoneWith result) {
diff --git a/src/plugins/vcsbase/vcsbaseeditor.cpp b/src/plugins/vcsbase/vcsbaseeditor.cpp
index 462412df06..7efdd7d20f 100644
--- a/src/plugins/vcsbase/vcsbaseeditor.cpp
+++ b/src/plugins/vcsbase/vcsbaseeditor.cpp
@@ -435,7 +435,7 @@ bool UrlTextCursorHandler::findContentsUnderCursor(const QTextCursor &cursor)
void UrlTextCursorHandler::highlightCurrentContents()
{
- const QColor linkColor = creatorTheme()->color(Theme::TextColorLink);
+ const QColor linkColor = creatorColor(Theme::TextColorLink);
QTextEdit::ExtraSelection sel;
sel.cursor = currentCursor();
sel.cursor.setPosition(currentCursor().position()
@@ -1638,7 +1638,7 @@ IEditor *VcsBaseEditor::locateEditorByTag(const QString &tag)
const QList<IDocument *> documents = DocumentModel::openedDocuments();
for (IDocument *document : documents) {
const QVariant tagPropertyValue = document->property(tagPropertyC);
- if (tagPropertyValue.type() == QVariant::String && tagPropertyValue.toString() == tag)
+ if (tagPropertyValue.typeId() == QMetaType::QString && tagPropertyValue.toString() == tag)
return DocumentModel::editorsForDocument(document).constFirst();
}
return nullptr;
diff --git a/src/plugins/vcsbase/wizard/vcscommandpage.cpp b/src/plugins/vcsbase/wizard/vcscommandpage.cpp
index c24cdfce0e..caabb20cf2 100644
--- a/src/plugins/vcsbase/wizard/vcscommandpage.cpp
+++ b/src/plugins/vcsbase/wizard/vcscommandpage.cpp
@@ -72,9 +72,9 @@ WizardPage *VcsCommandPageFactory::create(JsonWizard *wizard, Id typeId, const Q
QStringList args;
const QVariant argsVar = tmp.value(QLatin1String(VCSCOMMAND_EXTRA_ARGS));
if (!argsVar.isNull()) {
- if (argsVar.type() == QVariant::String) {
+ if (argsVar.typeId() == QMetaType::QString) {
args << argsVar.toString();
- } else if (argsVar.type() == QVariant::List) {
+ } else if (argsVar.typeId() == QMetaType::QVariantList) {
args = Utils::transform(argsVar.toList(), &QVariant::toString);
} else {
return nullptr;
@@ -101,7 +101,7 @@ WizardPage *VcsCommandPageFactory::create(JsonWizard *wizard, Id typeId, const Q
const QVariant &jobArgVar = job.value(QLatin1String(JOB_ARGUMENTS));
QStringList jobArgs;
if (!jobArgVar.isNull()) {
- if (jobArgVar.type() == QVariant::List)
+ if (jobArgVar.typeId() == QMetaType::QVariantList)
jobArgs = Utils::transform(jobArgVar.toList(), &QVariant::toString);
else
jobArgs << jobArgVar.toString();
@@ -127,7 +127,7 @@ bool VcsCommandPageFactory::validateData(Id typeId, const QVariant &data, QStrin
QTC_ASSERT(canCreate(typeId), return false);
QString em;
- if (data.type() != QVariant::Map)
+ if (data.typeId() != QMetaType::QVariantMap)
em = Tr::tr("\"data\" is no JSON object in \"VcsCommand\" page.");
if (em.isEmpty()) {
@@ -161,13 +161,13 @@ bool VcsCommandPageFactory::validateData(Id typeId, const QVariant &data, QStrin
}
const QVariant extra = tmp.value(QLatin1String(VCSCOMMAND_EXTRA_ARGS));
- if (!extra.isNull() && extra.type() != QVariant::String && extra.type() != QVariant::List) {
+ if (!extra.isNull() && extra.typeId() != QMetaType::QString && extra.typeId() != QMetaType::QVariantList) {
em = Tr::tr("\"%1\" in \"data\" section of \"VcsCommand\" page has unexpected type (unset, String or List).")
.arg(QLatin1String(VCSCOMMAND_EXTRA_ARGS));
}
const QVariant jobs = tmp.value(QLatin1String(VCSCOMMAND_JOBS));
- if (!jobs.isNull() && extra.type() != QVariant::List) {
+ if (!jobs.isNull() && extra.typeId() != QMetaType::QVariantList) {
em = Tr::tr("\"%1\" in \"data\" section of \"VcsCommand\" page has unexpected type (unset or List).")
.arg(QLatin1String(VCSCOMMAND_JOBS));
}
@@ -178,7 +178,7 @@ bool VcsCommandPageFactory::validateData(Id typeId, const QVariant &data, QStrin
em = Tr::tr("Job in \"VcsCommand\" page is empty.");
break;
}
- if (j.type() != QVariant::Map) {
+ if (j.typeId() != QMetaType::QVariantMap) {
em = Tr::tr("Job in \"VcsCommand\" page is not an object.");
break;
}
@@ -383,11 +383,11 @@ void VcsCommandPage::finished(bool success)
if (success) {
m_state = Succeeded;
message = Tr::tr("Succeeded.");
- palette.setColor(QPalette::WindowText, creatorTheme()->color(Theme::TextColorNormal).name());
+ palette.setColor(QPalette::WindowText, creatorColor(Theme::TextColorNormal).name());
} else {
m_state = Failed;
message = Tr::tr("Failed.");
- palette.setColor(QPalette::WindowText, creatorTheme()->color(Theme::TextColorError).name());
+ palette.setColor(QPalette::WindowText, creatorColor(Theme::TextColorError).name());
}
m_statusLabel->setText(message);
diff --git a/src/plugins/vcsbase/wizard/vcsconfigurationpage.cpp b/src/plugins/vcsbase/wizard/vcsconfigurationpage.cpp
index 2de4297563..0a591be695 100644
--- a/src/plugins/vcsbase/wizard/vcsconfigurationpage.cpp
+++ b/src/plugins/vcsbase/wizard/vcsconfigurationpage.cpp
@@ -56,7 +56,7 @@ bool VcsConfigurationPageFactory::validateData(Id typeId, const QVariant &data,
{
QTC_ASSERT(canCreate(typeId), return false);
- if (data.isNull() || data.type() != QVariant::Map) {
+ if (data.isNull() || data.typeId() != QMetaType::QVariantMap) {
//: Do not translate "VcsConfiguration", because it is the id of a page.
*errorMessage = ProjectExplorer::Tr::tr("\"data\" must be a JSON object for \"VcsConfiguration\" pages.");
return false;
diff --git a/src/plugins/webassembly/WebAssembly.json.in b/src/plugins/webassembly/WebAssembly.json.in
index f860568cee..4947a70983 100644
--- a/src/plugins/webassembly/WebAssembly.json.in
+++ b/src/plugins/webassembly/WebAssembly.json.in
@@ -15,7 +15,12 @@
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
"Category" : "Device Support",
- "Description" : "Helper for WebAssembly projects.",
- "Url" : "http://www.qt.io",
+ "Description" : "Integrate Qt applications into web browsers",
+ "LongDescription" : [
+ "WebAssembly is a binary format that allows sand-boxed executable code in web pages.",
+ "You also need:",
+ "- Qt for WebAssembly"
+ ],
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/webassembly/webassemblyrunconfiguration.cpp b/src/plugins/webassembly/webassemblyrunconfiguration.cpp
index 1c8f6b60c1..697808e70e 100644
--- a/src/plugins/webassembly/webassemblyrunconfiguration.cpp
+++ b/src/plugins/webassembly/webassemblyrunconfiguration.cpp
@@ -131,7 +131,7 @@ public:
addDataExtractor(this, &WebBrowserSelectionAspect::currentBrowser, &Data::currentBrowser);
}
- void addToLayout(Layouting::LayoutItem &parent) override
+ void addToLayout(Layouting::Layout &parent) override
{
QTC_CHECK(!m_webBrowserComboBox);
m_webBrowserComboBox = new QComboBox;
diff --git a/src/plugins/webassembly/webassemblysettings.cpp b/src/plugins/webassembly/webassemblysettings.cpp
index d09919a05b..74e4f65b84 100644
--- a/src/plugins/webassembly/webassemblysettings.cpp
+++ b/src/plugins/webassembly/webassemblysettings.cpp
@@ -21,6 +21,7 @@
#include <utils/pathchooser.h>
#include <utils/utilsicons.h>
+#include <QGroupBox>
#include <QTextBrowser>
#include <QTimer>
diff --git a/src/plugins/welcome/Welcome.json.in b/src/plugins/welcome/Welcome.json.in
index 40f7e1ee3f..4dc8529e66 100644
--- a/src/plugins/welcome/Welcome.json.in
+++ b/src/plugins/welcome/Welcome.json.in
@@ -20,6 +20,6 @@
"Description" : "Do not ask for taking a UI tour on startup"
}
],
- "Url" : "http://www.qt.io",
+ "Url" : "https://www.qt.io",
${IDE_PLUGIN_DEPENDENCIES}
}
diff --git a/src/plugins/welcome/welcomeplugin.cpp b/src/plugins/welcome/welcomeplugin.cpp
index 069fe1e263..432ce59cb9 100644
--- a/src/plugins/welcome/welcomeplugin.cpp
+++ b/src/plugins/welcome/welcomeplugin.cpp
@@ -162,10 +162,10 @@ public:
welcomeLabel,
st,
spacing(ExVPaddingGapXl),
- customMargin({HPaddingM, VPaddingM, HPaddingM, VPaddingM}),
+ customMargins(HPaddingM, VPaddingM, HPaddingM, VPaddingM),
},
createRule(Qt::Horizontal),
- noMargin(), spacing(0),
+ noMargin, spacing(0),
}.attachTo(this);
}
};
@@ -188,13 +188,13 @@ public:
Column mainColumn {
spacing(0),
- customMargin({ExVPaddingGapXl, 0, ExVPaddingGapXl, 0}),
+ customMargins(ExVPaddingGapXl, 0, ExVPaddingGapXl, 0),
};
m_essentials = new QWidget;
Column essentials {
spacing(0),
- noMargin(),
+ noMargin,
};
{
@@ -205,7 +205,7 @@ public:
newButton,
openButton,
spacing(ExPaddingGapL),
- customMargin({0, ExVPaddingGapXl, 0, ExVPaddingGapXl}),
+ customMargins(0, ExVPaddingGapXl, 0, ExVPaddingGapXl),
};
essentials.addItem(projectButtons);
@@ -232,13 +232,13 @@ public:
mainColumn.addItem(st);
{
- auto label = new Label(Tr::tr("Explore more"), Label::Secondary);
+ auto label = new Core::Label(Tr::tr("Explore more"), Core::Label::Secondary);
label->setContentsMargins(HPaddingXxs, 0, 0, 0); // Is indented in Figma design
Column linksLayout {
label,
spacing(VGapS),
- customMargin({0, VGapL, 0, ExVPaddingGapXl}),
+ customMargins(0, VGapL, 0, ExVPaddingGapXl),
};
const struct {
@@ -275,7 +275,7 @@ public:
Row {
mainColumn,
createRule(Qt::Vertical),
- noMargin(), spacing(0),
+ noMargin, spacing(0),
}.attachTo(mainWidget);
setWidget(mainWidget);
@@ -342,7 +342,7 @@ WelcomeMode::WelcomeMode()
m_sideArea,
m_pageStack,
},
- noMargin(),
+ noMargin,
spacing(0),
}.attachTo(m_modeWidget);