diff options
Diffstat (limited to 'src/widgets')
47 files changed, 1284 insertions, 359 deletions
diff --git a/src/widgets/CMakeLists.txt b/src/widgets/CMakeLists.txt index 2ba8e4719a..fdef309a4a 100644 --- a/src/widgets/CMakeLists.txt +++ b/src/widgets/CMakeLists.txt @@ -55,6 +55,7 @@ qt_internal_add_module(Widgets QT_NO_CONTEXTLESS_CONNECT QT_NO_USING_NAMESPACE QT_NO_FOREACH + QT_USE_NODISCARD_FILE_OPEN INCLUDE_DIRECTORIES dialogs LIBRARIES diff --git a/src/widgets/Qt6WidgetsMacros.cmake b/src/widgets/Qt6WidgetsMacros.cmake index b15d1fe81d..a7ff7af2f3 100644 --- a/src/widgets/Qt6WidgetsMacros.cmake +++ b/src/widgets/Qt6WidgetsMacros.cmake @@ -49,3 +49,293 @@ if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS) set("${outfiles}" "${${outfiles}}" PARENT_SCOPE) endfunction() endif() + +function(qt6_add_ui target) + if(NOT TARGET ${target}) + message(FATAL_ERROR "Target \"${target}\" does not exist.") + endif() + + set(options) + set(oneValueArgs INCLUDE_PREFIX) + set(multiValueArgs SOURCES OPTIONS) + + cmake_parse_arguments(arg "${options}" "${oneValueArgs}" "${multiValueArgs}" + ${ARGN}) + + set(sources ${arg_SOURCES}) + set(ui_options ${arg_OPTIONS}) + set(raw_include_prefix "${arg_INCLUDE_PREFIX}") + + if("${sources}" STREQUAL "") + message(FATAL_ERROR "The \"SOURCES\" parameter is empty.") + endif() + + + # Disable AUTOUIC for the target explicitly to avoid the AUTOUIC + # error message when the AUTOUIC is enabled for the target. + set_target_properties(${target} PROPERTIES AUTOUIC OFF) + + if(NOT "${raw_include_prefix}" STREQUAL "") + # generate dummy `_` folders from the given path + # e.g. if the given path is `../a/b/c`, then the generated path will be + # `_`. if the given path is ``../a/../b/c`, then the generated path will + # be `_/_` + function(_qt_internal_generate_dash_path_from_input input_path + out_generated_dash_path) + string(REGEX MATCHALL "\\.\\." upper_folder_list "${input_path}") + + list(JOIN upper_folder_list "" upper_folder_list) + string(REGEX REPLACE "\\.\\." "_/" additional_path + "${upper_folder_list}") + + # remove last / character + if(NOT "${additional_path}" STREQUAL "") + string(LENGTH "${additional_path}" additional_path_length) + math(EXPR additional_path_length "${additional_path_length} - 1") + string(SUBSTRING "${additional_path}" 0 ${additional_path_length} + additional_path) + endif() + + set(${out_generated_dash_path} "${additional_path}" PARENT_SCOPE) + endfunction() + # NOTE: If previous folders are less than ../ folder count in + # ${raw_include_prefix}, relative path calculation will miscalculate, + # so we need to add dummy folders to calculate the relative path + # correctly + _qt_internal_generate_dash_path_from_input("${raw_include_prefix}" + dummy_path_for_relative_calculation) + if(NOT "${dummy_path_for_relative_calculation}" STREQUAL "") + set(dummy_path_for_relative_calculation + "/${dummy_path_for_relative_calculation}") + set(raw_include_prefix_to_compare + "${dummy_path_for_relative_calculation}/${raw_include_prefix}") + else() + set(dummy_path_for_relative_calculation "/") + set(raw_include_prefix_to_compare "/${raw_include_prefix}") + endif() + # NOTE: This relative path calculation could be done just by using the + # below code + # cmake_path(RELATIVE_PATH normalized_include_prefix "${CMAKE_CURRENT_SOURCE_DIR}" + # but due to the backward compatibility, we need to use + # file(RELATIVE_PATH) and ../ folder calculation the with + # _qt_internal_generate_dash_path_from_input() function + if(WIN32) + set(dummy_path_for_relative_calculation + "${CMAKE_CURRENT_SOURCE_DIR}/${dummy_path_for_relative_calculation}") + set(raw_include_prefix_to_compare + "${CMAKE_CURRENT_SOURCE_DIR}/${raw_include_prefix_to_compare}") + string(REGEX REPLACE "//" "/" dummy_path_for_relative_calculation + "${dummy_path_for_relative_calculation}") + string(REGEX REPLACE "//" "/" raw_include_prefix_to_compare + "${raw_include_prefix_to_compare}") + endif() + + file(RELATIVE_PATH normalized_include_prefix + "${dummy_path_for_relative_calculation}" + "${raw_include_prefix_to_compare}") + + # if normalized_include_prefix ends with `/` remove it + string(REGEX REPLACE "/$" "" normalized_include_prefix + "${normalized_include_prefix}") + + _qt_internal_generate_dash_path_from_input("${normalized_include_prefix}" + additional_path) + endif() + + if(ui_options MATCHES "\\$<CONFIG") + set(is_ui_options_contains_config true) + endif() + get_property(is_multi GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) + if(is_multi AND is_ui_options_contains_config) + set(include_folder "$<CONFIG>") + endif() + + set(include_folder_to_add "${include_folder}") + if(NOT "${additional_path}" STREQUAL "") + set(include_folder_to_add "${include_folder_to_add}/${additional_path}") + endif() + + set(prefix_info_file_cmake + "${CMAKE_CURRENT_BINARY_DIR}/${target}_autogen/prefix_info.cmake") + if(EXISTS ${prefix_info_file_cmake}) + include(${prefix_info_file_cmake}) + set(prefix_info_file_cmake_exists true) + else() + set(prefix_info_file_cmake_exists false) + endif() + + foreach(source_file ${sources}) + get_filename_component(outfile "${source_file}" NAME_WE) + get_filename_component(infile "${source_file}" ABSOLUTE) + string(SHA1 infile_hash "${target}${infile}") + string(SUBSTRING "${infile_hash}" 0 6 short_hash) + set(file_ui_folder "${CMAKE_CURRENT_BINARY_DIR}/.qt/${short_hash}") + target_include_directories(${target} SYSTEM PRIVATE + "${file_ui_folder}/${include_folder_to_add}") + + set(target_include_folder_with_add_path + "${file_ui_folder}/${include_folder}") + # Add additional path to include folder if it is not empty + if(NOT "${additional_path}" STREQUAL "") + set(target_include_folder_with_add_path + "${target_include_folder_with_add_path}/${additional_path}") + endif() + + set(inc_dir_to_create "${target_include_folder_with_add_path}") + if(NOT "${raw_include_prefix}" STREQUAL "") + set(inc_dir_to_create + "${target_include_folder_with_add_path}/${raw_include_prefix}") + endif() + + set(output_directory "${target_include_folder_with_add_path}") + if(NOT "${normalized_include_prefix}" STREQUAL "") + set(output_directory + "${output_directory}/${normalized_include_prefix}") + endif() + + if(NOT EXISTS "${infile}") + message(FATAL_ERROR "${infile} does not exist.") + endif() + set_source_files_properties(${infile} PROPERTIES SKIP_AUTOUIC ON) + + macro(_qt_internal_set_output_file_properties file) + set_source_files_properties(${file} PROPERTIES + SKIP_AUTOMOC TRUE + SKIP_AUTOUIC TRUE + SKIP_LINTING TRUE) + endmacro() + + set(outfile "${output_directory}/ui_${outfile}.h") + # Before CMake 3.27, there is a bug for using $<CONFIG> in a generated + # file with Ninja Multi-Config generator. To avoid this issue, we need + # to add the generated file for each configuration. + # Explicitly set the output file properties for each configuration + # to avoid Policy CMP0071 warnings. + # See https://gitlab.kitware.com/cmake/cmake/-/issues/24848 + # Xcode generator cannot handle $<CONFIG> in the output file name. + # it changes $<CONFIG> with NOCONFIG. That's why we need to add the + # generated file for each configuration for Xcode generator as well. + if(is_multi AND outfile MATCHES "\\$<CONFIG>" + AND CMAKE_VERSION VERSION_LESS "3.27" + OR CMAKE_GENERATOR MATCHES "Xcode") + foreach(config ${CMAKE_CONFIGURATION_TYPES}) + string(REPLACE "$<CONFIG>" "${config}" outfile_with_config + "${outfile}") + _qt_internal_set_output_file_properties( + ${outfile_with_config}) + target_sources(${target} PRIVATE ${outfile_with_config}) + endforeach() + else() + _qt_internal_set_output_file_properties(${outfile}) + target_sources(${target} PRIVATE ${outfile}) + endif() + # remove double slashes + string(REGEX REPLACE "//" "/" outfile "${outfile}") + + # Note: If INCLUDE_PREFIX is changed without changing the corresponding + # include path in the source file and Ninja generator is used, this + # casues the double build issue. To avoid this issue, we need to + # remove the output file in configure step if the include_prefix is + # changed. + if(CMAKE_GENERATOR MATCHES "Ninja") + if(prefix_info_file_cmake_exists) + if(NOT "${${short_hash}_prefix}" STREQUAL + "${normalized_include_prefix}") + file(REMOVE_RECURSE "${file_ui_folder}") + list(APPEND prefix_info_list + "set(${short_hash}_prefix \"${normalized_include_prefix}\")") + elseif(NOT DEFINED ${short_hash}_prefix) + list(APPEND prefix_info_list + "set(${short_hash}_prefix \"${normalized_include_prefix}\")") + endif() + else() + set(prefix_info_list "") + list(APPEND prefix_info_list + "set(${short_hash}_prefix \"${normalized_include_prefix}\")") + endif() + file(MAKE_DIRECTORY ${file_ui_folder}) + endif() + + if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.17") + set(remove_command rm -rf) + else() + set(remove_command "remove_directory") + endif() + + add_custom_command(OUTPUT ${outfile} + DEPENDS ${QT_CMAKE_EXPORT_NAMESPACE}::uic + COMMAND ${CMAKE_COMMAND} -E ${remove_command} "${file_ui_folder}/${include_folder}" + COMMAND ${CMAKE_COMMAND} -E make_directory ${inc_dir_to_create} + COMMAND ${QT_CMAKE_EXPORT_NAMESPACE}::uic ${ui_options} -o + ${outfile} ${infile} + COMMAND_EXPAND_LISTS + MAIN_DEPENDENCY ${infile} VERBATIM) + endforeach() + + + get_target_property(is_guard_on ${target} _qt_ui_property_check_guard) + if(NOT is_guard_on) + # Ninja fails when a newline is used. That's why message is + # divided into parts. + set(error_message_1 + "Error: The target \"${target}\" has \"AUTOUIC\" enabled.") + set(error_message_2 + "Error: Please disable \"AUTOUIC\" for the target \"${target}\".") + + set(ui_property_check_dummy_file + "${CMAKE_CURRENT_BINARY_DIR}/${target}_autogen/ui_property_check_timestamp") + + add_custom_command(OUTPUT ${ui_property_check_dummy_file} + COMMAND ${CMAKE_COMMAND} -E make_directory + "${CMAKE_CURRENT_BINARY_DIR}/${target}_autogen" + COMMAND ${CMAKE_COMMAND} -E touch ${ui_property_check_dummy_file} + COMMAND + "$<$<BOOL:$<TARGET_PROPERTY:${target},AUTOUIC>>:${CMAKE_COMMAND}\ +;-E;echo;${error_message_1}>" + COMMAND + "$<$<BOOL:$<TARGET_PROPERTY:${target},AUTOUIC>>:${CMAKE_COMMAND}\ +;-E;echo;${error_message_2}>" + # Remove the dummy file so that the error message is shown until + # the AUTOUIC is disabled. Otherwise, the error message is shown + # only once when the AUTOUIC is enabled with Visual Studio generator. + COMMAND + "$<$<BOOL:$<TARGET_PROPERTY:${target},AUTOUIC>>:${CMAKE_COMMAND}\ +;-E;remove;${ui_property_check_dummy_file}>" + COMMAND + "$<$<BOOL:$<TARGET_PROPERTY:${target},AUTOUIC>>:${CMAKE_COMMAND}\ +;-E;false>" + COMMAND_EXPAND_LISTS + VERBATIM) + + # When AUTOUIC is enabled, AUTOUIC runs before ${target}_ui_property_check + # target. So we need to add ${target}_ui_property_check as a dependency + # to ${target} to make sure that AUTOUIC runs after ${target}_ui_property_check + if (NOT QT_NO_MIX_UI_AUTOUIC_CHECK) + set_target_properties(${target} PROPERTIES + _qt_ui_property_check_guard ON) + add_custom_target(${target}_ui_property_check + ALL DEPENDS ${ui_property_check_dummy_file}) + _qt_internal_assign_to_internal_targets_folder( + ${target}_ui_property_check) + add_dependencies(${target} ${target}_ui_property_check) + endif() + endif() + + # write prefix info file + if(CMAKE_GENERATOR MATCHES "Ninja") + list(PREPEND prefix_info_list "include_guard()") + string(REPLACE ";" "\n" prefix_info_list "${prefix_info_list}") + file(WRITE "${prefix_info_file_cmake}" "${prefix_info_list}") + endif() +endfunction() + +if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS) + function(qt_add_ui) + if(QT_DEFAULT_MAJOR_VERSION EQUAL 6) + qt6_add_ui(${ARGN}) + else() + message(FATAL_ERROR "qt_add_ui() is only available in Qt 6.") + endif() + endfunction() +endif() + diff --git a/src/widgets/accessible/itemviews_p.h b/src/widgets/accessible/itemviews_p.h index ddc97baba1..077f14de1d 100644 --- a/src/widgets/accessible/itemviews_p.h +++ b/src/widgets/accessible/itemviews_p.h @@ -144,8 +144,6 @@ public: private: QModelIndex indexFromLogical(int row, int column = 0) const; - - inline int logicalIndex(const QModelIndex &index) const; }; #endif diff --git a/src/widgets/accessible/qaccessiblewidgetfactory.cpp b/src/widgets/accessible/qaccessiblewidgetfactory.cpp index e13b7ebcf7..664e35a6e7 100644 --- a/src/widgets/accessible/qaccessiblewidgetfactory.cpp +++ b/src/widgets/accessible/qaccessiblewidgetfactory.cpp @@ -121,7 +121,6 @@ QAccessibleInterface *qAccessibleFactory(const QString &classname, QObject *obje #if QT_CONFIG(itemviews) } else if (classname == "QTableView"_L1 || classname == "QListView"_L1) { iface = new QAccessibleTable(widget); - // ### This should be cleaned up. We return the parent for the scrollarea to hide it. #endif // QT_CONFIG(itemviews) #if QT_CONFIG(tabbar) } else if (classname == "QTabBar"_L1) { diff --git a/src/widgets/accessible/qaccessiblewidgets.cpp b/src/widgets/accessible/qaccessiblewidgets.cpp index 1c29de8caf..5c2a3bd02b 100644 --- a/src/widgets/accessible/qaccessiblewidgets.cpp +++ b/src/widgets/accessible/qaccessiblewidgets.cpp @@ -95,7 +95,7 @@ QWidgetList _q_ac_childWidgets(const QWidget *widget) QAccessiblePlainTextEdit::QAccessiblePlainTextEdit(QWidget* o) :QAccessibleTextWidget(o) { - Q_ASSERT(widget()->inherits("QPlainTextEdit")); + Q_ASSERT(qobject_cast<QPlainTextEdit *>(widget())); } QPlainTextEdit* QAccessiblePlainTextEdit::plainTextEdit() const @@ -192,7 +192,7 @@ void QAccessiblePlainTextEdit::scrollToSubstring(int startIndex, int endIndex) QAccessibleTextEdit::QAccessibleTextEdit(QWidget *o) : QAccessibleTextWidget(o, QAccessible::EditableText) { - Q_ASSERT(widget()->inherits("QTextEdit")); + Q_ASSERT(qobject_cast<QTextEdit *>(widget())); } /*! Returns the text edit. */ diff --git a/src/widgets/dialogs/qcolordialog.cpp b/src/widgets/dialogs/qcolordialog.cpp index 27315fe53c..22efecedc9 100644 --- a/src/widgets/dialogs/qcolordialog.cpp +++ b/src/widgets/dialogs/qcolordialog.cpp @@ -1398,8 +1398,8 @@ void QColorShower::htmlEd() if (t.isEmpty()) return; - if (!t.startsWith(QStringLiteral("#"))) { - t = QStringLiteral("#") + t; + if (!t.startsWith(u"#")) { + t.prepend(u"#"); QSignalBlocker blocker(htEd); htEd->setText(t); } diff --git a/src/widgets/dialogs/qdialog.h b/src/widgets/dialogs/qdialog.h index 4d11fe2d8d..22360bc358 100644 --- a/src/widgets/dialogs/qdialog.h +++ b/src/widgets/dialogs/qdialog.h @@ -28,6 +28,7 @@ public: ~QDialog(); enum DialogCode { Rejected, Accepted }; + Q_ENUM(DialogCode) int result() const; diff --git a/src/widgets/dialogs/qfontdialog.cpp b/src/widgets/dialogs/qfontdialog.cpp index afc46e7506..628297d22b 100644 --- a/src/widgets/dialogs/qfontdialog.cpp +++ b/src/widgets/dialogs/qfontdialog.cpp @@ -509,7 +509,7 @@ void QFontDialogPrivate::updateFamilies() //and try some fall backs match_t type = MATCH_NONE; - if (bestFamilyType <= MATCH_NONE && familyName2 == QStringLiteral("helvetica")) + if (bestFamilyType <= MATCH_NONE && familyName2 == "helvetica"_L1) type = MATCH_LAST_RESORT; if (bestFamilyType <= MATCH_LAST_RESORT && familyName2 == f.families().constFirst()) type = MATCH_APP; diff --git a/src/widgets/doc/snippets/cmake-macros/examples.cmake b/src/widgets/doc/snippets/cmake-macros/examples.cmake index 3c58509fdf..7d0023aed4 100644 --- a/src/widgets/doc/snippets/cmake-macros/examples.cmake +++ b/src/widgets/doc/snippets/cmake-macros/examples.cmake @@ -6,3 +6,30 @@ set(SOURCES mainwindow.cpp main.cpp) qt_wrap_ui(SOURCES mainwindow.ui) qt_add_executable(myapp ${SOURCES}) #! [qt_wrap_ui] + +#! [qt6_add_ui_1] +qt_add_executable(myapp mainwindow.cpp main.cpp) +qt6_add_ui(myapp SOURCES mainwindow.ui) +#! [qt6_add_ui_1] + +#! [qt6_add_ui_2] +qt_add_executable(myapp mainwindow.cpp main.cpp) +qt6_add_ui(myapp INCLUDE_PREFIX "src/files" SOURCES mainwindow.ui) +#! [qt6_add_ui_2] + +#! [qt6_add_ui_3] +qt_add_executable(myapp widget1.cpp widget2.cpp main.cpp) +qt6_add_ui(myapp INCLUDE_PREFIX "src/files" SOURCES widget1.ui widget2.ui) +#! [qt6_add_ui_3] + +#! [qt6_add_ui_4] +qt_add_executable(myapp widget1.cpp widget2.cpp main.cpp) +qt6_add_ui(myapp INCLUDE_PREFIX "src/files" + SOURCES "my_ui_files_1/widget1.ui" "my_ui_files_2/widget2.ui") +#! [qt6_add_ui_4] + +#! [qt6_add_ui_5] +qt_add_executable(myapp widget1.cpp widget2.cpp main.cpp) +qt6_add_ui(myapp INCLUDE_PREFIX "src/files_1" SOURCES "my_ui_files/widget1.ui") +qt6_add_ui(myapp INCLUDE_PREFIX "src/files_2" SOURCES "my_ui_files/widget2.ui") +#! [qt6_add_ui_5] diff --git a/src/widgets/doc/snippets/cmake-macros/examples.cpp b/src/widgets/doc/snippets/cmake-macros/examples.cpp new file mode 100644 index 0000000000..683f8453e4 --- /dev/null +++ b/src/widgets/doc/snippets/cmake-macros/examples.cpp @@ -0,0 +1,26 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + + +//! [qt6_add_ui_1] +#include "mainwindow.h" +#include "ui_mainwindow.h" +//! [qt6_add_ui_1] + +//! [qt6_add_ui_2] +#include "mainwindow.h" +#include "src/files/ui_mainwindow.h" +//! [qt6_add_ui_2] + +//! [qt6_add_ui_3_1] +#include "src/files/ui_widget1.h" +//! [qt6_add_ui_3_1] + +//! [qt6_add_ui_3_2] +#include "src/files/ui_widget2.h" +//! [qt6_add_ui_3_2] + +//! [qt6_add_ui_5] +#include "src/files_1/ui_widget1.h" +#include "src/files_2/ui_widget2.h" +//! [qt6_add_ui_5] diff --git a/src/widgets/doc/snippets/code/src_gui_itemviews_qitemeditorfactory.cpp b/src/widgets/doc/snippets/code/src_gui_itemviews_qitemeditorfactory.cpp index d314c88fd9..1a311015c4 100644 --- a/src/widgets/doc/snippets/code/src_gui_itemviews_qitemeditorfactory.cpp +++ b/src/widgets/doc/snippets/code/src_gui_itemviews_qitemeditorfactory.cpp @@ -14,12 +14,16 @@ QItemEditorFactory *factory = new QItemEditorFactory; //! [1] +//! [setDefaultFactory] //! [2] QItemEditorFactory *editorFactory = new QItemEditorFactory; QItemEditorCreatorBase *creator = new QStandardItemEditorCreator<MyFancyDateTimeEdit>(); editorFactory->registerEditor(QMetaType::QDateTime, creator); //! [2] +QItemEditorFactory::setDefaultFactory(editorFactory); +//! [setDefaultFactory] + //! [3] Q_PROPERTY(QColor color READ color WRITE setColor USER true) diff --git a/src/widgets/doc/snippets/code/src_gui_widgets_qgroupbox.cpp b/src/widgets/doc/snippets/code/src_gui_widgets_qgroupbox.cpp index 1298147de1..cff537c862 100644 --- a/src/widgets/doc/snippets/code/src_gui_widgets_qgroupbox.cpp +++ b/src/widgets/doc/snippets/code/src_gui_widgets_qgroupbox.cpp @@ -4,3 +4,20 @@ //! [0] g->setTitle("&User information"); //! [0] + +//! [Set up QGroupBox with layout] +QGroupBox *groupBox = new QGroupBox(tr("Group Box with Layout")); + +QRadioButton *radio1 = new QRadioButton(tr("&Radio button 1")); +QRadioButton *radio2 = new QRadioButton(tr("R&adio button 2")); +QRadioButton *radio3 = new QRadioButton(tr("Ra&dio button 3")); + +radio1->setChecked(true); + +QVBoxLayout *vbox = new QVBoxLayout; +vbox->addWidget(radio1); +vbox->addWidget(radio2); +vbox->addWidget(radio3); +vbox->addStretch(1); +groupBox->setLayout(vbox); +//! [Set up QGroupBox with layout] diff --git a/src/widgets/doc/snippets/code/src_gui_widgets_qmenu.cpp b/src/widgets/doc/snippets/code/src_gui_widgets_qmenu.cpp index b0b0500fab..eb0897d5c5 100644 --- a/src/widgets/doc/snippets/code/src_gui_widgets_qmenu.cpp +++ b/src/widgets/doc/snippets/code/src_gui_widgets_qmenu.cpp @@ -12,7 +12,7 @@ exec(somewidget.mapToGlobal(QPoint(0,0))); //! [2] -exec(e->globalPos()); +exec(e->globalPosition().toPoint()); //! [2] @@ -27,7 +27,7 @@ exec(somewidget.mapToGlobal(QPoint(0, 0))); //! [5] -exec(e->globalPos()); +exec(e->globalPosition().toPoint()); //! [5] diff --git a/src/widgets/doc/src/cmake-macros.qdoc b/src/widgets/doc/src/cmake-macros.qdoc index 13bdcca67b..906a18b2b0 100644 --- a/src/widgets/doc/src/cmake-macros.qdoc +++ b/src/widgets/doc/src/cmake-macros.qdoc @@ -30,6 +30,10 @@ directory. The paths of the generated header files are added to \c{<VAR>}. \note This is a low-level macro. See the \l{CMake AUTOUIC Documentation} for a more convenient way to process \c{.ui} files with \c{uic}. +Since 6.8: +\note \l{qt6_add_ui}{qt_add_ui} is the recommended way to process \c{.ui} +files. + \section1 Options You can set additional \c{OPTIONS} that should be added to the \c{uic} calls. @@ -39,3 +43,132 @@ You can find possible options in the \l{uic}{uic documentation}. \snippet cmake-macros/examples.cmake qt_wrap_ui */ + +/*! +\page qt-add-ui.html +\ingroup cmake-macros-qtwidgets + +\title qt_add_ui +\keyword qt6_add_ui + +\summary {Adds .ui files to a target.} + +\include cmake-find-package-widgets.qdocinc + +\section1 Synopsis + +\badcode +qt_add_ui(<TARGET> + SOURCES file1.ui [file2.ui ...] + [INCLUDE_PREFIX <PREFIX>] + [OPTIONS ...]) +\endcode + +\versionlessCMakeCommandsNote qt6_add_ui() + +\cmakecommandsince 6.8 + +\section1 Description + +Creates rules for calling the \l{uic}{User Interface Compiler (uic)} on the +\c{.ui} files. For each input file, a header file is generated in the build +directory. The generated header files are added to sources of the target. + +\section1 Arguments + +\section2 TARGET + +The \c{TARGET} argument specifies the CMake target to which the generated header +files will be added. + +\section2 SOURCES + +The \c{SOURCES} argument specifies the list of \c{.ui} files to process. + +\section2 INCLUDE_PREFIX + +\c INCLUDE_PREFIX specifies the include prefix for the generated header files. +Use the same include prefix as in the \c{#include} directive in the source +files. If \c{ui_<basename>.h} is included without a prefix, then this argument +can be omitted. + +\section2 OPTIONS + +You can set additional \c{OPTIONS} that should be added to the \c{uic} calls. +You can find possible options in the \l{uic}{uic documentation}. + +\section1 Examples + +\section2 Without INCLUDE_PREFIX + +In the following snippet, the \c{mainwindow.cpp} file includes +\c{ui_mainwindow.h} and \c{mainwindow.h}. + +\snippet cmake-macros/examples.cpp qt6_add_ui_1 + +\c{CMakeLists.txt} is implemented as follows and it calls +\l{qt6_add_ui}{qt_add_ui} to add \c{ui_mainwindow.h} to the \c{myapp} target. + +\snippet cmake-macros/examples.cmake qt6_add_ui_1 + +In the above example, \c{ui_mainwindow.h} is included without a prefix. So the +\c{INCLUDE_PREFIX} argument is not specified. + +\section2 With INCLUDE_PREFIX + +\snippet cmake-macros/examples.cpp qt6_add_ui_2 + +In the above snippet, \c{mainwindow.cpp} includes \c{ui_mainwindow.h} with a +prefix. + +\snippet cmake-macros/examples.cmake qt6_add_ui_2 + +Since \c{ui_mainwindow.h} is included with a prefix, the \c{INCLUDE_PREFIX} +argument is specified as \c{src/files} in the above example. + +\section2 Multiple .ui files + +In the following snippets, both \c{widget1.cpp} and \c{widget2.cpp} include +\c{ui_widget1.h} and \c{ui_widget2.h} respectively. + +\c{widget1.cpp}: + +\snippet cmake-macros/examples.cpp qt6_add_ui_3_1 + +\c{widget2.cpp}: + +\snippet cmake-macros/examples.cpp qt6_add_ui_3_2 + +Both \c{ui_widget1.h} and \c{ui_widget2.h} are included with the same prefix + +\snippet cmake-macros/examples.cmake qt6_add_ui_3 + +In this case, the \c{INCLUDE_PREFIX} argument can be specified as \c{src/files} +for both files in the above snippet. + +\section1 When to prefer \l{qt6_add_ui}{qt_add_ui} over \c{AUTOUIC}? + +\l{qt6_add_ui}{qt_add_ui} has some advantages over \c{AUTOUIC}: + +\list +\li \l{qt6_add_ui}{qt_add_ui} ensures that the \c{.ui} files are generated +correctly during the first build, for the \c{Ninja} and \c{Ninja Multi-Config} +generators. + +\li \l{qt6_add_ui}{qt_add_ui} guarantees that the generated \c{.h} files are +not leaked outside the build directory. + +\li Since \l{qt6_add_ui}{qt_add_ui} does not scan source files, it provides a +faster build than \c{AUTOUIC}. + +\endlist + +\section1 When to prefer \l{qt6_add_ui}{qt_add_ui} over \c{qt_wrap_ui}? + +\l{qt6_add_ui}{qt_add_ui} has the \c{INCLUDE_PREFIX} argument, which can be used to +specify the include prefix for the generated header files. + +\note It is not recommended to use both \l{qt6_add_ui}{qt_add_ui} and +\c{AUTOUIC} for the same target. + +*/ diff --git a/src/widgets/itemviews/qitemdelegate.cpp b/src/widgets/itemviews/qitemdelegate.cpp index e4a90278b4..d1c7bb3d58 100644 --- a/src/widgets/itemviews/qitemdelegate.cpp +++ b/src/widgets/itemviews/qitemdelegate.cpp @@ -257,15 +257,25 @@ QSizeF QItemDelegatePrivate::doTextLayout(int lineWidth) const When subclassing QItemDelegate to create a delegate that displays items using a custom renderer, it is important to ensure that the delegate can - render items suitably for all the required states; e.g. selected, + render items suitably for all the required states; such as selected, disabled, checked. The documentation for the paint() function contains some hints to show how this can be achieved. - You can provide custom editors by using a QItemEditorFactory. The - \l{Color Editor Factory Example} shows how a custom editor can be - made available to delegates with the default item editor - factory. This way, there is no need to subclass QItemDelegate. An - alternative is to reimplement createEditor(), setEditorData(), + You can provide custom editors by using a QItemEditorFactory. The following + code shows how a custom editor can be made available to delegates with the + default item editor factory. + + \snippet code/src_gui_itemviews_qitemeditorfactory.cpp setDefaultFactory + + After the default factory has been set, all standard item delegates + will use it (also the delegates that were created before setting the + default factory). + + This way, you can avoid subclassing QItemDelegate, and all values of the + specified type (for example QMetaType::QDateTime) will be edited using the + provided editor (like \c{MyFancyDateTimeEdit} in the above example). + + An alternative is to reimplement createEditor(), setEditorData(), setModelData(), and updateEditorGeometry(). This process is described in the \l{A simple delegate}{Model/View Programming overview documentation}. diff --git a/src/widgets/itemviews/qitemeditorfactory.cpp b/src/widgets/itemviews/qitemeditorfactory.cpp index 609df364cf..70d11e1b38 100644 --- a/src/widgets/itemviews/qitemeditorfactory.cpp +++ b/src/widgets/itemviews/qitemeditorfactory.cpp @@ -120,7 +120,7 @@ Q_SIGNALS: Additional editors can be registered with the registerEditor() function. - \sa QStyledItemDelegate, {Model/View Programming}, {Color Editor Factory Example} + \sa QStyledItemDelegate, {Model/View Programming} */ /*! @@ -363,7 +363,7 @@ void QItemEditorFactory::setDefaultFactory(QItemEditorFactory *factory) to register widgets without the need to subclass QItemEditorCreatorBase. \sa QStandardItemEditorCreator, QItemEditorFactory, - {Model/View Programming}, {Color Editor Factory Example} + {Model/View Programming} */ /*! @@ -432,7 +432,7 @@ QItemEditorCreatorBase::~QItemEditorCreatorBase() property, you should use QStandardItemEditorCreator instead. \sa QItemEditorCreatorBase, QStandardItemEditorCreator, - QItemEditorFactory, {Color Editor Factory Example} + QItemEditorFactory */ /*! @@ -488,7 +488,7 @@ QItemEditorCreatorBase::~QItemEditorCreatorBase() \snippet code/src_gui_itemviews_qitemeditorfactory.cpp 3 \sa QItemEditorCreatorBase, QItemEditorCreator, - QItemEditorFactory, QStyledItemDelegate, {Color Editor Factory Example} + QItemEditorFactory, QStyledItemDelegate */ /*! diff --git a/src/widgets/itemviews/qstyleditemdelegate.cpp b/src/widgets/itemviews/qstyleditemdelegate.cpp index 0587e8d0be..54c1fb4f52 100644 --- a/src/widgets/itemviews/qstyleditemdelegate.cpp +++ b/src/widgets/itemviews/qstyleditemdelegate.cpp @@ -137,12 +137,17 @@ public: instance provided by QItemEditorFactory is installed on all item delegates. You can set a custom factory using setItemEditorFactory() or set a new default factory with - QItemEditorFactory::setDefaultFactory(). It is the data stored in - the item model with the \l{Qt::}{EditRole} that is edited. See the - QItemEditorFactory class for a more high-level introduction to - item editor factories. The \l{Color Editor Factory Example}{Color - Editor Factory} example shows how to create custom editors with a - factory. + QItemEditorFactory::setDefaultFactory(). + + \snippet code/src_gui_itemviews_qitemeditorfactory.cpp setDefaultFactory + + After the new factory has been set, all standard item delegates + will use it (i.e, also delegates that were created before the new + default factory was set). + + It is the data stored in the item model with the \l{Qt::}{EditRole} + that is edited. See the QItemEditorFactory class for a more + high-level introduction to item editor factories. \section1 Subclassing QStyledItemDelegate @@ -204,7 +209,7 @@ public: documentation for details. \sa {Delegate Classes}, QItemDelegate, QAbstractItemDelegate, QStyle, - {Star Delegate Example}, {Color Editor Factory Example} + {Star Delegate Example} */ diff --git a/src/widgets/itemviews/qtableview.cpp b/src/widgets/itemviews/qtableview.cpp index 1c2184620a..5726348bc5 100644 --- a/src/widgets/itemviews/qtableview.cpp +++ b/src/widgets/itemviews/qtableview.cpp @@ -595,7 +595,7 @@ void QTableViewPrivate::init() cornerWidget->setFocusPolicy(Qt::NoFocus); cornerWidgetConnection = QObject::connect( cornerWidget, &QTableCornerButton::clicked, - q, &QTableView::reset); + q, &QTableView::selectAll); #endif } @@ -2432,12 +2432,12 @@ int QTableView::sizeHintForRow(int row) const break; } - int actualRight = d->model->columnCount(d->root) - 1; + const int actualRight = d->model->columnCount(d->root) - 1; int idxLeft = left; int idxRight = column - 1; - if (maximumProcessCols == 0) - columnsProcessed = 0; // skip the while loop + if (maximumProcessCols == 0 || actualRight < idxLeft) + columnsProcessed = maximumProcessCols; // skip the while loop while (columnsProcessed != maximumProcessCols && (idxLeft > 0 || idxRight < actualRight)) { int logicalIdx = -1; @@ -2461,11 +2461,10 @@ int QTableView::sizeHintForRow(int row) const break; } } - if (logicalIdx < 0) - continue; - - index = d->model->index(row, logicalIdx, d->root); - hint = d->heightHintForIndex(index, hint, option); + if (logicalIdx >= 0) { + index = d->model->index(row, logicalIdx, d->root); + hint = d->heightHintForIndex(index, hint, option); + } ++columnsProcessed; } @@ -2521,12 +2520,12 @@ int QTableView::sizeHintForColumn(int column) const break; } - int actualBottom = d->model->rowCount(d->root) - 1; + const int actualBottom = d->model->rowCount(d->root) - 1; int idxTop = top; int idxBottom = row - 1; - if (maximumProcessRows == 0) - rowsProcessed = 0; // skip the while loop + if (maximumProcessRows == 0 || actualBottom < idxTop) + rowsProcessed = maximumProcessRows; // skip the while loop while (rowsProcessed != maximumProcessRows && (idxTop > 0 || idxBottom < actualBottom)) { int logicalIdx = -1; @@ -2550,11 +2549,10 @@ int QTableView::sizeHintForColumn(int column) const break; } } - if (logicalIdx < 0) - continue; - - index = d->model->index(logicalIdx, column, d->root); - hint = d->widthHintForIndex(index, hint, option); + if (logicalIdx >= 0) { + index = d->model->index(logicalIdx, column, d->root); + hint = d->widthHintForIndex(index, hint, option); + } ++rowsProcessed; } diff --git a/src/widgets/kernel/qapplication.cpp b/src/widgets/kernel/qapplication.cpp index de7d93ce20..a1392e10dc 100644 --- a/src/widgets/kernel/qapplication.cpp +++ b/src/widgets/kernel/qapplication.cpp @@ -1094,6 +1094,12 @@ QPalette QApplicationPrivate::basePalette() const if (const QPalette *themePalette = platformTheme() ? platformTheme()->palette() : nullptr) palette = themePalette->resolve(palette); + // This palette now is Qt-generated, so reset the resolve mask. This allows + // QStyle::polish implementations to respect palettes that are user provided, + // by checking if the palette has a brush set for a color that the style might + // otherwise overwrite. + palette.setResolveMask(0); + // Finish off by letting the application style polish the palette. This will // not result in the polished palette becoming a user-set palette, as the // resulting base palette is only used as a fallback, with the resolve mask diff --git a/src/widgets/kernel/qgesturemanager.cpp b/src/widgets/kernel/qgesturemanager.cpp index c93876c500..edb159bbbf 100644 --- a/src/widgets/kernel/qgesturemanager.cpp +++ b/src/widgets/kernel/qgesturemanager.cpp @@ -610,7 +610,8 @@ void QGestureManager::deliverEvents(const QSet<QGesture *> &gestures, QWidget *child = topLevel->childAt(topLevel->mapFromGlobal(pt)); target = child ? child : topLevel; } - } else { + } + if (!target) { // or use the context of the gesture QObject *context = m_gestureOwners.value(gesture, 0); if (context->isWidgetType()) diff --git a/src/widgets/kernel/qlayout.cpp b/src/widgets/kernel/qlayout.cpp index 0251ecd7fd..a826ea75bc 100644 --- a/src/widgets/kernel/qlayout.cpp +++ b/src/widgets/kernel/qlayout.cpp @@ -520,10 +520,11 @@ void QLayoutPrivate::doResize() void QLayout::widgetEvent(QEvent *e) { Q_D(QLayout); - if (!d->enabled) + const QEvent::Type type = e->type(); + if (!d->enabled && type != QEvent::ChildRemoved) return; - switch (e->type()) { + switch (type) { case QEvent::Resize: if (d->activated) d->doResize(); diff --git a/src/widgets/kernel/qwidget.cpp b/src/widgets/kernel/qwidget.cpp index 30daee1b79..e8342a4788 100644 --- a/src/widgets/kernel/qwidget.cpp +++ b/src/widgets/kernel/qwidget.cpp @@ -6,6 +6,7 @@ #include "qapplication_p.h" #include "qbrush.h" #include "qcursor.h" +#include "private/qduplicatetracker_p.h" #include "qevent.h" #include "qlayout.h" #if QT_CONFIG(menu) @@ -84,6 +85,7 @@ using namespace Qt::StringLiterals; Q_LOGGING_CATEGORY(lcWidgetPainting, "qt.widgets.painting", QtWarningMsg); Q_LOGGING_CATEGORY(lcWidgetShowHide, "qt.widgets.showhide", QtWarningMsg); Q_LOGGING_CATEGORY(lcWidgetWindow, "qt.widgets.window", QtWarningMsg); +Q_LOGGING_CATEGORY(lcWidgetFocus, "qt.widgets.focus") #ifndef QT_NO_DEBUG_STREAM namespace { @@ -818,12 +820,7 @@ struct QWidgetExceptionCleaner Q_UNUSED(d); #else QWidgetPrivate::allWidgets->remove(that); - if (d->focus_next != that) { - if (d->focus_next) - d->focus_next->d_func()->focus_prev = d->focus_prev; - if (d->focus_prev) - d->focus_prev->d_func()->focus_next = d->focus_next; - } + d->removeFromFocusChain(); #endif } }; @@ -991,7 +988,7 @@ void QWidgetPrivate::init(QWidget *parentWidget, Qt::WindowFlags f) //give potential windows a bigger "pre-initial" size; create() will give them a new size later data.crect = parentWidget ? QRect(0,0,100,30) : QRect(0,0,640,480); - focus_next = focus_prev = q; + initFocusChain(); if ((f & Qt::WindowType_Mask) == Qt::Desktop) q->create(); @@ -1292,7 +1289,7 @@ void QWidgetPrivate::create() Qt::WindowFlags &flags = data.window_flags; -#if defined(Q_OS_IOS) || defined(Q_OS_TVOS) +#if defined(QT_PLATFORM_UIKIT) if (q->testAttribute(Qt::WA_ContentsMarginsRespectsSafeArea)) flags |= Qt::MaximizeUsingFullscreenGeometryHint; #endif @@ -1477,17 +1474,9 @@ QWidget::~QWidget() // delete layout while we still are a valid widget delete d->layout; d->layout = nullptr; - // Remove myself from focus list - - Q_ASSERT(d->focus_next->d_func()->focus_prev == this); - Q_ASSERT(d->focus_prev->d_func()->focus_next == this); - - if (d->focus_next != this) { - d->focus_next->d_func()->focus_prev = d->focus_prev; - d->focus_prev->d_func()->focus_next = d->focus_next; - d->focus_next = d->focus_prev = nullptr; - } + // Remove this from focus list + d->removeFromFocusChain(QWidgetPrivate::FocusChainRemovalRule::AssertConsistency); QT_TRY { #if QT_CONFIG(graphicsview) @@ -6406,39 +6395,18 @@ void QWidget::setFocusProxy(QWidget * w) break; } Q_ASSERT(firstChild); // can't be nullptr since w is a child - QWidget *oldNext = d->focus_next; - QWidget *oldPrev = d->focus_prev; - oldNext->d_func()->focus_prev = oldPrev; - oldPrev->d_func()->focus_next = oldNext; - - oldPrev = firstChild->d_func()->focus_prev; - d->focus_next = firstChild; - d->focus_prev = oldPrev; - oldPrev->d_func()->focus_next = this; - firstChild->d_func()->focus_prev = this; + d->insertIntoFocusChainBefore(firstChild); } else if (w && w->isAncestorOf(this)) { // If the focus proxy is a parent, 'this' has to be inserted directly after its parent in the focus chain // remove it from the chain and insert this into the focus chain after its parent // is this the case already? - QWidget *parentsNext = w->d_func()->focus_next; + QWidget *parentsNext = w->nextInFocusChain(); if (parentsNext == this) { // nothing to do. - Q_ASSERT(d->focus_prev == w); + Q_ASSERT(previousInFocusChain() == w); } else { - // Remove 'this' from the focus chain by making prev and next point directly to each other - QWidget *myOldNext = d->focus_next; - QWidget *myOldPrev = d->focus_prev; - if (myOldNext && myOldPrev) { - myOldNext->d_func()->focus_prev = myOldPrev; - myOldPrev->d_func()->focus_next = myOldNext; - } - - // Insert 'this' behind the parent - w->d_func()->focus_next = this; - d->focus_prev = w; - d->focus_next = parentsNext; - parentsNext->d_func()->focus_prev = this; + d->QWidgetPrivate::insertIntoFocusChainAfter(w); } } @@ -6648,7 +6616,9 @@ void QWidgetPrivate::setFocus_sys() { Q_Q(QWidget); // Embedded native widget may have taken the focus; get it back to toplevel - // if that is the case (QTBUG-25852) + // if that is the case (QTBUG-25852), unless widget is a window container. + if (extra && extra->hasWindowContainer) + return; // Do not activate in case the popup menu opens another application (QTBUG-70810) // unless the application is embedded (QTBUG-71991). if (QWindow *nativeWindow = q->testAttribute(Qt::WA_WState_Created) ? q->window()->windowHandle() : nullptr) { @@ -6871,7 +6841,8 @@ QObject *QWidgetPrivate::focusObject() */ QWidget *QWidget::nextInFocusChain() const { - return const_cast<QWidget *>(d_func()->focus_next); + Q_D(const QWidget); + return d->nextPrevElementInFocusChain(QWidgetPrivate::FocusDirection::Next); } /*! @@ -6884,7 +6855,8 @@ QWidget *QWidget::nextInFocusChain() const */ QWidget *QWidget::previousInFocusChain() const { - return const_cast<QWidget *>(d_func()->focus_prev); + Q_D(const QWidget); + return d->nextPrevElementInFocusChain(QWidgetPrivate::FocusDirection::Previous); } /*! @@ -7039,9 +7011,9 @@ void QWidget::setTabOrder(QWidget* first, QWidget *second) } } else if (target->isAncestorOf(focusProxy)) { lastFocusChild = focusProxy; - for (QWidget *focusNext = lastFocusChild->d_func()->focus_next; + for (QWidget *focusNext = lastFocusChild->nextInFocusChain(); focusNext != focusProxy && target->isAncestorOf(focusNext) && focusNext->window() == focusProxy->window(); - focusNext = focusNext->d_func()->focus_next) { + focusNext = focusNext->nextInFocusChain()) { if (focusNext == noFurtherThan) break; if (focusNext->focusPolicy() != Qt::NoFocus) @@ -7050,13 +7022,6 @@ void QWidget::setTabOrder(QWidget* first, QWidget *second) } return lastFocusChild; }; - auto setPrev = [](QWidget *w, QWidget *prev) { - w->d_func()->focus_prev = prev; - }; - auto setNext = [](QWidget *w, QWidget *next) { - w->d_func()->focus_next = next; - }; - // detect inflection in case we have compound widgets QWidget *lastFocusChildOfFirst = determineLastFocusChild(first, second); if (lastFocusChildOfFirst == second) @@ -7065,28 +7030,15 @@ void QWidget::setTabOrder(QWidget* first, QWidget *second) if (lastFocusChildOfSecond == first) lastFocusChildOfSecond = second; - // remove the second widget from the chain - { - QWidget *oldPrev = second->d_func()->focus_prev; - QWidget *prevWithFocus = oldPrev; - while (prevWithFocus->focusPolicy() == Qt::NoFocus) - prevWithFocus = prevWithFocus->d_func()->focus_prev; - // only widgets between first and second -> all is fine - if (prevWithFocus == first) - return; - QWidget *oldNext = lastFocusChildOfSecond->d_func()->focus_next; - setPrev(oldNext, oldPrev); - setNext(oldPrev, oldNext); - } - - // insert the second widget into the chain - { - QWidget *oldNext = lastFocusChildOfFirst->d_func()->focus_next; - setPrev(second, lastFocusChildOfFirst); - setNext(lastFocusChildOfFirst, second); - setPrev(oldNext, lastFocusChildOfSecond); - setNext(lastFocusChildOfSecond, oldNext); - } + // Return if only NoFocus widgets are between first and second + QWidget *oldPrev = second->previousInFocusChain(); + QWidget *prevWithFocus = oldPrev; + while (prevWithFocus->focusPolicy() == Qt::NoFocus) + prevWithFocus = prevWithFocus->previousInFocusChain(); + if (prevWithFocus == first) + return; + const QWidgetList chain = QWidgetPrivate::takeFromFocusChain(second, lastFocusChildOfSecond); + QWidgetPrivate::insertIntoFocusChain(chain, QWidgetPrivate::FocusDirection::Next, lastFocusChildOfFirst); } void QWidget::setTabOrder(std::initializer_list<QWidget *> widgets) @@ -7124,67 +7076,8 @@ void QWidgetPrivate::reparentFocusWidgets(QWidget * oldtlw) if (focus_child) focus_child->clearFocus(); - // separate the focus chain into new (children of myself) and old (the rest) - QWidget *firstOld = nullptr; - //QWidget *firstNew = q; //invariant - QWidget *o = nullptr; // last in the old list - QWidget *n = q; // last in the new list - - bool prevWasNew = true; - QWidget *w = focus_next; - - //Note: for efficiency, we do not maintain the list invariant inside the loop - //we append items to the relevant list, and we optimize by not changing pointers - //when subsequent items are going into the same list. - while (w != q) { - bool currentIsNew = q->isAncestorOf(w); - if (currentIsNew) { - if (!prevWasNew) { - //prev was old -- append to new list - n->d_func()->focus_next = w; - w->d_func()->focus_prev = n; - } - n = w; - } else { - if (prevWasNew) { - //prev was new -- append to old list, if there is one - if (o) { - o->d_func()->focus_next = w; - w->d_func()->focus_prev = o; - } else { - // "create" the old list - firstOld = w; - } - } - o = w; - } - w = w->d_func()->focus_next; - prevWasNew = currentIsNew; - } - - //repair the old list: - if (firstOld) { - o->d_func()->focus_next = firstOld; - firstOld->d_func()->focus_prev = o; - } - - if (!q->isWindow()) { - QWidget *topLevel = q->window(); - //insert new chain into toplevel's chain - - QWidget *prev = topLevel->d_func()->focus_prev; - - topLevel->d_func()->focus_prev = n; - prev->d_func()->focus_next = q; - - focus_prev = prev; - n->d_func()->focus_next = topLevel; - } else { - //repair the new list - n->d_func()->focus_next = q; - focus_prev = n; - } - + insertIntoFocusChain(QWidgetPrivate::FocusDirection::Previous, q->window()); + reparentFocusChildren(QWidgetPrivate::FocusDirection::Next); } /*! @@ -10720,9 +10613,15 @@ void qSendWindowChangeToTextureChildrenRecursively(QWidget *widget, QEvent::Type for (int i = 0; i < d->children.size(); ++i) { QWidget *w = qobject_cast<QWidget *>(d->children.at(i)); - if (w && !w->isWindow() && QWidgetPrivate::get(w)->textureChildSeen) + if (w && !w->isWindow()) qSendWindowChangeToTextureChildrenRecursively(w, eventType); } + + // Notify QWidgetWindow after we've notified all child QWidgets + if (auto *window = d->windowHandle(QWidgetPrivate::WindowHandleMode::Direct)) { + QEvent e(eventType); + QCoreApplication::sendEvent(window, &e); + } } /*! @@ -10753,6 +10652,7 @@ void QWidget::setParent(QWidget *parent, Qt::WindowFlags f) const bool resized = testAttribute(Qt::WA_Resized); const bool wasCreated = testAttribute(Qt::WA_WState_Created); QWidget *oldtlw = window(); + Q_ASSERT(oldtlw); if (f & Qt::Window) // Frame geometry likely changes, refresh. d->data.fstrut_dirty = true; @@ -10795,7 +10695,7 @@ void QWidget::setParent(QWidget *parent, Qt::WindowFlags f) // texture-based widgets need a pre-notification when their associated top-level window changes // This is not under the wasCreated/newParent conditions above in order to also play nice with QDockWidget. - if (d->textureChildSeen && ((!parent && parentWidget()) || (parent && parent->window() != oldtlw))) + if (oldtlw->d_func()->usesRhiFlush && ((!parent && parentWidget()) || (parent && parent->window() != oldtlw))) qSendWindowChangeToTextureChildrenRecursively(this, QEvent::WindowAboutToChangeInternal); // If we get parented into another window, children will be folded @@ -10876,7 +10776,7 @@ void QWidget::setParent(QWidget *parent, Qt::WindowFlags f) // texture-based widgets need another event when their top-level window // changes (more precisely, has already changed at this point) - if (d->textureChildSeen && oldtlw != window()) + if (oldtlw->d_func()->usesRhiFlush && oldtlw != window()) qSendWindowChangeToTextureChildrenRecursively(this, QEvent::WindowChangeInternal); if (!wasCreated) { @@ -13371,6 +13271,404 @@ QDebug operator<<(QDebug debug, const QWidget *widget) } #endif // !QT_NO_DEBUG_STREAM + +// *************************** Focus abstraction ************************************ + +#define FOCUS_NEXT(w) w->d_func()->focus_next +#define FOCUS_PREV(w) w->d_func()->focus_prev + +/*! + \internal + \return next or previous element in the focus chain, depending on + \param direction, irrespective of focus proxies or widgets with Qt::NoFocus. + */ +QWidget *QWidgetPrivate::nextPrevElementInFocusChain(FocusDirection direction) const +{ + Q_Q(const QWidget); + return direction == FocusDirection::Next ? FOCUS_NEXT(q) : FOCUS_PREV(q); +} + +/*! + \internal + Removes a widget from the focus chain, respecting the flags set in \param rules. + \list + \li EnsureFocusOut: If the widget has input focus, transfer focus to the next or previous widget + in the focus chain, depending on \param direction. + \li RemoveInconsistent: Remove the widget, even if its focus chain is inconsistent. + \li AssertConsistency: qFatal, if the focus chain is inconsistent. This is used in the QWidget destructor. + \endlist + \return \c true if the widget has been removed, otherwise \c false. + */ +bool QWidgetPrivate::removeFromFocusChain(FocusChainRemovalRules rules, FocusDirection direction) +{ + Q_Q(QWidget); + if (!isFocusChainConsistent()) { +#ifdef QT_DEBUG + if (rules.testFlag(FocusChainRemovalRule::AssertConsistency)) + qFatal() << q << "has inconsistent focus chain."; +#endif + qCDebug(lcWidgetFocus) << q << "wasn't removed, because of inconsistent focus chain."; + return false; + } + + if (!isInFocusChain()) { + qCDebug(lcWidgetFocus) << q << "wasn't removed, because it is not part of a focus chain."; + return false; + } + + if (rules.testFlag(FocusChainRemovalRule::EnsureFocusOut)) + q->focusNextPrevChild(direction == FocusDirection::Next); + + FOCUS_NEXT(FOCUS_PREV(q)) = FOCUS_NEXT(q); + FOCUS_PREV(FOCUS_NEXT(q)) = FOCUS_PREV(q); + initFocusChain(); + qCDebug(lcWidgetFocus) << q << "removed from focus chain."; + return true; +} + +/*! + \internal + Initialises the focus chain by making the widget point to itself. + */ +void QWidgetPrivate::initFocusChain() +{ + Q_Q(QWidget); + qCDebug(lcWidgetFocus) << "Initializing focus chain of" << q; + FOCUS_PREV(q) = q; + FOCUS_NEXT(q) = q; +} + +/*! + \internal + Reads QWidget children, which are not part of a focus chain yet. + Inserts them into the focus chain before or after the widget, + depending on \param direction and in the order of their creation. + This is used, when QWidget::setParent() causes a widget to change toplevel windows. + */ +void QWidgetPrivate::reparentFocusChildren(FocusDirection direction) +{ + Q_Q(QWidget); + QWidgetList focusChildrenInsideChain; + QDuplicateTracker<QWidget *> seen; + QWidget *widget = q->nextInFocusChain(); + while (q->isAncestorOf(widget) + && !seen.hasSeen(widget) + && widget != q->window()) { + if (widget->focusPolicy() != Qt::NoFocus) + focusChildrenInsideChain << widget; + + widget = direction == FocusDirection::Next ? widget->nextInFocusChain() + : widget->previousInFocusChain(); + } + + const QWidgetList children = q->findChildren<QWidget *>(Qt::FindDirectChildrenOnly); + QWidgetList focusChildrenOutsideChain; + for (auto *child : children) { + if (!focusChildrenInsideChain.contains(child)) + focusChildrenOutsideChain << child; + } + if (focusChildrenOutsideChain.isEmpty()) + return; + + QWidget *previous = q; + for (auto *child : focusChildrenOutsideChain) { + child->d_func()->insertIntoFocusChain(direction, previous); + previous = child; + } +} + +/*! + \internal + Inserts a widget into the focus chain before or after \param position, depending on + \param direction. + \return \c true, if the insertion has changed the focus chain, otherwise \c false. + */ +bool QWidgetPrivate::insertIntoFocusChain(FocusDirection direction, QWidget *position) +{ + Q_Q(QWidget); + Q_ASSERT(position); + QWidget *next = FOCUS_NEXT(q); + QWidget *previous = FOCUS_PREV(q); + + switch (direction) { + case FocusDirection::Next: + if (previous == position) { + qCDebug(lcWidgetFocus) << "No-op insertion." << q << "is already before" << position; + return false; + } + + removeFromFocusChain(FocusChainRemovalRule::AssertConsistency); + + FOCUS_NEXT(q) = FOCUS_NEXT(position); + FOCUS_PREV(FOCUS_NEXT(position)) = q; + FOCUS_NEXT(position) = q; + FOCUS_PREV(q) = position; + qCDebug(lcWidgetFocus) << q << "inserted after" << position; + break; + + case FocusDirection::Previous: + if (next == position) { + qCDebug(lcWidgetFocus) << "No-op insertion." << q << "is already after" << position; + return false; + } + + removeFromFocusChain(FocusChainRemovalRule::AssertConsistency); + + FOCUS_PREV(q) = FOCUS_PREV(position); + FOCUS_NEXT(FOCUS_PREV(position)) = q; + FOCUS_PREV(position) = q; + FOCUS_NEXT(q) = position; + qCDebug(lcWidgetFocus) << q << "inserted before" << position; + break; + } + + Q_ASSERT(isFocusChainConsistent()); + return true; +} + +/*! + \internal + Convenience override to insert a QWidgetList \param toBeInserted into the focus chain + before or after \param position, depending on \param direction. + \return \c true, if the insertion has changed the focus chain, otherwise \c false. + \note + \param toBeInserted must be a consistent focus chain. + */ +bool QWidgetPrivate::insertIntoFocusChain(const QWidgetList &toBeInserted, + FocusDirection direction, QWidget *position) +{ + if (toBeInserted.isEmpty()) { + qCDebug(lcWidgetFocus) << "No-op insertion of an empty list"; + return false; + } + + Q_ASSERT_X(!toBeInserted.contains(position), + Q_FUNC_INFO, + "Coding error: toBeInserted contains position"); + + QWidget *first = toBeInserted.constFirst(); + QWidget *last = toBeInserted.constLast(); + + // Call QWidget override to log accordingly + if (toBeInserted.count() == 1) + return first->d_func()->insertIntoFocusChain(direction, position); + + Q_ASSERT(first != last); + switch (direction) { + case FocusDirection::Previous: + if (FOCUS_PREV(position) == last) { + qCDebug(lcWidgetFocus) << "No-op insertion." << toBeInserted << "is already before" + << position; + return false; + } + FOCUS_NEXT(FOCUS_PREV(position)) = first; + FOCUS_PREV(first) = FOCUS_PREV(position); + FOCUS_NEXT(last) = position; + FOCUS_PREV(position) = last; + qCDebug(lcWidgetFocus) << toBeInserted << "inserted before" << position; + break; + case FocusDirection::Next: + if (FOCUS_PREV(position) == last) { + qCDebug(lcWidgetFocus) << "No-op insertion." << toBeInserted << "is already after" + << position; + return false; + } + FOCUS_PREV(FOCUS_NEXT(position)) = last; + FOCUS_NEXT(last) = FOCUS_NEXT(position); + FOCUS_PREV(first) = position; + FOCUS_NEXT(position) = first; + qCDebug(lcWidgetFocus) << toBeInserted << "inserted after" << position; + break; + } + + Q_ASSERT(position->d_func()->isFocusChainConsistent()); + return true; +} + +/*! + \internal + \return a QWidgetList, representing the part of the focus chain, + starting with \param from and ending with \param to, in \param direction. + */ +QWidgetList focusPath(QWidget *from, QWidget *to, QWidgetPrivate::FocusDirection direction) +{ + QWidgetList path({from}); + if (from == to) + return path; + + QWidget *current = from; + do { + switch (direction) { + case QWidgetPrivate::FocusDirection::Previous: + current = current->previousInFocusChain(); + break; + case QWidgetPrivate::FocusDirection::Next: + current = current->nextInFocusChain(); + break; + } + if (path.contains(current)) + return QWidgetList(); + path << current; + } while (current != to); + + return path; +} + +/*! + \internal + Removes the part from the focus chain starting with \param from and ending with \param to, + in \param direction. + \return removed part as a QWidgetList. + */ +QWidgetList QWidgetPrivate::takeFromFocusChain(QWidget *from, + QWidget *to, + FocusDirection direction) +{ + // Check if there is a path from->to in direction + const QWidgetList path = focusPath(from, to , direction); + if (path.isEmpty()) { + qCDebug(lcWidgetFocus) << "No-op removal. Focus chain from" << from << "doesn't lead to " << to; + return QWidgetList(); + } + + QWidget *first = path.constFirst(); + QWidget *last = path.constLast(); + if (first == last) { + first->d_func()->removeFromFocusChain(); + return QWidgetList({first}); + } + + FOCUS_NEXT(FOCUS_PREV(first)) = FOCUS_NEXT(last); + FOCUS_PREV(FOCUS_NEXT(last)) = FOCUS_PREV(first); + FOCUS_PREV(first) = last; + FOCUS_NEXT(last) = first; + qCDebug(lcWidgetFocus) << path << "removed from focus chain"; + return path; +} + +/*! + \internal + \return The last focus child of the widget, traversing the focus chain no further than + \param noFurtherThan. + */ +QWidget *QWidgetPrivate::determineLastFocusChild(QWidget *noFurtherThan) +{ + Q_Q(QWidget); + // Since we need to repeat the same logic for both 'first' and 'second', we add a function + // that determines the last focus child for a widget, taking proxies and compound widgets into + // account. If the target is not a compound widget (it doesn't have a focus proxy that points + // to a child), 'lastFocusChild' will be set to the target itself. + QWidget *lastFocusChild = q; + + QWidget *focusProxy = deepestFocusProxy(); + if (!focusProxy) { + // QTBUG-81097: Another case is possible here. We can have a child + // widget, that sets its focusProxy() to the parent (target). + // An example of such widget is a QLineEdit, nested into + // a QAbstractSpinBox. In this case such widget should be considered + // the last focus child. + for (auto *object : std::as_const(q->children())) { + QWidget *w = qobject_cast<QWidget *>(object); + if (w && w->focusProxy() == q) { + lastFocusChild = w; + break; + } + } + } else if (q->isAncestorOf(focusProxy)) { + lastFocusChild = focusProxy; + for (QWidget *focusNext = lastFocusChild->nextInFocusChain(); + focusNext != focusProxy && q->isAncestorOf(focusNext) + && focusNext->window() == focusProxy->window(); + focusNext = focusNext->nextInFocusChain()) { + if (focusNext == noFurtherThan) + break; + if (focusNext->focusPolicy() != Qt::NoFocus) + lastFocusChild = focusNext; + } + } + return lastFocusChild; +}; + +/*! + \internal + \return \c true, if the widget is part of a focus chain and \c false otherwise. + A widget is considered to be part of a focus chain, neither FOCUS_NEXT, nor FOCUS_PREV + are pointing to the widget itself. + + \note + This method doesn't check the consistency of the focus chain. + If multiple widgets have been removed from the focus chain by takeFromFocusChain(), + isInFocusChain() will return \c true for all of those widgets, even if they represent + an inconsistent focus chain. + */ +bool QWidgetPrivate::isInFocusChain() const +{ + Q_Q(const QWidget); + return !(FOCUS_NEXT(q) == q && FOCUS_PREV(q) == q); +} + +/*! + \internal + A focus chain is consistent, when it is circular: Following the chain in either direction + has to return to the beginning. This is why a newly constructed widget points to itself, + when the focus chain has been initialized. A newly constructed widget is considered to have + a consistent focus chain, while not being part of a focus chain. + + The method always returns \c true, when the logging category "qt.widgets.focus" is disabled. + When it is enabled, the method returns \c true early, if a widget is pointing to itself. + It returns \c false, if one of the following is detected: + \list + \li nullptr found in a previous/next pointer. + \li broken chain: widget A is B's previous, but B isn't A's next. + \li chain isn't closed: starting at A doesn't lead back to A. + \endlist + It return \c true, if none of the above is observed. + + \note + The focus chain is checked only in forward direction. + This is sufficient, because the check for a broken chain asserts consistent paths + in both directions. + */ +bool QWidgetPrivate::isFocusChainConsistent() const +{ + Q_Q(const QWidget); + const bool skip = !QLoggingCategory("qt.widgets.focus").isDebugEnabled(); + if (skip) + return true; + + if (!isInFocusChain()) + return true; + + const QWidget *position = q; + + for (int i = 0; i < QApplication::allWidgets().count(); ++i) { + if (!FOCUS_PREV(position) || !FOCUS_NEXT(position)) { + qCDebug(lcWidgetFocus) << "Nullptr found at:" << position + << "Previous pointing to" << FOCUS_PREV(position) + << "Next pointing to" << FOCUS_NEXT(position); + return false; + } + if (!(FOCUS_PREV(FOCUS_NEXT(position)) == position + && FOCUS_NEXT(FOCUS_PREV(position)) == position)) { + qCDebug(lcWidgetFocus) << "Inconsistent focus chain at:" << position + << "Previous pointing to" << FOCUS_PREV(FOCUS_NEXT(position)) + << "Next pointing to" << FOCUS_NEXT(FOCUS_PREV(position)); + return false; + } + position = FOCUS_NEXT(position); + if (position == q) + return true; + + } + + qCDebug(lcWidgetFocus) << "Focus chain leading from" << q << "to" << position << "is not closed."; + return false; +} + +#undef FOCUS_NEXT +#undef FOCUS_PREV + + QT_END_NAMESPACE #include "moc_qwidget.cpp" diff --git a/src/widgets/kernel/qwidget_p.h b/src/widgets/kernel/qwidget_p.h index d16d0f438c..9fab9efa62 100644 --- a/src/widgets/kernel/qwidget_p.h +++ b/src/widgets/kernel/qwidget_p.h @@ -41,6 +41,7 @@ #include <private/qgesture_p.h> #include <qpa/qplatformbackingstore.h> #include <QtGui/private/qbackingstorerhisupport_p.h> +#include <private/qapplication_p.h> #include <QtCore/qpointer.h> @@ -733,6 +734,40 @@ public: uint childrenHiddenByWState : 1; uint childrenShownByExpose : 1; + // *************************** Focus abstraction ************************************ + enum class FocusDirection { + Previous, + Next, + }; + + enum class FocusChainRemovalRule { + EnsureFocusOut = 0x01, + AssertConsistency = 0x02, + }; + Q_DECLARE_FLAGS(FocusChainRemovalRules, FocusChainRemovalRule) + + // Getters + QWidget *nextPrevElementInFocusChain(FocusDirection direction) const; + + // manipulators + bool removeFromFocusChain(FocusChainRemovalRules rules = FocusChainRemovalRules(), + FocusDirection direction = FocusDirection::Next); + bool insertIntoFocusChain(FocusDirection direction, QWidget *position); + static bool insertIntoFocusChain(const QWidgetList &toBeInserted, FocusDirection direction, QWidget *position); + bool insertIntoFocusChainBefore(QWidget *position) + { return insertIntoFocusChain(FocusDirection::Previous, position); } + bool insertIntoFocusChainAfter(QWidget *position) + { return insertIntoFocusChain(FocusDirection::Next, position); } + static QWidgetList takeFromFocusChain(QWidget *from, QWidget *to, + FocusDirection direction = FocusDirection::Next); + void reparentFocusChildren(FocusDirection direction); + QWidget *determineLastFocusChild(QWidget *noFurtherThan); + + // Initialization and tests + void initFocusChain(); + bool isInFocusChain() const; + bool isFocusChainConsistent() const; + // *************************** Platform specific ************************************ #if defined(Q_OS_WIN) uint noPaintOnScreen : 1; // see qwidget.cpp ::paintEngine() diff --git a/src/widgets/kernel/qwidgetrepaintmanager.cpp b/src/widgets/kernel/qwidgetrepaintmanager.cpp index abe5e8016e..607a767a20 100644 --- a/src/widgets/kernel/qwidgetrepaintmanager.cpp +++ b/src/widgets/kernel/qwidgetrepaintmanager.cpp @@ -355,7 +355,11 @@ void QWidgetRepaintManager::sendUpdateRequest(QWidget *widget, UpdateTime update switch (updateTime) { case UpdateLater: - updateRequestSent = true; + // Prevent redundant update request events, unless it's a + // paint on screen widget, as these don't go through the + // normal backingstore sync machinery. + if (!widget->d_func()->shouldPaintOnScreen()) + updateRequestSent = true; QCoreApplication::postEvent(widget, new QEvent(QEvent::UpdateRequest), Qt::LowEventPriority); break; case UpdateNow: { diff --git a/src/widgets/kernel/qwidgetwindow.cpp b/src/widgets/kernel/qwidgetwindow.cpp index f51baba88b..e7f0a84004 100644 --- a/src/widgets/kernel/qwidgetwindow.cpp +++ b/src/widgets/kernel/qwidgetwindow.cpp @@ -77,6 +77,39 @@ public: widget->focusWidget()->clearFocus(); } + void setFocusToTarget(FocusTarget target, Qt::FocusReason reason) override + { + Q_Q(QWidgetWindow); + QWidget *widget = q->widget(); + if (!widget) + return; + QWidget *newFocusWidget = nullptr; + + switch (target) { + case FocusTarget::First: + newFocusWidget = q->getFocusWidget(QWidgetWindow::FirstFocusWidget); + break; + case FocusTarget::Last: + newFocusWidget = q->getFocusWidget(QWidgetWindow::LastFocusWidget); + break; + case FocusTarget::Next: { + QWidget *focusWidget = widget->focusWidget() ? widget->focusWidget() : widget; + newFocusWidget = focusWidget->nextInFocusChain() ? focusWidget->nextInFocusChain() : focusWidget; + break; + } + case FocusTarget::Prev: { + QWidget *focusWidget = widget->focusWidget() ? widget->focusWidget() : widget; + newFocusWidget = focusWidget->previousInFocusChain() ? focusWidget->previousInFocusChain() : focusWidget; + break; + } + default: + break; + } + + if (newFocusWidget) + newFocusWidget->setFocus(reason); + } + QRectF closestAcceptableGeometry(const QRectF &rect) const override; void processSafeAreaMarginsChanged() override diff --git a/src/widgets/kernel/qwindowcontainer.cpp b/src/widgets/kernel/qwindowcontainer.cpp index cfd785a82c..1aaf04af43 100644 --- a/src/widgets/kernel/qwindowcontainer.cpp +++ b/src/widgets/kernel/qwindowcontainer.cpp @@ -5,6 +5,7 @@ #include "qwidget_p.h" #include "qwidgetwindow_p.h" #include <QtGui/qwindow.h> +#include <QtGui/private/qwindow_p.h> #include <QtGui/private/qguiapplication_p.h> #include <qpa/qplatformintegration.h> #include <QDebug> @@ -28,7 +29,6 @@ public: QWindowContainerPrivate() : window(nullptr) - , oldFocusWindow(nullptr) , usesNativeWidgets(false) { } @@ -103,7 +103,6 @@ public: } QPointer<QWindow> window; - QWindow *oldFocusWindow; QWindow fakeParent; uint usesNativeWidgets : 1; @@ -207,6 +206,7 @@ QWindowContainer::QWindowContainer(QWindow *embeddedWindow, QWidget *parent, Qt: } d->window = embeddedWindow; + d->window->installEventFilter(this); QString windowName = d->window->objectName(); if (windowName.isEmpty()) @@ -219,8 +219,8 @@ QWindowContainer::QWindowContainer(QWindow *embeddedWindow, QWidget *parent, Qt: setAcceptDrops(true); - connect(qGuiApp, &QGuiApplication::focusWindowChanged, - this, &QWindowContainer::focusWindowChanged); + connect(containedWindow(), &QWindow::minimumHeightChanged, this, &QWindowContainer::updateGeometry); + connect(containedWindow(), &QWindow::minimumWidthChanged, this, &QWindowContainer::updateGeometry); } QWindow *QWindowContainer::containedWindow() const @@ -241,30 +241,12 @@ QWindowContainer::~QWindowContainer() // QEvent::PlatformSurface delivery relies on virtuals. Getting // SurfaceAboutToBeDestroyed can be essential for OpenGL, Vulkan, etc. // QWindow subclasses in particular. Keep these working. - if (d->window) + if (d->window) { + d->window->removeEventFilter(this); d->window->destroy(); + } delete d->window; - - disconnect(qGuiApp, &QGuiApplication::focusWindowChanged, - this, &QWindowContainer::focusWindowChanged); -} - - - -/*! - \internal - */ - -void QWindowContainer::focusWindowChanged(QWindow *focusWindow) -{ - Q_D(QWindowContainer); - d->oldFocusWindow = focusWindow; - if (focusWindow == d->window) { - QWidget *widget = QApplication::focusWidget(); - if (widget) - widget->clearFocus(); - } } /*! @@ -281,8 +263,12 @@ bool QWindowContainer::eventFilter(QObject *o, QEvent *e) QChildEvent *ce = static_cast<QChildEvent *>(e); if (ce->child() == d->window) { o->removeEventFilter(this); + d->window->removeEventFilter(this); d->window = nullptr; } + } else if (e->type() == QEvent::FocusIn) { + if (o == d->window) + setFocus(Qt::ActiveWindowFocusReason); } return false; } @@ -332,11 +318,16 @@ bool QWindowContainer::event(QEvent *e) break; case QEvent::FocusIn: if (d->window->parent()) { - if (d->oldFocusWindow != d->window) { + if (QGuiApplication::focusWindow() != d->window) { + QFocusEvent *event = static_cast<QFocusEvent *>(e); + const auto reason = event->reason(); + QWindowPrivate::FocusTarget target = QWindowPrivate::FocusTarget::Current; + if (reason == Qt::TabFocusReason) + target = QWindowPrivate::FocusTarget::First; + else if (reason == Qt::BacktabFocusReason) + target = QWindowPrivate::FocusTarget::Last; + qt_window_private(d->window)->setFocusToTarget(target, reason); d->window->requestActivate(); - } else { - QWidget *next = nextInFocusChain(); - next->setFocus(); } } break; @@ -373,6 +364,11 @@ bool QWindowContainer::event(QEvent *e) return QWidget::event(e); } +QSize QWindowContainer::minimumSizeHint() const +{ + return containedWindow() ? containedWindow()->minimumSize() : QSize(0, 0); +} + typedef void (*qwindowcontainer_traverse_callback)(QWidget *parent); static void qwindowcontainer_traverse(QWidget *parent, qwindowcontainer_traverse_callback callback) { diff --git a/src/widgets/kernel/qwindowcontainer_p.h b/src/widgets/kernel/qwindowcontainer_p.h index 8dc5c64af4..0cbcc5321d 100644 --- a/src/widgets/kernel/qwindowcontainer_p.h +++ b/src/widgets/kernel/qwindowcontainer_p.h @@ -31,6 +31,7 @@ public: explicit QWindowContainer(QWindow *embeddedWindow, QWidget *parent = nullptr, Qt::WindowFlags f = { }); ~QWindowContainer(); QWindow *containedWindow() const; + QSize minimumSizeHint() const override; static void toplevelAboutToBeDestroyed(QWidget *parent); static void parentWasChanged(QWidget *parent); @@ -41,9 +42,6 @@ public: protected: bool event(QEvent *ev) override; bool eventFilter(QObject *, QEvent *ev) override; - -private slots: - void focusWindowChanged(QWindow *focusWindow); }; QT_END_NAMESPACE diff --git a/src/widgets/styles/qcommonstyle.cpp b/src/widgets/styles/qcommonstyle.cpp index e2d0e5227b..aab1192d50 100644 --- a/src/widgets/styles/qcommonstyle.cpp +++ b/src/widgets/styles/qcommonstyle.cpp @@ -750,70 +750,75 @@ void QCommonStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, Q case PE_IndicatorArrowRight: case PE_IndicatorArrowLeft: { - if (opt->rect.width() <= 1 || opt->rect.height() <= 1) + const QRect &r = opt->rect; + if (r.width() <= 1 || r.height() <= 1) break; - QRect r = opt->rect; int size = qMin(r.height(), r.width()); QPixmap pixmap; + const qreal dpr = p->device()->devicePixelRatio(); const QString pixmapName = QStyleHelper::uniqueName("$qt_ia-"_L1 % QLatin1StringView(metaObject()->className()) % HexString<uint>(pe), - opt, QSize(size, size)); + opt, QSize(size, size), dpr); if (!QPixmapCache::find(pixmapName, &pixmap)) { - const qreal pixelRatio = p->device()->devicePixelRatio(); - const qreal border = pixelRatio * (size / 5.); - const qreal sqsize = pixelRatio * size; - QImage image(sqsize, sqsize, QImage::Format_ARGB32_Premultiplied); - image.fill(Qt::transparent); - QPainter imagePainter(&image); - - QPolygonF poly; + // dpr scaling does not work well on such small pixel sizes, do it on our own + const int border = 1 * dpr; + const int sizeDpr = size * dpr; + int width = sizeDpr - 2 * border - 1; + int height = width / 2; + const int add = ((width & 1) == 1); + if (pe == PE_IndicatorArrowRight || pe == PE_IndicatorArrowLeft) + std::swap(width, height); + pixmap = styleCachePixmap(QSize(sizeDpr, sizeDpr), 1); + + std::array<QPointF, 4> poly; switch (pe) { case PE_IndicatorArrowUp: - poly = {QPointF(border, sqsize / 2), QPointF(sqsize / 2, border), QPointF(sqsize - border, sqsize / 2)}; + poly = {QPointF(0, height), QPointF(width, height), + QPointF(width / 2 + add, 0), QPointF(width / 2, 0)}; break; case PE_IndicatorArrowDown: - poly = {QPointF(border, sqsize / 2), QPointF(sqsize / 2, sqsize - border), QPointF(sqsize - border, sqsize / 2)}; + poly = {QPointF(0, 0), QPointF(width, 0), + QPointF(width / 2 + add, height), QPointF(width / 2, height)}; break; case PE_IndicatorArrowRight: - poly = {QPointF(sqsize - border, sqsize / 2), QPointF(sqsize / 2, border), QPointF(sqsize / 2, sqsize - border)}; + poly = {QPointF(0, 0), QPointF(0, height), + QPointF(width, height / 2 + add), QPointF(width, height / 2)}; break; case PE_IndicatorArrowLeft: - poly = {QPointF(border, sqsize / 2), QPointF(sqsize / 2, border), QPointF(sqsize / 2, sqsize - border)}; + poly = {QPointF(width, 0), QPointF(width, height), + QPointF(0, height / 2 + add), QPointF(0, height / 2)}; break; default: break; } - int bsx = 0; - int bsy = 0; - + QPainter imagePainter(&pixmap); + imagePainter.translate((sizeDpr - width) / 2, (sizeDpr - height) / 2); if (opt->state & State_Sunken) { - bsx = proxy()->pixelMetric(PM_ButtonShiftHorizontal, opt, widget); - bsy = proxy()->pixelMetric(PM_ButtonShiftVertical, opt, widget); + const auto bsx = proxy()->pixelMetric(PM_ButtonShiftHorizontal, opt, widget); + const auto bsy = proxy()->pixelMetric(PM_ButtonShiftVertical, opt, widget); + imagePainter.translate(bsx, bsy); } - - const QRectF bounds = poly.boundingRect(); - const qreal sx = sqsize / 2 - bounds.center().x() - 1; - const qreal sy = sqsize / 2 - bounds.center().y() - 1; - imagePainter.translate(sx + bsx, sy + bsy); imagePainter.setPen(opt->palette.buttonText().color()); imagePainter.setBrush(opt->palette.buttonText()); if (!(opt->state & State_Enabled)) { - imagePainter.translate(1, 1); + const int ofs = qRound(1 * dpr); + imagePainter.translate(ofs, ofs); imagePainter.setBrush(opt->palette.light().color()); imagePainter.setPen(opt->palette.light().color()); - imagePainter.drawPolygon(poly); - imagePainter.translate(-1, -1); + imagePainter.drawPolygon(poly.data(), int(poly.size())); + imagePainter.drawPoints(poly.data(), int(poly.size())); + imagePainter.translate(-ofs, -ofs); imagePainter.setBrush(opt->palette.mid().color()); imagePainter.setPen(opt->palette.mid().color()); } - - imagePainter.drawPolygon(poly); + imagePainter.drawPolygon(poly.data(), int(poly.size())); + // sometimes the corners are not drawn by drawPolygon for unknown reaons, so re-draw them again + imagePainter.drawPoints(poly.data(), int(poly.size())); imagePainter.end(); - pixmap = QPixmap::fromImage(image); - pixmap.setDevicePixelRatio(pixelRatio); + pixmap.setDevicePixelRatio(dpr); QPixmapCache::insert(pixmapName, pixmap); } int xOffset = r.x() + (r.width() - size)/2; @@ -1679,9 +1684,12 @@ void QCommonStyle::drawControl(ControlElement element, const QStyleOption *opt, QFontMetrics fm(header->fontMetrics); if (header->state & QStyle::State_On) { QFont fnt = p->font(); - fnt.setBold(true); - p->setFont(fnt); - fm = QFontMetrics((p->font())); + // the font already has a weight set; don't override that + if (!(fnt.resolveMask() & QFont::WeightResolved)) { + fnt.setBold(true); + p->setFont(fnt); + fm = QFontMetrics((p->font())); + } } QString text = header->text; if (const QStyleOptionHeaderV2 *headerV2 = qstyleoption_cast<const QStyleOptionHeaderV2 *>(header)) { @@ -4518,15 +4526,6 @@ int QCommonStyle::pixelMetric(PixelMetric m, const QStyleOption *opt, const QWid case PM_MenuBarHMargin: ret = 0; break; - case PM_DialogButtonsSeparator: - ret = int(QStyleHelper::dpiScaled(5, opt)); - break; - case PM_DialogButtonsButtonWidth: - ret = int(QStyleHelper::dpiScaled(70, opt)); - break; - case PM_DialogButtonsButtonHeight: - ret = int(QStyleHelper::dpiScaled(30, opt)); - break; case PM_TitleBarHeight: { if (const QStyleOptionTitleBar *tb = qstyleoption_cast<const QStyleOptionTitleBar *>(opt)) { @@ -5501,8 +5500,8 @@ static inline QPixmap titleBarMenuCachedPixmapFromXPM() { return cachedPixmapFro #endif // QT_CONFIG(imageformat_xpm) #if QT_CONFIG(imageformat_png) -static inline QString iconResourcePrefix() { return QStringLiteral(":/qt-project.org/styles/commonstyle/images/"); } -static inline QString iconPngSuffix() { return QStringLiteral(".png"); } +static constexpr QLatin1StringView iconResourcePrefix() noexcept { return ":/qt-project.org/styles/commonstyle/images/"_L1; } +static constexpr QLatin1StringView iconPngSuffix() noexcept { return ".png"_L1; } template <typename T> static void addIconFiles(QStringView prefix, std::initializer_list<T> sizes, QIcon &icon, @@ -5817,14 +5816,15 @@ QIcon QCommonStylePrivate::iconFromMacTheme(QCommonStyle::StandardPixmap standar case QStyle::SP_TitleBarNormalButton: case QStyle::SP_TitleBarCloseButton: { QIcon titleBarIcon; - QString prefix = standardIcon == QStyle::SP_TitleBarCloseButton - ? QStringLiteral(":/qt-project.org/styles/macstyle/images/closedock-") - : QStringLiteral(":/qt-project.org/styles/macstyle/images/dockdock-"); + constexpr auto imagesPrefix = ":/qt-project.org/styles/macstyle/images/"_L1; + const auto namePrefix = standardIcon == QStyle::SP_TitleBarCloseButton + ? "closedock-"_L1 + : "dockdock-"_L1; for (const auto size : dockTitleIconSizes) { - titleBarIcon.addFile(prefix + QStringLiteral("macstyle-") + QString::number(size) + iconPngSuffix(), - QSize(size, size), QIcon::Normal, QIcon::Off); - titleBarIcon.addFile(prefix + QStringLiteral("down-macstyle-") + QString::number(size) + iconPngSuffix(), - QSize(size, size), QIcon::Normal, QIcon::On); + titleBarIcon.addFile(imagesPrefix + namePrefix + "macstyle-"_L1 + QString::number(size) + + iconPngSuffix(), QSize(size, size), QIcon::Normal, QIcon::Off); + titleBarIcon.addFile(imagesPrefix + namePrefix + "down-macstyle-"_L1 + QString::number(size) + + iconPngSuffix(), QSize(size, size), QIcon::Normal, QIcon::On); } return titleBarIcon; } diff --git a/src/widgets/styles/qfusionstyle.cpp b/src/widgets/styles/qfusionstyle.cpp index d2b1524796..6b8fd979a9 100644 --- a/src/widgets/styles/qfusionstyle.cpp +++ b/src/widgets/styles/qfusionstyle.cpp @@ -145,6 +145,7 @@ static void qt_fusion_draw_arrow(Qt::ArrowType type, QPainter *painter, const QS return; const qreal dpi = QStyleHelper::dpi(option); + const qreal dpr = painter->device()->devicePixelRatio(); const int arrowWidth = int(QStyleHelper::dpiScaled(14, dpi)); const int arrowHeight = int(QStyleHelper::dpiScaled(8, dpi)); @@ -156,10 +157,9 @@ static void qt_fusion_draw_arrow(Qt::ArrowType type, QPainter *painter, const QS const QString cacheKey = QStyleHelper::uniqueName("fusion-arrow"_L1 % HexString<uint>(type) % HexString<uint>(color.rgba()), - option, rect.size()); + option, rect.size(), dpr); if (!QPixmapCache::find(cacheKey, &cachePixmap)) { - cachePixmap = styleCachePixmap(rect.size()); - cachePixmap.fill(Qt::transparent); + cachePixmap = styleCachePixmap(rect.size(), dpr); QPainter cachePainter(&cachePixmap); QRectF arrowRect; @@ -800,7 +800,7 @@ void QFusionStyle::drawPrimitive(PrimitiveElement elem, if (isDefault) buttonColor = mergedColors(buttonColor, highlightedOutline.lighter(130), 90); - BEGIN_STYLE_PIXMAPCACHE(QStringLiteral("pushbutton-") + buttonColor.name(QColor::HexArgb)) + BEGIN_STYLE_PIXMAPCACHE(u"pushbutton-" + buttonColor.name(QColor::HexArgb)) r = rect.adjusted(0, 1, -1, 0); p->setRenderHint(QPainter::Antialiasing, true); @@ -1138,15 +1138,15 @@ void QFusionStyle::drawControl(ControlElement element, const QStyleOption *optio if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(option)) { const QStyleOptionHeaderV2 *headerV2 = qstyleoption_cast<const QStyleOptionHeaderV2 *>(option); const bool isSectionDragTarget = headerV2 ? headerV2->isSectionDragTarget : false; + const qreal dpr = painter->device()->devicePixelRatio(); const QString pixmapName = QStyleHelper::uniqueName("headersection-"_L1 % HexString(header->position) % HexString(header->orientation) % QLatin1Char(isSectionDragTarget ? '1' : '0'), - option, option->rect.size()); + option, option->rect.size(), dpr); QPixmap cache; if (!QPixmapCache::find(pixmapName, &cache)) { - cache = styleCachePixmap(rect.size()); - cache.fill(Qt::transparent); + cache = styleCachePixmap(rect.size(), dpr); QRect pixmapRect(0, 0, rect.width(), rect.height()); QPainter cachePainter(&cache); QColor buttonColor = d->buttonColor(option->palette); @@ -1879,12 +1879,12 @@ void QFusionStyle::drawComplexControl(ComplexControl control, const QStyleOption #if QT_CONFIG(spinbox) case CC_SpinBox: if (const QStyleOptionSpinBox *spinBox = qstyleoption_cast<const QStyleOptionSpinBox *>(option)) { + const qreal dpr = painter->device()->devicePixelRatio(); QPixmap cache; - QString pixmapName = QStyleHelper::uniqueName("spinbox"_L1, spinBox, spinBox->rect.size()); + QString pixmapName = QStyleHelper::uniqueName("spinbox"_L1, spinBox, spinBox->rect.size(), dpr); if (!QPixmapCache::find(pixmapName, &cache)) { - cache = styleCachePixmap(spinBox->rect.size()); - cache.fill(Qt::transparent); + cache = styleCachePixmap(spinBox->rect.size(), dpr); QRect pixmapRect(0, 0, spinBox->rect.width(), spinBox->rect.height()); QRect rect = pixmapRect; @@ -2576,16 +2576,16 @@ void QFusionStyle::drawComplexControl(ComplexControl control, const QStyleOption bool hasFocus = option->state & State_HasFocus && option->state & State_KeyboardFocusChange; bool sunken = comboBox->state & State_On; // play dead, if combobox has no items bool isEnabled = (comboBox->state & State_Enabled); + const qreal dpr = painter->device()->devicePixelRatio(); QPixmap cache; const QString pixmapName = QStyleHelper::uniqueName("combobox"_L1 % QLatin1StringView(sunken ? "-sunken" : "") % QLatin1StringView(comboBox->editable ? "-editable" : "") % QLatin1StringView(isEnabled ? "-enabled" : "") % QLatin1StringView(!comboBox->frame ? "-frameless" : ""), - option, comboBox->rect.size()); + option, comboBox->rect.size(), dpr); if (!QPixmapCache::find(pixmapName, &cache)) { - cache = styleCachePixmap(comboBox->rect.size()); - cache.fill(Qt::transparent); + cache = styleCachePixmap(comboBox->rect.size(), dpr); QPainter cachePainter(&cache); QRect pixmapRect(0, 0, comboBox->rect.width(), comboBox->rect.height()); QStyleOptionComboBox comboBoxCopy = *comboBox; @@ -2673,6 +2673,7 @@ void QFusionStyle::drawComplexControl(ComplexControl control, const QStyleOption #if QT_CONFIG(slider) case CC_Slider: if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(option)) { + const qreal dpr = painter->device()->devicePixelRatio(); QRect groove = proxy()->subControlRect(CC_Slider, option, SC_SliderGroove, widget); QRect handle = proxy()->subControlRect(CC_Slider, option, SC_SliderHandle, widget); @@ -2694,13 +2695,13 @@ void QFusionStyle::drawComplexControl(ComplexControl control, const QStyleOption grooveColor.setHsv(buttonColor.hue(), qMin(255, (int)(buttonColor.saturation())), qMin(255, (int)(buttonColor.value()*0.9))); - QString groovePixmapName = QStyleHelper::uniqueName("slider_groove"_L1, option, groove.size()); + QString groovePixmapName = QStyleHelper::uniqueName("slider_groove"_L1, option, + groove.size(), dpr); QRect pixmapRect(0, 0, groove.width(), groove.height()); // draw background groove if (!QPixmapCache::find(groovePixmapName, &cache)) { - cache = styleCachePixmap(pixmapRect.size()); - cache.fill(Qt::transparent); + cache = styleCachePixmap(pixmapRect.size(), dpr); QPainter groovePainter(&cache); groovePainter.setRenderHint(QPainter::Antialiasing, true); groovePainter.translate(0.5, 0.5); @@ -2728,8 +2729,7 @@ void QFusionStyle::drawComplexControl(ComplexControl control, const QStyleOption if (!groovePixmapName.isEmpty()) groovePixmapName += "_blue"_L1; if (!QPixmapCache::find(groovePixmapName, &cache)) { - cache = styleCachePixmap(pixmapRect.size()); - cache.fill(Qt::transparent); + cache = styleCachePixmap(pixmapRect.size(), dpr); QPainter groovePainter(&cache); QLinearGradient gradient; if (horizontal) { @@ -2839,10 +2839,10 @@ void QFusionStyle::drawComplexControl(ComplexControl control, const QStyleOption } // draw handle if ((option->subControls & SC_SliderHandle) ) { - QString handlePixmapName = QStyleHelper::uniqueName("slider_handle"_L1, option, handle.size()); + QString handlePixmapName = QStyleHelper::uniqueName("slider_handle"_L1, option, + handle.size(), dpr); if (!QPixmapCache::find(handlePixmapName, &cache)) { - cache = styleCachePixmap(handle.size()); - cache.fill(Qt::transparent); + cache = styleCachePixmap(handle.size(), dpr); QRect pixmapRect(0, 0, handle.width(), handle.height()); QPainter handlePainter(&cache); QRect gradRect = pixmapRect.adjusted(2, 2, -2, -2); @@ -2926,7 +2926,6 @@ int QFusionStyle::pixelMetric(PixelMetric metric, const QStyleOption *option, co case PM_ListViewIconSize: val = 24; break; - case PM_DialogButtonsSeparator: case PM_ScrollBarSliderMin: val = 26; break; @@ -3661,7 +3660,7 @@ QRect QFusionStyle::subElementRect(SubElement sr, const QStyleOption *opt, const } /*! - \reimp + \internal */ QIcon QFusionStyle::iconFromTheme(StandardPixmap standardIcon) const { diff --git a/src/widgets/styles/qstyle.h b/src/widgets/styles/qstyle.h index 23a5bd5ac2..198aea9557 100644 --- a/src/widgets/styles/qstyle.h +++ b/src/widgets/styles/qstyle.h @@ -464,11 +464,13 @@ public: PM_ExclusiveIndicatorWidth, PM_ExclusiveIndicatorHeight, - PM_DialogButtonsSeparator, - PM_DialogButtonsButtonWidth, - PM_DialogButtonsButtonHeight, +#if QT_DEPRECATED_SINCE(6, 8) + PM_DialogButtonsSeparator Q_DECL_ENUMERATOR_DEPRECATED_X("Not used and no effect since Qt 4"), + PM_DialogButtonsButtonWidth Q_DECL_ENUMERATOR_DEPRECATED_X("Not used and no effect since Qt 4"), + PM_DialogButtonsButtonHeight Q_DECL_ENUMERATOR_DEPRECATED_X("Not used and no effect since Qt 4"), +#endif - PM_MdiSubWindowFrameWidth, + PM_MdiSubWindowFrameWidth = 44, PM_MdiSubWindowMinimizedWidth, PM_HeaderMargin, diff --git a/src/widgets/styles/qstyle_p.h b/src/widgets/styles/qstyle_p.h index c4daa09c11..59e87810c5 100644 --- a/src/widgets/styles/qstyle_p.h +++ b/src/widgets/styles/qstyle_p.h @@ -29,38 +29,26 @@ class QStylePrivate: public QObjectPrivate { Q_DECLARE_PUBLIC(QStyle) public: - inline QStylePrivate() - : layoutSpacingIndex(-1), proxyStyle(nullptr) {} - static bool useFullScreenForPopup(); - mutable int layoutSpacingIndex; QStyle *proxyStyle; QString name; }; -inline QImage styleCacheImage(const QSize &size) -{ - const qreal pixelRatio = qApp->devicePixelRatio(); - QImage cacheImage = QImage(size * pixelRatio, QImage::Format_ARGB32_Premultiplied); - cacheImage.setDevicePixelRatio(pixelRatio); - return cacheImage; -} - -inline QPixmap styleCachePixmap(const QSize &size) +inline QPixmap styleCachePixmap(const QSize &size, qreal pixelRatio) { - const qreal pixelRatio = qApp->devicePixelRatio(); QPixmap cachePixmap = QPixmap(size * pixelRatio); cachePixmap.setDevicePixelRatio(pixelRatio); + cachePixmap.fill(Qt::transparent); return cachePixmap; } #define BEGIN_STYLE_PIXMAPCACHE(a) \ QRect rect = option->rect; \ QPixmap internalPixmapCache; \ - QImage imageCache; \ QPainter *p = painter; \ - const QString unique = QStyleHelper::uniqueName((a), option, option->rect.size()); \ + const auto dpr = p->device()->devicePixelRatio(); \ + const QString unique = QStyleHelper::uniqueName((a), option, option->rect.size(), dpr); \ int txType = painter->deviceTransform().type() | painter->worldTransform().type(); \ const bool doPixmapCache = (!option->rect.isEmpty()) \ && ((txType <= QTransform::TxTranslate) || (painter->deviceTransform().type() == QTransform::TxScale)); \ @@ -69,9 +57,8 @@ inline QPixmap styleCachePixmap(const QSize &size) } else { \ if (doPixmapCache) { \ rect.setRect(0, 0, option->rect.width(), option->rect.height()); \ - imageCache = styleCacheImage(option->rect.size()); \ - imageCache.fill(0); \ - p = new QPainter(&imageCache); \ + internalPixmapCache = styleCachePixmap(option->rect.size(), dpr); \ + p = new QPainter(&internalPixmapCache); \ } @@ -80,7 +67,6 @@ inline QPixmap styleCachePixmap(const QSize &size) if (doPixmapCache) { \ p->end(); \ delete p; \ - internalPixmapCache = QPixmap::fromImage(imageCache); \ painter->drawPixmap(option->rect.topLeft(), internalPixmapCache); \ QPixmapCache::insert(unique, internalPixmapCache); \ } \ diff --git a/src/widgets/styles/qstylehelper.cpp b/src/widgets/styles/qstylehelper.cpp index 1084761d30..02827de847 100644 --- a/src/widgets/styles/qstylehelper.cpp +++ b/src/widgets/styles/qstylehelper.cpp @@ -32,7 +32,7 @@ static inline bool usePixmapCache(const QStyleOption *opt) return true; } -QString uniqueName(const QString &key, const QStyleOption *option, const QSize &size) +QString uniqueName(const QString &key, const QStyleOption *option, const QSize &size, qreal dpr) { if (!usePixmapCache(option)) return {}; @@ -43,7 +43,8 @@ QString uniqueName(const QString &key, const QStyleOption *option, const QSize & % HexString<uint>(complexOption ? uint(complexOption->activeSubControls) : 0u) % HexString<quint64>(option->palette.cacheKey()) % HexString<uint>(size.width()) - % HexString<uint>(size.height()); + % HexString<uint>(size.height()) + % HexString<qreal>(dpr); #if QT_CONFIG(spinbox) if (const QStyleOptionSpinBox *spinBox = qstyleoption_cast<const QStyleOptionSpinBox *>(option)) { diff --git a/src/widgets/styles/qstylehelper_p.h b/src/widgets/styles/qstylehelper_p.h index 524417e405..98470ad1ce 100644 --- a/src/widgets/styles/qstylehelper_p.h +++ b/src/widgets/styles/qstylehelper_p.h @@ -39,7 +39,7 @@ class QWindow; namespace QStyleHelper { - QString uniqueName(const QString &key, const QStyleOption *option, const QSize &size); + QString uniqueName(const QString &key, const QStyleOption *option, const QSize &size, qreal dpr); Q_WIDGETS_EXPORT qreal dpi(const QStyleOption *option); diff --git a/src/widgets/styles/qstylesheetstyle.cpp b/src/widgets/styles/qstylesheetstyle.cpp index 7602217f24..655b224617 100644 --- a/src/widgets/styles/qstylesheetstyle.cpp +++ b/src/widgets/styles/qstylesheetstyle.cpp @@ -6512,6 +6512,9 @@ bool QStyleSheetStyle::isNaturalChild(const QObject *obj) QPixmap QStyleSheetStyle::loadPixmap(const QString &fileName, const QObject *context) { + if (fileName.isEmpty()) + return {}; + qreal ratio = -1.0; if (const QWidget *widget = qobject_cast<const QWidget *>(context)) { if (QScreen *screen = QApplication::screenAt(widget->mapToGlobal(QPoint(0, 0)))) diff --git a/src/widgets/styles/qstylesheetstyle_default.cpp b/src/widgets/styles/qstylesheetstyle_default.cpp index e50e18f291..6356835ff4 100644 --- a/src/widgets/styles/qstylesheetstyle_default.cpp +++ b/src/widgets/styles/qstylesheetstyle_default.cpp @@ -122,7 +122,8 @@ StyleSheet QStyleSheetStyle::getDefaultStyleSheet() const // pixmap based style doesn't support any features bool styleIsPixmapBased = baseStyle()->inherits("QMacStyle") - || baseStyle()->inherits("QWindowsVistaStyle"); + || (baseStyle()->inherits("QWindowsVistaStyle") + && !baseStyle()->inherits("QWindows11Style")); /*QLineEdit { diff --git a/src/widgets/util/qcompleter.cpp b/src/widgets/util/qcompleter.cpp index 394a968aad..f52321a3e1 100644 --- a/src/widgets/util/qcompleter.cpp +++ b/src/widgets/util/qcompleter.cpp @@ -1296,10 +1296,21 @@ bool QCompleter::eventFilter(QObject *o, QEvent *e) { Q_D(QCompleter); - if (d->eatFocusOut && o == d->widget && e->type() == QEvent::FocusOut) { - d->hiddenBecauseNoMatch = false; - if (d->popup && d->popup->isVisible()) - return true; + if (o == d->widget) { + switch (e->type()) { + case QEvent::FocusOut: + if (d->eatFocusOut) { + d->hiddenBecauseNoMatch = false; + if (d->popup && d->popup->isVisible()) + return true; + } + break; + case QEvent::Hide: + if (d->popup) + d->popup->hide(); + default: + break; + } } if (o != d->popup) diff --git a/src/widgets/widgets/qcalendarwidget.cpp b/src/widgets/widgets/qcalendarwidget.cpp index 034127b4f3..0495b20422 100644 --- a/src/widgets/widgets/qcalendarwidget.cpp +++ b/src/widgets/widgets/qcalendarwidget.cpp @@ -2731,12 +2731,29 @@ bool QCalendarWidget::isGridVisible() const return d->m_view->showGrid(); } +/*! + \since 5.14 + Report the calendar system in use by this widget. + + \sa setCalendar() +*/ + QCalendar QCalendarWidget::calendar() const { Q_D(const QCalendarWidget); return d->m_model->m_calendar; } +/*! + \since 5.14 + Set \a c as the calendar system to be used by this widget. + + The widget can use any supported calendar system. + By default, it uses the Gregorian calendar. + + \sa calendar() +*/ + void QCalendarWidget::setCalendar(QCalendar c) { Q_D(QCalendarWidget); diff --git a/src/widgets/widgets/qcheckbox.cpp b/src/widgets/widgets/qcheckbox.cpp index 88cd603d70..3c03e9efa5 100644 --- a/src/widgets/widgets/qcheckbox.cpp +++ b/src/widgets/widgets/qcheckbox.cpp @@ -93,6 +93,11 @@ public: \fn void QCheckBox::stateChanged(int state) \deprecated [6.9] Use checkStateChanged(Qt::CheckState) instead. + + This signal is emitted whenever the checkbox's state changes, i.e., + whenever the user checks or unchecks it. + + \a state contains the checkbox's new Qt::CheckState. */ /*! diff --git a/src/widgets/widgets/qcombobox.cpp b/src/widgets/widgets/qcombobox.cpp index 06de5566ff..1fe9a8d7be 100644 --- a/src/widgets/widgets/qcombobox.cpp +++ b/src/widgets/widgets/qcombobox.cpp @@ -3003,24 +3003,8 @@ void QComboBox::changeEvent(QEvent *e) Q_D(QComboBox); switch (e->type()) { case QEvent::StyleChange: - if (d->container) { -// If on Windows, force recreation of ComboBox container, since -// windows11 style depends on WA_TranslucentBackground -#ifdef Q_OS_WIN - auto delegate = itemDelegate(); - d->container->deleteLater(); - // d->container needs to be set explicitly to nullptr - // since QComboBoxPrivate::viewContainer() only - // creates a new QComboBoxPrivateContainer when - // d->container has the value of nullptr - d->container = nullptr; - d->container = d->viewContainer(); - delegate->setParent(d->container); - setItemDelegate(delegate); - -#endif + if (d->container) d->container->updateStyleSettings(); - } d->updateDelegate(); #ifdef Q_OS_MAC diff --git a/src/widgets/widgets/qdatetimeedit.cpp b/src/widgets/widgets/qdatetimeedit.cpp index 01e52b2fa6..a9b5babde5 100644 --- a/src/widgets/widgets/qdatetimeedit.cpp +++ b/src/widgets/widgets/qdatetimeedit.cpp @@ -303,6 +303,12 @@ void QDateTimeEdit::setTime(QTime time) } } +/*! + \since 5.14 + Report the calendar system in use by this widget. + + \sa setCalendar() +*/ QCalendar QDateTimeEdit::calendar() const { @@ -310,6 +316,16 @@ QCalendar QDateTimeEdit::calendar() const return d->calendar; } +/*! + \since 5.14 + Set \a calendar as the calendar system to be used by this widget. + + The widget can use any supported calendar system. + By default, it uses the Gregorian calendar. + + \sa calendar() +*/ + void QDateTimeEdit::setCalendar(QCalendar calendar) { Q_D(QDateTimeEdit); @@ -2462,7 +2478,7 @@ int QDateTimeEditPrivate::absoluteIndex(QDateTimeEdit::Section s, int index) con return NoSectionIndex; } -int QDateTimeEditPrivate::absoluteIndex(const SectionNode &s) const +int QDateTimeEditPrivate::absoluteIndex(SectionNode s) const { return sectionNodes.indexOf(s); } diff --git a/src/widgets/widgets/qdatetimeedit_p.h b/src/widgets/widgets/qdatetimeedit_p.h index 215ee75bfe..f93afd1519 100644 --- a/src/widgets/widgets/qdatetimeedit_p.h +++ b/src/widgets/widgets/qdatetimeedit_p.h @@ -67,7 +67,7 @@ public: int cursorPosition() const override { return edit ? edit->cursorPosition() : -1; } int absoluteIndex(QDateTimeEdit::Section s, int index) const; - int absoluteIndex(const SectionNode &s) const; + int absoluteIndex(SectionNode s) const; QDateTime stepBy(int index, int steps, bool test = false) const; int sectionAt(int pos) const; int closestSection(int index, bool forward) const; diff --git a/src/widgets/widgets/qdialogbuttonbox.cpp b/src/widgets/widgets/qdialogbuttonbox.cpp index 30ace89fa8..30c68ad18b 100644 --- a/src/widgets/widgets/qdialogbuttonbox.cpp +++ b/src/widgets/widgets/qdialogbuttonbox.cpp @@ -647,12 +647,12 @@ void QDialogButtonBox::clear() d->standardButtonHash.clear(); for (int i = 0; i < NRoles; ++i) { QList<QAbstractButton *> &list = d->buttonLists[i]; - while (list.size()) { - QAbstractButton *button = list.takeAt(0); + for (auto button : std::as_const(list)) { QObjectPrivate::disconnect(button, &QAbstractButton::destroyed, d, &QDialogButtonBoxPrivate::handleButtonDestroyed); delete button; } + list.clear(); } } @@ -818,8 +818,8 @@ void QDialogButtonBox::setStandardButtons(StandardButtons buttons) { Q_D(QDialogButtonBox); // Clear out all the old standard buttons, then recreate them. - qDeleteAll(d->standardButtonHash.keyBegin(), d->standardButtonHash.keyEnd()); - d->standardButtonHash.clear(); + const auto toDelete = std::exchange(d->standardButtonHash, {}); + qDeleteAll(toDelete.keyBegin(), toDelete.keyEnd()); d->createStandardButtons(buttons); } diff --git a/src/widgets/widgets/qgroupbox.cpp b/src/widgets/widgets/qgroupbox.cpp index 25dcee95c8..eb37599382 100644 --- a/src/widgets/widgets/qgroupbox.cpp +++ b/src/widgets/widgets/qgroupbox.cpp @@ -133,9 +133,9 @@ void QGroupBoxPrivate::click() widgets). The following example shows how we can set up a QGroupBox with a layout: - \snippet widgets/groupbox/window.cpp 2 + \snippet code/src_gui_widgets_qgroupbox.cpp Set up QGroupBox with layout - \sa QButtonGroup, {Group Box Example} + \sa QButtonGroup */ diff --git a/src/widgets/widgets/qlineedit.cpp b/src/widgets/widgets/qlineedit.cpp index 8909ac80d9..0db46bf175 100644 --- a/src/widgets/widgets/qlineedit.cpp +++ b/src/widgets/widgets/qlineedit.cpp @@ -2211,11 +2211,13 @@ QMenu *QLineEdit::createStandardContextMenu() if (!isReadOnly()) { action = popup->addAction(QLineEdit::tr("&Undo") + ACCEL_KEY(QKeySequence::Undo)); action->setEnabled(d->control->isUndoAvailable()); + action->setObjectName(QStringLiteral("edit-undo")); setActionIcon(action, QStringLiteral("edit-undo")); connect(action, &QAction::triggered, this, &QLineEdit::undo); action = popup->addAction(QLineEdit::tr("&Redo") + ACCEL_KEY(QKeySequence::Redo)); action->setEnabled(d->control->isRedoAvailable()); + action->setObjectName(QStringLiteral("edit-redo")); setActionIcon(action, QStringLiteral("edit-redo")); connect(action, &QAction::triggered, this, &QLineEdit::redo); @@ -2227,6 +2229,7 @@ QMenu *QLineEdit::createStandardContextMenu() action = popup->addAction(QLineEdit::tr("Cu&t") + ACCEL_KEY(QKeySequence::Cut)); action->setEnabled(!d->control->isReadOnly() && d->control->hasSelectedText() && d->control->echoMode() == QLineEdit::Normal); + action->setObjectName(QStringLiteral("edit-cut")); setActionIcon(action, QStringLiteral("edit-cut")); connect(action, &QAction::triggered, this, &QLineEdit::cut); } @@ -2234,12 +2237,14 @@ QMenu *QLineEdit::createStandardContextMenu() action = popup->addAction(QLineEdit::tr("&Copy") + ACCEL_KEY(QKeySequence::Copy)); action->setEnabled(d->control->hasSelectedText() && d->control->echoMode() == QLineEdit::Normal); + action->setObjectName(QStringLiteral("edit-copy")); setActionIcon(action, QStringLiteral("edit-copy")); connect(action, &QAction::triggered, this, &QLineEdit::copy); if (!isReadOnly()) { action = popup->addAction(QLineEdit::tr("&Paste") + ACCEL_KEY(QKeySequence::Paste)); action->setEnabled(!d->control->isReadOnly() && !QGuiApplication::clipboard()->text().isEmpty()); + action->setObjectName(QStringLiteral("edit-paste")); setActionIcon(action, QStringLiteral("edit-paste")); connect(action, &QAction::triggered, this, &QLineEdit::paste); } @@ -2248,6 +2253,7 @@ QMenu *QLineEdit::createStandardContextMenu() if (!isReadOnly()) { action = popup->addAction(QLineEdit::tr("Delete")); action->setEnabled(!d->control->isReadOnly() && !d->control->text().isEmpty() && d->control->hasSelectedText()); + action->setObjectName(QStringLiteral("edit-delete")); setActionIcon(action, QStringLiteral("edit-delete")); connect(action, &QAction::triggered, d->control, &QWidgetLineControl::_q_deleteSelected); @@ -2258,6 +2264,7 @@ QMenu *QLineEdit::createStandardContextMenu() action = popup->addAction(QLineEdit::tr("Select All") + ACCEL_KEY(QKeySequence::SelectAll)); action->setEnabled(!d->control->text().isEmpty() && !d->control->allSelected()); + action->setObjectName(QStringLiteral("select-all")); setActionIcon(action, QStringLiteral("edit-select-all")); d->selectAllAction = action; connect(action, &QAction::triggered, this, &QLineEdit::selectAll); diff --git a/src/widgets/widgets/qmainwindowlayout.cpp b/src/widgets/widgets/qmainwindowlayout.cpp index db17e50d5c..85c58fd70f 100644 --- a/src/widgets/widgets/qmainwindowlayout.cpp +++ b/src/widgets/widgets/qmainwindowlayout.cpp @@ -1931,6 +1931,11 @@ bool QMainWindowTabBar::contains(const QDockWidget *dockWidget) const return false; } +// When a dock widget is removed from a floating tab, +// Events need to be processed for the tab bar to realize that the dock widget is gone. +// In this case count() counts the dock widget in transition and accesses dockAt +// with an out-of-bounds index. +// => return nullptr in contrast to other xxxxxAt() functions QDockWidget *QMainWindowTabBar::dockAt(int index) const { QMainWindowTabBar *that = const_cast<QMainWindowTabBar *>(this); @@ -1938,10 +1943,15 @@ QDockWidget *QMainWindowTabBar::dockAt(int index) const QDockAreaLayoutInfo *info = mlayout->dockInfo(that); if (!info) return nullptr; + const int itemIndex = info->tabIndexToListIndex(index); - Q_ASSERT(itemIndex >= 0 && itemIndex < info->item_list.count()); - const QDockAreaLayoutItem &item = info->item_list.at(itemIndex); - return item.widgetItem ? qobject_cast<QDockWidget *>(item.widgetItem->widget()) : nullptr; + if (itemIndex >= 0) { + Q_ASSERT(itemIndex < info->item_list.count()); + const QDockAreaLayoutItem &item = info->item_list.at(itemIndex); + return item.widgetItem ? qobject_cast<QDockWidget *>(item.widgetItem->widget()) : nullptr; + } + + return nullptr; } void QMainWindowTabBar::mouseMoveEvent(QMouseEvent *e) diff --git a/src/widgets/widgets/qradiobutton.cpp b/src/widgets/widgets/qradiobutton.cpp index e34b04e5ee..af1bd3f649 100644 --- a/src/widgets/widgets/qradiobutton.cpp +++ b/src/widgets/widgets/qradiobutton.cpp @@ -82,7 +82,7 @@ void QRadioButtonPrivate::init() setDown(), isDown(), autoRepeat(), group(), setAutoRepeat(), toggle(), pressed(), released(), clicked(), and toggled(). - \sa QPushButton, QToolButton, QCheckBox, {Group Box Example} + \sa QPushButton, QToolButton, QCheckBox */ diff --git a/src/widgets/widgets/qtoolbutton.cpp b/src/widgets/widgets/qtoolbutton.cpp index f3e3c8261e..e0775afd26 100644 --- a/src/widgets/widgets/qtoolbutton.cpp +++ b/src/widgets/widgets/qtoolbutton.cpp @@ -584,8 +584,10 @@ void QToolButton::mousePressEvent(QMouseEvent *e) void QToolButton::mouseReleaseEvent(QMouseEvent *e) { Q_D(QToolButton); + QPointer<QAbstractButton> guard(this); QAbstractButton::mouseReleaseEvent(e); - d->buttonPressed = QToolButtonPrivate::NoButtonPressed; + if (guard) + d->buttonPressed = QToolButtonPrivate::NoButtonPressed; } /*! |