diff options
Diffstat (limited to 'src/widgets')
39 files changed, 977 insertions, 337 deletions
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/dialogs/qmessagebox.cpp b/src/widgets/dialogs/qmessagebox.cpp index bf56b17f55..f24cd905cc 100644 --- a/src/widgets/dialogs/qmessagebox.cpp +++ b/src/widgets/dialogs/qmessagebox.cpp @@ -2158,7 +2158,11 @@ int QMessageBoxPrivate::showOldMessageBox(QWidget *parent, QMessageBox::Icon ico messageBox.setDefaultButton(static_cast<QPushButton *>(buttonList.value(defaultButtonNumber))); messageBox.setEscapeButton(buttonList.value(escapeButtonNumber)); - return messageBox.exec(); + messageBox.exec(); + + // Ignore exec return value and use button index instead, + // as that's what the documentation promises. + return buttonList.indexOf(messageBox.clickedButton()); } void QMessageBoxPrivate::retranslateStrings() 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/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/qtreeview.cpp b/src/widgets/itemviews/qtreeview.cpp index 744f29ca17..1ae81023c5 100644 --- a/src/widgets/itemviews/qtreeview.cpp +++ b/src/widgets/itemviews/qtreeview.cpp @@ -1412,13 +1412,48 @@ QRect QTreeViewPrivate::intersectedRect(const QRect rect, const QModelIndex &top const auto parentIdx = topLeft.parent(); executePostedLayout(); QRect updateRect; - for (int r = topLeft.row(); r <= bottomRight.row(); ++r) { - if (isRowHidden(model->index(r, 0, parentIdx))) + int left = std::numeric_limits<int>::max(); + int right = std::numeric_limits<int>::min(); + for (int row = topLeft.row(); row <= bottomRight.row(); ++row) { + const auto idxCol0 = model->index(row, 0, parentIdx); + if (isRowHidden(idxCol0)) continue; - for (int c = topLeft.column(); c <= bottomRight.column(); ++c) { - const QModelIndex idx(model->index(r, c, parentIdx)); - updateRect |= visualRect(idx, SingleSection); + QRect rowRect; + if (left != std::numeric_limits<int>::max()) { + // we already know left and right boundary of the rect to update + rowRect = visualRect(idxCol0, FullRow); + if (!rowRect.intersects(rect)) + continue; + rowRect = QRect(left, rowRect.top(), right, rowRect.bottom()); + } else if (!spanningIndexes.isEmpty() && spanningIndexes.contains(idxCol0)) { + // isFirstColumnSpanned re-creates the child index so take a shortcut here + // spans the whole row, therefore ask for FullRow instead for every cell + rowRect = visualRect(idxCol0, FullRow); + if (!rowRect.intersects(rect)) + continue; + } else { + for (int col = topLeft.column(); col <= bottomRight.column(); ++col) { + if (header->isSectionHidden(col)) + continue; + const QModelIndex idx(model->index(row, col, parentIdx)); + const QRect idxRect = visualRect(idx, SingleSection); + if (idxRect.isNull()) + continue; + // early exit when complete row is out of viewport + if (idxRect.top() > rect.bottom() && idxRect.bottom() < rect.top()) + break; + if (!idxRect.intersects(rect)) + continue; + rowRect = rowRect.united(idxRect); + if (rowRect.left() < rect.left() && rowRect.right() > rect.right()) + break; + } + left = std::min(left, rowRect.left()); + right = std::max(right, rowRect.right()); } + updateRect = updateRect.united(rowRect); + if (updateRect.contains(rect)) // already full rect covered? + break; } return rect.intersected(updateRect); } diff --git a/src/widgets/kernel/qapplication.cpp b/src/widgets/kernel/qapplication.cpp index de7d93ce20..bf6b41cbcd 100644 --- a/src/widgets/kernel/qapplication.cpp +++ b/src/widgets/kernel/qapplication.cpp @@ -98,6 +98,8 @@ static void initResources() QT_BEGIN_NAMESPACE +Q_LOGGING_CATEGORY(lcWidgetPopup, "qt.widgets.popup"); + using namespace Qt::StringLiterals; Q_TRACE_PREFIX(qtwidgets, @@ -352,8 +354,6 @@ Q_GLOBAL_STATIC(FontHash, app_fonts) // Exported accessor for use outside of this file FontHash *qt_app_fonts_hash() { return app_fonts(); } -QWidgetList *QApplicationPrivate::popupWidgets = nullptr; // has keyboard input focus - QWidget *qt_desktopWidget = nullptr; // root window widgets /*! @@ -627,8 +627,8 @@ void QApplicationPrivate::initializeWidgetFontHash() QWidget *QApplication::activePopupWidget() { - return QApplicationPrivate::popupWidgets && !QApplicationPrivate::popupWidgets->isEmpty() ? - QApplicationPrivate::popupWidgets->constLast() : nullptr; + auto *win = qobject_cast<QWidgetWindow *>(QGuiApplicationPrivate::activePopupWindow()); + return win ? win->widget() : nullptr; } @@ -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 @@ -1869,7 +1875,7 @@ void QApplicationPrivate::setActiveWindow(QWidget* act) QApplication::sendSpontaneousEvent(w, &activationChange); } - if (QApplicationPrivate::popupWidgets == nullptr) { // !inPopupMode() + if (!inPopupMode()) { // then focus events if (!QApplicationPrivate::active_window && QApplicationPrivate::focus_widget) { QApplicationPrivate::setFocusWidget(nullptr, Qt::ActiveWindowFocusReason); @@ -3293,11 +3299,12 @@ bool QApplicationPrivate::notify_helper(QObject *receiver, QEvent * e) bool QApplicationPrivate::inPopupMode() { - return QApplicationPrivate::popupWidgets != nullptr; + return QGuiApplicationPrivate::activePopupWindow() != nullptr; } static void ungrabKeyboardForPopup(QWidget *popup) { + qCDebug(lcWidgetPopup) << "ungrab keyboard for" << popup; if (QWidget::keyboardGrabber()) qt_widget_private(QWidget::keyboardGrabber())->stealKeyboardGrab(true); else @@ -3306,6 +3313,7 @@ static void ungrabKeyboardForPopup(QWidget *popup) static void ungrabMouseForPopup(QWidget *popup) { + qCDebug(lcWidgetPopup) << "ungrab mouse for" << popup; if (QWidget::mouseGrabber()) qt_widget_private(QWidget::mouseGrabber())->stealMouseGrab(true); else @@ -3325,54 +3333,23 @@ static void grabForPopup(QWidget *popup) ungrabKeyboardForPopup(popup); } } -} - -extern QWidget *qt_popup_down; -extern bool qt_replay_popup_mouse_event; -extern bool qt_popup_down_closed; - -bool QApplicationPrivate::closeAllPopups() -{ - // Close all popups: In case some popup refuses to close, - // we give up after 1024 attempts (to avoid an infinite loop). - int maxiter = 1024; - QWidget *popup; - while ((popup = QApplication::activePopupWidget()) && maxiter--) - popup->close(); // this will call QApplicationPrivate::closePopup - return true; + qCDebug(lcWidgetPopup) << "grabbed mouse and keyboard?" << popupGrabOk << "for popup" << popup; } void QApplicationPrivate::closePopup(QWidget *popup) { - if (!popupWidgets) + QWindow *win = popup->windowHandle(); + if (!win) + return; + if (!QGuiApplicationPrivate::closePopup(win)) return; - popupWidgets->removeAll(popup); - - if (popup == qt_popup_down) { - qt_button_down = nullptr; - qt_popup_down_closed = true; - qt_popup_down = nullptr; - } - if (QApplicationPrivate::popupWidgets->size() == 0) { // this was the last popup - delete QApplicationPrivate::popupWidgets; - QApplicationPrivate::popupWidgets = nullptr; - qt_popup_down_closed = false; + const QWindow *nextRemainingPopup = QGuiApplicationPrivate::activePopupWindow(); + if (!nextRemainingPopup) { // this was the last popup if (popupGrabOk) { popupGrabOk = false; - // TODO on multi-seat window systems, we have to know which mouse - auto devPriv = QPointingDevicePrivate::get(QPointingDevice::primaryPointingDevice()); - auto mousePressPos = devPriv->pointById(0)->eventPoint.globalPressPosition(); - if (popup->geometry().contains(mousePressPos.toPoint()) - || popup->testAttribute(Qt::WA_NoMouseReplay)) { - // mouse release event or inside - qt_replay_popup_mouse_event = false; - } else { // mouse press event - qt_replay_popup_mouse_event = true; - } - // transfer grab back to mouse grabber if any, otherwise release the grab ungrabMouseForPopup(popup); @@ -3391,30 +3368,23 @@ void QApplicationPrivate::closePopup(QWidget *popup) } } - } else { + } else if (const auto *popupWin = qobject_cast<const QWidgetWindow *>(nextRemainingPopup)) { // A popup was closed, so the previous popup gets the focus. - QWidget* aw = QApplicationPrivate::popupWidgets->constLast(); - if (QWidget *fw = aw->focusWidget()) + if (QWidget *fw = popupWin->widget()->focusWidget()) fw->setFocus(Qt::PopupFocusReason); // can become nullptr due to setFocus() above - if (QApplicationPrivate::popupWidgets && - QApplicationPrivate::popupWidgets->size() == 1) // grab mouse/keyboard - grabForPopup(aw); + if (QGuiApplicationPrivate::popupCount() == 1) // grab mouse/keyboard + grabForPopup(popupWin->widget()); } } -int openPopupCount = 0; - void QApplicationPrivate::openPopup(QWidget *popup) { - openPopupCount++; - if (!popupWidgets) // create list - popupWidgets = new QWidgetList; - popupWidgets->append(popup); // add to end of list + QGuiApplicationPrivate::activatePopup(popup->windowHandle()); - if (QApplicationPrivate::popupWidgets->size() == 1) // grab mouse/keyboard + if (QGuiApplicationPrivate::popupCount() == 1) // grab mouse/keyboard grabForPopup(popup); // popups are not focus-handled by the window system (the first @@ -3422,7 +3392,7 @@ void QApplicationPrivate::openPopup(QWidget *popup) // new popup gets the focus if (popup->focusWidget()) { popup->focusWidget()->setFocus(Qt::PopupFocusReason); - } else if (popupWidgets->size() == 1) { // this was the first popup + } else if (QGuiApplicationPrivate::popupCount() == 1) { // this was the first popup if (QWidget *fw = QApplication::focusWidget()) { QFocusEvent e(QEvent::FocusOut, Qt::PopupFocusReason); QCoreApplication::sendEvent(fw, &e); diff --git a/src/widgets/kernel/qapplication_p.h b/src/widgets/kernel/qapplication_p.h index 21b1605dfc..7de9f54b58 100644 --- a/src/widgets/kernel/qapplication_p.h +++ b/src/widgets/kernel/qapplication_p.h @@ -107,8 +107,6 @@ public: static void setActiveWindow(QWidget* act); static bool inPopupMode(); - bool popupActive() override { return inPopupMode(); } - bool closeAllPopups() override; void closePopup(QWidget *popup); void openPopup(QWidget *popup); static void setFocusWidget(QWidget *focus, Qt::FocusReason reason); diff --git a/src/widgets/kernel/qwidget.cpp b/src/widgets/kernel/qwidget.cpp index e8342a4788..0af28e3cfd 100644 --- a/src/widgets/kernel/qwidget.cpp +++ b/src/widgets/kernel/qwidget.cpp @@ -1029,10 +1029,13 @@ void QWidgetPrivate::createRecursively() QRhi *QWidgetPrivate::rhi() const { - if (QWidgetRepaintManager *repaintManager = maybeRepaintManager()) - return repaintManager->rhi(); - else + Q_Q(const QWidget); + if (auto *backingStore = q->backingStore()) { + auto *window = windowHandle(WindowHandleMode::Closest); + return backingStore->handle()->rhi(window); + } else { return nullptr; + } } /*! @@ -1112,8 +1115,15 @@ static bool q_evaluateRhiConfigRecursive(const QWidget *w, QPlatformBackingStore } for (const QObject *child : w->children()) { if (const QWidget *childWidget = qobject_cast<const QWidget *>(child)) { - if (q_evaluateRhiConfigRecursive(childWidget, outConfig, outType)) + if (q_evaluateRhiConfigRecursive(childWidget, outConfig, outType)) { + static bool optOut = qEnvironmentVariableIsSet("QT_WIDGETS_NO_CHILD_RHI"); + // Native child widgets should not trigger RHI for its parent + // hierarchy, but will still flush the native child using RHI. + if (!optOut && childWidget->testAttribute(Qt::WA_NativeWindow)) + continue; + return true; + } } } return false; @@ -1356,19 +1366,19 @@ void QWidgetPrivate::create() QBackingStore *store = q->backingStore(); usesRhiFlush = false; - if (!store) { - if (q->windowType() != Qt::Desktop) { - if (q->isWindow()) { - q->setBackingStore(new QBackingStore(win)); - QPlatformBackingStoreRhiConfig rhiConfig; - usesRhiFlush = q_evaluateRhiConfig(q, &rhiConfig, nullptr); - topData()->backingStore->handle()->setRhiConfig(rhiConfig); - } - } else { - q->setAttribute(Qt::WA_PaintOnScreen, true); + if (q->windowType() == Qt::Desktop) { + q->setAttribute(Qt::WA_PaintOnScreen, true); + } else { + if (!store && q->isWindow()) + q->setBackingStore(new QBackingStore(win)); + + QPlatformBackingStoreRhiConfig rhiConfig; + usesRhiFlush = q_evaluateRhiConfig(q, &rhiConfig, nullptr); + if (usesRhiFlush && q->backingStore()) { + // Trigger creation of support infrastructure up front, + // now that we have a specific RHI configuration. + q->backingStore()->handle()->createRhi(win, rhiConfig); } - } else if (win->handle()) { - usesRhiFlush = q_evaluateRhiConfig(q, nullptr, nullptr); } setWindowModified_helper(); @@ -2715,8 +2725,10 @@ void QWidgetPrivate::inheritStyle() // to be running a proxy if (!qApp->styleSheet().isEmpty() || qt_styleSheet(parentStyle)) { QStyle *newStyle = parentStyle; - if (q->testAttribute(Qt::WA_SetStyle)) + if (q->testAttribute(Qt::WA_SetStyle) && qt_styleSheet(origStyle) == nullptr) newStyle = new QStyleSheetStyle(origStyle); + else if (auto *styleSheetStyle = qt_styleSheet(origStyle)) + newStyle = styleSheetStyle; else if (QStyleSheetStyle *newProxy = qt_styleSheet(parentStyle)) newProxy->ref(); @@ -5160,6 +5172,7 @@ void QWidget::render(QPainter *painter, const QPoint &targetOffset, const QRegion oldSystemClip = enginePriv->systemClip; const QRegion oldBaseClip = enginePriv->baseSystemClip; const QRegion oldSystemViewport = enginePriv->systemViewport; + const Qt::LayoutDirection oldLayoutDirection = painter->layoutDirection(); // This ensures that all painting triggered by render() is clipped to the current engine clip. if (painter->hasClipping()) { @@ -5168,6 +5181,7 @@ void QWidget::render(QPainter *painter, const QPoint &targetOffset, } else { enginePriv->setSystemViewport(oldSystemClip); } + painter->setLayoutDirection(layoutDirection()); d->render(target, targetOffset, toBePainted, renderFlags); @@ -5175,6 +5189,7 @@ void QWidget::render(QPainter *painter, const QPoint &targetOffset, enginePriv->baseSystemClip = oldBaseClip; enginePriv->setSystemTransformAndViewport(oldTransform, oldSystemViewport); enginePriv->systemStateChanged(); + painter->setLayoutDirection(oldLayoutDirection); // Restore shared painter. d->setSharedPainter(oldPainter); @@ -6104,6 +6119,13 @@ void QWidget::setWindowTitle(const QString &title) if (QWidget::windowTitle() == title && !title.isEmpty() && !title.isNull()) return; +#if QT_CONFIG(accessibility) + QString oldAccessibleName; + const QAccessibleInterface *accessible = QAccessible::queryAccessibleInterface(this); + if (accessible) + oldAccessibleName = accessible->text(QAccessible::Name); +#endif + Q_D(QWidget); d->topData()->caption = title; d->setWindowTitle_helper(title); @@ -6112,6 +6134,13 @@ void QWidget::setWindowTitle(const QString &title) QCoreApplication::sendEvent(this, &e); emit windowTitleChanged(title); + +#if QT_CONFIG(accessibility) + if (accessible && accessible->text(QAccessible::Name) != oldAccessibleName) { + QAccessibleEvent event(this, QAccessible::NameChanged); + QAccessible::updateAccessibility(&event); + } +#endif } @@ -7076,7 +7105,6 @@ void QWidgetPrivate::reparentFocusWidgets(QWidget * oldtlw) if (focus_child) focus_child->clearFocus(); - insertIntoFocusChain(QWidgetPrivate::FocusDirection::Previous, q->window()); reparentFocusChildren(QWidgetPrivate::FocusDirection::Next); } @@ -7691,11 +7719,15 @@ QMargins QWidgetPrivate::safeAreaMargins() const return QMargins(); // Or, if one of our ancestors are in a layout that does not have WA_LayoutOnEntireRect - // set, then we know that the layout has already taken care of placing us inside the - // safe area, by taking the contents rect of its parent widget into account. + // set, and the widget respects the safe area, then we know that the layout has already + // taken care of placing us inside the safe area, by taking the contents rect of its + // parent widget into account. const QWidget *assumedSafeWidget = nullptr; for (const QWidget *w = q; w != nativeWidget; w = w->parentWidget()) { QWidget *parentWidget = w->parentWidget(); + if (!parentWidget->testAttribute(Qt::WA_ContentsMarginsRespectsSafeArea)) + continue; // Layout can't help us + if (parentWidget->testAttribute(Qt::WA_LayoutOnEntireRect)) continue; // Layout not going to help us @@ -10653,6 +10685,7 @@ void QWidget::setParent(QWidget *parent, Qt::WindowFlags f) const bool wasCreated = testAttribute(Qt::WA_WState_Created); QWidget *oldtlw = window(); Q_ASSERT(oldtlw); + QWidget *oldParentWithWindow = d->closestParentWidgetWithWindowHandle(); if (f & Qt::Window) // Frame geometry likely changes, refresh. d->data.fstrut_dirty = true; @@ -10695,7 +10728,8 @@ 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 (oldtlw->d_func()->usesRhiFlush && ((!parent && parentWidget()) || (parent && parent->window() != oldtlw))) + const bool oldParentUsesRhiFlush = oldParentWithWindow ? oldParentWithWindow->d_func()->usesRhiFlush : false; + if (oldParentUsesRhiFlush && ((!parent && parentWidget()) || (parent && parent->window() != oldtlw))) qSendWindowChangeToTextureChildrenRecursively(this, QEvent::WindowAboutToChangeInternal); // If we get parented into another window, children will be folded @@ -10776,7 +10810,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 (oldtlw->d_func()->usesRhiFlush && oldtlw != window()) + if (oldParentUsesRhiFlush && oldtlw != window()) qSendWindowChangeToTextureChildrenRecursively(this, QEvent::WindowChangeInternal); if (!wasCreated) { @@ -10804,33 +10838,47 @@ void QWidget::setParent(QWidget *parent, Qt::WindowFlags f) if (d->extra && d->extra->hasWindowContainer) QWindowContainer::parentWasChanged(this); - QWidget *newtlw = window(); - if (oldtlw != newtlw) { + QWidget *newParentWithWindow = d->closestParentWidgetWithWindowHandle(); + if (newParentWithWindow && newParentWithWindow != oldParentWithWindow) { + // Check if the native parent now needs to switch to RHI + qCDebug(lcWidgetPainting) << "Evaluating whether reparenting of" << this + << "into" << parent << "requires RHI enablement for" << newParentWithWindow; + + QPlatformBackingStoreRhiConfig rhiConfig; QSurface::SurfaceType surfaceType = QSurface::RasterSurface; - // Only evaluate the reparented subtree. While it might be tempting to - // do it on newtlw instead, the performance implications of that are + + // First evaluate whether the reparented widget uses RHI. + // We do this as a separate step because the performance + // implications of always checking the native parent are // problematic when it comes to large widget trees. - if (q_evaluateRhiConfig(this, nullptr, &surfaceType)) { - const bool wasUsingRhiFlush = newtlw->d_func()->usesRhiFlush; - newtlw->d_func()->usesRhiFlush = true; - bool recreate = false; - if (QWindow *w = newtlw->windowHandle()) { - if (w->surfaceType() != surfaceType || !wasUsingRhiFlush) - recreate = true; - } - // QTBUG-115652: Besides the toplevel the nativeParentWidget()'s QWindow must be checked as well. - if (QWindow *w = d->windowHandle(QWidgetPrivate::WindowHandleMode::Closest)) { - if (w->surfaceType() != surfaceType) - recreate = true; - } - if (recreate) { - const auto windowStateBeforeDestroy = newtlw->windowState(); - const auto visibilityBeforeDestroy = newtlw->isVisible(); - newtlw->destroy(); - newtlw->create(); - Q_ASSERT(newtlw->windowHandle()); - newtlw->windowHandle()->setWindowStates(windowStateBeforeDestroy); - QWidgetPrivate::get(newtlw)->setVisible(visibilityBeforeDestroy); + if (q_evaluateRhiConfig(this, &rhiConfig, &surfaceType)) { + // Then check whether the native parent requires RHI + // as a result. It may not, if this widget is a native + // window, and can handle its own RHI flushing. + if (q_evaluateRhiConfig(newParentWithWindow, nullptr, nullptr)) { + // Finally, check whether we need to recreate the + // native parent to enable RHI flushing. + auto *existingWindow = newParentWithWindow->windowHandle(); + auto existingSurfaceType = existingWindow->surfaceType(); + if (existingSurfaceType != surfaceType) { + qCDebug(lcWidgetPainting) + << "Recreating" << existingWindow + << "with current type" << existingSurfaceType + << "to support" << surfaceType; + const auto windowStateBeforeDestroy = newParentWithWindow->windowState(); + const auto visibilityBeforeDestroy = newParentWithWindow->isVisible(); + newParentWithWindow->destroy(); + newParentWithWindow->create(); + Q_ASSERT(newParentWithWindow->windowHandle()); + newParentWithWindow->windowHandle()->setWindowStates(windowStateBeforeDestroy); + QWidgetPrivate::get(newParentWithWindow)->setVisible(visibilityBeforeDestroy); + } else if (auto *backingStore = newParentWithWindow->backingStore()) { + // If we don't recreate we still need to make sure the native parent + // widget has a RHI config that the reparented widget can use. + backingStore->handle()->createRhi(existingWindow, rhiConfig); + // And that it knows it's now flushing with RHI + QWidgetPrivate::get(newParentWithWindow)->usesRhiFlush = true; + } } } } @@ -12288,8 +12336,10 @@ QBackingStore *QWidget::backingStore() const if (extra && extra->backingStore) return extra->backingStore; - QWidgetRepaintManager *repaintManager = d->maybeRepaintManager(); - return repaintManager ? repaintManager->backingStore() : nullptr; + if (!isWindow()) + return window()->backingStore(); + + return nullptr; } void QWidgetPrivate::getLayoutItemMargins(int *left, int *top, int *right, int *bottom) const @@ -12936,6 +12986,10 @@ int QWidget::metric(PaintDeviceMetric m) const return resolveDevicePixelRatio(); case PdmDevicePixelRatioScaled: return QPaintDevice::devicePixelRatioFScale() * resolveDevicePixelRatio(); + case PdmDevicePixelRatioF_EncodedA: + Q_FALLTHROUGH(); + case PdmDevicePixelRatioF_EncodedB: + return QPaintDevice::encodeMetricF(m, resolveDevicePixelRatio()); default: break; } @@ -13348,32 +13402,61 @@ void QWidgetPrivate::initFocusChain() 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; + // separate the focus chain into new (children of myself) and old (the rest) + QWidget *firstOld = nullptr; + QWidget *lastOld = nullptr; // last in the old list + QWidget *lastNew = q; // last in the new list + bool prevWasNew = true; + QWidget *widget = nextPrevElementInFocusChain(direction); + + // For efficiency, do not maintain the list invariant inside the loop. + // Append items to the relevant list, and we optimize by not changing pointers, + // when subsequent items are going into the same list. + while (widget != q) { + bool currentIsNew = q->isAncestorOf(widget); + if (currentIsNew) { + if (!prevWasNew) { + // previous was old => append to new list + FOCUS_NEXT(lastNew) = widget; + FOCUS_PREV(widget) = lastNew; + } + lastNew = widget; + } else { + if (prevWasNew) { + // prev was new => append to old list, if it exists + if (lastOld) { + FOCUS_NEXT(lastOld) = widget; + FOCUS_PREV(widget) = lastOld; + } else { + // start the old list + firstOld = widget; + } + } + lastOld = widget; + } + widget = widget->d_func()->nextPrevElementInFocusChain(direction); + prevWasNew = currentIsNew; + } + + // repair old list: + if (firstOld) { + FOCUS_NEXT(lastOld) = firstOld; + FOCUS_PREV(firstOld) = lastOld; + } + + if (!q->isWindow()) { + QWidget *topLevel = q->window(); + // insert new chain into toplevel's chain + QWidget *prev = FOCUS_PREV(topLevel); + FOCUS_PREV(topLevel) = lastNew; + FOCUS_NEXT(prev) = q; + FOCUS_PREV(q) = prev; + FOCUS_NEXT(lastNew) = topLevel; + } else { + // repair new list + FOCUS_NEXT(lastNew) = q; + FOCUS_PREV(q) = lastNew; } } diff --git a/src/widgets/kernel/qwidgetrepaintmanager.cpp b/src/widgets/kernel/qwidgetrepaintmanager.cpp index 607a767a20..0dee380a91 100644 --- a/src/widgets/kernel/qwidgetrepaintmanager.cpp +++ b/src/widgets/kernel/qwidgetrepaintmanager.cpp @@ -1016,11 +1016,13 @@ void QWidgetRepaintManager::flush(QWidget *widget, const QRegion ®ion, QPlatf if (tlw->testAttribute(Qt::WA_DontShowOnScreen) || widget->testAttribute(Qt::WA_DontShowOnScreen)) return; + QWindow *window = widget->windowHandle(); + // We should only be flushing to native widgets + Q_ASSERT(window); + // Foreign Windows do not have backing store content and must not be flushed - if (QWindow *widgetWindow = widget->windowHandle()) { - if (widgetWindow->type() == Qt::ForeignWindow) - return; - } + if (window->type() == Qt::ForeignWindow) + return; static bool fpsDebug = qEnvironmentVariableIntValue("QT_DEBUG_FPS"); if (fpsDebug) { @@ -1037,69 +1039,51 @@ void QWidgetRepaintManager::flush(QWidget *widget, const QRegion ®ion, QPlatf if (widget != tlw) offset += widget->mapTo(tlw, QPoint()); - // Use a condition that tries to keep both QTBUG-108344 and QTBUG-113557 - // happy, i.e. support both (A) "native rhi-based child in a rhi-based - // toplevel" and (B) "native raster child in a rhi-based toplevel". - // - // If the tlw and the backingstore are RHI-based, then there are two cases - // to consider: - // - // (1) widget is not a native child, i.e. the QWindow for widget and tlw are - // the same, - // - // (2) widget is a native child which we now attempt to flush with tlw's - // backingstore to widget's native window. This is the interesting one. - // - // Using the condition tlw->usesRhiFlush on its own is insufficient since - // it fails to capture the case of a raster-based native child widget - // within tlw. (which must hit the non-rhi flush path) - // - // Extending the condition with tlw->windowHandle() == widget->windowHandle() - // would be logical but wrong, when it comes to (A) since flushing a - // RHI-based native child with a given 3D API using a RHI-based - // tlw/backingstore with the same 3D API needs to be supported still. (this - // happens when e.g. someone calls winId() on a QOpenGLWidget) - // - // Different 3D APIs do not need to be supported since we do not allow to - // do things like having a QQuickWidget with Vulkan and a QOpenGLWidget in - // the same toplevel, regardless of the widgets being native children or - // not. Hence comparing the surfaceType() instead. This satisfies both (A) - // and (B) given that an RHI-based toplevel cannot be RasterSurface. - // - if (tlw->d_func()->usesRhiFlush && tlw->windowHandle()->surfaceType() == widget->windowHandle()->surfaceType()) { - QRhi *rhi = store->handle()->rhi(); - qCDebug(lcWidgetPainting) << "Flushing" << region << "of" << widget - << "with QRhi" << rhi - << "to window" << widget->windowHandle(); + // A widget uses RHI flush if itself, or one of its non-native children + // uses RHI for its drawing. If so, we composite the backing store raster + // data along with textures produced by the RHI widgets. + const bool flushWithRhi = widget->d_func()->usesRhiFlush; + + qCDebug(lcWidgetPainting) << "Flushing" << region << "of" << widget + << "to" << window << (flushWithRhi ? "using RHI" : ""); + + // A widget uses RHI flush if itself, or one of its non-native children + // uses RHI for its drawing. If so, we composite the backing store raster + // data along with textures produced by the RHI widgets. + if (flushWithRhi) { if (!widgetTextures) widgetTextures = qt_dummy_platformTextureList; - QWidgetPrivate *widgetWindowPrivate = widget->window()->d_func(); - widgetWindowPrivate->sendComposeStatus(widget->window(), false); + // We only need to let the widget sub-hierarchy that + // we are flushing know that we're compositing. + auto *widgetPrivate = QWidgetPrivate::get(widget); + widgetPrivate->sendComposeStatus(widget, false); + // A window may have alpha even when the app did not request // WA_TranslucentBackground. Therefore the compositor needs to know whether the app intends // to rely on translucency, in order to decide if it should clear to transparent or opaque. const bool translucentBackground = widget->testAttribute(Qt::WA_TranslucentBackground); QPlatformBackingStore::FlushResult flushResult; - flushResult = store->handle()->rhiFlush(widget->windowHandle(), + flushResult = store->handle()->rhiFlush(window, widget->devicePixelRatio(), region, offset, widgetTextures, translucentBackground); - widgetWindowPrivate->sendComposeStatus(widget->window(), true); + + widgetPrivate->sendComposeStatus(widget, true); + if (flushResult == QPlatformBackingStore::FlushFailedDueToLostDevice) { qSendWindowChangeToTextureChildrenRecursively(widget->window(), QEvent::WindowAboutToChangeInternal); - store->handle()->graphicsDeviceReportedLost(); + store->handle()->graphicsDeviceReportedLost(window); qSendWindowChangeToTextureChildrenRecursively(widget->window(), QEvent::WindowChangeInternal); widget->update(); } } else { - qCInfo(lcWidgetPainting) << "Flushing" << region << "of" << widget; - store->flush(region, widget->windowHandle(), offset); + store->flush(region, window, offset); } } @@ -1308,11 +1292,6 @@ void QWidgetPrivate::invalidateBackingStore_resizeHelper(const QPoint &oldPos, c } } -QRhi *QWidgetRepaintManager::rhi() const -{ - return store->handle()->rhi(); -} - QT_END_NAMESPACE #include "qwidgetrepaintmanager.moc" diff --git a/src/widgets/kernel/qwidgetrepaintmanager_p.h b/src/widgets/kernel/qwidgetrepaintmanager_p.h index 4378974610..13190a0bb0 100644 --- a/src/widgets/kernel/qwidgetrepaintmanager_p.h +++ b/src/widgets/kernel/qwidgetrepaintmanager_p.h @@ -26,8 +26,6 @@ QT_BEGIN_NAMESPACE class QPlatformTextureList; class QPlatformTextureListWatcher; class QWidgetRepaintManager; -class QRhi; -class QRhiSwapChain; class Q_WIDGETS_EXPORT QWidgetRepaintManager { @@ -72,8 +70,6 @@ public: bool bltRect(const QRect &rect, int dx, int dy, QWidget *widget); - QRhi *rhi() const; - private: void updateLists(QWidget *widget); diff --git a/src/widgets/kernel/qwidgetwindow.cpp b/src/widgets/kernel/qwidgetwindow.cpp index c5b045c8db..ce9bb44b45 100644 --- a/src/widgets/kernel/qwidgetwindow.cpp +++ b/src/widgets/kernel/qwidgetwindow.cpp @@ -27,9 +27,8 @@ Q_WIDGETS_EXPORT QWidget *qt_button_down = nullptr; // widget got last button-do // popup control QWidget *qt_popup_down = nullptr; // popup that contains the pressed widget -extern int openPopupCount; bool qt_popup_down_closed = false; // qt_popup_down has been closed -bool qt_replay_popup_mouse_event = false; + extern bool qt_try_modal(QWidget *widget, QEvent::Type type); class QWidgetWindowPrivate : public QWindowPrivate @@ -77,7 +76,7 @@ public: widget->focusWidget()->clearFocus(); } - void setFocusToTarget(QWindowPrivate::FocusTarget target) override + void setFocusToTarget(FocusTarget target, Qt::FocusReason reason) override { Q_Q(QWidgetWindow); QWidget *widget = q->widget(); @@ -107,7 +106,7 @@ public: } if (newFocusWidget) - newFocusWidget->setFocus(); + newFocusWidget->setFocus(reason); } QRectF closestAcceptableGeometry(const QRectF &rect) const override; @@ -161,8 +160,8 @@ QWidgetWindow::QWidgetWindow(QWidget *widget) updateObjectName(); if (!QCoreApplication::testAttribute(Qt::AA_ForceRasterWidgets)) { QSurface::SurfaceType type = QSurface::RasterSurface; - q_evaluateRhiConfig(m_widget, nullptr, &type); - setSurfaceType(type); + if (q_evaluateRhiConfig(m_widget, nullptr, &type)) + setSurfaceType(type); } connect(widget, &QObject::objectNameChanged, this, &QWidgetWindow::updateObjectName); @@ -505,9 +504,6 @@ void QWidgetWindow::handleNonClientAreaMouseEvent(QMouseEvent *e) void QWidgetWindow::handleMouseEvent(QMouseEvent *event) { - static const QEvent::Type contextMenuTrigger = - QGuiApplicationPrivate::platformTheme()->themeHint(QPlatformTheme::ContextMenuOnMouseRelease).toBool() ? - QEvent::MouseButtonRelease : QEvent::MouseButtonPress; if (QApplicationPrivate::inPopupMode()) { QPointer<QWidget> activePopupWidget = QApplication::activePopupWidget(); QPointF mapped = event->position(); @@ -535,11 +531,8 @@ void QWidgetWindow::handleMouseEvent(QMouseEvent *event) break; // nothing for mouse move } - int oldOpenPopupCount = openPopupCount; - if (activePopupWidget->isEnabled()) { // deliver event - qt_replay_popup_mouse_event = false; QPointer<QWidget> receiver = activePopupWidget; QPointF widgetPos = mapped; if (qt_button_down) @@ -591,57 +584,6 @@ void QWidgetWindow::handleMouseEvent(QMouseEvent *event) } } - if (QApplication::activePopupWidget() != activePopupWidget - && qt_replay_popup_mouse_event - && QGuiApplicationPrivate::platformIntegration()->styleHint(QPlatformIntegration::ReplayMousePressOutsidePopup).toBool()) { - if (m_widget->windowType() != Qt::Popup) - qt_button_down = nullptr; - if (event->type() == QEvent::MouseButtonPress) { - // the popup disappeared, replay the mouse press event - QWidget *w = QApplication::widgetAt(event->globalPosition().toPoint()); - if (w && !QApplicationPrivate::isBlockedByModal(w)) { - // activate window of the widget under mouse pointer - if (!w->isActiveWindow()) { - w->activateWindow(); - w->window()->raise(); - } - - if (auto win = qt_widget_private(w)->windowHandle(QWidgetPrivate::WindowHandleMode::Closest)) { - const QRect globalGeometry = win->isTopLevel() - ? win->geometry() - : QRect(win->mapToGlobal(QPoint(0, 0)), win->size()); - if (globalGeometry.contains(event->globalPosition().toPoint())) { - // Use postEvent() to ensure the local QEventLoop terminates when called from QMenu::exec() - const QPoint localPos = win->mapFromGlobal(event->globalPosition().toPoint()); - QMouseEvent *e = new QMouseEvent(QEvent::MouseButtonPress, localPos, localPos, event->globalPosition().toPoint(), - event->button(), event->buttons(), event->modifiers(), event->source()); - QCoreApplicationPrivate::setEventSpontaneous(e, true); - e->setTimestamp(event->timestamp()); - QCoreApplication::postEvent(win, e); - } - } - } - } - qt_replay_popup_mouse_event = false; -#ifndef QT_NO_CONTEXTMENU - } else if (event->type() == contextMenuTrigger - && event->button() == Qt::RightButton - && (openPopupCount == oldOpenPopupCount)) { - QWidget *receiver = activePopupWidget; - if (qt_button_down) - receiver = qt_button_down; - else if (popupChild) - receiver = popupChild; - const QPoint localPos = receiver->mapFromGlobal(event->globalPosition().toPoint()); - QContextMenuEvent e(QContextMenuEvent::Mouse, localPos, event->globalPosition().toPoint(), event->modifiers()); - QApplication::forwardEvent(receiver, &e, event); - } -#else - Q_UNUSED(contextMenuTrigger); - Q_UNUSED(oldOpenPopupCount); - } -#endif - if (releaseAfter) { qt_button_down = nullptr; qt_popup_down_closed = false; @@ -671,6 +613,11 @@ void QWidgetWindow::handleMouseEvent(QMouseEvent *event) if (!receiver) return; + if (d_func()->isPopup() && receiver->window()->windowHandle() != this) { + receiver = widget; + mapped = event->position().toPoint(); + } + if ((event->type() != QEvent::MouseButtonPress) || !QMutableSinglePointEvent::from(event)->isDoubleClick()) { // The preceding statement excludes MouseButtonPress events which caused @@ -684,7 +631,8 @@ void QWidgetWindow::handleMouseEvent(QMouseEvent *event) event->setAccepted(translated.isAccepted()); } #ifndef QT_NO_CONTEXTMENU - if (event->type() == contextMenuTrigger && event->button() == Qt::RightButton + if (event->type() == QGuiApplicationPrivate::contextMenuEventType() + && event->button() == Qt::RightButton && m_widget->rect().contains(event->position().toPoint())) { QContextMenuEvent e(QContextMenuEvent::Mouse, mapped, event->globalPosition().toPoint(), event->modifiers()); QGuiApplication::forwardEvent(receiver, &e, event); @@ -862,6 +810,10 @@ void QWidgetWindow::handleResizeEvent(QResizeEvent *event) void QWidgetWindow::closeEvent(QCloseEvent *event) { Q_D(QWidgetWindow); + if (qt_popup_down == m_widget) { + qt_popup_down = nullptr; + qt_popup_down_closed = true; + } bool accepted = m_widget->d_func()->handleClose(d->inClose ? QWidgetPrivate::CloseWithEvent : QWidgetPrivate::CloseWithSpontaneousEvent); event->setAccepted(accepted); diff --git a/src/widgets/kernel/qwindowcontainer.cpp b/src/widgets/kernel/qwindowcontainer.cpp index 376a93c758..1aaf04af43 100644 --- a/src/widgets/kernel/qwindowcontainer.cpp +++ b/src/widgets/kernel/qwindowcontainer.cpp @@ -326,7 +326,7 @@ bool QWindowContainer::event(QEvent *e) target = QWindowPrivate::FocusTarget::First; else if (reason == Qt::BacktabFocusReason) target = QWindowPrivate::FocusTarget::Last; - qt_window_private(d->window)->setFocusToTarget(target); + qt_window_private(d->window)->setFocusToTarget(target, reason); d->window->requestActivate(); } } diff --git a/src/widgets/styles/images/standardbutton-apply-128.png b/src/widgets/styles/images/standardbutton-apply-128.png Binary files differindex 85f07a57ef..35eb54bfd0 100644 --- a/src/widgets/styles/images/standardbutton-apply-128.png +++ b/src/widgets/styles/images/standardbutton-apply-128.png diff --git a/src/widgets/styles/images/standardbutton-apply-16.png b/src/widgets/styles/images/standardbutton-apply-16.png Binary files differindex 8f11ce6504..1f97b52566 100644 --- a/src/widgets/styles/images/standardbutton-apply-16.png +++ b/src/widgets/styles/images/standardbutton-apply-16.png diff --git a/src/widgets/styles/images/standardbutton-apply-32.png b/src/widgets/styles/images/standardbutton-apply-32.png Binary files differindex e8f7853a1e..0837fae244 100644 --- a/src/widgets/styles/images/standardbutton-apply-32.png +++ b/src/widgets/styles/images/standardbutton-apply-32.png diff --git a/src/widgets/styles/images/standardbutton-no-128.png b/src/widgets/styles/images/standardbutton-no-128.png Binary files differindex 491c048ebd..4d9cdb1566 100644 --- a/src/widgets/styles/images/standardbutton-no-128.png +++ b/src/widgets/styles/images/standardbutton-no-128.png diff --git a/src/widgets/styles/images/standardbutton-no-16.png b/src/widgets/styles/images/standardbutton-no-16.png Binary files differindex 812d3f57dd..a04af9c37a 100644 --- a/src/widgets/styles/images/standardbutton-no-16.png +++ b/src/widgets/styles/images/standardbutton-no-16.png diff --git a/src/widgets/styles/images/standardbutton-no-32.png b/src/widgets/styles/images/standardbutton-no-32.png Binary files differindex 9548d59196..01d4401a2b 100644 --- a/src/widgets/styles/images/standardbutton-no-32.png +++ b/src/widgets/styles/images/standardbutton-no-32.png diff --git a/src/widgets/styles/images/standardbutton-ok-128.png b/src/widgets/styles/images/standardbutton-ok-128.png Binary files differindex 63cc5279ae..b204b6f272 100644 --- a/src/widgets/styles/images/standardbutton-ok-128.png +++ b/src/widgets/styles/images/standardbutton-ok-128.png diff --git a/src/widgets/styles/images/standardbutton-yes-128.png b/src/widgets/styles/images/standardbutton-yes-128.png Binary files differindex 79c8296016..6266e5bc6a 100644 --- a/src/widgets/styles/images/standardbutton-yes-128.png +++ b/src/widgets/styles/images/standardbutton-yes-128.png diff --git a/src/widgets/styles/images/standardbutton-yes-16.png b/src/widgets/styles/images/standardbutton-yes-16.png Binary files differindex cc16dbbec3..f66b5da6f2 100644 --- a/src/widgets/styles/images/standardbutton-yes-16.png +++ b/src/widgets/styles/images/standardbutton-yes-16.png diff --git a/src/widgets/styles/images/standardbutton-yes-32.png b/src/widgets/styles/images/standardbutton-yes-32.png Binary files differindex e3340c6453..6685d19e9f 100644 --- a/src/widgets/styles/images/standardbutton-yes-32.png +++ b/src/widgets/styles/images/standardbutton-yes-32.png diff --git a/src/widgets/styles/qcommonstyle.cpp b/src/widgets/styles/qcommonstyle.cpp index 6ff85e2c1b..aab1192d50 100644 --- a/src/widgets/styles/qcommonstyle.cpp +++ b/src/widgets/styles/qcommonstyle.cpp @@ -761,56 +761,62 @@ void QCommonStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, Q % HexString<uint>(pe), opt, QSize(size, size), dpr); if (!QPixmapCache::find(pixmapName, &pixmap)) { - const qreal border = dpr * (size / 5.); - const qreal sqsize = dpr * size; - pixmap = QPixmap(QSize(size, size)); - pixmap.fill(Qt::transparent); - QPainter imagePainter(&pixmap); - - 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 QPointF boundsCenter = poly.boundingRect().center(); - const qreal sx = sqsize / 2 - boundsCenter.x(); - const qreal sy = sqsize / 2 - boundsCenter.y(); - 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.setDevicePixelRatio(dpr); QPixmapCache::insert(pixmapName, pixmap); diff --git a/src/widgets/styles/qfusionstyle.cpp b/src/widgets/styles/qfusionstyle.cpp index 34a0105b80..6b8fd979a9 100644 --- a/src/widgets/styles/qfusionstyle.cpp +++ b/src/widgets/styles/qfusionstyle.cpp @@ -3660,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/qstylehelper.cpp b/src/widgets/styles/qstylehelper.cpp index 02827de847..b4616b8c24 100644 --- a/src/widgets/styles/qstylehelper.cpp +++ b/src/widgets/styles/qstylehelper.cpp @@ -202,7 +202,7 @@ QPolygonF calcLines(const QStyleOptionSlider *dial) qreal xc = width / 2 + 0.5; qreal yc = height / 2 + 0.5; const int ns = dial->tickInterval; - if (!ns) // Invalid values may be set by Qt Designer. + if (!ns) // Invalid values may be set by Qt Widgets Designer. return poly; int notches = (dial->maximum + ns - 1 - dial->minimum) / ns; if (notches <= 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..21612ad6d1 100644 --- a/src/widgets/util/qcompleter.cpp +++ b/src/widgets/util/qcompleter.cpp @@ -1296,10 +1296,22 @@ 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(); + break; + 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/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..0b6a4df41a 100644 --- a/src/widgets/widgets/qdialogbuttonbox.cpp +++ b/src/widgets/widgets/qdialogbuttonbox.cpp @@ -374,7 +374,7 @@ QPushButton *QDialogButtonBoxPrivate::createButton(QDialogButtonBox::StandardBut button->setIcon(style->standardIcon(QStyle::StandardPixmap(icon), nullptr, q)); if (style != QApplication::style()) // Propagate style button->setStyle(style); - standardButtonHash.insert(button, sbutton); + standardButtonMap.insert(button, sbutton); QPlatformDialogHelper::ButtonRole role = QPlatformDialogHelper::buttonRole(static_cast<QPlatformDialogHelper::StandardButton>(sbutton)); if (Q_UNLIKELY(role == QPlatformDialogHelper::InvalidRole)) qWarning("QDialogButtonBox::createButton: Invalid ButtonRole, button not added"); @@ -426,10 +426,10 @@ void QDialogButtonBoxPrivate::createStandardButtons(QDialogButtonBox::StandardBu void QDialogButtonBoxPrivate::retranslateStrings() { - for (auto &&[key, value] : std::as_const(standardButtonHash).asKeyValueRange()) { - const QString text = QGuiApplicationPrivate::platformTheme()->standardButtonText(value); + for (const auto &it : std::as_const(standardButtonMap)) { + const QString text = QGuiApplicationPrivate::platformTheme()->standardButtonText(it.second); if (!text.isEmpty()) - key->setText(text); + it.first->setText(text); } } @@ -644,15 +644,15 @@ void QDialogButtonBox::clear() Q_D(QDialogButtonBox); // Remove the created standard buttons, they should be in the other lists, which will // do the deletion - d->standardButtonHash.clear(); + d->standardButtonMap.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(); } } @@ -680,7 +680,11 @@ QList<QAbstractButton *> QDialogButtonBoxPrivate::visibleButtons() const QList<QAbstractButton *> QDialogButtonBoxPrivate::allButtons() const { - return visibleButtons() << hiddenButtons.keys(); + QList<QAbstractButton *> ret(visibleButtons()); + ret.reserve(ret.size() + hiddenButtons.size()); + for (const auto &it : hiddenButtons) + ret.push_back(it.first); + return ret; } /*! @@ -718,9 +722,9 @@ void QDialogButtonBox::removeButton(QAbstractButton *button) Removes \param button. \param reason determines the behavior following the removal: \list - \li \c ManualRemove disconnects all signals and removes the button from standardButtonHash. - \li \c HideEvent keeps connections alive, standard buttons remain in standardButtonHash. - \li \c Destroyed removes the button from standardButtonHash. Signals remain untouched, because + \li \c ManualRemove disconnects all signals and removes the button from standardButtonMap. + \li \c HideEvent keeps connections alive, standard buttons remain in standardButtonMap. + \li \c Destroyed removes the button from standardButtonMap. Signals remain untouched, because the button might already be only a QObject, the destructor of which handles disconnecting. \endlist */ @@ -744,7 +748,7 @@ void QDialogButtonBoxPrivate::removeButton(QAbstractButton *button, RemoveReason button->removeEventFilter(filter.get()); Q_FALLTHROUGH(); case RemoveReason::Destroyed: - standardButtonHash.remove(reinterpret_cast<QPushButton *>(button)); + standardButtonMap.remove(reinterpret_cast<QPushButton *>(button)); break; case RemoveReason::HideEvent: break; @@ -818,8 +822,9 @@ 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 oldButtons = d->standardButtonMap.keys(); + d->standardButtonMap.clear(); + qDeleteAll(oldButtons); d->createStandardButtons(buttons); } @@ -828,11 +833,8 @@ QDialogButtonBox::StandardButtons QDialogButtonBox::standardButtons() const { Q_D(const QDialogButtonBox); StandardButtons standardButtons = NoButton; - QHash<QPushButton *, StandardButton>::const_iterator it = d->standardButtonHash.constBegin(); - while (it != d->standardButtonHash.constEnd()) { - standardButtons |= it.value(); - ++it; - } + for (const auto value : d->standardButtonMap.values()) + standardButtons |= value; return standardButtons; } @@ -845,7 +847,12 @@ QDialogButtonBox::StandardButtons QDialogButtonBox::standardButtons() const QPushButton *QDialogButtonBox::button(StandardButton which) const { Q_D(const QDialogButtonBox); - return d->standardButtonHash.key(which); + + for (const auto &it : std::as_const(d->standardButtonMap)) { + if (it.second == which) + return it.first; + } + return nullptr; } /*! @@ -857,7 +864,7 @@ QPushButton *QDialogButtonBox::button(StandardButton which) const QDialogButtonBox::StandardButton QDialogButtonBox::standardButton(QAbstractButton *button) const { Q_D(const QDialogButtonBox); - return d->standardButtonHash.value(static_cast<QPushButton *>(button)); + return d->standardButtonMap.value(static_cast<QPushButton *>(button)); } void QDialogButtonBoxPrivate::handleButtonClicked() @@ -965,16 +972,13 @@ bool QDialogButtonBox::centerButtons() const */ void QDialogButtonBox::changeEvent(QEvent *event) { - typedef QHash<QPushButton *, QDialogButtonBox::StandardButton> StandardButtonHash; - Q_D(QDialogButtonBox); switch (event->type()) { case QEvent::StyleChange: // Propagate style - if (!d->standardButtonHash.empty()) { + if (!d->standardButtonMap.empty()) { QStyle *newStyle = style(); - const StandardButtonHash::iterator end = d->standardButtonHash.end(); - for (StandardButtonHash::iterator it = d->standardButtonHash.begin(); it != end; ++it) - it.key()->setStyle(newStyle); + for (auto key : d->standardButtonMap.keys()) + key->setStyle(newStyle); } #ifdef Q_OS_MAC Q_FALLTHROUGH(); diff --git a/src/widgets/widgets/qdialogbuttonbox_p.h b/src/widgets/widgets/qdialogbuttonbox_p.h index c3d7e03489..e439819c49 100644 --- a/src/widgets/widgets/qdialogbuttonbox_p.h +++ b/src/widgets/widgets/qdialogbuttonbox_p.h @@ -16,6 +16,7 @@ // #include <private/qwidget_p.h> +#include <private/qflatmap_p.h> #include <qdialogbuttonbox.h> QT_BEGIN_NAMESPACE @@ -42,8 +43,8 @@ public: QDialogButtonBoxPrivate(Qt::Orientation orient); QList<QAbstractButton *> buttonLists[QDialogButtonBox::NRoles]; - QHash<QPushButton *, QDialogButtonBox::StandardButton> standardButtonHash; - QHash<QAbstractButton *, QDialogButtonBox::ButtonRole> hiddenButtons; + QVarLengthFlatMap<QPushButton *, QDialogButtonBox::StandardButton, 8> standardButtonMap; + QVarLengthFlatMap<QAbstractButton *, QDialogButtonBox::ButtonRole, 8> hiddenButtons; Qt::Orientation orientation; QDialogButtonBox::ButtonLayout layoutPolicy; diff --git a/src/widgets/widgets/qdockwidget.cpp b/src/widgets/widgets/qdockwidget.cpp index 706306000c..f353525553 100644 --- a/src/widgets/widgets/qdockwidget.cpp +++ b/src/widgets/widgets/qdockwidget.cpp @@ -1448,22 +1448,60 @@ QDockWidget::DockWidgetFeatures QDockWidget::features() const void QDockWidget::setFloating(bool floating) { Q_D(QDockWidget); + d->setFloating(floating); +} +/*! + \internal implementation of setFloating + */ +void QDockWidgetPrivate::setFloating(bool floating) +{ + Q_Q(QDockWidget); // the initial click of a double-click may have started a drag... - if (d->state != nullptr) - d->endDrag(QDockWidgetPrivate::EndDragMode::Abort); + if (state != nullptr) + endDrag(QDockWidgetPrivate::EndDragMode::Abort); - QRect r = d->undockedGeometry; // Keep position when undocking for the first time. - if (floating && isVisible() && !r.isValid()) - r = QRect(mapToGlobal(QPoint(0, 0)), size()); + QRect r = undockedGeometry; + if (floating && q->isVisible() && !r.isValid()) + r = QRect(q->mapToGlobal(QPoint(0, 0)), q->size()); + + // Reparent, if setFloating() was called on a floating tab + // Reparenting has to happen before setWindowState. + // The reparented dock widget will inherit visibility from the floating tab. + // => Remember visibility and the necessity to update it. + enum class VisibilityRule { + NoUpdate, + Show, + Hide, + }; + + VisibilityRule updateRule = VisibilityRule::NoUpdate; + + if (floating && !q->isFloating()) { + if (auto *groupWindow = qobject_cast<QDockWidgetGroupWindow *>(q->parentWidget())) { + updateRule = groupWindow->isVisible() ? VisibilityRule::Show : VisibilityRule::Hide; + q->setParent(groupWindow->parentWidget()); + } + } - d->setWindowState(floating, false, floating ? r : QRect()); + setWindowState(floating, false, floating ? r : QRect()); if (floating && r.isNull()) { - if (x() < 0 || y() < 0) //may happen if we have been hidden - move(QPoint()); - setAttribute(Qt::WA_Moved, false); //we want it at the default position + if (q->x() < 0 || q->y() < 0) //may happen if we have been hidden + q->move(QPoint()); + q->setAttribute(Qt::WA_Moved, false); //we want it at the default position + } + + switch (updateRule) { + case VisibilityRule::NoUpdate: + break; + case VisibilityRule::Show: + q->show(); + break; + case VisibilityRule::Hide: + q->hide(); + break; } } diff --git a/src/widgets/widgets/qdockwidget_p.h b/src/widgets/widgets/qdockwidget_p.h index fa936599c6..6d3d307729 100644 --- a/src/widgets/widgets/qdockwidget_p.h +++ b/src/widgets/widgets/qdockwidget_p.h @@ -104,6 +104,7 @@ public: void unplug(const QRect &rect); void plug(const QRect &rect); void setResizerActive(bool active); + void setFloating(bool floating); bool isAnimating() const; bool isTabbed() const; diff --git a/src/widgets/widgets/qframe.cpp b/src/widgets/widgets/qframe.cpp index db8dc20be2..1973fd24ee 100644 --- a/src/widgets/widgets/qframe.cpp +++ b/src/widgets/widgets/qframe.cpp @@ -32,7 +32,13 @@ QFramePrivate::~QFramePrivate() inline void QFramePrivate::init() { + Q_Q(QFrame); setLayoutItemMargins(QStyle::SE_FrameLayoutItem); + + // The frameRect property is implemented in terms of the widget's + // contentsRect, which conflicts with the implicit inclusion of + // the safe area margins in the contentsRect. + q->setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false); } /*! 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..34c18c8f88 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,39 @@ 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; +} + +/*! + \internal + Move \a dockWidget to its ideal unplug position. + \list + \li If \a dockWidget has a title bar widget, place its center under the mouse cursor. + \li Otherwise place it in the middle of the title bar's long side, with a + QApplication::startDragDistance() offset on the short side. + \endlist + */ +static void moveToUnplugPosition(QPoint mouse, QDockWidget *dockWidget) +{ + Q_ASSERT(dockWidget); + + if (auto *tbWidget = dockWidget->titleBarWidget()) { + dockWidget->move(mouse - tbWidget->rect().center()); + return; + } + + const bool vertical = dockWidget->features().testFlag(QDockWidget::DockWidgetVerticalTitleBar); + const int deltaX = vertical ? QApplication::startDragDistance() : dockWidget->width() / 2; + const int deltaY = vertical ? dockWidget->height() / 2 : QApplication::startDragDistance(); + dockWidget->move(mouse - QPoint(deltaX, deltaY)); } void QMainWindowTabBar::mouseMoveEvent(QMouseEvent *e) @@ -1981,9 +2015,8 @@ void QMainWindowTabBar::mouseMoveEvent(QMouseEvent *e) if (draggingDock) { QDockWidgetPrivate *dockPriv = static_cast<QDockWidgetPrivate *>(QObjectPrivate::get(draggingDock)); if (dockPriv->state && dockPriv->state->dragging) { - QPoint pos = e->globalPosition().toPoint() - dockPriv->state->pressPos; - draggingDock->move(pos); // move will call QMainWindowLayout::hover + moveToUnplugPosition(e->globalPosition().toPoint(), draggingDock); } } QTabBar::mouseMoveEvent(e); |