diff options
49 files changed, 2488 insertions, 90 deletions
diff --git a/cmake/QtFlagHandlingHelpers.cmake b/cmake/QtFlagHandlingHelpers.cmake index b32acc4cfa3..8d616cebd2c 100644 --- a/cmake/QtFlagHandlingHelpers.cmake +++ b/cmake/QtFlagHandlingHelpers.cmake @@ -1495,3 +1495,87 @@ function(qt_internal_set_up_config_optimizations_like_in_qmake) "${target_link_types}") endif() endfunction() + +# Adds optimized flags to targets created in the calling directory scope. +# This is the only way to add release flags in a debug build, which would build successfully. +# The approach of just appending -O3, and thus overriding any previous -O0 works on GCC / Clang +# but not MSVC, because MSVC has other debug flags which are not compatible when -Ox optimizations +# are turned on. +function(qt_internal_add_optimized_flags_for_debug_config_in_current_scope) + qt_internal_remove_known_optimization_flags(CONFIGS DEBUG) + + # Remove incompatible Windows debug flags with release flags. + if(MSVC) + set(default_debug_flags_to_remove "/RTC1") + + qt_internal_get_enabled_languages_for_flag_manipulation(enabled_languages) + foreach(lang ${enabled_languages}) + set(flag_var_name "CMAKE_${lang}_FLAGS_DEBUG") + qt_internal_remove_flags_impl(${flag_var_name} "${default_debug_flags_to_remove}" "") + endforeach() + endif() + + qt_internal_get_optimize_full_flags(optimize_full_flags) + qt_internal_add_compiler_flags(FLAGS "${optimize_full_flags}" CONFIGS DEBUG) + + # Set the flags in the parent scope. This will apply the flags to all targets within + # that directory scope. + qt_internal_set_optimized_flags_for_debug_config_in_parent_scope() +endfunction() + +# Conditionally adds optimized flags to the calling directory scope, depending on whether +# the QT_FEATURE_optimized_tools is ON, and whether a target or project specific override was not +# set. +# Overrides can be set via: +# -DQT_FORCE_NO_OPTIMIZE_<target>=ON +# -DQT_FORCE_NO_OPTIMIZE_<project_name>=ON +# e.g -DQT_FORCE_NO_OPTIMIZE_moc=ON -DQT_FORCE_NO_OPTIMIZE_qtdeclarative=ON +function(qt_internal_add_target_optimized_flags_for_debug_config_in_current_scope target) + set(opt_args "") + set(single_args + OUT_VAR_DID_ADD + ) + set(multi_args "") + + cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}") + _qt_internal_validate_all_args_are_parsed(arg) + + string(TOLOWER "${PROJECT_NAME}" project_name_lower) + + if(QT_FEATURE_optimized_tools + AND NOT QT_FORCE_NO_OPTIMIZE_${target} + AND NOT QT_FORCE_NO_OPTIMIZE_${project_name_lower} + ) + qt_internal_add_optimized_flags_for_debug_config_in_current_scope() + qt_internal_set_optimized_flags_for_debug_config_in_parent_scope() + set(did_add TRUE) + else() + set(did_add FALSE) + endif() + + if(arg_OUT_VAR_DID_ADD) + set(${arg_OUT_VAR_DID_ADD} "${did_add}" PARENT_SCOPE) + endif() +endfunction() + +# Helper to propagate the optimized flags for the debug config to the parent scope. +macro(qt_internal_set_optimized_flags_for_debug_config_in_parent_scope) + qt_internal_get_enabled_languages_for_flag_manipulation(__qt_internal_debug_enabled_languages) + foreach(__qt_internal_debug_lang IN LISTS __qt_internal_debug_enabled_languages) + set(flag_var_name "CMAKE_${__qt_internal_debug_lang}_FLAGS_DEBUG") + set(${flag_var_name} "${${flag_var_name}}" PARENT_SCOPE) + endforeach() + unset(__qt_internal_debug_lang) + unset(__qt_internal_debug_enabled_languages) +endmacro() + +# Same as qt_internal_add_target_optimized_flags_for_debug_config_in_current_scope, but also +# propagates the optimized flags to the parent scope. +macro(qt_internal_add_target_optimized_flags_for_debug_config_in_parent_scope target) + qt_internal_add_target_optimized_flags_for_debug_config_in_current_scope("${target}" + OUT_VAR_DID_ADD __qt_internal_did_add_optimized_flag_in_current_scope) + if(__qt_internal_did_add_optimized_flag_in_current_scope) + qt_internal_set_optimized_flags_for_debug_config_in_parent_scope() + endif() + unset(__qt_internal_did_add_optimized_flag_in_current_scope) +endmacro() diff --git a/cmake/QtModuleHelpers.cmake b/cmake/QtModuleHelpers.cmake index b8c9914aba5..358cdadc59c 100644 --- a/cmake/QtModuleHelpers.cmake +++ b/cmake/QtModuleHelpers.cmake @@ -326,13 +326,7 @@ function(qt_internal_add_module target) endif() endif() - if((FEATURE_ltcg OR CMAKE_INTERPROCEDURAL_OPTIMIZATION) AND GCC AND is_static_lib) - # CMake <= 3.19 appends -fno-fat-lto-objects for all library types if - # CMAKE_INTERPROCEDURAL_OPTIMIZATION is enabled. Static libraries need - # the opposite compiler option. - # (https://gitlab.kitware.com/cmake/cmake/-/issues/21696) - target_compile_options(${target} PRIVATE -ffat-lto-objects) - endif() + qt_internal_workaround_static_lib_gcc_lto_issue("${target}") qt_internal_add_target_aliases("${target}") diff --git a/cmake/QtTargetHelpers.cmake b/cmake/QtTargetHelpers.cmake index da210820bb2..10d60807212 100644 --- a/cmake/QtTargetHelpers.cmake +++ b/cmake/QtTargetHelpers.cmake @@ -2023,3 +2023,61 @@ function(qt_internal_apply_dynamic_list_linker_flags target dynlist_template) target_link_options(${target} PRIVATE "LINKER:--dynamic-list=${dynlist_file_abspath}") endfunction() + +function(qt_internal_workaround_static_lib_gcc_lto_issue target) + get_target_property(target_type "${target}" TYPE) + get_target_property(target_lto_enabled "${target}" INTERPROCEDURAL_OPTIMIZATION) + get_target_property(target_lto_enabled_debug "${target}" INTERPROCEDURAL_OPTIMIZATION_DEBUG) + set(target_lto_enabled_debug_unset FALSE) + + if(target_lto_enabled_debug MATCHES "-NOTFOUND") + set(target_lto_enabled_debug_unset TRUE) + endif() + + set(general_lto_enabled FALSE) + + if(FEATURE_ltcg + OR QT_FEATURE_ltcg + OR CMAKE_INTERPROCEDURAL_OPTIMIZATION + OR target_lto_enabled) + set(general_lto_enabled TRUE) + endif() + + if((general_lto_enabled OR target_lto_enabled_debug) + AND GCC + AND target_type STREQUAL "STATIC_LIBRARY") + # CMake <= 3.19 appends -fno-fat-lto-objects for all library types if + # CMAKE_INTERPROCEDURAL_OPTIMIZATION is enabled. Static libraries need + # the opposite compiler option. + # (https://gitlab.kitware.com/cmake/cmake/-/issues/21696) + set(flag_name -ffat-lto-objects) + + if((general_lto_enabled AND target_lto_enabled_debug_unset) + OR (general_lto_enabled AND target_lto_enabled) + ) + set(flag_wrapped "${flag_name}") + elseif(general_lto_enabled AND NOT target_lto_enabled_debug) + set(flag_wrapped "$<$<NOT:$<CONFIG:Debug>>:${flag_name}>") + elseif(target_lto_enabled_debug) + set(flag_wrapped "$<$<CONFIG:Debug>:${flag_name}>") + endif() + + target_compile_options("${target}" PRIVATE "${flag_wrapped}") + endif() +endfunction() + +# Enable LTO for the debug configuration of optimized tool or library targets. +# Can be opted out via various options. +function(qt_internal_enable_optimized_tools_lto target) + string(TOLOWER "${PROJECT_NAME}" project_name_lower) + + if(QT_FEATURE_optimized_tools + AND NOT QT_FORCE_NO_OPTIMIZED_TOOLS_LTO + AND NOT QT_FORCE_NO_OPTIMIZE_${target} + AND NOT QT_FORCE_NO_OPTIMIZE_${project_name_lower} + AND NOT QT_FORCE_NO_LTO_${target} + AND NOT QT_FORCE_NO_LTO_${project_name_lower} + ) + set_target_properties("${target}" PROPERTIES INTERPROCEDURAL_OPTIMIZATION_DEBUG ON) + endif() +endfunction() diff --git a/cmake/QtToolHelpers.cmake b/cmake/QtToolHelpers.cmake index 5d531818ebd..16743dcd9eb 100644 --- a/cmake/QtToolHelpers.cmake +++ b/cmake/QtToolHelpers.cmake @@ -292,6 +292,9 @@ function(qt_internal_add_tool target_name) ) qt_internal_install_pdb_files(${target_name} "${install_dir}") + qt_internal_add_target_optimized_flags_for_debug_config_in_parent_scope("${target_name}") + qt_internal_enable_optimized_tools_lto("${target_name}") + if(QT_GENERATE_SBOM) _qt_internal_forward_function_args( FORWARD_APPEND diff --git a/configure.cmake b/configure.cmake index 0d4e5b04688..efec5435f85 100644 --- a/configure.cmake +++ b/configure.cmake @@ -588,6 +588,10 @@ qt_feature("optimize_full" AUTODETECT OFF ) qt_feature_config("optimize_full" QMAKE_PRIVATE_CONFIG) +qt_feature("optimized_tools" PRIVATE + LABEL "Build optimized tools for the Debug configuration" + AUTODETECT OFF +) qt_feature("msvc_obj_debug_info" LABEL "Embed debug info in object files (MSVC)" ENABLE QT_USE_CCACHE OR CMAKE_CXX_COMPILER_LAUNCHER diff --git a/qt_cmdline.cmake b/qt_cmdline.cmake index 97a225d670d..132adeb8695 100644 --- a/qt_cmdline.cmake +++ b/qt_cmdline.cmake @@ -104,7 +104,7 @@ qt_commandline_option(opensource TYPE void NAME commercial VALUE no) qt_commandline_option(optimize-debug TYPE boolean NAME optimize_debug) qt_commandline_option(optimize-size TYPE boolean NAME optimize_size) qt_commandline_option(optimized-qmake TYPE boolean NAME release_tools) -qt_commandline_option(optimized-tools TYPE boolean NAME release_tools) +qt_commandline_option(optimized-tools TYPE boolean NAME optimized_tools) qt_commandline_option(pch TYPE boolean CMAKE_VARIABLE BUILD_WITH_PCH) qt_commandline_option(pkg-config TYPE boolean) qt_commandline_option(platform TYPE string CMAKE_VARIABLE QT_QMAKE_TARGET_MKSPEC) diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt index 539ad753ca6..e50838730cb 100644 --- a/src/corelib/CMakeLists.txt +++ b/src/corelib/CMakeLists.txt @@ -1533,6 +1533,7 @@ qt_internal_extend_target(Core CONDITION WASM platform/wasm/qstdweb.cpp platform/wasm/qstdweb_p.h platform/wasm/qwasmsocket.cpp platform/wasm/qwasmsocket_p.h platform/wasm/qwasmsuspendresumecontrol.cpp platform/wasm/qwasmsuspendresumecontrol_p.h + platform/wasm/qwasmlocalfileengine.cpp platform/wasm/qwasmlocalfileengine_p.h kernel/qeventdispatcher_wasm.cpp kernel/qeventdispatcher_wasm_p.h ) diff --git a/src/corelib/configure.cmake b/src/corelib/configure.cmake index 7274b51cc0a..0f717ff2ae0 100644 --- a/src/corelib/configure.cmake +++ b/src/corelib/configure.cmake @@ -812,7 +812,7 @@ qt_feature("winsdkicu" PRIVATE ) qt_feature("windows-ioring" PRIVATE LABEL "Windows I/O Ring" - AUTODETECT WIN32 AND CMAKE_HOST_SYSTEM_VERSION VERSION_GREATER_EQUAL 10.0.22000 + AUTODETECT WIN32 CONDITION TEST_windows_ioring ) qt_feature("inotify" PUBLIC PRIVATE diff --git a/src/corelib/doc/images/modelindex-no-parent.svg b/src/corelib/doc/images/modelindex-no-parent.svg new file mode 100644 index 00000000000..4ce4b86b1f2 --- /dev/null +++ b/src/corelib/doc/images/modelindex-no-parent.svg @@ -0,0 +1,99 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="300" + height="160" + version="1.1" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + +<style> + svg .line-style { stroke: black; fill: none } + svg .fill-style { stroke: black; fill: #c0c0c0 } + svg .text-style { font: 12px arial; fill: black } + svg .italic-style { font: 12px arial; fill: black; font-style: italic } + svg .bold-style { font: 12px arial; fill: black; font-weight: bold } + + svg.dark .line-style { stroke: #f2f2f2; fill: none } + svg.dark .fill-style { stroke: #f2f2f2; fill: #606060 } + svg.dark .text-style { font: 12px arial; fill: #f2f2f2 } + svg.dark .italic-style { font: 12px arial; fill: #f2f2f2; font-style: italic } + svg.dark .bold-style { font: 12px arial; fill: #f2f2f2; font-weight: bold } + + [data-theme="dark"] svg .line-style { stroke: #f2f2f2; fill: none } + [data-theme="dark"] svg .fill-style { stroke: #f2f2f2; fill: #606060 } + [data-theme="dark"] svg .text-style { font: 12px arial; fill: #f2f2f2 } + [data-theme="dark"] svg .italic-style { font: 12px arial; fill: #f2f2f2; font-style: italic } + [data-theme="dark"] svg .bold-style { font: 12px arial; fill: #f2f2f2; font-weight: bold } + + [data-theme="light"] svg .line-style { stroke: black; fill: none } + [data-theme="light"] svg .fill-style { stroke: black; fill: #c0c0c0 } + [data-theme="light"] svg .text-style { font: 12px arial; fill: black } + [data-theme="light"] svg .italic-style { font: 12px arial; fill: black; font-style: italic } + [data-theme="light"] svg .bold-style { font: 12px arial; fill: black; font-weight: bold } +</style> + +<text x="25" y="49" font-family="arial" font-size="12px" + class="text-style">0</text> +<text x="25" y="79" font-family="arial" font-size="12px" + class="text-style">1</text> +<text x="25" y="109" font-family="arial" font-size="12px" + class="text-style">2</text> +<g transform="translate(30,139)"> +<text x="0" y="0" font-family="arial" font-size="12px" + class="text-style" transform="rotate(270)">...</text> +</g> + +<path d="m 40.5,30.5 h 30 v 30 h -30 z" stroke="black" fill="none" + class="line-style" /> +<path d="m 70.5,30.5 h 30 v 30 h -30 z" stroke="black" fill="none" + class="line-style" /> +<path d="m 100.5,30.5 h 30 v 30 h -30 z" stroke="black" fill="none" + class="line-style" /> +<path d="m 130.5,30.5 h 30 v 30" stroke="black" fill="none" + class="line-style" stroke-dasharray="3, 3" /> +<path d="m 40.5,60.5 h 30 v 30 h -30 z" stroke="black" fill="none" + class="line-style" /> +<path d="m 70.5,60.5 h 30 v 30 h -30 z" stroke="black" fill="none" + class="line-style" /> +<path d="m 100.5,60.5 h 30 v 30 h -30 z" stroke="black" fill="#c0c0c0" + class="fill-style" /> +<path d="m 130.5,60.5 h 30 v 30 h -30 z" stroke="black" fill="none" + class="line-style" stroke-dasharray="3, 3" /> +<path d="m 40.5,90.5 h 30 v 30 h -30 z" stroke="black" fill="none" + class="line-style" /> +<path d="m 70.5,90.5 h 30 v 30 h -30 z" stroke="black" fill="none" + class="line-style" /> +<path d="m 100.5,90.5 h 30 v 30 h -30 z" stroke="black" fill="none" + class="line-style" /> +<path d="m 70.5,120.5 h 30 v 30 h -30 z" stroke="black" fill="none" + class="line-style" stroke-dasharray="3, 3" /> +<path d="m 40.5,120.5 v 30 h 30" stroke="black" fill="none" + class="line-style" stroke-dasharray="3, 3" /> +<path d="m 100.5,150.5 h 30" stroke="black" + class="line-style" stroke-dasharray="3, 3" /> +<path d="m 130.5,120.5 h 30 v 30 h -30 z" stroke="black" fill="none" + class="line-style" stroke-dasharray="3, 3" /> +<path d="m 160.5,90.5 v 30" stroke="black" + class="line-style" stroke-dasharray="3, 3" /> + +<text x="50" y="20" font-family="arial" font-size="12px" + class="text-style">0</text> +<text x="80" y="20" font-family="arial" font-size="12px" + class="text-style">1</text> +<text x="110" y="20" font-family="arial" font-size="12px" + class="text-style">2</text> +<text x="140" y="20" font-family="arial" font-size="12px" + class="text-style">...</text> + +<text x="190" y="55" font-family="arial" font-size="12px" font-weight="bold" + class="bold-style">Model index</text> +<rect x="185.5" y="60.5" width="77" height="37" stroke="black" fill="none" + class="line-style" /> +<text x="190" y="75" font-family="arial" font-size="12px" + class="text-style">row = 1</text> +<text x="190" y="92" font-family="arial" font-size="12px" + class="text-style">column = 2</text> + +<path d="m 185.5,85.5 c -35,0 -35,-10 -70,-10" stroke="black" fill="none" + stroke-dasharray="2, 2" class="line-style" /> +</svg> diff --git a/src/corelib/doc/images/modelview-begin-append-columns.svg b/src/corelib/doc/images/modelview-begin-append-columns.svg new file mode 100644 index 00000000000..937a2c03206 --- /dev/null +++ b/src/corelib/doc/images/modelview-begin-append-columns.svg @@ -0,0 +1,80 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + version="1.1" + width="291" + height="166" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + +<style> + svg .box-style { stroke: black; fill: white } + svg .highlighted-style { stroke: black; fill: #c0ffc0 } + svg .fill-style { stroke: none; fill: black } + svg .line-style { stroke: black; fill: none } + svg .text-style { font: 20px arial; fill: black } + svg .high-text-style { font: 20px arial; fill: black } + + svg.dark .box-style { stroke: #f2f2f2; fill: black } + svg.dark .highlighted-style { stroke: #f2f2f2; fill: #c0ffc0 } + svg.dark .fill-style { stroke: none; fill: #f2f2f2 } + svg.dark .line-style { stroke: #f2f2f2; fill: none } + svg.dark .text-style { font: 20px arial; fill: #f2f2f2 } + svg.dark .high-text-style { font: 20px arial; fill: black } + + [data-theme="dark"] svg .box-style { stroke: #f2f2f2; fill: black } + [data-theme="dark"] svg .highlighted-style { stroke: #f2f2f2; fill: #c0ffc0 } + [data-theme="dark"] svg .fill-style { stroke: none; fill: #f2f2f2 } + [data-theme="dark"] svg .line-style { stroke: #f2f2f2; fill: none } + [data-theme="dark"] svg .text-style { font: 20px arial; fill: #f2f2f2 } + [data-theme="dark"] svg .high-text-style { font: 20px arial; fill: black } + + [data-theme="light"] svg .box-style { stroke: black; fill: white } + [data-theme="light"] svg .highlighted-style { stroke: black; fill: #c0ffc0 } + [data-theme="light"] svg .fill-style { stroke: none; fill: black } + [data-theme="light"] svg .line-style { stroke: black; fill: none } + [data-theme="light"] svg .text-style { font: 20px arial; fill: black } + [data-theme="light"] svg .high-text-style { font: 20px arial; fill: black } +</style> + +<path d="m 10.5,10.5 h 30 v 30 h -30 v -30" class="box-style" /> +<text x="20" y="32" font-family="arial" font-size="20px" class="text-style">0</text> +<path d="m 40.5,10.5 h 30 v 30 h -30 v -30" class="box-style" /> +<text x="50" y="32" font-family="arial" font-size="20px" class="text-style">1</text> +<path d="m 70.5,10.5 h 30 v 30 h -30 v -30" class="box-style" /> +<text x="80" y="32" font-family="arial" font-size="20px" class="text-style">2</text> +<path d="m 100.5,10.5 h 30 v 30 h -30 v -30" class="box-style" /> +<text x="110" y="32" font-family="arial" font-size="20px" class="text-style">3</text> +<path d="m 130.5,10.5 h 30 v 30 h -30 v -30" class="box-style" /> +<text x="140" y="32" font-family="arial" font-size="20px" class="text-style">4</text> +<path d="m 160.5,10.5 h 30 v 30 h -30 v -30" class="box-style" /> +<text x="170" y="32" font-family="arial" font-size="20px" class="text-style">5</text> + +<path d="m 190.5,68.5 h 30 v 30 h -30 z" class="highlighted-style" /> +<text x="200" y="90" font-family="arial" font-size="20px" class="high-text-style">6</text> +<path d="m 220.5,68.5 h 30 v 30 h -30 z" class="highlighted-style" /> +<text x="230" y="90" font-family="arial" font-size="20px" class="high-text-style">7</text> +<path d="m 250.5,68.5 h 30 v 30 h -30 z" class="highlighted-style" /> +<text x="260" y="90" font-family="arial" font-size="20px" class="high-text-style">8</text> + +<path d="m 10.5,125.5 h 30 v 30 h -30 v -30" class="box-style" /> +<text x="20" y="147" font-family="arial" font-size="20px" class="text-style">0</text> +<path d="m 40.5,125.5 h 30 v 30 h -30 v -30" class="box-style" /> +<text x="50" y="147" font-family="arial" font-size="20px" class="text-style">1</text> +<path d="m 70.5,125.5 h 30 v 30 h -30 v -30" class="box-style" /> +<text x="80" y="147" font-family="arial" font-size="20px" class="text-style">2</text> +<path d="m 100.5,125.5 h 30 v 30 h -30 v -30" class="box-style" /> +<text x="110" y="147" font-family="arial" font-size="20px" class="text-style">3</text> +<path d="m 130.5,125.5 h 30 v 30 h -30 v -30" class="box-style" /> +<text x="140" y="147" font-family="arial" font-size="20px" class="text-style">4</text> +<path d="m 160.5,125.5 h 30 v 30 h -30 v -30" class="box-style" /> +<text x="170" y="147" font-family="arial" font-size="20px" class="text-style">5</text> +<path d="m 190.5,125.5 h 30 v 30 h -30 v -30" class="highlighted-style" /> +<text x="200" y="147" font-family="arial" font-size="20px" class="high-text-style">6</text> +<path d="m 220.5,125.5 h 30 v 30 h -30 v -30" class="highlighted-style" /> +<text x="230" y="147" font-family="arial" font-size="20px" class="high-text-style">7</text> +<path d="m 250.5,125.5 h 30 v 30 h -30 v -30" class="highlighted-style" /> +<text x="260" y="147" font-family="arial" font-size="20px" class="high-text-style">8</text> + +<path d="m 190.5,65.5 v -20" class="line-style" /> +<path d="M 190.5 42.5 l 5,10 l -10,0 z" class="fill-style" /> +</svg> diff --git a/src/corelib/doc/images/modelview-begin-append-rows.svg b/src/corelib/doc/images/modelview-begin-append-rows.svg new file mode 100644 index 00000000000..7e66d0d8756 --- /dev/null +++ b/src/corelib/doc/images/modelview-begin-append-rows.svg @@ -0,0 +1,66 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + version="1.1" + width="165" + height="200" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + +<style> + svg .box-style { stroke: black; fill: white } + svg .highlighted-style { stroke: black; fill: #c0ffc0 } + svg .fill-style { stroke: none; fill: black } + svg .line-style { stroke: black; fill: none } + svg .text-style { font: 20px arial; fill: black } + svg .high-text-style { font: 20px arial; fill: black } + + svg.dark .box-style { stroke: #f2f2f2; fill: black } + svg.dark .highlighted-style { stroke: #f2f2f2; fill: #c0ffc0 } + svg.dark .fill-style { stroke: none; fill: #f2f2f2 } + svg.dark .line-style { stroke: #f2f2f2; fill: none } + svg.dark .text-style { font: 20px arial; fill: #f2f2f2 } + svg.dark .high-text-style { font: 20px arial; fill: black } + + [data-theme="dark"] svg .box-style { stroke: #f2f2f2; fill: black } + [data-theme="dark"] svg .highlighted-style { stroke: #f2f2f2; fill: #c0ffc0 } + [data-theme="dark"] svg .fill-style { stroke: none; fill: #f2f2f2 } + [data-theme="dark"] svg .line-style { stroke: #f2f2f2; fill: none } + [data-theme="dark"] svg .text-style { font: 20px arial; fill: #f2f2f2 } + [data-theme="dark"] svg .high-text-style { font: 20px arial; fill: black } + + [data-theme="light"] svg .box-style { stroke: black; fill: white } + [data-theme="light"] svg .highlighted-style { stroke: black; fill: #c0ffc0 } + [data-theme="light"] svg .text-style { font: 20px arial; fill: black } + [data-theme="light"] svg .fill-style { stroke: none; fill: black } + [data-theme="light"] svg .line-style { stroke: black; fill: none } +</style> + +<path d="m 10.5,10.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="20" y="32" font-family="arial" font-size="20px" class="text-style">0</text> +<path d="m 10.5,40.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="20" y="62" font-family="arial" font-size="20px" class="text-style">1</text> +<path d="m 10.5,70.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="20" y="92" font-family="arial" font-size="20px" class="text-style">2</text> +<path d="m 10.5,100.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="20" y="122" font-family="arial" font-size="20px" class="text-style">3</text> +<path d="m 68.5,130.5 h 30 v 30 h -30 z" class="highlighted-style" /> +<text x="77" y="152" font-family="arial" font-size="20px" class="high-text-style">4</text> +<path d="m 68.5,160.5 h 30 v 30 h -30 z" class="highlighted-style" /> +<text x="77" y="182" font-family="arial" font-size="20px" class="high-text-style">5</text> + +<path d="m 125.5,10.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="135" y="32" font-family="arial" font-size="20px" class="text-style">0</text> +<path d="m 125.5,40.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="135" y="62" font-family="arial" font-size="20px" class="text-style">1</text> +<path d="m 125.5,70.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="135" y="92" font-family="arial" font-size="20px" class="text-style">2</text> +<path d="m 125.5,100.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="135" y="122" font-family="arial" font-size="20px" class="text-style">3</text> +<path d="m 125.5,130.5 h 30 v 30 h -30 z" class="highlighted-style" /> +<text x="135" y="152" font-family="arial" font-size="20px" class="high-text-style">4</text> +<path d="m 125.5,160.5 h 30 v 30 h -30 z" class="highlighted-style" /> +<text x="135" y="182" font-family="arial" font-size="20px" class="high-text-style">5</text> + +<path d="m 65.5,130.5 h -20" class="line-style" /> +<path d="M 42.5,130.5 l 10,-5 l 0,10 z" class="fill-style" /> +</svg> diff --git a/src/corelib/doc/images/modelview-begin-insert-columns.svg b/src/corelib/doc/images/modelview-begin-insert-columns.svg new file mode 100644 index 00000000000..51664b80568 --- /dev/null +++ b/src/corelib/doc/images/modelview-begin-insert-columns.svg @@ -0,0 +1,91 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + version="1.1" + width="290" + height="165" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + +<style> + svg .box-style { stroke: black; fill: white } + svg .highlighted-style { stroke: black; fill: #c0ffc0 } + svg .fill-style { stroke: none; fill: black } + svg .line-style { stroke: black; fill: none } + svg .text-style { font: 20px arial; fill: black } + svg .high-text-style { font: 20px arial; fill: black } + svg .faded-text-style { font: 20px arial; fill: #808080 } + + svg.dark .box-style { stroke: #f2f2f2; fill: black } + svg.dark .highlighted-style { stroke: #f2f2f2; fill: #c0ffc0 } + svg.dark .fill-style { stroke: none; fill: #f2f2f2 } + svg.dark .line-style { stroke: #f2f2f2; fill: none } + svg.dark .text-style { font: 20px arial; fill: #f2f2f2 } + svg.dark .high-text-style { font: 20px arial; fill: black } + svg.dark .faded-text-style { font: 20px arial; fill: #808080 } + + [data-theme="dark"] svg .box-style { stroke: #f2f2f2; fill: black } + [data-theme="dark"] svg .highlighted-style { stroke: #f2f2f2; fill: #c0ffc0 } + [data-theme="dark"] svg .fill-style { stroke: none; fill: #f2f2f2 } + [data-theme="dark"] svg .line-style { stroke: #f2f2f2; fill: none } + [data-theme="dark"] svg .text-style { font: 20px arial; fill: #f2f2f2 } + [data-theme="dark"] svg .high-text-style { font: 20px arial; fill: black } + [data-theme="dark"] svg .faded-text-style { font: 20px arial; fill: #808080 } + + [data-theme="light"] svg .box-style { stroke: black; fill: white } + [data-theme="light"] svg .highlighted-style { stroke: black; fill: #c0ffc0 } + [data-theme="light"] svg .fill-style { stroke: none; fill: black } + [data-theme="light"] svg .line-style { stroke: black; fill: none } + [data-theme="light"] svg .text-style { font: 20px arial; fill: black } + [data-theme="light"] svg .high-text-style { font: 20px arial; fill: black } + [data-theme="light"] svg .faded-text-style { font: 20px arial; fill: #808080 } +</style> + +<path d="m 10.5,10.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="20" y="32" font-family="arial" font-size="20px" class="text-style">0</text> +<path d="m 40.5,10.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="50" y="32" font-family="arial" font-size="20px" class="text-style">1</text> +<path d="m 70.5,10.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="80" y="32" font-family="arial" font-size="20px" class="text-style">2</text> +<path d="m 100.5,10.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="110" y="32" font-family="arial" font-size="20px" class="text-style">3</text> +<path d="m 130.5,10.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="140" y="32" font-family="arial" font-size="20px" class="text-style">4</text> +<path d="m 160.5,10.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="170" y="32" font-family="arial" font-size="20px" class="text-style">5</text> +<path d="m 190.5,10.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="200" y="32" font-family="arial" font-size="20px" class="text-style">6</text> +<path d="m 220.5,10.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="230" y="32" font-family="arial" font-size="20px" class="text-style">7</text> +<path d="m 250.5,10.5 h 30 v 30 h -30 z" class="line-style" + stroke-dasharray="5, 5" /> + +<path d="m 130.5,68.5 h 30 v 30 h -30 z" class="highlighted-style" /> +<text x="140" y="90" font-family="arial" font-size="20px" class="high-text-style">4</text> +<path d="m 160.5,68.5 h 30 v 30 h -30 z" class="highlighted-style" /> +<text x="170" y="90" font-family="arial" font-size="20px" class="high-text-style">5</text> +<path d="m 190.5,68.5 h 30 v 30 h -30 z" class="highlighted-style" /> +<text x="200" y="90" font-family="arial" font-size="20px" class="high-text-style">6</text> + +<path d="m 10.5,125.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="20" y="147" font-family="arial" font-size="20px" class="text-style">0</text> +<path d="m 40.5,125.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="50" y="147" font-family="arial" font-size="20px" class="text-style">1</text> +<path d="m 70.5,125.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="80" y="147" font-family="arial" font-size="20px" class="text-style">2</text> +<path d="m 100.5,125.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="110" y="147" font-family="arial" font-size="20px" class="text-style">3</text> +<path d="m 130.5,125.5 h 30 v 30 h -30 z" class="highlighted-style" /> +<text x="140" y="147" font-family="arial" font-size="20px" class="high-text-style">4</text> +<path d="m 160.5,125.5 h 30 v 30 h -30 z" class="highlighted-style" /> +<text x="170" y="147" font-family="arial" font-size="20px" class="high-text-style">5</text> +<path d="m 190.5,125.5 h 30 v 30 h -30 z" class="highlighted-style" /> +<text x="200" y="147" font-family="arial" font-size="20px" class="high-text-style">6</text> +<path d="m 220.5,125.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="230" y="147" font-family="arial" font-size="20px" fill="#808080" class="faded-text-style">4</text> +<path d="m 250.5,125.5 h 30 v 30 h -30 z" class="line-style" + stroke-dasharray="5, 5" /> + + +<path d="m 130.5,65.5 v -20" class="line-style" /> +<path d="M 130.5,42.5 l 5,10 l -10,0 z" class="fill-style" /> +</svg> diff --git a/src/corelib/doc/images/modelview-begin-insert-rows.svg b/src/corelib/doc/images/modelview-begin-insert-rows.svg new file mode 100644 index 00000000000..fd6641218ed --- /dev/null +++ b/src/corelib/doc/images/modelview-begin-insert-rows.svg @@ -0,0 +1,82 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + version="1.1" + width="165" + height="230" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + +<style> + svg .box-style { stroke: black; fill: white } + svg .highlighted-style { stroke: black; fill: #c0ffc0 } + svg .fill-style { stroke: none; fill: black } + svg .line-style { stroke: black; fill: none } + svg .text-style { font: 20px arial; fill: black } + svg .high-text-style { font: 20px arial; fill: black } + svg .faded-text-style { font: 20px arial; fill: #808080 } + + svg.dark .box-style { stroke: #f2f2f2; fill: black } + svg.dark .highlighted-style { stroke: #f2f2f2; fill: #c0ffc0 } + svg.dark .fill-style { stroke: none; fill: #f2f2f2 } + svg.dark .line-style { stroke: #f2f2f2; fill: none } + svg.dark .text-style { font: 20px arial; fill: #f2f2f2 } + svg.dark .high-text-style { font: 20px arial; fill: black } + svg.dark .faded-text-style { font: 20px arial; fill: #808080 } + + [data-theme="dark"] svg .box-style { stroke: #f2f2f2; fill: black } + [data-theme="dark"] svg .highlighted-style { stroke: #f2f2f2; fill: #c0ffc0 } + [data-theme="dark"] svg .fill-style { stroke: none; fill: #f2f2f2 } + [data-theme="dark"] svg .line-style { stroke: #f2f2f2; fill: none } + [data-theme="dark"] svg .text-style { font: 20px arial; fill: #f2f2f2 } + [data-theme="dark"] svg .high-text-style { font: 20px arial; fill: black } + [data-theme="dark"] svg .faded-text-style { font: 20px arial; fill: #808080 } + + [data-theme="light"] svg .box-style { stroke: black; fill: white } + [data-theme="light"] svg .highlighted-style { stroke: black; fill: #c0ffc0 } + [data-theme="light"] svg .fill-style { stroke: none; fill: black } + [data-theme="light"] svg .line-style { stroke: black; fill: none } + [data-theme="light"] svg .text-style { font: 20px arial; fill: black } + [data-theme="light"] svg .high-text-style { font: 20px arial; fill: black } + [data-theme="light"] svg .faded-text-style { font: 20px arial; fill: #808080 } +</style> + +<path d="m 10.5,190.5 h 30 v 30 h -30 z" class="box-style" + stroke-dasharray="5, 5" /> +<path d="m 125.5,190.5 h 30 v 30 h -30 z" class="box-style" + stroke-dasharray="5, 5" /> +<path d="m 10.5,10.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="20" y="32" font-family="arial" font-size="20px" class="text-style">0</text> +<path d="m 10.5,40.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="20" y="62" font-family="arial" font-size="20px" class="text-style">1</text> +<path d="m 10.5,70.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="20" y="92" font-family="arial" font-size="20px" class="text-style">2</text> +<path d="m 10.5,100.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="20" y="122" font-family="arial" font-size="20px" class="text-style">3</text> +<path d="m 10.5,130.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="20" y="152" font-family="arial" font-size="20px" class="text-style">4</text> +<path d="m 10.5,160.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="20" y="182" font-family="arial" font-size="20px" class="text-style">5</text> + +<path d="m 68.5,70.5 h 30 v 30 h -30 z" class="highlighted-style" /> +<text x="77" y="92" font-family="arial" font-size="20px" class="high-text-style">2</text> +<path d="m 68.5,100.5 h 30 v 30 h -30 z" class="highlighted-style" /> +<text x="77" y="122" font-family="arial" font-size="20px" class="high-text-style">3</text> +<path d="m 68.5,130.5 h 30 v 30 h -30 z" class="highlighted-style" /> +<text x="77" y="152" font-family="arial" font-size="20px" class="high-text-style">4</text> + +<path d="m 125.5,10.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="135" y="32" font-family="arial" font-size="20px" class="text-style">0</text> +<path d="m 125.5,40.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="135" y="62" font-family="arial" font-size="20px" class="text-style">1</text> +<path d="m 125.5,70.5 h 30 v 30 h -30 z" class="highlighted-style" /> +<text x="135" y="92" font-family="arial" font-size="20px" class="high-text-style">2</text> +<path d="m 125.5,100.5 h 30 v 30 h -30 z" class="highlighted-style" /> +<text x="135" y="122" font-family="arial" font-size="20px" class="high-text-style">3</text> +<path d="m 125.5,130.5 h 30 v 30 h -30 z" class="highlighted-style" /> +<text x="135" y="152" font-family="arial" font-size="20px" class="high-text-style">4</text> +<path d="m 125.5,160.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="135" y="182" font-family="arial" font-size="20px" fill="#808080" class="faded-text-style">2</text> + +<path d="m 65.5,70.5 h -20" class="line-style" /> +<path d="M 42.5,70.5 l 10,-5 l 0,10 z" class="fill-style" /> +</svg> diff --git a/src/corelib/doc/images/modelview-begin-remove-columns.svg b/src/corelib/doc/images/modelview-begin-remove-columns.svg new file mode 100644 index 00000000000..a221bfda9fc --- /dev/null +++ b/src/corelib/doc/images/modelview-begin-remove-columns.svg @@ -0,0 +1,91 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + version="1.1" + width="290" + height="165" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + +<style> + svg .box-style { stroke: black; fill: white } + svg .highlighted-style { stroke: black; fill: #c0ffc0 } + svg .fill-style { stroke: none; fill: black } + svg .line-style { stroke: black; fill: none } + svg .text-style { font: 20px arial; fill: black } + svg .high-text-style { font: 20px arial; fill: black } + svg .faded-text-style { font: 20px arial; fill: #808080 } + + svg.dark .box-style { stroke: #f2f2f2; fill: black } + svg.dark .highlighted-style { stroke: #f2f2f2; fill: #c0ffc0 } + svg.dark .fill-style { stroke: none; fill: #f2f2f2 } + svg.dark .line-style { stroke: #f2f2f2; fill: none } + svg.dark .text-style { font: 20px arial; fill: #f2f2f2 } + svg.dark .high-text-style { font: 20px arial; fill: black } + svg.dark .faded-text-style { font: 20px arial; fill: #808080 } + + [data-theme="dark"] svg .box-style { stroke: #f2f2f2; fill: black } + [data-theme="dark"] svg .highlighted-style { stroke: #f2f2f2; fill: #c0ffc0 } + [data-theme="dark"] svg .fill-style { stroke: none; fill: #f2f2f2 } + [data-theme="dark"] svg .line-style { stroke: #f2f2f2; fill: none } + [data-theme="dark"] svg .text-style { font: 20px arial; fill: #f2f2f2 } + [data-theme="dark"] svg .high-text-style { font: 20px arial; fill: black } + [data-theme="dark"] svg .faded-text-style { font: 20px arial; fill: #808080 } + + [data-theme="light"] svg .box-style { stroke: black; fill: white } + [data-theme="light"] svg .highlighted-style { stroke: black; fill: #c0ffc0 } + [data-theme="light"] svg .fill-style { stroke: none; fill: black } + [data-theme="light"] svg .line-style { stroke: black; fill: none } + [data-theme="light"] svg .text-style { font: 20px arial; fill: black } + [data-theme="light"] svg .high-text-style { font: 20px arial; fill: black } + [data-theme="light"] svg .faded-text-style { font: 20px arial; fill: #808080 } +</style> + +<path d="m 10.5,10.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="20" y="32" font-family="arial" font-size="20px" class="text-style">0</text> +<path d="m 40.5,10.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="50" y="32" font-family="arial" font-size="20px" class="text-style">1</text> +<path d="m 70.5,10.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="80" y="32" font-family="arial" font-size="20px" class="text-style">2</text> +<path d="m 100.5,10.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="110" y="32" font-family="arial" font-size="20px" class="text-style">3</text> +<path d="m 130.5,10.5 h 30 v 30 h -30 z" class="highlighted-style" /> +<text x="140" y="32" font-family="arial" font-size="20px" class="high-text-style">4</text> +<path d="m 160.5,10.5 h 30 v 30 h -30 z" class="highlighted-style" /> +<text x="170" y="32" font-family="arial" font-size="20px" class="high-text-style">5</text> +<path d="m 190.5,10.5 h 30 v 30 h -30 z" class="highlighted-style" /> +<text x="200" y="32" font-family="arial" font-size="20px" class="high-text-style">6</text> +<path d="m 220.5,10.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="230" y="32" font-family="arial" font-size="20px" class="text-style">7</text> +<path d="m 250.5,10.5 h 30 v 30 h -30 z" class="box-style" + stroke-dasharray="5, 5" /> + +<path d="m 130.5,68.5 h 30 v 30 h -30 z" class="highlighted-style" /> +<text x="140" y="89" font-family="arial" font-size="20px" class="high-text-style">4</text> +<path d="m 160.5,68.5 h 30 v 30 h -30 z" class="highlighted-style" /> +<text x="170" y="89" font-family="arial" font-size="20px" class="high-text-style">5</text> +<path d="m 190.5,68.5 h 30 v 30 h -30 z" class="highlighted-style" /> +<text x="200" y="89" font-family="arial" font-size="20px" class="high-text-style">6</text> + +<path d="m 10.5,125.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="20" y="147" font-family="arial" font-size="20px" class="text-style">0</text> +<path d="m 40.5,125.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="50" y="147" font-family="arial" font-size="20px" class="text-style">1</text> +<path d="m 70.5,125.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="80" y="147" font-family="arial" font-size="20px" class="text-style">2</text> +<path d="m 100.5,125.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="110" y="147" font-family="arial" font-size="20px" class="text-style">3</text> +<path d="m 130.5,125.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="140" y="147" font-family="arial" font-size="20px" fill="#808080" class="faded-text-style">7</text> +<path d="m 160.5,125.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="170" y="147" font-family="arial" font-size="20px" fill="#808080" class="faded-text-style">8</text> +<path d="m 190.5,125.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="200" y="147" font-family="arial" font-size="20px" fill="#808080" class="faded-text-style">9</text> +<path d="m 220.5,125.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="224" y="147" font-family="arial" font-size="20px" fill="#808080" class="faded-text-style">10</text> +<path d="m 250.5,125.5 h 30 v 30 h -30 z" class="box-style" + stroke-dasharray="5, 5" /> + +<path d="M 130.5,63 V 43" class="line-style" /> +<path d="M 130.5,66 l -5,-10 l 10,0 z" class="fill-style" /> + +</svg> diff --git a/src/corelib/doc/images/modelview-begin-remove-rows.svg b/src/corelib/doc/images/modelview-begin-remove-rows.svg new file mode 100644 index 00000000000..0c67b1b6404 --- /dev/null +++ b/src/corelib/doc/images/modelview-begin-remove-rows.svg @@ -0,0 +1,76 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + version="1.1" + width="165" + height="230" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + +<style> + svg .box-style { stroke: black; fill: white } + svg .highlighted-style { stroke: black; fill: #c0ffc0 } + svg .fill-style { stroke: none; fill: black } + svg .line-style { stroke: black; fill: none } + svg .text-style { font: 20px arial; fill: black } + svg .high-text-style { font: 20px arial; fill: black } + svg .faded-text-style { font: 20px arial; fill: #808080 } + + svg.dark .box-style { stroke: #f2f2f2; fill: black } + svg.dark .highlighted-style { stroke: #f2f2f2; fill: #c0ffc0 } + svg.dark .fill-style { stroke: none; fill: #f2f2f2 } + svg.dark .line-style { stroke: #f2f2f2; fill: none } + svg.dark .text-style { font: 20px arial; fill: #f2f2f2 } + svg.dark .high-text-style { font: 20px arial; fill: black } + svg.dark .faded-text-style { font: 20px arial; fill: #808080 } + + [data-theme="dark"] svg .box-style { stroke: #f2f2f2; fill: black } + [data-theme="dark"] svg .highlighted-style { stroke: #f2f2f2; fill: #c0ffc0 } + [data-theme="dark"] svg .fill-style { stroke: none; fill: #f2f2f2 } + [data-theme="dark"] svg .line-style { stroke: #f2f2f2; fill: none } + [data-theme="dark"] svg .text-style { font: 20px arial; fill: #f2f2f2 } + [data-theme="dark"] svg .high-text-style { font: 20px arial; fill: black } + [data-theme="dark"] svg .faded-text-style { font: 20px arial; fill: #808080 } + + [data-theme="light"] svg .box-style { stroke: black; fill: white } + [data-theme="light"] svg .highlighted-style { stroke: black; fill: #c0ffc0 } + [data-theme="light"] svg .fill-style { stroke: none; fill: black } + [data-theme="light"] svg .line-style { stroke: black; fill: none } + [data-theme="light"] svg .text-style { font: 20px arial; fill: black } + [data-theme="light"] svg .high-text-style { font: 20px arial; fill: black } + [data-theme="light"] svg .faded-text-style { font: 20px arial; fill: #808080 } +</style> + +<path d="m 10.5,10.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="20" y="32" font-family="arial" font-size="20px" class="text-style">0</text> +<path d="m 10.5,40.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="20" y="62" font-family="arial" font-size="20px" class="text-style">1</text> +<path d="m 10.5,70.5 h 30 v 30 h -30 z" class="highlighted-style" /> +<text x="20" y="92" font-family="arial" font-size="20px" class="high-text-style">2</text> +<path d="m 10.5,100.5 h 30 v 30 h -30 z" class="highlighted-style" /> +<text x="20" y="122" font-family="arial" font-size="20px" class="high-text-style">3</text> +<path d="m 10.5,130.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="20" y="152" font-family="arial" font-size="20px" class="text-style">4</text> +<path d="m 10.5,160.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="20" y="182" font-family="arial" font-size="20px" class="text-style">5</text> +<path d="m 10.5,190.5 h 30 v 30 h -30 z" class="box-style" + stroke-dasharray="5, 5" /> + +<path d="m 125.5,10.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="135" y="32" font-family="arial" font-size="20px" class="text-style">0</text> +<path d="m 125.5,40.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="135" y="62" font-family="arial" font-size="20px" class="text-style">1</text> +<path d="m 125.5,70.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="135" y="92" font-family="arial" font-size="20px" fill="#808080" class="faded-text-style">4</text> +<path d="m 125.5,100.5 h 30 v 30 h -30 z" class="box-style" /> +<text x="135" y="122" font-family="arial" font-size="20px" fill="#808080" class="faded-text-style">5</text> +<path d="m 125.5,130.5 h 30 v 30 h -30 z" class="box-style" + stroke-dasharray="5, 5" /> + +<path d="m 68.5,70.5 h 30 v 30 h -30 z" class="highlighted-style" /> +<text x="77" y="92" font-family="arial" font-size="20px" class="high-text-style">2</text> +<path d="m 68.5,100.5 h 30 v 30 h -30 z" class="highlighted-style" /> +<text x="77" y="122" font-family="arial" font-size="20px" class="high-text-style">3</text> + +<path d="M 63,70.5 H 43" class="line-style" /> +<path d="M 66,70.5 l -10,-5 l 0,10 z" class="fill-style" /> +</svg> diff --git a/src/corelib/doc/images/modelview-move-rows-1.svg b/src/corelib/doc/images/modelview-move-rows-1.svg new file mode 100644 index 00000000000..1c90c42f730 --- /dev/null +++ b/src/corelib/doc/images/modelview-move-rows-1.svg @@ -0,0 +1,169 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + version="1.1" + width="200" + height="440" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + +<style> + svg .box-style { stroke: black; fill: white } + svg .highlighted-style { stroke: black; fill: #c0ffc0 } + svg .fill-style { stroke: none; fill: black } + svg .line-style { stroke: black; fill: none } + svg .text-style { font: 20px arial; fill: black } + svg .high-text-style { font: 20px arial; fill: black } + svg .faded-text-style { font: 20px arial; fill: #808080 } + + svg.dark .box-style { stroke: #f2f2f2; fill: black } + svg.dark .highlighted-style { stroke: #f2f2f2; fill: #c0ffc0 } + svg.dark .fill-style { stroke: none; fill: #f2f2f2 } + svg.dark .line-style { stroke: #f2f2f2; fill: none } + svg.dark .text-style { font: 20px arial; fill: #f2f2f2 } + svg.dark .high-text-style { font: 20px arial; fill: black } + svg.dark .faded-text-style { font: 20px arial; fill: #808080 } + + [data-theme="dark"] svg .box-style { stroke: #f2f2f2; fill: black } + [data-theme="dark"] svg .highlighted-style { stroke: #f2f2f2; fill: #c0ffc0 } + [data-theme="dark"] svg .fill-style { stroke: none; fill: #f2f2f2 } + [data-theme="dark"] svg .line-style { stroke: #f2f2f2; fill: none } + [data-theme="dark"] svg .text-style { font: 20px arial; fill: #f2f2f2 } + [data-theme="dark"] svg .high-text-style { font: 20px arial; fill: black } + [data-theme="dark"] svg .faded-text-style { font: 20px arial; fill: #808080 } + + [data-theme="light"] svg .box-style { stroke: black; fill: white } + [data-theme="light"] svg .highlighted-style { stroke: black; fill: #c0ffc0 } + [data-theme="light"] svg .fill-style { stroke: none; fill: black } + [data-theme="light"] svg .line-style { stroke: black; fill: none } + [data-theme="light"] svg .text-style { font: 20px arial; fill: black } + [data-theme="light"] svg .high-text-style { font: 20px arial; fill: black } + [data-theme="light"] svg .faded-text-style { font: 20px arial; fill: #808080 } +</style> + +<g transform="translate(10,10)"> +<path d="m 0.5,0.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="10" y="22" font-family="arial" font-size="20px" + class="text-style">0</text> +<path d="m 0.5,30.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="10" y="52" font-family="arial" font-size="20px" + class="text-style">1</text> +<path d="m 0.5,60.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="10" y="82" font-family="arial" font-size="20px" + class="text-style">2</text> +<path d="m 0.5,90.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="10" y="112" font-family="arial" font-size="20px" + class="text-style">3</text> +<path d="m 0.5,120.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="10" y="142" font-family="arial" font-size="20px" + class="text-style">4</text> +<path d="m 0.5,180.5 h 30 v 30 h -30 z" stroke="black" fill="white" + stroke-dasharray="5, 5" + class="box-style" /> +<path d="m 0.5,150.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="10" y="172" font-family="arial" font-size="20px" + class="text-style">5</text> + +<path d="m 0.5,240.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="10" y="262" font-family="arial" font-size="20px" + class="text-style">0</text> +<path d="m 0.5,270.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="10" y="292" font-family="arial" font-size="20px" + class="text-style">1</text> +<path d="m 0.5,300.5 h 30 v 30 h -30 z" stroke="black" fill="#c0ffc0" + class="highlighted-style" /> +<text x="10" y="322" font-family="arial" font-size="20px" + class="high-text-style">2</text> +<path d="m 0.5,330.5 h 30 v 30 h -30 z" stroke="black" fill="#c0ffc0" + class="highlighted-style" /> +<text x="10" y="352" font-family="arial" font-size="20px" + class="high-text-style">3</text> +<path d="m 0.5,360.5 h 30 v 30 h -30 z" stroke="black" fill="#c0ffc0" + class="highlighted-style" /> +<text x="10" y="382" font-family="arial" font-size="20px" + class="high-text-style">4</text> +<path d="m 0.5,390.5 h 30 V 420.5 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="10" y="412" font-family="arial" font-size="20px" + class="text-style">5</text> + +<path d="m 58.5,60.5 h 30 v 30 h -30 z" stroke="black" fill="#c0ffc0" + class="highlighted-style" /> +<text x="67" y="82" font-family="arial" font-size="20px" + class="high-text-style">2</text> +<path d="m 58.5,90.5 h 30 v 30 h -30 z" stroke="black" fill="#c0ffc0" + class="highlighted-style" /> +<text x="67" y="112" font-family="arial" font-size="20px" + class="high-text-style">3</text> +<path d="m 58.5,120.5 h 30 v 30 h -30 z" stroke="black" fill="#c0ffc0" + class="highlighted-style" /> +<text x="67" y="142" font-family="arial" font-size="20px" + class="high-text-style">4</text> + +<path d="m 145.5,0.5 h 30 v 30.956 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="155" y="22" font-family="arial" font-size="20px" + class="text-style">0</text> +<path d="m 145.5,31.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="155" y="52" font-family="arial" font-size="20px" + class="text-style">1</text> +<path d="m 145.5,60.5 h 30 v 30 h -30 z" stroke="black" fill="#c0ffc0" + class="highlighted-style" /> +<text x="155" y="82" font-family="arial" font-size="20px" + class="high-text-style">2</text> +<path d="m 145.5,90.5 h 30 v 30 h -30 z" stroke="black" fill="#c0ffc0" + class="highlighted-style" /> +<text x="155" y="112" font-family="arial" font-size="20px" + class="high-text-style">3</text> +<path d="m 145.5,180.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="155" y="202" font-family="arial" font-size="20px" fill="#808080" + class="faded-text-style">3</text> +<path d="m 145.5,210.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="155" y="232" font-family="arial" font-size="20px" fill="#808080" + class="faded-text-style">4</text> +<path d="m 145.5,120.5 h 30 v 30 h -30 z" stroke="black" fill="#c0ffc0" + class="highlighted-style" /> +<text x="155" y="142" font-family="arial" font-size="20px" + class="high-text-style">4</text> +<path d="m 145.5,150.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="155" y="172" font-family="arial" font-size="20px" fill="#808080" + class="faded-text-style">2</text> +<path d="m 145.5,240.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="155" y="262" font-family="arial" font-size="20px" fill="#808080" + class="faded-text-style">5</text> + +<path d="m 145.5,330.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="155" y="352" font-family="arial" font-size="20px" + class="text-style">0</text> +<path d="m 145.5,360.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="155" y="382" font-family="arial" font-size="20px" + class="text-style">1</text> +<path d="m 145.5,390.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="155" y="412" font-family="arial" font-size="20px" fill="#808080" + class="faded-text-style">5</text> + +<path d="M 58.5,60.5 h -25" stroke="black" + class="line-style" /> +<path d="M 31.5,60.5 l 10,-5 l 0,10 z" stroke="none" fill="black" + class="fill-style" /> +<path d="M 31.5,315.5 h 42 v -163" stroke="black" fill="none" + class="line-style" /> +<path d="M 73.5,151.5 l -5,10 l 10,0 z" stroke="none" fill="black" + class="fill-style" /> +</g> +</svg> diff --git a/src/corelib/doc/images/modelview-move-rows-2.svg b/src/corelib/doc/images/modelview-move-rows-2.svg new file mode 100644 index 00000000000..12ebfc43a16 --- /dev/null +++ b/src/corelib/doc/images/modelview-move-rows-2.svg @@ -0,0 +1,169 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + version="1.1" + width="200" + height="440" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + +<style> + svg .box-style { stroke: black; fill: white } + svg .highlighted-style { stroke: black; fill: #c0ffc0 } + svg .fill-style { stroke: none; fill: black } + svg .line-style { stroke: black; fill: none } + svg .text-style { font: 20px arial; fill: black } + svg .high-text-style { font: 20px arial; fill: black } + svg .faded-text-style { font: 20px arial; fill: #808080 } + + svg.dark .box-style { stroke: #f2f2f2; fill: black } + svg.dark .highlighted-style { stroke: #f2f2f2; fill: #c0ffc0 } + svg.dark .fill-style { stroke: none; fill: #f2f2f2 } + svg.dark .line-style { stroke: #f2f2f2; fill: none } + svg.dark .text-style { font: 20px arial; fill: #f2f2f2 } + svg.dark .high-text-style { font: 20px arial; fill: black } + svg.dark .faded-text-style { font: 20px arial; fill: #808080 } + + [data-theme="dark"] svg .box-style { stroke: #f2f2f2; fill: black } + [data-theme="dark"] svg .highlighted-style { stroke: #f2f2f2; fill: #c0ffc0 } + [data-theme="dark"] svg .fill-style { stroke: none; fill: #f2f2f2 } + [data-theme="dark"] svg .line-style { stroke: #f2f2f2; fill: none } + [data-theme="dark"] svg .text-style { font: 20px arial; fill: #f2f2f2 } + [data-theme="dark"] svg .high-text-style { font: 20px arial; fill: black } + [data-theme="dark"] svg .faded-text-style { font: 20px arial; fill: #808080 } + + [data-theme="light"] svg .box-style { stroke: black; fill: white } + [data-theme="light"] svg .highlighted-style { stroke: black; fill: #c0ffc0 } + [data-theme="light"] svg .fill-style { stroke: none; fill: black } + [data-theme="light"] svg .line-style { stroke: black; fill: none } + [data-theme="light"] svg .text-style { font: 20px arial; fill: black } + [data-theme="light"] svg .high-text-style { font: 20px arial; fill: black } + [data-theme="light"] svg .faded-text-style { font: 20px arial; fill: #808080 } +</style> + +<g transform="translate(10,10)"> +<path d="m 0.5,0.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="10" y="22" font-family="arial" font-size="20px" + class="text-style">0</text> +<path d="m 0.5,30.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="10" y="52" font-family="arial" font-size="20px" + class="text-style">1</text> +<path d="m 0.5,60.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="10" y="82" font-family="arial" font-size="20px" + class="text-style">2</text> +<path d="m 0.5,90.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="10" y="112" font-family="arial" font-size="20px" + class="text-style">3</text> +<path d="m 0.5,120.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="10" y="142" font-family="arial" font-size="20px" + class="text-style">4</text> +<path d="m 0.5,180.5 h 30 v 30 h -30 z" stroke="black" fill="white" + stroke-dasharray="5, 5" + class="box-style" /> +<path d="m 0.5,150.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="10" y="172" font-family="arial" font-size="20px" + class="text-style">5</text> + +<path d="m 0.5,240.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="10" y="262" font-family="arial" font-size="20px" + class="text-style">0</text> +<path d="m 0.5,270.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="10" y="292" font-family="arial" font-size="20px" + class="text-style">1</text> +<path d="m 0.5,300.5 h 30 v 30 h -30 z" stroke="black" fill="#c0ffc0" + class="highlighted-style" /> +<text x="10" y="322" font-family="arial" font-size="20px" + class="high-text-style">2</text> +<path d="m 0.5,330.5 h 30 v 30 h -30 z" stroke="black" fill="#c0ffc0" + class="highlighted-style" /> +<text x="10" y="352" font-family="arial" font-size="20px" + class="high-text-style">3</text> +<path d="m 0.5,360.5 h 30 v 30 h -30 z" stroke="black" fill="#c0ffc0" + class="highlighted-style" /> +<text x="10" y="382" font-family="arial" font-size="20px" + class="high-text-style">4</text> +<path d="m 0.5,390.5 h 30 V 420.5 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="10" y="412" font-family="arial" font-size="20px" + class="text-style">5</text> + +<path d="m 58.5,180.5 h 30 v 30 h -30 z" stroke="black" fill="#c0ffc0" + class="highlighted-style" /> +<text x="67" y="202" font-family="arial" font-size="20px" + class="high-text-style">2</text> +<path d="m 58.5,210.5 h 30 v 30 h -30 z" stroke="black" fill="#c0ffc0" + class="highlighted-style" /> +<text x="67" y="232" font-family="arial" font-size="20px" + class="high-text-style">3</text> +<path d="m 58.5,240.5 h 30 v 30 h -30 z" stroke="black" fill="#c0ffc0" + class="highlighted-style" /> +<text x="67" y="262" font-family="arial" font-size="20px" + class="high-text-style">4</text> + +<path d="m 145.5,0.5 h 30 v 30.956 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="155" y="22" font-family="arial" font-size="20px" + class="text-style">0</text> +<path d="m 145.5,31.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="155" y="52" font-family="arial" font-size="20px" + class="text-style">1</text> +<path d="m 145.5,60.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="155" y="82" font-family="arial" font-size="20px" + class="text-style">2</text> +<path d="m 145.5,90.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="155" y="112" font-family="arial" font-size="20px" + class="text-style">3</text> +<path d="m 145.5,120.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="155" y="142" font-family="arial" font-size="20px" + class="text-style">4</text> +<path d="m 145.5,150.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="155" y="172" font-family="arial" font-size="20px" + class="text-style">5</text> +<path d="m 145.5,180.5 h 30 v 30 h -30 z" stroke="black" fill="#c0ffc0" + class="highlighted-style" /> +<text x="155" y="202" font-family="arial" font-size="20px" + class="high-text-style">2</text> +<path d="m 145.5,210.5 h 30 v 30 h -30 z" stroke="black" fill="#c0ffc0" + class="highlighted-style" /> +<text x="155" y="232" font-family="arial" font-size="20px" + class="high-text-style">3</text> +<path d="m 145.5,240.5 h 30 v 30 h -30 z" stroke="black" fill="#c0ffc0" + class="highlighted-style" /> +<text x="155" y="262" font-family="arial" font-size="20px" + class="high-text-style">4</text> + +<path d="m 145.5,330.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="155" y="352" font-family="arial" font-size="20px" + class="text-style">0</text> +<path d="m 145.5,360.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="155" y="382" font-family="arial" font-size="20px" + class="text-style">1</text> +<path d="m 145.5,390.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="155" y="412" font-family="arial" font-size="20px" fill="#808080" + class="faded-text-style">5</text> + +<path d="M 58.5,180.5 h -25" stroke="black" + class="line-style" /> +<path d="M 31.5,180.5 l 10,-5 l 0,10 z" stroke="none" fill="black" + class="fill-style" /> +<path d="M 31.5,315.5 h 42 v -43" stroke="black" fill="none" + class="line-style" /> +<path d="M 73.5,271.5 l -5,10 l 10,0 z" stroke="none" fill="black" + class="fill-style" /> +</g> +</svg> diff --git a/src/corelib/doc/images/modelview-move-rows-3.svg b/src/corelib/doc/images/modelview-move-rows-3.svg new file mode 100644 index 00000000000..21d9803eb91 --- /dev/null +++ b/src/corelib/doc/images/modelview-move-rows-3.svg @@ -0,0 +1,108 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + version="1.1" + width="200" + height="200" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + +<style> + svg .box-style { stroke: black; fill: white } + svg .highlighted-style { stroke: black; fill: #c0ffc0 } + svg .fill-style { stroke: none; fill: black } + svg .line-style { stroke: black; fill: none } + svg .text-style { font: 20px arial; fill: black } + svg .high-text-style { font: 20px arial; fill: black } + svg .faded-text-style { font: 20px arial; fill: #808080 } + + svg.dark .box-style { stroke: #f2f2f2; fill: black } + svg.dark .highlighted-style { stroke: #f2f2f2; fill: #c0ffc0 } + svg.dark .fill-style { stroke: none; fill: #f2f2f2 } + svg.dark .line-style { stroke: #f2f2f2; fill: none } + svg.dark .text-style { font: 20px arial; fill: #f2f2f2 } + svg.dark .high-text-style { font: 20px arial; fill: black } + svg.dark .faded-text-style { font: 20px arial; fill: #808080 } + + [data-theme="dark"] svg .box-style { stroke: #f2f2f2; fill: black } + [data-theme="dark"] svg .highlighted-style { stroke: #f2f2f2; fill: #c0ffc0 } + [data-theme="dark"] svg .fill-style { stroke: none; fill: #f2f2f2 } + [data-theme="dark"] svg .line-style { stroke: #f2f2f2; fill: none } + [data-theme="dark"] svg .text-style { font: 20px arial; fill: #f2f2f2 } + [data-theme="dark"] svg .high-text-style { font: 20px arial; fill: black } + [data-theme="dark"] svg .faded-text-style { font: 20px arial; fill: #808080 } + + [data-theme="light"] svg .box-style { stroke: black; fill: white } + [data-theme="light"] svg .highlighted-style { stroke: black; fill: #c0ffc0 } + [data-theme="light"] svg .fill-style { stroke: none; fill: black } + [data-theme="light"] svg .line-style { stroke: black; fill: none } + [data-theme="light"] svg .text-style { font: 20px arial; fill: black } + [data-theme="light"] svg .high-text-style { font: 20px arial; fill: black } + [data-theme="light"] svg .faded-text-style { font: 20px arial; fill: #808080 } +</style> + +<g transform="translate(10,10)"> +<path d="m 0.5,0.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="10" y="22" font-family="arial" font-size="20px" + class="text-style">0</text> +<path d="m 0.5,30.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="10" y="52" font-family="arial" font-size="20px" + class="text-style">1</text> +<path d="m 0.5,60.5 h 30 v 30 h -30 z" stroke="black" fill="#c0ffc0" + class="highlighted-style" /> +<text x="10" y="82" font-family="arial" font-size="20px" + class="high-text-style">2</text> +<path d="m 0.5,90.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="10" y="112" font-family="arial" font-size="20px" + class="text-style">3</text> +<path d="m 0.5,120.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="10" y="142" font-family="arial" font-size="20px" + class="text-style">4</text> +<path d="m 0.5,150.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="10" y="172" font-family="arial" font-size="20px" + class="text-style">5</text> + +<path d="m 58.5,0.5 h 30 v 30 h -30 z" stroke="black" fill="#c0ffc0" + class="highlighted-style" /> +<text x="67" y="22" font-family="arial" font-size="20px" + class="high-text-style">2</text> + +<path d="m 145.5,0.5 h 30 v 30.956 h -30 z" stroke="black" fill="#c0ffc0" + class="highlighted-style" /> +<text x="155" y="22" font-family="arial" font-size="20px" + class="high-text-style">2</text> +<path d="m 145.5,31.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="155" y="52" font-family="arial" font-size="20px" fill="#808080" + class="faded-text-style">0</text> +<path d="m 145.5,60.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="155" y="82" font-family="arial" font-size="20px" fill="#808080" + class="faded-text-style">1</text> +<path d="m 145.5,90.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="155" y="112" font-family="arial" font-size="20px" + class="text-style">3</text> +<path d="m 145.5,120.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="155" y="142" font-family="arial" font-size="20px" + class="text-style">4</text> +<path d="m 145.5,150.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="155" y="172" font-family="arial" font-size="20px" + class="text-style">5</text> + +<path d="M 58.5,0.5 h -25" stroke="black" fill="none" + class="line-style" /> +<path d="M 31.5,0.5 l 10,-5 l 0,10 z" stroke="none" fill="black" + class="fill-style" /> +<path d="M 31.5,75.5 h 42 v -43" stroke="black" fill="none" + class="line-style" /> +<path d="M 73.5,31.5 l -5,10 l 10,0 z" stroke="black" fill="black" + class="fill-style" /> +</g> +</svg> diff --git a/src/corelib/doc/images/modelview-move-rows-4.svg b/src/corelib/doc/images/modelview-move-rows-4.svg new file mode 100644 index 00000000000..708eb71f440 --- /dev/null +++ b/src/corelib/doc/images/modelview-move-rows-4.svg @@ -0,0 +1,108 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + version="1.1" + width="200" + height="200" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + +<style> + svg .box-style { stroke: black; fill: white } + svg .highlighted-style { stroke: black; fill: #c0ffc0 } + svg .fill-style { stroke: none; fill: black } + svg .line-style { stroke: black; fill: none } + svg .text-style { font: 20px arial; fill: black } + svg .high-text-style { font: 20px arial; fill: black } + svg .faded-text-style { font: 20px arial; fill: #808080 } + + svg.dark .box-style { stroke: #f2f2f2; fill: black } + svg.dark .highlighted-style { stroke: #f2f2f2; fill: #c0ffc0 } + svg.dark .fill-style { stroke: none; fill: #f2f2f2 } + svg.dark .line-style { stroke: #f2f2f2; fill: none } + svg.dark .text-style { font: 20px arial; fill: #f2f2f2 } + svg.dark .high-text-style { font: 20px arial; fill: black } + svg.dark .faded-text-style { font: 20px arial; fill: #808080 } + + [data-theme="dark"] svg .box-style { stroke: #f2f2f2; fill: black } + [data-theme="dark"] svg .highlighted-style { stroke: #f2f2f2; fill: #c0ffc0 } + [data-theme="dark"] svg .fill-style { stroke: none; fill: #f2f2f2 } + [data-theme="dark"] svg .line-style { stroke: #f2f2f2; fill: none } + [data-theme="dark"] svg .text-style { font: 20px arial; fill: #f2f2f2 } + [data-theme="dark"] svg .high-text-style { font: 20px arial; fill: black } + [data-theme="dark"] svg .faded-text-style { font: 20px arial; fill: #808080 } + + [data-theme="light"] svg .box-style { stroke: black; fill: white } + [data-theme="light"] svg .highlighted-style { stroke: black; fill: #c0ffc0 } + [data-theme="light"] svg .fill-style { stroke: none; fill: black } + [data-theme="light"] svg .line-style { stroke: black; fill: none } + [data-theme="light"] svg .text-style { font: 20px arial; fill: black } + [data-theme="light"] svg .high-text-style { font: 20px arial; fill: black } + [data-theme="light"] svg .faded-text-style { font: 20px arial; fill: #808080 } +</style> + +<g transform="translate(10,10)"> +<path d="m 0.5,0.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="10" y="22" font-family="arial" font-size="20px" + class="text-style">0</text> +<path d="m 0.5,30.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="10" y="52" font-family="arial" font-size="20px" + class="text-style">1</text> +<path d="m 0.5,60.5 h 30 v 30 h -30 z" stroke="black" fill="#c0ffc0" + class="highlighted-style" /> +<text x="10" y="82" font-family="arial" font-size="20px" + class="high-text-style">2</text> +<path d="m 0.5,90.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="10" y="112" font-family="arial" font-size="20px" + class="text-style">3</text> +<path d="m 0.5,120.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="10" y="142" font-family="arial" font-size="20px" + class="text-style">4</text> +<path d="m 0.5,150.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="10" y="172" font-family="arial" font-size="20px" + class="text-style">5</text> + +<path d="m 58.5,120.5 h 30 v 30 h -30 z" stroke="black" fill="#c0ffc0" + class="highlighted-style" /> +<text x="67" y="142" font-family="arial" font-size="20px" + class="high-text-style">2</text> + +<path d="m 145.5,0.5 h 30 v 30.956 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="155" y="22" font-family="arial" font-size="20px" + class="text-style">0</text> +<path d="m 145.5,31.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="155" y="52" font-family="arial" font-size="20px" + class="text-style">1</text> +<path d="m 145.5,60.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="155" y="82" font-family="arial" font-size="20px" fill="#808080" + class="faded-text-style">3</text> +<path d="m 145.5,90.5 h 30 v 30 h -30 z" stroke="black" fill="#c0ffc0" + class="highlighted-style" /> +<text x="155" y="112" font-family="arial" font-size="20px" + class="high-text-style">2</text> +<path d="m 145.5,120.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="155" y="142" font-family="arial" font-size="20px" + class="text-style">4</text> +<path d="m 145.5,150.5 h 30 v 30 h -30 z" stroke="black" fill="white" + class="box-style" /> +<text x="155" y="172" font-family="arial" font-size="20px" + class="text-style">5</text> + +<path d="M 58.5,120.5 h -25" stroke="black" fill="none" + class="line-style" /> +<path d="M 31.5,120.5 l 10,-5 l 0,10 z" stroke="black" fill="black" + class="fill-style" /> +<path d="M 31.5,75.5 h 42 v 43" stroke="black" fill="none" + class="line-style" /> +<path d="M 73.5,119.5 l -5,-10 l 10,0 z" stroke="black" fill="black" + class="fill-style" /> +</g> +</svg> diff --git a/src/corelib/itemmodels/qabstractitemmodel.cpp b/src/corelib/itemmodels/qabstractitemmodel.cpp index c960e11f6bb..8b0a05ee6ed 100644 --- a/src/corelib/itemmodels/qabstractitemmodel.cpp +++ b/src/corelib/itemmodels/qabstractitemmodel.cpp @@ -1390,7 +1390,7 @@ void QAbstractItemModel::resetInternalData() simple table of rows and columns. Each item has a unique index specified by a QModelIndex. - \image modelindex-no-parent.png {Screenshot showing a 3x3 grid with numbered + \image modelindex-no-parent.svg {Diagram showing a 3x3 grid with numbered rows and columns that shows the cell at row 1, column 2 highlighted.} Every item of data that can be accessed via a model has an associated model @@ -2867,7 +2867,7 @@ bool QAbstractItemModel::decodeData(int row, int column, const QModelIndex &pare \table 80% \row - \li \inlineimage modelview-begin-insert-rows.png Inserting rows + \li \inlineimage modelview-begin-insert-rows.svg Inserting rows \li Specify the first and last row numbers for the span of rows you want to insert into an item in a model. @@ -2878,7 +2878,7 @@ bool QAbstractItemModel::decodeData(int row, int column, const QModelIndex &pare This inserts the three new rows as rows 2, 3, and 4. \row - \li \inlineimage modelview-begin-append-rows.png Appending rows + \li \inlineimage modelview-begin-append-rows.svg Appending rows \li To append rows, insert them after the last row. For example, as shown in the diagram, we append two rows to a @@ -2934,7 +2934,7 @@ void QAbstractItemModel::endInsertRows() \table 80% \row - \li \inlineimage modelview-begin-remove-rows.png Removing rows + \li \inlineimage modelview-begin-remove-rows.svg Removing rows \li Specify the first and last row numbers for the span of rows you want to remove from an item in a model. @@ -3055,7 +3055,7 @@ void QAbstractItemModelPrivate::executePendingOperations() const { } \table 80% \row - \li \inlineimage modelview-move-rows-1.png Moving rows to another parent + \li \inlineimage modelview-move-rows-1.svg Moving rows to another parent \li Specify the first and last row numbers for the span of rows in the source parent you want to move in the model. Also specify the row in the destination parent to move the span to. @@ -3069,7 +3069,7 @@ void QAbstractItemModelPrivate::executePendingOperations() const { } This moves the three rows rows 2, 3, and 4 in the source to become 2, 3 and 4 in the destination. Other affected siblings are displaced accordingly. \row - \li \inlineimage modelview-move-rows-2.png Moving rows to append to another parent + \li \inlineimage modelview-move-rows-2.svg Moving rows to append to another parent \li To append rows to another parent, move them to after the last row. For example, as shown in the diagram, we move three rows to a @@ -3079,7 +3079,7 @@ void QAbstractItemModelPrivate::executePendingOperations() const { } This moves the target rows to the end of the target parent as 6, 7 and 8. \row - \li \inlineimage modelview-move-rows-3.png Moving rows in the same parent up + \li \inlineimage modelview-move-rows-3.svg Moving rows in the same parent up \li To move rows within the same parent, specify the row to move them to. For example, as shown in the diagram, we move one item from row 2 to row 0, @@ -3094,7 +3094,7 @@ void QAbstractItemModelPrivate::executePendingOperations() const { } it is already) \row - \li \inlineimage modelview-move-rows-4.png Moving rows in the same parent down + \li \inlineimage modelview-move-rows-4.svg Moving rows in the same parent down \li To move rows within the same parent, specify the row to move them to. For example, as shown in the diagram, we move one item from row 2 to row 4, @@ -3176,7 +3176,7 @@ void QAbstractItemModel::endMoveRows() \table 80% \row - \li \inlineimage modelview-begin-insert-columns.png Inserting columns + \li \inlineimage modelview-begin-insert-columns.svg Inserting columns \li Specify the first and last column numbers for the span of columns you want to insert into an item in a model. @@ -3187,7 +3187,7 @@ void QAbstractItemModel::endMoveRows() This inserts the three new columns as columns 4, 5, and 6. \row - \li \inlineimage modelview-begin-append-columns.png Appending columns + \li \inlineimage modelview-begin-append-columns.svg Appending columns \li To append columns, insert them after the last column. For example, as shown in the diagram, we append three columns to a @@ -3245,7 +3245,7 @@ void QAbstractItemModel::endInsertColumns() \table 80% \row - \li \inlineimage modelview-begin-remove-columns.png Removing columns + \li \inlineimage modelview-begin-remove-columns.svg Removing columns \li Specify the first and last column numbers for the span of columns you want to remove from an item in a model. diff --git a/src/corelib/kernel/qeventdispatcher_wasm.cpp b/src/corelib/kernel/qeventdispatcher_wasm.cpp index f7edf3285f8..25a27a0c22b 100644 --- a/src/corelib/kernel/qeventdispatcher_wasm.cpp +++ b/src/corelib/kernel/qeventdispatcher_wasm.cpp @@ -150,6 +150,18 @@ bool QEventDispatcherWasm::isValidEventDispatcherPointer(QEventDispatcherWasm *e bool QEventDispatcherWasm::processEvents(QEventLoop::ProcessEventsFlags flags) { + // Accept the current event if event handling recurses / event loop is reentered, + // to prevent the browser from propagating it to other event handlers. + if (useAsyncify() && isMainThreadEventDispatcher()) { + auto control = QWasmSuspendResumeControl::get(); + auto currentEvent = control->currentEvent(); + if (!currentEvent.isUndefined() && currentEvent.instanceof(emscripten::val::global("Event"))) { + currentEvent.call<void>("preventDefault"); + currentEvent.call<void>("stopPropagation"); + control->setCurrentEvent(emscripten::val::undefined()); + } + } + emit awake(); if (!useAsyncify() && isMainThreadEventDispatcher()) diff --git a/src/corelib/kernel/qeventdispatcher_wasm_p.h b/src/corelib/kernel/qeventdispatcher_wasm_p.h index 674af85dac2..1946bf63ede 100644 --- a/src/corelib/kernel/qeventdispatcher_wasm_p.h +++ b/src/corelib/kernel/qeventdispatcher_wasm_p.h @@ -40,7 +40,6 @@ public: ~QEventDispatcherWasm(); bool processEvents(QEventLoop::ProcessEventsFlags flags) override; - bool sendAllEvents(QEventLoop::ProcessEventsFlags flag); void registerTimer(Qt::TimerId timerId, Duration interval, Qt::TimerType timerType, QObject *object) override final; @@ -69,6 +68,7 @@ protected: virtual bool sendPostedEvents(); private: + bool sendAllEvents(QEventLoop::ProcessEventsFlags flag); bool isMainThreadEventDispatcher(); bool isSecondaryThreadEventDispatcher(); bool isValidEventDispatcher(); diff --git a/src/corelib/platform/wasm/qwasmlocalfileengine.cpp b/src/corelib/platform/wasm/qwasmlocalfileengine.cpp new file mode 100644 index 00000000000..58e662d5fde --- /dev/null +++ b/src/corelib/platform/wasm/qwasmlocalfileengine.cpp @@ -0,0 +1,422 @@ +// Copyright (C) 2025 Qt Group +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +#include "qwasmlocalfileengine_p.h" +#include <QtCore/QDebug> +#include <QtCore/QUrl> + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +// Custom URL scheme for files handled by this file engine. A complete file URL can +// look like +// +// "weblocalfile:/n/file.ext" +// +// where n is a counter to ensure uniqueness, needed since web platform gives us the file name only +// and does not provide the file path. +// +// The scheme may be visible to end users if the application displays it, so +// we avoid using "wasm" and instead say "web", which should be more recognizable. +static constexpr QLatin1StringView wasmlocalfileScheme = "weblocalfile"_L1; + +// Instantiate the engine, the system will pick it up automatically +// Never destroy it to avoid problems with static destruction. +// The OS will reclaim the memory anyway, +// TODO: cleanup on QApplication destruction and re-init on QApplication re-creation. +static QWasmFileEngineHandler *singleton = new QWasmFileEngineHandler(); + +QWasmFileEngineHandler::QWasmFileEngineHandler() +{ +} + +QWasmFileEngineHandler::~QWasmFileEngineHandler() +{ +} + +std::unique_ptr<QAbstractFileEngine> QWasmFileEngineHandler::create(const QString &fileName) const +{ + if (!QWasmFileEngineHandler::isWasmFileName(fileName)) + return {}; + + // Check if it's a File or FileSystemFileHandle + if (singleton->m_files.contains(fileName)) { + qstdweb::File file = singleton->m_files.value(fileName); + return std::make_unique<QWasmFileEngine>(fileName, file); + } else if (singleton->m_fileSystemFiles.contains(fileName)) { + qstdweb::FileSystemFileHandle file = singleton->m_fileSystemFiles.value(fileName); + return std::make_unique<QWasmFileEngine>(fileName, file); + } + + // Not an error, this function will be called with partial paths like "weblocalfile:/1/" + return {}; +} + +// Check if this is a wasm filename by checking the URL scheme. +bool QWasmFileEngineHandler::isWasmFileName(const QString& fileName) +{ + return QUrl(fileName).scheme() == wasmlocalfileScheme; +} + +// Creates a wasm filename using the custom URL scheme and a counter. +QString QWasmFileEngineHandler::makeWasmFileName(const QString &nativeFileName) +{ + static std::atomic<uint64_t> sid = 0; + const uint64_t id = ++sid; + return wasmlocalfileScheme + QStringLiteral(":/%1/%2").arg(id).arg(nativeFileName); +} + +// Extracts the native filename from the custom URL (removes scheme and counter). +QString QWasmFileEngineHandler::nativeFileName(const QString &wasmFileName) +{ + QUrl url(wasmFileName); + if (url.scheme() == wasmlocalfileScheme) { + QString path = url.path(); + // Path is "/n/filename", find the second '/' and extract the filename + const qsizetype idx = path.indexOf(u'/', 1); + if (idx != -1) + return path.mid(idx + 1); + } + return wasmFileName; +} + +// Adds a File to the set of open files. Returns a prefixed wasm file name. +QString QWasmFileEngineHandler::addFile(qstdweb::File file) +{ + QString nativeFileName = QString::fromStdString(file.name()); + QString wasmFileName = makeWasmFileName(nativeFileName); + singleton->m_files.insert(wasmFileName, file); + return wasmFileName; +} + +// Adds a FileSystemFileHandle to the set of open files. Returns a prefixed wasm file name. +QString QWasmFileEngineHandler::addFile(qstdweb::FileSystemFileHandle file) +{ + QString nativeFileName = QString::fromStdString(file.name()); + QString wasmFileName = makeWasmFileName(nativeFileName); + singleton->m_fileSystemFiles.insert(wasmFileName, file); + return wasmFileName; +} + +// Removes a File or FileSystemFileHandle from the set of open file handlers +void QWasmFileEngineHandler::removeFile(const QString fileName) +{ + singleton->m_files.remove(fileName); + singleton->m_fileSystemFiles.remove(fileName); +} + +qstdweb::File QWasmFileEngineHandler::getFile(const QString fileName) +{ + return singleton->m_files.value(fileName); +} + +qstdweb::FileSystemFileHandle QWasmFileEngineHandler::getFileSystemFile(const QString fileName) +{ + return singleton->m_fileSystemFiles.value(fileName); +} + +/*! + \class QWasmFileEngine + \brief The QWasmFileEngine class provides a QAbstractFileEngine + for files that has the prefix ':weblocalfile/'. +*/ + +// Constructs a QWasmFileEngine with a File for read-only access +QWasmFileEngine::QWasmFileEngine(const QString &fileName, qstdweb::File file) + : m_fileName(fileName), + m_blobDevice(std::make_unique<qstdweb::BlobIODevice>(file.slice(0, file.size()))) +{ + // TODO use m_blobDevice in unbuffered mode? if there is already a buffer higher up. +} + +// Constructs a QWasmFileEngine with a FileSystemFileHandle for read-write access +QWasmFileEngine::QWasmFileEngine(const QString &fileName, qstdweb::FileSystemFileHandle file) + : m_fileName(fileName), + m_fileDevice(std::make_unique<qstdweb::FileSystemFileIODevice>(file)) +{ + +} + +QWasmFileEngine::~QWasmFileEngine() +{ + close(); +} + +bool QWasmFileEngine::open(QIODevice::OpenMode openMode, std::optional<QFile::Permissions> permissions) +{ + Q_UNUSED(permissions); + m_openMode = openMode; + + if (m_fileDevice) + return m_fileDevice->open(openMode); + else if (m_blobDevice) + return m_blobDevice->open(openMode); + + return false; +} + +bool QWasmFileEngine::close() +{ + if (m_openMode == QIODevice::NotOpen) + return false; + + bool success = true; + if (m_fileDevice) { + m_fileDevice->close(); + } + if (m_blobDevice) { + m_blobDevice->close(); + } + + m_openMode = QIODevice::NotOpen; + return success; +} + +bool QWasmFileEngine::flush() +{ + return true; +} + +bool QWasmFileEngine::syncToDisk() +{ + return true; +} + +qint64 QWasmFileEngine::size() const +{ + if (m_fileDevice) + return m_fileDevice->size(); + if (m_blobDevice) + return m_blobDevice->size(); + return 0; +} + +qint64 QWasmFileEngine::pos() const +{ + if (m_fileDevice) + return m_fileDevice->pos(); + if (m_blobDevice) + return m_blobDevice->pos(); + return 0; +} + +bool QWasmFileEngine::seek(qint64 pos) +{ + if (m_fileDevice) + return m_fileDevice->seek(pos); + if (m_blobDevice) + return m_blobDevice->seek(pos); + return false; +} + +bool QWasmFileEngine::isSequential() const +{ + return false; +} + +bool QWasmFileEngine::remove() +{ + return false; +} + +bool QWasmFileEngine::copy(const QString &newName) +{ + Q_UNUSED(newName); + return false; +} + +bool QWasmFileEngine::rename(const QString &newName) +{ + Q_UNUSED(newName); + return false; +} + +bool QWasmFileEngine::renameOverwrite(const QString &newName) +{ + Q_UNUSED(newName); + return false; +} + +bool QWasmFileEngine::link(const QString &newName) +{ + Q_UNUSED(newName); + return false; +} + +bool QWasmFileEngine::mkdir(const QString &dirName, bool createParentDirectories, + std::optional<QFile::Permissions> permissions) const +{ + Q_UNUSED(dirName); + Q_UNUSED(createParentDirectories); + Q_UNUSED(permissions); + return false; +} + +bool QWasmFileEngine::rmdir(const QString &dirName, bool recurseParentDirectories) const +{ + Q_UNUSED(dirName); + Q_UNUSED(recurseParentDirectories); + return false; +} + +bool QWasmFileEngine::setSize(qint64 size) +{ + Q_UNUSED(size); + return false; +} + +bool QWasmFileEngine::caseSensitive() const +{ + return true; +} + +bool QWasmFileEngine::isRelativePath() const +{ + return false; +} + + +QAbstractFileEngine::FileFlags QWasmFileEngine::fileFlags(FileFlags type) const +{ + return type & (QAbstractFileEngine::FileFlag::ExistsFlag | + QAbstractFileEngine::FileFlag::FileType | + QAbstractFileEngine::FileFlag::ReadOwnerPerm | + QAbstractFileEngine::FileFlag::WriteOwnerPerm); +} + +bool QWasmFileEngine::setPermissions(uint perms) +{ + Q_UNUSED(perms); + return false; +} + +QByteArray QWasmFileEngine::id() const +{ + return {}; +} + +QString QWasmFileEngine::fileName(FileName file) const +{ + switch (file) { + case DefaultName: + case AbsoluteName: + case CanonicalName: + return m_fileName; + case BaseName: { + QString native = QWasmFileEngineHandler::nativeFileName(m_fileName); + QFileInfo info(native); + return info.fileName(); + } + case PathName: + case AbsolutePathName: + case CanonicalPathName: { + QString native = QWasmFileEngineHandler::nativeFileName(m_fileName); + QFileInfo info(native); + QString path = info.path(); + return path.isEmpty() ? "."_L1 : path; + } + default: + return QString(); + } +} + +uint QWasmFileEngine::ownerId(FileOwner) const +{ + return 0; +} + +QString QWasmFileEngine::owner(FileOwner) const +{ + return {}; +} + +bool QWasmFileEngine::setFileTime(const QDateTime &newDate, QFile::FileTime time) +{ + Q_UNUSED(newDate); + Q_UNUSED(time); + return false; +} + +QDateTime QWasmFileEngine::fileTime(QFile::FileTime time) const +{ + Q_UNUSED(time); + return {}; +} + +void QWasmFileEngine::setFileName(const QString &file) +{ + if (m_fileName == file) + return; + close(); + m_fileName = file; +} + +int QWasmFileEngine::handle() const +{ + return -1; +} + +QAbstractFileEngine::TriStateResult QWasmFileEngine::cloneTo(QAbstractFileEngine *target) +{ + Q_UNUSED(target); + return QAbstractFileEngine::TriStateResult::NotSupported; +} + +QAbstractFileEngine::IteratorUniquePtr QWasmFileEngine::beginEntryList(const QString &path, QDirListing::IteratorFlags filters, + const QStringList &filterNames) +{ + Q_UNUSED(path); + Q_UNUSED(filters); + Q_UNUSED(filterNames); + return nullptr; +} + +qint64 QWasmFileEngine::read(char *data, qint64 maxlen) +{ + if (!(m_openMode & QIODevice::ReadOnly)) + return -1; + + if (m_fileDevice) + return m_fileDevice->read(data, maxlen); + if (m_blobDevice) + return m_blobDevice->read(data, maxlen); + return -1; +} + +qint64 QWasmFileEngine::readLine(char *data, qint64 maxlen) +{ + if (!(m_openMode & QIODevice::ReadOnly)) + return -1; + + if (m_fileDevice) + return m_fileDevice->readLine(data, maxlen); + if (m_blobDevice) + return m_blobDevice->readLine(data, maxlen); + return -1; +} + +qint64 QWasmFileEngine::write(const char *data, qint64 len) +{ + if (!(m_openMode & QIODevice::WriteOnly)) + return -1; + + if (m_fileDevice) + return m_fileDevice->write(data, len); + return -1; +} + +bool QWasmFileEngine::extension(Extension extension, const ExtensionOption *option, ExtensionReturn *output) +{ + Q_UNUSED(extension); + Q_UNUSED(option); + Q_UNUSED(output); + return false; +} + +bool QWasmFileEngine::supportsExtension(Extension extension) const +{ + Q_UNUSED(extension); + return false; +} + +QT_END_NAMESPACE diff --git a/src/corelib/platform/wasm/qwasmlocalfileengine_p.h b/src/corelib/platform/wasm/qwasmlocalfileengine_p.h new file mode 100644 index 00000000000..dd82788dd90 --- /dev/null +++ b/src/corelib/platform/wasm/qwasmlocalfileengine_p.h @@ -0,0 +1,102 @@ +// Copyright (C) 2024 Qt Group +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +#ifndef QWASMLOCALFILEENGINE_P_H +#define QWASMLOCALFILEENGINE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/private/qabstractfileengine_p.h> +#include <QtCore/private/qstdweb_p.h> + +QT_BEGIN_NAMESPACE + +class QWasmFileEngine; + +class QWasmFileEngineHandler: public QAbstractFileEngineHandler +{ +public: + Q_DISABLE_COPY_MOVE(QWasmFileEngineHandler) + + QWasmFileEngineHandler(); + virtual ~QWasmFileEngineHandler() override; + virtual std::unique_ptr<QAbstractFileEngine> create(const QString &fileName) const override; + + static bool isWasmFileName(const QString& fileName); + static QString makeWasmFileName(const QString &fileName); + static QString nativeFileName(const QString &wasmFileName); + + static QString addFile(qstdweb::File file); + static QString addFile(qstdweb::FileSystemFileHandle file); + static void removeFile(const QString fileName); + qstdweb::File getFile(const QString fileName); + qstdweb::FileSystemFileHandle getFileSystemFile(const QString fileName); + +private: + QHash<QString, qstdweb::File> m_files; + QHash<QString, qstdweb::FileSystemFileHandle> m_fileSystemFiles; +}; + +class QWasmFileEngine: public QAbstractFileEngine +{ +public: + explicit QWasmFileEngine(const QString &fileName, qstdweb::File file); + explicit QWasmFileEngine(const QString &fileName, qstdweb::FileSystemFileHandle file); + ~QWasmFileEngine() override; + + virtual bool open(QIODevice::OpenMode openMode, std::optional<QFile::Permissions> permissions) override; + virtual bool close() override; + virtual bool flush() override; + virtual bool syncToDisk() override; + virtual qint64 size() const override; + virtual qint64 pos() const override; + virtual bool seek(qint64 pos) override; + virtual bool isSequential() const override; + virtual bool remove() override; + virtual bool copy(const QString &newName) override; + virtual bool rename(const QString &newName) override; + virtual bool renameOverwrite(const QString &newName) override; + virtual bool link(const QString &newName) override; + virtual bool mkdir(const QString &dirName, bool createParentDirectories, + std::optional<QFile::Permissions> permissions = std::nullopt) const override; + virtual bool rmdir(const QString &dirName, bool recurseParentDirectories) const override; + virtual bool setSize(qint64 size) override; + virtual bool caseSensitive() const override; + virtual bool isRelativePath() const override; + virtual FileFlags fileFlags(FileFlags type=FileInfoAll) const override; + virtual bool setPermissions(uint perms) override; + virtual QByteArray id() const override; + virtual QString fileName(FileName file = DefaultName) const override; + virtual uint ownerId(FileOwner) const override; + virtual QString owner(FileOwner) const override; + virtual bool setFileTime(const QDateTime &newDate, QFile::FileTime time) override; + virtual QDateTime fileTime(QFile::FileTime time) const override; + virtual void setFileName(const QString &file) override; + virtual int handle() const override; + virtual TriStateResult cloneTo(QAbstractFileEngine *target) override; + virtual IteratorUniquePtr beginEntryList(const QString &path, QDirListing::IteratorFlags filters, + const QStringList &filterNames) override; + virtual qint64 read(char *data, qint64 maxlen) override; + virtual qint64 readLine(char *data, qint64 maxlen) override; + virtual qint64 write(const char *data, qint64 len) override; + virtual bool extension(Extension extension, const ExtensionOption *option = nullptr, + ExtensionReturn *output = nullptr) override; + virtual bool supportsExtension(Extension extension) const override; + +private: + QString m_fileName; + QIODevice::OpenMode m_openMode = QIODevice::NotOpen; + std::unique_ptr<qstdweb::BlobIODevice> m_blobDevice; + std::unique_ptr<qstdweb::FileSystemFileIODevice> m_fileDevice; +}; + +QT_END_NAMESPACE +#endif // QWASMFILEENGINEHANDLER_H diff --git a/src/corelib/platform/wasm/qwasmsuspendresumecontrol.cpp b/src/corelib/platform/wasm/qwasmsuspendresumecontrol.cpp index a4bc7843380..85358d1daf4 100644 --- a/src/corelib/platform/wasm/qwasmsuspendresumecontrol.cpp +++ b/src/corelib/platform/wasm/qwasmsuspendresumecontrol.cpp @@ -225,8 +225,11 @@ int QWasmSuspendResumeControl::sendPendingEvents() pendingEvents.call<void>("splice", i, 1); auto it = m_eventHandlers.find(event["index"].as<int>()); - if (it != m_eventHandlers.end()) - it->second(event["arg"]); + if (it != m_eventHandlers.end()) { + setCurrentEvent(event["arg"]); + it->second(currentEvent()); + setCurrentEvent(emscripten::val::undefined()); + } ++count; } } diff --git a/src/corelib/platform/wasm/qwasmsuspendresumecontrol_p.h b/src/corelib/platform/wasm/qwasmsuspendresumecontrol_p.h index ff97ff3d7ea..4e75d5674be 100644 --- a/src/corelib/platform/wasm/qwasmsuspendresumecontrol_p.h +++ b/src/corelib/platform/wasm/qwasmsuspendresumecontrol_p.h @@ -42,12 +42,22 @@ public: void suspendExclusive(QList<uint32_t> eventHandlerIndices); int sendPendingEvents(); + emscripten::val currentEvent() const + { + return m_currentEvent; + } + void setCurrentEvent(emscripten::val currentEvent) + { + m_currentEvent = currentEvent; + } + private: friend void qtSendPendingEvents(); static QWasmSuspendResumeControl *s_suspendResumeControl; std::map<int, std::function<void(emscripten::val)>> m_eventHandlers; std::function<bool(int)> m_eventFilter = [](int) { return true; }; + emscripten::val m_currentEvent = emscripten::val::undefined(); }; class Q_CORE_EXPORT QWasmEventHandler diff --git a/src/corelib/text/qstringconverter_p.h b/src/corelib/text/qstringconverter_p.h index 3ac60ce8c70..3923c2f302f 100644 --- a/src/corelib/text/qstringconverter_p.h +++ b/src/corelib/text/qstringconverter_p.h @@ -334,6 +334,7 @@ struct QUtf8 static char16_t *convertToUnicode(char16_t *dst, QByteArrayView in, QStringConverter::State *state); + Q_CORE_EXPORT static char *convertFromUnicode(char *dst, QStringView in) noexcept; Q_CORE_EXPORT static QByteArray convertFromUnicode(QStringView in); Q_CORE_EXPORT static QByteArray convertFromUnicode(QStringView in, QStringConverter::State *state); diff --git a/src/gui/platform/wasm/qwasmlocalfileaccess.cpp b/src/gui/platform/wasm/qwasmlocalfileaccess.cpp index 051e2b1a04e..8d06821d4c1 100644 --- a/src/gui/platform/wasm/qwasmlocalfileaccess.cpp +++ b/src/gui/platform/wasm/qwasmlocalfileaccess.cpp @@ -285,6 +285,44 @@ void saveFile(const char *content, size_t size, const std::string &fileNameHint) }); } +void showOpenFileDialog(const std::string &accept, + const std::function<void (bool accepted, std::vector<qstdweb::File> files)> fileDialogClosed) +{ + FileDialog::showOpen(makeFilterList(accept), FileSelectMode::MultipleFiles, { + .thenFunc = [=](emscripten::val result) { + if (result.isUndefined() || result.isNull()) { + fileDialogClosed(false, std::vector<qstdweb::File>()); + } else { + std::vector<qstdweb::File> files; + int length = result["length"].as<int>(); + files.reserve(length); + for (int i = 0; i < length; ++i) { + emscripten::val fileVal = result[i]; + if (!fileVal.isUndefined() && !fileVal.isNull()) { + files.push_back(qstdweb::File(fileVal)); + } + } + fileDialogClosed(true, files); + } + }, + .catchFunc = [=](emscripten::val) { + fileDialogClosed(false, std::vector<qstdweb::File>()); + } + }); +} + +void showSaveFileDialog(const std::string &fileNameHint, const std::function<void (bool accepted, qstdweb::FileSystemFileHandle fileHandle)> fileDialogClosed) +{ + FileDialog::showSave(fileNameHint, { + .thenFunc = [=](emscripten::val result) { + fileDialogClosed(true, qstdweb::FileSystemFileHandle(result)); + }, + .catchFunc = [=](emscripten::val) { + fileDialogClosed(false, qstdweb::FileSystemFileHandle()); + } + }); +} + } // namespace QWasmLocalFileAccess QT_END_NAMESPACE diff --git a/src/gui/platform/wasm/qwasmlocalfileaccess_p.h b/src/gui/platform/wasm/qwasmlocalfileaccess_p.h index 77b14577f7e..5597746a5f3 100644 --- a/src/gui/platform/wasm/qwasmlocalfileaccess_p.h +++ b/src/gui/platform/wasm/qwasmlocalfileaccess_p.h @@ -16,6 +16,7 @@ // #include <private/qglobal_p.h> +#include <private/qstdweb_p.h> #include <cstdint> #include <functional> @@ -38,6 +39,12 @@ Q_CORE_EXPORT void openFile(const std::string &accept, Q_CORE_EXPORT void saveFile(const QByteArray &data, const std::string &fileNameHint); Q_CORE_EXPORT void saveFile(const char *content, size_t size, const std::string &fileNameHint); +Q_CORE_EXPORT void showOpenFileDialog(const std::string &accept, + const std::function<void (bool accepted, std::vector<qstdweb::File> files)> fileDialogClosed); +Q_CORE_EXPORT void showSaveFileDialog(const std::string &fileNameHint, + const std::function<void (bool accepted, qstdweb::FileSystemFileHandle fileHandle)> fileDialogClosed); + + } // namespace QWasmLocalFileAccess QT_END_NAMESPACE diff --git a/src/network/kernel/qauthenticator.cpp b/src/network/kernel/qauthenticator.cpp index 2ef1c31ce1c..7be1d7fe3aa 100644 --- a/src/network/kernel/qauthenticator.cpp +++ b/src/network/kernel/qauthenticator.cpp @@ -365,7 +365,7 @@ bool QAuthenticator::isNull() const state. */ -void QAuthenticator::clear() noexcept +void QAuthenticator::clear() { *d = QAuthenticatorPrivate(); d->phase = QAuthenticatorPrivate::Done; diff --git a/src/network/kernel/qauthenticator.h b/src/network/kernel/qauthenticator.h index db309fb87ad..c4993ff0fc7 100644 --- a/src/network/kernel/qauthenticator.h +++ b/src/network/kernel/qauthenticator.h @@ -44,7 +44,7 @@ public: bool isNull() const; void detach(); - void clear() noexcept; + void clear(); private: friend class QAuthenticatorPrivate; QAuthenticatorPrivate *d; diff --git a/src/plugins/platforms/cocoa/qnsview.mm b/src/plugins/platforms/cocoa/qnsview.mm index a6d96df0fbb..b00e6375e1a 100644 --- a/src/plugins/platforms/cocoa/qnsview.mm +++ b/src/plugins/platforms/cocoa/qnsview.mm @@ -269,6 +269,23 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSViewMenuHelper); qCDebug(lcQpaWindow) << "Done moving" << self << "to" << self.window; } +// QWindow::setParent() promises that the child window will be clipped +// to its parent, which we rely on in e.g. Qt Widgets when a native window +// is added to a scroll area. We try to be smart and only enable clipping +// if we have potential child QWindows that rely on this behavior. +// FIXME: Be even smarter, and only consider QWindow based subviews, +// in a way that also includes foreign windows. + +- (void)didAddSubview:(NSView *)subview +{ + self.clipsToBounds = YES; +} + +- (void)willRemoveSubview:(NSView *)subview +{ + self.clipsToBounds = self.subviews.count > 1; +} + // ---------------------------------------------------------------------------- - (QWindow *)topLevelWindow diff --git a/src/plugins/platforms/wasm/CMakeLists.txt b/src/plugins/platforms/wasm/CMakeLists.txt index 03cb0d52ca1..7e9beb7e832 100644 --- a/src/plugins/platforms/wasm/CMakeLists.txt +++ b/src/plugins/platforms/wasm/CMakeLists.txt @@ -33,6 +33,7 @@ qt_internal_add_plugin(QWasmIntegrationPlugin qwasmwindownonclientarea.cpp qwasmwindownonclientarea.h qwasminputcontext.cpp qwasminputcontext.h qwasmwindowstack.h + qwasmfiledialoghelper.cpp qwasmfiledialoghelper.h DEFINES QT_EGL_NO_X11 QT_NO_FOREACH diff --git a/src/plugins/platforms/wasm/qwasmdom.cpp b/src/plugins/platforms/wasm/qwasmdom.cpp index 71efcee7887..eefd18154aa 100644 --- a/src/plugins/platforms/wasm/qwasmdom.cpp +++ b/src/plugins/platforms/wasm/qwasmdom.cpp @@ -10,6 +10,7 @@ #include <QtCore/qrect.h> #include <QtGui/qimage.h> #include <private/qstdweb_p.h> +#include <private/qwasmlocalfileengine_p.h> #include <QtCore/qurl.h> #include <utility> @@ -112,11 +113,14 @@ void DataTransfer::toMimeDataWithFile(std::function<void(QMimeData *)> callback) m_callback(mimeData); - // Delete files; we expect that the user callback reads/copies - // file content before returning. - // Fixme: tie file lifetime to lifetime of the QMimeData? - for (QUrl fileUrl: fileUrls) - QFile(fileUrl.toLocalFile()).remove(); + // Delete temporary files; we expect that the user callback reads/copies + // file content before returning.// Fixme: tie file lifetime to lifetime of the QMimeData? + // Note: QWasmFileEngine files (weblocalfile://) are managed by QWasmFileEngine + // and are not deleted here + for (QUrl fileUrl: fileUrls) { + if (!QWasmFileEngineHandler::isWasmFileName(fileUrl.toString())) + QFile(fileUrl.toLocalFile()).remove(); + } delete this; } @@ -144,49 +148,56 @@ void DataTransfer::toMimeDataWithFile(std::function<void(QMimeData *)> callback) case ItemKind::File: { qstdweb::File webfile(item.call<emscripten::val>("getAsFile")); - if (webfile.size() > 1e+9) { // limit file size to 1 GB - qWarning() << "File is too large (> 1GB) and will be skipped. File size is" << webfile.size(); + // Add a file access url for the local file. If asyncify is available, + // add a QWasmFileEngine managed url. Else fall back to placing a copy + // of the file at /tmp on Emsripten's in-memory file system. + if (qstdweb::haveAsyncify()) { + QUrl fileUrl(QWasmFileEngineHandler::addFile(webfile)); + mimeContext->fileUrls.append(fileUrl); mimeContext->deref(); - continue; - } + } else { + // Limit in-memory file size to 1 GB + if (webfile.size() > 1e+9) { + qWarning() << "File is too large (> 1GB) and will be skipped. File size is" << webfile.size(); + mimeContext->deref(); + continue; + } - QString mimeFormat = QString::fromStdString(webfile.type()); - QString fileName = QString::fromStdString(webfile.name()); + // Read file content + QByteArray fileContent(webfile.size(), Qt::Uninitialized); + webfile.stream(fileContent.data(), [=]() { + QDir qtTmpDir("/qt/tmp/"); // "tmp": indicate that these files won't stay around + qtTmpDir.mkpath(qtTmpDir.path()); + + QUrl fileUrl = QUrl::fromLocalFile(qtTmpDir.filePath(QString::fromStdString(webfile.name()))); + mimeContext->fileUrls.append(fileUrl); + + QFile file(fileUrl.toLocalFile()); + if (!file.open(QFile::WriteOnly)) { + qWarning() << "File was not opened"; + mimeContext->deref(); + return; + } + if (file.write(fileContent) < 0) + qWarning() << "Write failed"; + file.close(); + mimeContext->deref(); + }); - // there's a file, now read it - QByteArray fileContent(webfile.size(), Qt::Uninitialized); - webfile.stream(fileContent.data(), [=]() { - // If we get a single file, and that file is an image, then // try to decode the image data. This handles the case where // image data (i.e. not an image file) is pasted. The browsers // will then create a fake "image.png" file which has the image - // data. As a side effect Qt will also decode the image for + // data. As a side effect Qt will also decode the image for // single-image-file drops, since there is no way to differentiate // the fake "image.png" from a real one. + QString mimeFormat = QString::fromStdString(webfile.type()); if (fileCount == 1 && mimeFormat.contains("image/")) { QImage image; if (image.loadFromData(fileContent)) mimeContext->mimeData->setImageData(image); } - - QDir qtTmpDir("/qt/tmp/"); // "tmp": indicate that these files won't stay around - qtTmpDir.mkpath(qtTmpDir.path()); - - QUrl fileUrl = QUrl::fromLocalFile(qtTmpDir.filePath(QString::fromStdString(webfile.name()))); - mimeContext->fileUrls.append(fileUrl); - - QFile file(fileUrl.toLocalFile()); - if (!file.open(QFile::WriteOnly)) { - qWarning() << "File was not opened"; - mimeContext->deref(); - return; - } - if (file.write(fileContent) < 0) - qWarning() << "Write failed"; - file.close(); - mimeContext->deref(); - }); + } break; } case ItemKind::String: diff --git a/src/plugins/platforms/wasm/qwasmfiledialoghelper.cpp b/src/plugins/platforms/wasm/qwasmfiledialoghelper.cpp new file mode 100644 index 00000000000..1e6e2b1f644 --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmfiledialoghelper.cpp @@ -0,0 +1,157 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "qwasmfiledialoghelper.h" + +#include <QtCore/QDebug> +#include <QtCore/QUrl> +#include <QtGui/private/qwasmlocalfileaccess_p.h> +#include <QtCore/private/qwasmlocalfileengine_p.h> + +QT_BEGIN_NAMESPACE + +QWasmFileDialogHelper::QWasmFileDialogHelper() + : m_eventLoop(nullptr) +{ + +} + +QWasmFileDialogHelper::~QWasmFileDialogHelper() +{ + +} + +bool QWasmFileDialogHelper::defaultNameFilterDisables() const +{ + return false; +} + +void QWasmFileDialogHelper::setDirectory(const QUrl &directory) +{ + Q_UNUSED(directory) +} + +QUrl QWasmFileDialogHelper::directory() const +{ + return QUrl(); +} + +void QWasmFileDialogHelper::selectFile(const QUrl &file) +{ + m_selectedFiles.clear(); + m_selectedFiles.append(file); +} + +QList<QUrl> QWasmFileDialogHelper::selectedFiles() const +{ + return m_selectedFiles; +} + +void QWasmFileDialogHelper::setFilter() +{ + +} + +void QWasmFileDialogHelper::selectNameFilter(const QString &filter) +{ + Q_UNUSED(filter); + // TODO +} + +QString QWasmFileDialogHelper::selectedNameFilter() const +{ + return QString(); +} + +void QWasmFileDialogHelper::exec() +{ + QEventLoop eventLoop; + m_eventLoop = &eventLoop; + eventLoop.exec(); + m_eventLoop = nullptr; +} + +bool QWasmFileDialogHelper::show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent) +{ + Q_UNUSED(flags) + Q_UNUSED(modality) + Q_UNUSED(parent) + showFileDialog(); + return true; +} + +void QWasmFileDialogHelper::hide() +{ + +} + +void QWasmFileDialogHelper::showFileDialog() +{ + if (options()->acceptMode() == QFileDialogOptions::AcceptOpen) { + // Use name filters from options + QString nameFilter = options()->nameFilters().join(";;"); + if (nameFilter.isEmpty()) + nameFilter = "*"; + + QWasmLocalFileAccess::showOpenFileDialog(nameFilter.toStdString(), [this](bool accepted, std::vector<qstdweb::File> files) { + onOpenDialogClosed(accepted, files); + }); + } else if (options()->acceptMode() == QFileDialogOptions::AcceptSave) { + QString suggestion = m_selectedFiles.isEmpty() ? QString() : QUrl(m_selectedFiles.first()).fileName(); + m_selectedFiles.clear(); + + QWasmLocalFileAccess::showSaveFileDialog(suggestion.toStdString(), [this](bool accepted, qstdweb::FileSystemFileHandle file){ + onSaveDialogClosed(accepted, file); + }); + } +} + +void QWasmFileDialogHelper::onOpenDialogClosed(bool accepted, std::vector<qstdweb::File> files) +{ + m_selectedFiles.clear(); + + if (!accepted) { + emit reject(); + return; + } + + // Track opened files + for (const auto &file : files) { + QString wasmFileName = QWasmFileEngineHandler::addFile(file); + QUrl fileUrl(wasmFileName); + m_selectedFiles.append(fileUrl); + } + + // Emit signals + if (m_selectedFiles.size() > 0) { + emit fileSelected(m_selectedFiles.first()); + emit filesSelected(m_selectedFiles); + } + emit accept(); + + // exit exec() if in exec() + if (m_eventLoop) + m_eventLoop->quit(); +} + +void QWasmFileDialogHelper::onSaveDialogClosed(bool accepted, qstdweb::FileSystemFileHandle file) +{ + if (!accepted) { + emit reject(); + return; + } + + // Track save file + QString wasmFileName = QWasmFileEngineHandler::addFile(file); + QUrl fileUrl(wasmFileName); + m_selectedFiles.append(fileUrl); + + // Emit signals + emit fileSelected(m_selectedFiles.first()); + emit accept(); + + if (m_eventLoop) + m_eventLoop->quit(); +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmfiledialoghelper.h b/src/plugins/platforms/wasm/qwasmfiledialoghelper.h new file mode 100644 index 00000000000..c5a5b57e518 --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmfiledialoghelper.h @@ -0,0 +1,49 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef QWASMFILEDIALOGHELPER_H +#define QWASMFILEDIALOGHELPER_H + +#include <QtCore/QObject> +#include <QtCore/QUrl> +#include <QtCore/QEventLoop> +#include <QtGui/qpa/qplatformdialoghelper.h> +#include <QtGui/private/qwasmlocalfileaccess_p.h> + +QT_BEGIN_NAMESPACE + +class QWasmFileDialogHelper : public QPlatformFileDialogHelper +{ + Q_OBJECT +public: + QWasmFileDialogHelper(); + ~QWasmFileDialogHelper(); +public: + virtual void exec() override; + virtual bool show(Qt::WindowFlags windowFlags, + Qt::WindowModality windowModality, + QWindow *parent) override; + virtual void hide() override; + virtual bool defaultNameFilterDisables() const override; + virtual void setDirectory(const QUrl &directory) override; + virtual QUrl directory() const override; + virtual void selectFile(const QUrl &filename) override; + virtual QList<QUrl> selectedFiles() const override; + virtual void setFilter() override; + virtual void selectNameFilter(const QString &filter) override; + virtual QString selectedNameFilter() const override; + static QStringList cleanFilterList(const QString &filter); +signals: + void fileDone(const QUrl &); +private: + void showFileDialog(); + void onOpenDialogClosed(bool accepted, std::vector<qstdweb::File> files); + void onSaveDialogClosed(bool accepted, qstdweb::FileSystemFileHandle file); + + QList<QUrl> m_selectedFiles; + QEventLoop *m_eventLoop; +}; + +QT_END_NAMESPACE + +#endif // QWASMFILEDIALOGHELPER_H diff --git a/src/plugins/platforms/wasm/qwasmtheme.cpp b/src/plugins/platforms/wasm/qwasmtheme.cpp index b9340f31275..b1e5c208c6c 100644 --- a/src/plugins/platforms/wasm/qwasmtheme.cpp +++ b/src/plugins/platforms/wasm/qwasmtheme.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "qwasmtheme.h" +#include "qwasmfiledialoghelper.h" #include <QtCore/qvariant.h> #include <QFontDatabase> #include <QList> @@ -127,6 +128,18 @@ const QFont *QWasmTheme::font(Font type) const return nullptr; } +bool QWasmTheme::usePlatformNativeDialog(DialogType type) const +{ + return (type == DialogType::FileDialog); +} + +QPlatformDialogHelper *QWasmTheme::createPlatformDialogHelper(DialogType type) const +{ + if (type == DialogType::FileDialog) + return new QWasmFileDialogHelper(); + return nullptr; +} + void QWasmTheme::onColorSchemeChange() { auto colorScheme = getColorSchemeFromMedia(); diff --git a/src/plugins/platforms/wasm/qwasmtheme.h b/src/plugins/platforms/wasm/qwasmtheme.h index 4eaad874c76..8b8dd6ebd97 100644 --- a/src/plugins/platforms/wasm/qwasmtheme.h +++ b/src/plugins/platforms/wasm/qwasmtheme.h @@ -57,6 +57,9 @@ public: Qt::ContrastPreference contrastPreference() const override; QVariant themeHint(ThemeHint hint) const override; const QFont *font(Font type) const override; + bool usePlatformNativeDialog(DialogType type) const override; + QPlatformDialogHelper *createPlatformDialogHelper(DialogType type) const override; + QFont *fixedFont = nullptr; void onColorSchemeChange(); diff --git a/src/plugins/platforms/wasm/qwasmwindow.cpp b/src/plugins/platforms/wasm/qwasmwindow.cpp index 6e8bd46ca58..7f1dd7eb34c 100644 --- a/src/plugins/platforms/wasm/qwasmwindow.cpp +++ b/src/plugins/platforms/wasm/qwasmwindow.cpp @@ -925,7 +925,7 @@ bool QWasmWindow::deliverPointerEvent(const PointerEvent &event) std::back_inserter(touchPointList), [](const QWindowSystemInterface::TouchPoint &val) { return val; }); - if (event.type == EventType::PointerUp) + if (event.type == EventType::PointerUp || event.type == EventType::PointerCancel) m_pointerIdToTouchPoints.remove(event.pointerId); return event.type == EventType::PointerCancel diff --git a/src/plugins/platforms/windows/qwindowswindow.cpp b/src/plugins/platforms/windows/qwindowswindow.cpp index 2816982b1a8..9459e8bdfd3 100644 --- a/src/plugins/platforms/windows/qwindowswindow.cpp +++ b/src/plugins/platforms/windows/qwindowswindow.cpp @@ -848,7 +848,7 @@ void WindowCreationData::fromWindow(const QWindow *w, const Qt::WindowFlags flag // make mouse events fall through this window // NOTE: WS_EX_TRANSPARENT flag can make mouse inputs fall through a layered window if (flagsIn & Qt::WindowTransparentForInput) - exStyle |= WS_EX_LAYERED | WS_EX_TRANSPARENT; + exStyle |= WS_EX_TRANSPARENT; // Currently only compatible with D3D surfaces, use it with care. if (qEnvironmentVariableIntValue("QT_QPA_DISABLE_REDIRECTION_SURFACE")) diff --git a/src/tools/bootstrap/CMakeLists.txt b/src/tools/bootstrap/CMakeLists.txt index e6f920dcf32..c12475d69af 100644 --- a/src/tools/bootstrap/CMakeLists.txt +++ b/src/tools/bootstrap/CMakeLists.txt @@ -220,6 +220,10 @@ target_link_libraries(Bootstrap PRIVATE PlatformCommonInternal) qt_internal_apply_gc_binaries(Bootstrap PUBLIC) set_target_properties(Bootstrap PROPERTIES AUTOMOC OFF AUTOUIC OFF AUTORCC OFF) qt_internal_add_target_aliases(Bootstrap) +qt_internal_add_target_optimized_flags_for_debug_config_in_current_scope(Bootstrap) +qt_internal_enable_optimized_tools_lto(Bootstrap) +qt_internal_workaround_static_lib_gcc_lto_issue(Bootstrap) + qt_set_msvc_cplusplus_options(Bootstrap PUBLIC) qt_set_common_target_properties(Bootstrap) qt_internal_apply_intel_cet(Bootstrap PUBLIC) diff --git a/src/widgets/kernel/qapplication.cpp b/src/widgets/kernel/qapplication.cpp index d2b0fb724ac..a735696adcb 100644 --- a/src/widgets/kernel/qapplication.cpp +++ b/src/widgets/kernel/qapplication.cpp @@ -3741,6 +3741,14 @@ void QApplicationPrivate::cleanupMultitouch_sys() { } +/*! \internal + Check the target widgets of the active touchpoints of the given \a device, + and choose the widget that is closest to any of the points. This widget + will then get all the touchpoints, even if it would not otherwise be the + target for some of them. + + \sa translateRawTouchEvent() +*/ QWidget *QApplicationPrivate::findClosestTouchPointTarget(const QPointingDevice *device, const QEventPoint &touchPoint) { const QPointF globalPos = touchPoint.globalPosition(); @@ -3754,7 +3762,10 @@ QWidget *QApplicationPrivate::findClosestTouchPointTarget(const QPointingDevice qreal dx = globalPos.x() - pt.globalPosition().x(); qreal dy = globalPos.y() - pt.globalPosition().y(); qreal distance = dx * dx + dy * dy; - if (closestTouchPointId == -1 || distance < closestDistance) { + // closestTouchPointId is -1 at the beginning. + // closestTouchPointId may be 0 if + // a synth-mouse eventPoint was found in activePoints: that's not relevant here. + if (closestTouchPointId <= 0 || distance < closestDistance) { closestTouchPointId = pt.id(); closestDistance = distance; closestTarget = QMutableEventPoint::target(pt); diff --git a/src/widgets/styles/qfusionstyle_p_p.h b/src/widgets/styles/qfusionstyle_p_p.h index dcb79f9e93c..821be49b2fa 100644 --- a/src/widgets/styles/qfusionstyle_p_p.h +++ b/src/widgets/styles/qfusionstyle_p_p.h @@ -94,7 +94,8 @@ public: QColor buttonColor = pal.button().color(); int val = qGray(buttonColor.rgb()); buttonColor = buttonColor.lighter(100 + qMax(1, (180 - val)/6)); - buttonColor.setHsv(buttonColor.hue(), buttonColor.saturation() * 0.75, buttonColor.value()); + buttonColor.setHsv(buttonColor.hue(), buttonColor.saturation() * 0.75, + buttonColor.value(), buttonColor.alpha()); return buttonColor; } diff --git a/src/widgets/widgets/qlcdnumber.cpp b/src/widgets/widgets/qlcdnumber.cpp index 2c4b4e334d5..ac8e00af95e 100644 --- a/src/widgets/widgets/qlcdnumber.cpp +++ b/src/widgets/widgets/qlcdnumber.cpp @@ -5,6 +5,7 @@ #include "qlcdnumber.h" #include "qbitarray.h" +#include "qnumeric.h" #include "qpainter.h" #include "private/qframe_p.h" @@ -110,22 +111,17 @@ public: */ -static QString int2string(int num, int base, int ndigits, bool *oflow) +static QString int2string(int number, int base, int ndigits, bool *oflow) { QString s; - bool negative; - if (num < 0) { - negative = true; - num = -num; - } else { - negative = false; - } + const bool negative = number < 0; + const uint num = QtPrivate::qUnsignedAbs(number); switch(base) { case QLCDNumber::Hex: s = QString::asprintf("%*x", ndigits, num); break; case QLCDNumber::Dec: - s = QString::asprintf("%*i", ndigits, num); + s = QString::asprintf("%*u", ndigits, num); break; case QLCDNumber::Oct: s = QString::asprintf("%*o", ndigits, num); diff --git a/tests/auto/corelib/io/qioring/tst_qioring.cpp b/tests/auto/corelib/io/qioring/tst_qioring.cpp index 75d4fe68c55..5d5662cd9b4 100644 --- a/tests/auto/corelib/io/qioring/tst_qioring.cpp +++ b/tests/auto/corelib/io/qioring/tst_qioring.cpp @@ -20,6 +20,7 @@ class tst_QIORing : public QObject Q_OBJECT private slots: + void initTestCase(); void construct(); void sharedInstance(); void open(); @@ -33,6 +34,12 @@ private: static qintptr openHelper(QIORing *ring, const QString &path, QIODevice::OpenMode flags); }; +void tst_QIORing::initTestCase() +{ + if (QIORing::sharedInstance() == nullptr) + QSKIP("QIORing wasn't able to initialize on this platform. Test will be skipped."); +} + void tst_QIORing::closeFile(qintptr fd) { #ifdef Q_OS_WIN diff --git a/tests/auto/corelib/kernel/qeventdispatcher/tst_qeventdispatcher.cpp b/tests/auto/corelib/kernel/qeventdispatcher/tst_qeventdispatcher.cpp index d25a6d60cca..cae462c526b 100644 --- a/tests/auto/corelib/kernel/qeventdispatcher/tst_qeventdispatcher.cpp +++ b/tests/auto/corelib/kernel/qeventdispatcher/tst_qeventdispatcher.cpp @@ -381,8 +381,17 @@ void tst_QEventDispatcher::postEventFromThread() QAtomicInt hadToQuit = false; QAtomicInt done = false; + int threadCount = 500; + const int timeout = (1000 +#if defined(QT_GUI_LIB) + // Aggressively posting events may on some platforms rate limit us to + // the display's refresh rate, so give us enough time if that happens. + + ((1000.0 / qGuiApp->primaryScreen()->refreshRate()) * threadCount) +#endif + ); + threadPool->start([&]{ - int loop = 1000 / 10; // give it a second + int loop = timeout / 10; while (!done && --loop) QThread::sleep(std::chrono::milliseconds{10}); if (done) @@ -399,8 +408,7 @@ void tst_QEventDispatcher::postEventFromThread() } } receiver; - int count = 500; - while (!hadToQuit && --count) { + while (!hadToQuit && --threadCount) { threadPool->start([&receiver]{ QCoreApplication::postEvent(&receiver, new QEvent(QEvent::User)); }); diff --git a/tests/auto/gui/kernel/qguieventdispatcher/BLACKLIST b/tests/auto/gui/kernel/qguieventdispatcher/BLACKLIST index a8f73d73f4d..e69de29bb2d 100644 --- a/tests/auto/gui/kernel/qguieventdispatcher/BLACKLIST +++ b/tests/auto/gui/kernel/qguieventdispatcher/BLACKLIST @@ -1,2 +0,0 @@ -[postEventFromThread] -macos-26 developer-build # QTBUG-142185 diff --git a/tests/auto/widgets/widgets/qlcdnumber/tst_qlcdnumber.cpp b/tests/auto/widgets/widgets/qlcdnumber/tst_qlcdnumber.cpp index 8fcf9c49fe6..88e67b34a2d 100644 --- a/tests/auto/widgets/widgets/qlcdnumber/tst_qlcdnumber.cpp +++ b/tests/auto/widgets/widgets/qlcdnumber/tst_qlcdnumber.cpp @@ -18,6 +18,9 @@ public: private slots: void getSetCheck(); + + void displayEdgeCases_data(); + void displayEdgeCases(); }; tst_QLCDNumber::tst_QLCDNumber() @@ -42,5 +45,30 @@ void tst_QLCDNumber::getSetCheck() QCOMPARE(99, obj1.digitCount()); // Range<0, 99> } +// Test case for undefined behavior when displaying INT_MIN +void tst_QLCDNumber::displayEdgeCases_data() +{ + QTest::addColumn<int>("number"); + QTest::addColumn<QString>("expected"); + + // INT_MIN previously caused UB due to -INT_MIN overflow in int2string(). + QTest::newRow("INT_MIN") << INT_MIN << QString::number(INT_MIN); + QTest::newRow("INT_MAX") << INT_MAX << QString::number(INT_MAX); + QTest::newRow("Zero") << 0 << QStringLiteral("0"); +} + +void tst_QLCDNumber::displayEdgeCases() +{ + QFETCH(int, number); + QFETCH(QString, expected); + + QLCDNumber lcd; + lcd.setDigitCount(12); + lcd.display(number); + + QString result = QString::number(lcd.intValue()); + QCOMPARE(result, expected); +} + QTEST_MAIN(tst_QLCDNumber) #include "tst_qlcdnumber.moc" diff --git a/tests/manual/wasm/localfiles/main.cpp b/tests/manual/wasm/localfiles/main.cpp index 862bff50a47..90e8c2e90f6 100644 --- a/tests/manual/wasm/localfiles/main.cpp +++ b/tests/manual/wasm/localfiles/main.cpp @@ -5,6 +5,81 @@ #include <emscripten/val.h> #include <emscripten.h> +class DropZone : public QLabel +{ + Q_OBJECT +public: + explicit DropZone(QWidget *parent = nullptr) : QLabel(parent) + { + setAcceptDrops(true); + setFrameStyle(QFrame::Box | QFrame::Sunken); + setAlignment(Qt::AlignCenter); + setText("Drop files here\n(will read first file)"); + setMinimumSize(400, 150); + setStyleSheet("QLabel { background-color: #f0f0f0; border: 2px dashed #999; padding: 20px; }"); + } + +Q_SIGNALS: + void filesDropped(const QList<QUrl> &urls); + +protected: + void dragEnterEvent(QDragEnterEvent *event) override + { + if (event->mimeData()->hasUrls()) { + event->acceptProposedAction(); + setStyleSheet("QLabel { background-color: #e0f0ff; border: 2px dashed #0066cc; padding: 20px; }"); + } + } + + void dragLeaveEvent(QDragLeaveEvent *event) override + { + Q_UNUSED(event); + setStyleSheet("QLabel { background-color: #f0f0f0; border: 2px dashed #999; padding: 20px; }"); + } + + void dropEvent(QDropEvent *event) override + { + const QMimeData *mimeData = event->mimeData(); + + if (mimeData->hasUrls()) { + QList<QUrl> urls = mimeData->urls(); + + qDebug() << "=== Files Dropped ==="; + qDebug() << "Number of files:" << urls.size(); + + for (int i = 0; i < urls.size(); ++i) { + const QUrl &url = urls.at(i); + qDebug() << "\n--- File" << (i + 1) << "---"; + qDebug() << "URL:" << url; + qDebug() << "URL toString:" << url.toString(); + qDebug() << "URL scheme:" << url.scheme(); + qDebug() << "URL path:" << url.path(); + qDebug() << "URL fileName:" << url.fileName(); + qDebug() << "isLocalFile:" << url.isLocalFile(); + + if (url.isLocalFile()) { + QString filePath = url.toLocalFile(); + qDebug() << "Local file path:" << filePath; + + QFileInfo fileInfo(filePath); + qDebug() << "File name:" << fileInfo.fileName(); + qDebug() << "File size:" << fileInfo.size(); + qDebug() << "File exists:" << fileInfo.exists(); + qDebug() << "Is readable:" << fileInfo.isReadable(); + qDebug() << "Absolute path:" << fileInfo.absoluteFilePath(); + qDebug() << "Last modified:" << fileInfo.lastModified().toString(); + } + } + qDebug() << "===================\n"; + + event->acceptProposedAction(); + emit filesDropped(urls); + } + + setStyleSheet("QLabel { background-color: #f0f0f0; border: 2px dashed #999; padding: 20px; }"); + } +}; + class AppWindow : public QObject { Q_OBJECT @@ -12,21 +87,39 @@ public: AppWindow() : m_layout(new QVBoxLayout(&m_loadFileUi)), m_window(emscripten::val::global("window")), m_showOpenFilePickerFunction(m_window["showOpenFilePicker"]), - m_showSaveFilePickerFunction(m_window["showSaveFilePicker"]) + m_showSaveFilePickerFunction(m_window["showSaveFilePicker"]), + m_fileDialog(new QFileDialog(&m_loadFileUi)), + m_isLoadOperation(true) { - addWidget<QLabel>("Filename filter"); - const bool localFileApiAvailable = !m_showOpenFilePickerFunction.isUndefined() && !m_showSaveFilePickerFunction.isUndefined(); m_useLocalFileApisCheckbox = addWidget<QCheckBox>("Use the window.showXFilePicker APIs"); m_useLocalFileApisCheckbox->setEnabled(localFileApiAvailable); m_useLocalFileApisCheckbox->setChecked(localFileApiAvailable); + connect(m_useLocalFileApisCheckbox, &QCheckBox::toggled, + std::bind(&AppWindow::onUseLocalFileApisCheckboxToggled, this)); + + m_useStandardFileDialogCheckbox = addWidget<QCheckBox>("Use standard QFileDialog API"); + connect(m_useStandardFileDialogCheckbox, &QCheckBox::toggled, + std::bind(&AppWindow::onUseStandardFileDialogCheckboxToggled, this)); + m_useStandardFileDialogCheckbox->setChecked(true); + + m_useExecModeCheckbox = addWidget<QCheckBox>("Use exec() instead of open()"); + m_useExecModeCheckbox->setChecked(false); + + addWidget<QLabel>("Filename filter"); - m_filterEdit = addWidget<QLineEdit>("Images (*.png *.jpg);;PDF (*.pdf);;*.txt"); + m_filterCombo = addWidget<QComboBox>(); + m_filterCombo->addItem("*"); + m_filterCombo->addItem("Images (*.png *.jpg);;PDF (*.pdf);;*.txt"); + m_filterCombo->setCurrentIndex(0); // Make "*" the default auto* loadFile = addWidget<QPushButton>("Load File"); + m_dropZone = addWidget<DropZone>(); + connect(m_dropZone, &DropZone::filesDropped, this, &AppWindow::onFilesDropped); + m_fileInfo = addWidget<QLabel>("Opened file:"); m_fileInfo->setTextInteractionFlags(Qt::TextSelectableByMouse); @@ -43,11 +136,13 @@ public: m_loadFileUi.setLayout(m_layout); - QObject::connect(m_useLocalFileApisCheckbox, &QCheckBox::toggled, std::bind(&AppWindow::onUseLocalFileApisCheckboxToggled, this)); QObject::connect(loadFile, &QPushButton::clicked, this, &AppWindow::onLoadClicked); - QObject::connect(m_saveFile, &QPushButton::clicked, std::bind(&AppWindow::onSaveClicked, this)); + + // Connect to both fileSelected and accepted signals for compatibility + QObject::connect(m_fileDialog, &QFileDialog::fileSelected, this, &AppWindow::onFileSelected); + QObject::connect(m_fileDialog, &QFileDialog::accepted, this, &AppWindow::onDialogAccepted); } void show() { @@ -67,6 +162,29 @@ private Q_SLOTS: m_showSaveFilePickerFunction : emscripten::val::undefined()); } + void onUseStandardFileDialogCheckboxToggled() + { + m_useLocalFileApisCheckbox->setChecked(m_useStandardFileDialogCheckbox->isChecked()); + } + + void onFilesDropped(const QList<QUrl> &urls) + { + if (urls.isEmpty()) + return; + + // Load the first dropped file + const QUrl &url = urls.first(); + + if (url.isLocalFile()) { + QString filePath = url.toLocalFile(); + loadFileWithQFile(filePath); + } else { + // Try using the URL string directly for non-file:// URLs (like weblocalfile://) + QString urlString = url.toString(); + loadFileWithQFile(urlString); + } + } + void onFileContentReady(const QString &fileName, const QByteArray &fileContents) { m_fileContent = fileContents; @@ -89,16 +207,108 @@ private Q_SLOTS: void onLoadClicked() { - QFileDialog::getOpenFileContent( - m_filterEdit->text(), - std::bind(&AppWindow::onFileContentReady, this, std::placeholders::_1, std::placeholders::_2), - &m_loadFileUi); + if (m_useStandardFileDialogCheckbox->isChecked()) { + m_isLoadOperation = true; + m_fileDialog->setFileMode(QFileDialog::ExistingFile); + m_fileDialog->setAcceptMode(QFileDialog::AcceptOpen); + m_fileDialog->setNameFilter(m_filterCombo->currentText()); + m_fileDialog->setWindowTitle("Open File"); + + if (m_useExecModeCheckbox->isChecked()) { + qDebug() << "Using exec() mode"; + int result = m_fileDialog->exec(); + if (result == QDialog::Accepted) { + QStringList files = m_fileDialog->selectedFiles(); + if (!files.isEmpty()) { + onFileSelected(files.first()); + } + } + } else { + qDebug() << "Using open() mode"; + m_fileDialog->open(); + } + } else { + QFileDialog::getOpenFileContent( + m_filterCombo->currentText(), + std::bind(&AppWindow::onFileContentReady, this, std::placeholders::_1, std::placeholders::_2), + &m_loadFileUi); + } } void onSaveClicked() { - m_fileInfo->setText("Saving file... (no result information with current API)"); - QFileDialog::saveFileContent(m_fileContent, m_savedFileNameEdit->text()); + if (m_useStandardFileDialogCheckbox->isChecked()) { + m_isLoadOperation = false; + m_fileDialog->setFileMode(QFileDialog::AnyFile); + m_fileDialog->setAcceptMode(QFileDialog::AcceptSave); + m_fileDialog->setNameFilter(m_filterCombo->currentText()); + m_fileDialog->setWindowTitle("Save File"); + m_fileDialog->selectFile(m_savedFileNameEdit->text()); + + if (m_useExecModeCheckbox->isChecked()) { + qDebug() << "Using exec() mode for save"; + int result = m_fileDialog->exec(); + if (result == QDialog::Accepted) { + QStringList files = m_fileDialog->selectedFiles(); + if (!files.isEmpty()) { + onFileSelected(files.first()); + } + } + } else { + qDebug() << "Using open() mode for save"; + m_fileDialog->open(); + } + } else { + m_fileInfo->setText("Saving file... (no result information with current API)"); + QFileDialog::saveFileContent(m_fileContent, m_savedFileNameEdit->text()); + } + } + + void onDialogAccepted() + { + QStringList files = m_fileDialog->selectedFiles(); + if (!files.isEmpty()) { + onFileSelected(files.first()); + } + } + + void onFileSelected(const QString &fileName) + { + qDebug() << "onFileSelected" << fileName; + + if (m_isLoadOperation) { + loadFileWithQFile(fileName); + } else { + saveFileWithQFile(fileName); + } + } + + void loadFileWithQFile(const QString &fileName) + { + qDebug() << "loadFileWithQFile" << fileName; + + QFile file(fileName); + if (file.open(QIODevice::ReadOnly)) { + qDebug() << "loadFileWithQFile" << fileName; + QByteArray fileContents = file.readAll(); + file.close(); + onFileContentReady(QFileInfo(fileName).fileName(), fileContents); + } else { + m_fileInfo->setText(QString("Failed to open file: %1").arg(file.errorString())); + } + } + + void saveFileWithQFile(const QString &fileName) + { + QFile file(fileName); + if (file.open(QIODevice::WriteOnly)) { + qint64 bytesWritten = file.write(m_fileContent); + file.close(); + bool success = (bytesWritten == m_fileContent.size()); + m_fileInfo->setText(QString("File save result: %1").arg(success ? "success" : "failed")); + } else { + m_fileInfo->setText(QString("Failed to save file: %1").arg(file.errorString())); + } } private: @@ -113,7 +323,10 @@ private: QWidget m_loadFileUi; QCheckBox* m_useLocalFileApisCheckbox; - QLineEdit* m_filterEdit; + QCheckBox* m_useStandardFileDialogCheckbox; + QCheckBox* m_useExecModeCheckbox; + DropZone* m_dropZone; + QComboBox* m_filterCombo; QVBoxLayout *m_layout; QLabel* m_fileInfo; QLabel* m_fileHash; @@ -124,6 +337,9 @@ private: emscripten::val m_showOpenFilePickerFunction; emscripten::val m_showSaveFilePickerFunction; + QFileDialog* m_fileDialog; + bool m_isLoadOperation; + QByteArray m_fileContent; }; |
