From 09b66cab9bbfe6f678989419ca14b999e0811539 Mon Sep 17 00:00:00 2001 From: "U-DESKTOP-SPFP6AQ\\twistedtechre" Date: Fri, 1 May 2026 18:16:29 +0200 Subject: [PATCH 01/14] retro_atomic: shield include from caller's extern "C" ui_qt.cpp wraps RetroArch headers in extern "C" { ... } when CXX_BUILD is not defined. In non-unity builds the chain ui_qt.cpp (extern "C") -> menu_driver.h -> gfx_thumbnail.h -> retro_atomic.h -> ends up parsing libstdc++ under C linkage, producing dozens of "template with C linkage" errors on g++. The same hazard exists for any C++ TU that wraps RetroArch headers in extern "C", and is currently masked elsewhere only because no other such TU happens to pull in a header that reaches retro_atomic.h. Fix at the header level so every caller is protected, not just ui_qt.cpp: - Add RETRO_BEGIN_DECLS_CXX / RETRO_END_DECLS_CXX to retro_common_api.h. They expand to extern "C++" { ... } in C++ mode (with CXX_BUILD off) and to nothing otherwise, mirroring the existing RETRO_BEGIN_DECLS / RETRO_END_DECLS pair. - Use them around the / include in retro_atomic.h's C++11 backend. The standard headers parse at C++ linkage regardless of any surrounding extern "C" the caller imposes; the rest of the file (typedefs and macros, no function declarations) is unaffected. The previous comment claiming "no need for RETRO_BEGIN_DECLS / extern \"C\" wrapping ... the wrapper would actively break that path" addressed only half the problem -- the file's own wrapping -- and missed that the caller's wrapper leaks into the include. Updated to reflect the real constraint. --- libretro-common/include/retro_atomic.h | 14 ++++++++++---- libretro-common/include/retro_common_api.h | 9 +++++++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/libretro-common/include/retro_atomic.h b/libretro-common/include/retro_atomic.h index f8d4be78f485..40923290fffb 100644 --- a/libretro-common/include/retro_atomic.h +++ b/libretro-common/include/retro_atomic.h @@ -23,6 +23,8 @@ #ifndef __LIBRETRO_SDK_ATOMIC_H #define __LIBRETRO_SDK_ATOMIC_H +#include + /* Minimal portable atomic operations for SPSC patterns. * * This header consolidates the ad-hoc atomic shims previously duplicated @@ -321,10 +323,12 @@ #endif /* The header contains only macros and integer typedefs; there are no - * function declarations and therefore no need for RETRO_BEGIN_DECLS / - * extern "C" wrapping. In particular the C++11 backend below - * #includes , whose templates cannot be declared with C - * linkage, so the wrapper would actively break that path. */ + * function declarations and therefore no need for RETRO_BEGIN_DECLS + * around the file. The C++11 backend below #includes , whose + * templates cannot be declared with C linkage; if a caller wraps its + * #include of this header in extern "C" { ... } (e.g. ui_qt.cpp under + * !CXX_BUILD), libstdc++ emits dozens of "template with C + * linkage" errors. RETRO_BEGIN_DECLS_CXX below escapes that. */ /* ---- C11 ------------------------------------------------- */ #if defined(RETRO_ATOMIC_BACKEND_C11) @@ -359,8 +363,10 @@ typedef atomic_size_t retro_atomic_size_t; /* ---- C++11 --------------------------------------------------- */ #elif defined(RETRO_ATOMIC_BACKEND_CXX11) +RETRO_BEGIN_DECLS_CXX #include #include +RETRO_END_DECLS_CXX /* This header is included by C++ TUs in C++11+ mode (gated on * __cplusplus >= 201103L or _MSVC_LANG >= 201103L). We use the * std::atomic_* free-function forms rather than the member-function diff --git a/libretro-common/include/retro_common_api.h b/libretro-common/include/retro_common_api.h index ace2ef6ac00a..a8b79e4310a4 100644 --- a/libretro-common/include/retro_common_api.h +++ b/libretro-common/include/retro_common_api.h @@ -40,6 +40,8 @@ special technique is called for. #define RETRO_BEGIN_DECLS #define RETRO_END_DECLS +#define RETRO_BEGIN_DECLS_CXX +#define RETRO_END_DECLS_CXX #ifdef __cplusplus @@ -50,6 +52,13 @@ special technique is called for. #undef RETRO_END_DECLS #define RETRO_BEGIN_DECLS extern "C" { #define RETRO_END_DECLS } +/* Force C++ linkage for a region inside a header that may be included + * from within a caller's extern "C" { ... } block -- needed when the + * region pulls in a C++ standard library header (e.g. ). */ +#undef RETRO_BEGIN_DECLS_CXX +#undef RETRO_END_DECLS_CXX +#define RETRO_BEGIN_DECLS_CXX extern "C++" { +#define RETRO_END_DECLS_CXX } #endif #else From a38a63853ebd33045e019a52e72a7447e9078e8f Mon Sep 17 00:00:00 2001 From: "U-DESKTOP-SPFP6AQ\\twistedtechre" Date: Fri, 1 May 2026 18:30:30 +0200 Subject: [PATCH 02/14] Add regression tests --- .../Linux-libretro-common-samples.yml | 62 ++++- .../retro_atomic_extern_c_linkage/Makefile | 50 ++++ .../retro_atomic_extern_c_linkage_test.cpp | 224 ++++++++++++++++++ 3 files changed, 328 insertions(+), 8 deletions(-) create mode 100644 libretro-common/samples/atomic/retro_atomic_extern_c_linkage/Makefile create mode 100644 libretro-common/samples/atomic/retro_atomic_extern_c_linkage/retro_atomic_extern_c_linkage_test.cpp diff --git a/.github/workflows/Linux-libretro-common-samples.yml b/.github/workflows/Linux-libretro-common-samples.yml index 378e02809197..5f83fb78f7eb 100644 --- a/.github/workflows/Linux-libretro-common-samples.yml +++ b/.github/workflows/Linux-libretro-common-samples.yml @@ -80,6 +80,8 @@ jobs: task_queue_title_error_test tpool_wait_test retro_atomic_test + retro_atomic_extern_c_linkage_test + retro_atomic_extern_c_linkage_test_cxx retro_spsc_test ) @@ -212,6 +214,25 @@ jobs: # behavioural SPSC stress is already covered by the C test # binary above on this same host, and the C++11 backend bottoms # out through the same libstdc++ __atomic_* builtins. + # + # Two fixtures share the g++/clang++ x c++11/14/17 sweep: + # + # cxx_smoke.cpp : header included at C++ linkage, + # the "ordinary" C++ TU shape. + # cxx_smoke_extern_c.cpp : header included from inside an + # extern "C" { ... } wrap, the + # shape ui_qt.cpp uses under + # !CXX_BUILD. This is the + # regression case for the + # RETRO_BEGIN_DECLS_CXX shield in + # retro_atomic.h's C++11 backend. + # The standalone build of the + # sample at samples/atomic/ + # retro_atomic_extern_c_linkage/ + # covers the same case under the + # default toolchain; this matrix + # extends coverage to clang++ and + # the higher language standards. set -u set -o pipefail @@ -254,16 +275,41 @@ jobs: } EOF + # Regression fixture: include retro_atomic.h from inside an + # extern "C" wrap. Faithful mirror of ui_qt.cpp's pattern. + # Pre-fix retro_atomic.h would emit ~80 "template with C + # linkage" errors here; the RETRO_BEGIN_DECLS_CXX shield + # around makes this compile cleanly. + cat > "$tmpdir/cxx_smoke_extern_c.cpp" <<'EOF' + #include + #include + + extern "C" { + #include + } + + int main(void) { + retro_atomic_int_t ai; retro_atomic_int_init(&ai, 0); + retro_atomic_store_release_int(&ai, 7); + int v = retro_atomic_load_acquire_int(&ai); + std::printf("backend: %s\n", RETRO_ATOMIC_BACKEND_NAME); + std::puts(v == 7 ? "ALL OK" : "FAIL"); + return v == 7 ? 0 : 1; + } + EOF + for cxx in g++ clang++; do for std in c++11 c++14 c++17; do - echo "==> compile-test with $cxx -std=$std" - $cxx -std=$std -Wall -Wextra -pedantic -O2 \ - -I include \ - "$tmpdir/cxx_smoke.cpp" \ - -o "$tmpdir/cxx_smoke" \ - || { echo "::error title=C++ compile failed::$cxx -std=$std"; exit 1; } - "$tmpdir/cxx_smoke" \ - || { echo "::error title=C++ smoke failed::$cxx -std=$std"; exit 1; } + for fixture in cxx_smoke cxx_smoke_extern_c; do + echo "==> compile-test with $cxx -std=$std ($fixture)" + $cxx -std=$std -Wall -Wextra -pedantic -O2 \ + -I include \ + "$tmpdir/$fixture.cpp" \ + -o "$tmpdir/$fixture" \ + || { echo "::error title=C++ compile failed::$cxx -std=$std $fixture"; exit 1; } + "$tmpdir/$fixture" \ + || { echo "::error title=C++ smoke failed::$cxx -std=$std $fixture"; exit 1; } + done done done diff --git a/libretro-common/samples/atomic/retro_atomic_extern_c_linkage/Makefile b/libretro-common/samples/atomic/retro_atomic_extern_c_linkage/Makefile new file mode 100644 index 000000000000..25b90fabf2f3 --- /dev/null +++ b/libretro-common/samples/atomic/retro_atomic_extern_c_linkage/Makefile @@ -0,0 +1,50 @@ +TARGET := retro_atomic_extern_c_linkage_test +TARGET_TEST := retro_atomic_extern_c_linkage_test_cxx + +LIBRETRO_COMM_DIR := ../../.. + +# Two binaries from one source. The source mirrors ui_qt.cpp's pattern +# of wrapping a RetroArch header include in extern "C" { ... }, gated +# on !CXX_BUILD. We build it once each way: +# +# $(TARGET) : non-unity / separate-TU build (extern "C" wrap is +# active). This is the configuration that triggered +# the original "template with C linkage" failure on +# g++ before the retro_common_api.h fix. +# +# $(TARGET_TEST) : unity / griffin build (CXX_BUILD defined, extern +# "C" wrappers compile out). Validates that the +# fix did not regress the unity-build path. +# +# Both must compile and pass. Build failure on $(TARGET) is the +# regression signal for the original bug. + +SOURCE := retro_atomic_extern_c_linkage_test.cpp + +# C++11 is the minimum that engages retro_atomic.h's CXX11 backend. +# -Wall -Wextra to catch any incidental warning the test's macro +# expansions might produce on a future toolchain. +CXXFLAGS += -Wall -Wextra -pedantic -std=c++11 -g -O0 \ + -I$(LIBRETRO_COMM_DIR)/include + +ifneq ($(SANITIZER),) + CXXFLAGS := -fsanitize=$(SANITIZER) -fno-omit-frame-pointer $(CXXFLAGS) + LDFLAGS := -fsanitize=$(SANITIZER) $(LDFLAGS) +endif + +# The unity-build target gets CXX_BUILD defined, exactly as +# Makefile.griffin does for griffin_cpp.cpp's TUs. +$(TARGET_TEST): CXXFLAGS += -DCXX_BUILD + +all: $(TARGET) $(TARGET_TEST) + +$(TARGET): $(SOURCE) + $(CXX) -o $@ $< $(CXXFLAGS) $(LDFLAGS) + +$(TARGET_TEST): $(SOURCE) + $(CXX) -o $@ $< $(CXXFLAGS) $(LDFLAGS) + +clean: + rm -f $(TARGET) $(TARGET_TEST) + +.PHONY: all clean diff --git a/libretro-common/samples/atomic/retro_atomic_extern_c_linkage/retro_atomic_extern_c_linkage_test.cpp b/libretro-common/samples/atomic/retro_atomic_extern_c_linkage/retro_atomic_extern_c_linkage_test.cpp new file mode 100644 index 000000000000..32ccd06b51e2 --- /dev/null +++ b/libretro-common/samples/atomic/retro_atomic_extern_c_linkage/retro_atomic_extern_c_linkage_test.cpp @@ -0,0 +1,224 @@ +/* Copyright (C) 2010-2026 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (retro_atomic_extern_c_linkage_test.cpp). + * --------------------------------------------------------------------------------------- + * + * Permission is hereby granted, free of charge, + * to any person obtaining a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* Regression test for the linkage hazard in + * libretro-common/include/retro_atomic.h's C++11 backend. + * + * Background + * ---------- + * Several C++ TUs in RetroArch wrap their includes of in-tree C + * headers in extern "C" { ... } when CXX_BUILD is not defined. The + * canonical example is ui/drivers/ui_qt.cpp: + * + * #ifndef CXX_BUILD + * extern "C" { + * #endif + * #include "../../menu/menu_driver.h" // pulls in retro_atomic.h + * ... + * #ifndef CXX_BUILD + * } + * #endif + * + * On a C++11+ build this chain reaches retro_atomic.h's CXX11 backend, + * which #include's . libstdc++'s declares dozens of + * templates and template specializations; under C linkage every one of + * them is rejected with + * + * error: template with C linkage + * + * yielding ~80 errors on a single TU. See the build log on master at + * the time of the original report; same failure on g++ 9 through 13. + * + * Fix + * --- + * retro_common_api.h gained RETRO_BEGIN_DECLS_CXX / RETRO_END_DECLS_CXX + * macros that expand to extern "C++" { ... } in C++ mode (with + * CXX_BUILD off) and to nothing otherwise. retro_atomic.h's C++11 + * backend wraps its / include in those macros, so + * the standard headers parse at C++ linkage regardless of any + * extern "C" the caller imposes. + * + * What this test asserts + * ---------------------- + * 1. Compile-time: a C++ TU that wraps #include + * in extern "C" { ... } compiles without error. The include + * pattern below is a faithful mirror of ui_qt.cpp's; if the + * header's linkage shield breaks, this file fails to compile + * and that is the regression signal. + * + * 2. Runtime: the macro expansions inside the extern "C" wrap still + * produce live std::atomic objects whose load_acquire / + * store_release / fetch_add / fetch_sub / inc / dec round-trip + * correctly. This guards against a future "fix" that papered + * over the compile error by, say, falling back to a non-atomic + * plain-int typedef under C linkage. exit(0) on success, + * exit(1) on any property failure. + * + * 3. Both build configurations exercise the same source with no + * code-level difference (see Makefile). The Makefile builds + * two targets: + * retro_atomic_extern_c_linkage_test -- !CXX_BUILD path + * retro_atomic_extern_c_linkage_test_cxx -- CXX_BUILD path + * The second one defines CXX_BUILD before including any + * RetroArch headers, which makes the in-tree extern "C" wrappers + * compile out (because RETRO_BEGIN_DECLS / RETRO_END_DECLS expand + * to nothing). Both must build and pass. + * + * What this test does NOT assert + * ------------------------------ + * It does not exercise weakly-ordered SMP correctness or thread + * safety -- those are covered by retro_atomic_test (single-thread + * property + SPSC stress) and retro_spsc_test (lock-free SPSC) in + * neighbouring sample directories, and by the cross-arch qemu lane + * in .github/workflows/Linux-libretro-common-samples.yml. This + * test's job is purely to police the include-from-C++ hazard. + * + * Build standalone + * ---------------- + * make clean all + * ./retro_atomic_extern_c_linkage_test + * ./retro_atomic_extern_c_linkage_test_cxx + * + * Both binaries exit 0 on success. A pre-fix retro_atomic.h would + * fail at the build step rather than at runtime. + */ + +/* Mirror of ui_qt.cpp's include guard pattern. retro_common_api.h + * (transitively included from retro_atomic.h) keys on this: + * + * #ifdef CXX_BUILD + * -- no extern "C" wrappers anywhere in libretro-common headers -- + * #else + * -- normal RETRO_BEGIN_DECLS = extern "C" { wrapping -- + * #endif + * + * The Makefile builds this file twice, once with CXX_BUILD undefined + * (the non-unity build path that triggered the original bug) and once + * with CXX_BUILD defined (the griffin/unity-build path). Both must + * compile and pass the runtime checks. */ + +#include +#include + +/* This is the regression's trigger pattern: a C++ TU includes a + * RetroArch header from inside its own extern "C" block. On a stock + * (broken) retro_atomic.h, the transitive #include here would + * be parsed at C linkage and the build would die. */ +#ifndef CXX_BUILD +extern "C" { +#endif + +#include + +#ifndef CXX_BUILD +} +#endif + +/* Capability flags must be set after the include succeeds. If a + * future refactor accidentally compiles the header out under + * extern "C", these gates fire at preprocessing time with a clearer + * message than the libstdc++ template avalanche. */ +#if !defined(HAVE_RETRO_ATOMIC) +# error "retro_atomic.h: HAVE_RETRO_ATOMIC not set after include" +#endif +#if !defined(RETRO_ATOMIC_BACKEND_NAME) +# error "retro_atomic.h: RETRO_ATOMIC_BACKEND_NAME not set after include" +#endif + +/* On a C++11+ host the CXX11 backend is the expected selection. If + * something promotes a different backend in this TU we want to know; + * the whole point of the test is to police the C++11 path. */ +#if !defined(RETRO_ATOMIC_BACKEND_CXX11) +# error "retro_atomic_extern_c_linkage_test: expected the C++11 backend; if a different backend is intentional, update this gate" +#endif + +/* ---- Single-threaded property checks --------------------------------- */ +/* Each returns 0 on success, 1 on failure. Identical in shape to + * retro_atomic_test.c so a regression in either test points at the + * same place. */ + +static int check_init(void) +{ + retro_atomic_int_t ai; retro_atomic_int_init (&ai, 7); + retro_atomic_size_t as; retro_atomic_size_init(&as, (std::size_t)9); + + if (retro_atomic_load_acquire_int (&ai) != 7) return 1; + if (retro_atomic_load_acquire_size(&as) != (std::size_t)9) return 1; + return 0; +} + +static int check_store_release_load_acquire(void) +{ + retro_atomic_int_t ai; retro_atomic_int_init (&ai, 0); + retro_atomic_size_t as; retro_atomic_size_init(&as, 0); + + retro_atomic_store_release_int (&ai, 42); + retro_atomic_store_release_size(&as, (std::size_t)42); + + if (retro_atomic_load_acquire_int (&ai) != 42) return 1; + if (retro_atomic_load_acquire_size(&as) != (std::size_t)42) return 1; + return 0; +} + +static int check_fetch_add_sub_returns_previous(void) +{ + retro_atomic_int_t ai; retro_atomic_int_init (&ai, 10); + retro_atomic_size_t as; retro_atomic_size_init(&as, (std::size_t)10); + + /* fetch_add returns the previous value (POSIX convention). */ + if (retro_atomic_fetch_add_int (&ai, 5) != 10) return 1; + if (retro_atomic_fetch_add_size(&as, 5) != (std::size_t)10) return 1; + + if (retro_atomic_load_acquire_int (&ai) != 15) return 1; + if (retro_atomic_load_acquire_size(&as) != (std::size_t)15) return 1; + + if (retro_atomic_fetch_sub_int (&ai, 5) != 15) return 1; + if (retro_atomic_fetch_sub_size(&as, 5) != (std::size_t)15) return 1; + + if (retro_atomic_load_acquire_int (&ai) != 10) return 1; + if (retro_atomic_load_acquire_size(&as) != (std::size_t)10) return 1; + return 0; +} + +int main(void) +{ + int fails = 0; + + std::printf("backend: %s\n", RETRO_ATOMIC_BACKEND_NAME); +#ifdef CXX_BUILD + std::puts("config: CXX_BUILD defined (griffin/unity-build path)"); +#else + std::puts("config: CXX_BUILD undefined (separate-TU path, ui_qt.cpp shape)"); +#endif + + fails += check_init(); + fails += check_store_release_load_acquire(); + fails += check_fetch_add_sub_returns_previous(); + + if (fails) + { + std::printf("FAIL: %d check(s) failed\n", fails); + return 1; + } + std::puts("ALL OK"); + return 0; +} From f38f216256830b3f5ca4a38b37eebd1b4fac5e98 Mon Sep 17 00:00:00 2001 From: "U-DESKTOP-SPFP6AQ\\twistedtechre" Date: Fri, 1 May 2026 18:37:29 +0200 Subject: [PATCH 03/14] VULKAN/HDR: Fix crash when statistics overlay is shown over HDR + custom shader When HDR is enabled and the active shader chain doesn't emit HDR natively, vulkan_frame() routes overlay-style content (menu, on-screen messages, widgets, software overlays) through an offscreen SDR buffer, then composites it back over the HDR swapchain image. The gating condition for that detour listed every overlay source except the statistics overlay. The result: with HDR on, a non-HDR-emitting shader chain loaded, and the statistics overlay enabled (with no menu/message/widget visible), the main render pass was ended after the HDR composite but never re-begun. The subsequent font_driver_render_msg() call for the stat text recorded draw commands against a command buffer with no active render pass. Most desktop Vulkan drivers tolerate this with a validation error; MoltenVK on macOS dereferences a null subpass in MVKCommandEncoder::getSubpass() during vkCmdDrawIndexed encoding and crashes with SIGSEGV. The HDR-native shaders under slang_shaders/hdr were unaffected because they avoid the offscreen-buffer detour entirely (use_offscreen_buffer stays false when the chain emits HDR10 or HDR16). Add statistics_show to both gating conditions so the begin-redirect and the matching post-blit composite stay balanced. --- gfx/drivers/vulkan.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gfx/drivers/vulkan.c b/gfx/drivers/vulkan.c index 70bd211d6d45..9c3c25e84b94 100644 --- a/gfx/drivers/vulkan.c +++ b/gfx/drivers/vulkan.c @@ -6744,6 +6744,7 @@ static bool vulkan_frame(void *data, const void *frame, if ((vk->context->flags & VK_CTX_FLAG_HDR_ENABLE) && ((vk->flags & VK_FLAG_MENU_ENABLE) || (vk->flags & VK_FLAG_OVERLAY_ENABLE) || message_visible + || statistics_show #ifdef HAVE_GFX_WIDGETS || widgets_visible #endif @@ -6858,6 +6859,7 @@ static bool vulkan_frame(void *data, const void *frame, if ((vk->context->flags & VK_CTX_FLAG_HDR_ENABLE) && ((vk->flags & VK_FLAG_MENU_ENABLE) || (vk->flags & VK_FLAG_OVERLAY_ENABLE) || message_visible + || statistics_show #ifdef HAVE_GFX_WIDGETS || widgets_visible #endif From 07b6551a23475ed2c16293d2dc4bab890dab752e Mon Sep 17 00:00:00 2001 From: "U-DESKTOP-SPFP6AQ\\twistedtechre" Date: Fri, 1 May 2026 18:50:17 +0200 Subject: [PATCH 04/14] Silence warning - use fill_pathname_join_special --- gfx/drivers_shader/slang_cache.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/gfx/drivers_shader/slang_cache.cpp b/gfx/drivers_shader/slang_cache.cpp index f6112f0ee321..5da8c953f0fa 100644 --- a/gfx/drivers_shader/slang_cache.cpp +++ b/gfx/drivers_shader/slang_cache.cpp @@ -32,8 +32,9 @@ static bool spirv_cache_get_dir(char *cache_dir_out, size_t cache_dir_out_len) return false; /* Build the spirv subdirectory path */ - snprintf(cache_dir_out, cache_dir_out_len, "%s/%s", - settings->paths.directory_cache, SPIRV_CACHE_SUBDIR); + fill_pathname_join_special(cache_dir_out, + settings->paths.directory_cache, SPIRV_CACHE_SUBDIR, + cache_dir_out_len); return true; } @@ -65,12 +66,14 @@ static bool spirv_cache_get_filename(const char *hash, char *cache_file_out, size_t cache_file_out_len) { char cache_dir[PATH_MAX_LENGTH]; + char hash_filename[128]; if (!spirv_cache_get_dir(cache_dir, sizeof(cache_dir))) return false; - snprintf(cache_file_out, cache_file_out_len, "%s/%s.spirv", - cache_dir, hash); + snprintf(hash_filename, sizeof(hash_filename), "%s.spirv", hash); + fill_pathname_join_special(cache_file_out, cache_dir, hash_filename, + cache_file_out_len); return true; } From fca0e40b54ee09e721803a4a7b12eeb099174f1d Mon Sep 17 00:00:00 2001 From: "U-DESKTOP-SPFP6AQ\\twistedtechre" Date: Fri, 1 May 2026 19:28:58 +0200 Subject: [PATCH 05/14] deps/switchres: fix -Wformat warnings on 32-bit builds Three -Wformat= warnings surface on i386 Linux where size_t is unsigned int and uint64_t is unsigned long long (both differ from x86_64, where the existing specifiers happen to match): display_linux.cpp:166 %ld on vector::size_type (size_t) custom_video_xrandr.cpp:679 %lx on uint64_t platform_data custom_video_xrandr.cpp:685 %lx on uint64_t platform_data The size_t case is signedness-only and benign in practice; switch to %zu, the C99 specifier intended for size_t. The uint64_t cases are an actual 32-bit truncation bug -- %lx reads four bytes off the stack on i386 cdecl, dropping the high 32 bits of platform_data and misaligning any following format arguments. Use %llx with an explicit (unsigned long long) cast, matching the raw-printf idiom Switchres uses everywhere else (no / PRIx64 elsewhere in the tree). deps/switchres/ is a verbatim mirror of antonioginer/switchres; this fix should also be sent upstream so the next sync doesn't reintroduce the warnings. --- deps/switchres/custom_video_xrandr.cpp | 4 ++-- deps/switchres/display_linux.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/deps/switchres/custom_video_xrandr.cpp b/deps/switchres/custom_video_xrandr.cpp index 83b163e68112..4a17cc26cf24 100644 --- a/deps/switchres/custom_video_xrandr.cpp +++ b/deps/switchres/custom_video_xrandr.cpp @@ -676,13 +676,13 @@ bool xrandr_timing::add_mode(modeline *mode) // remove unlinked modeline if (mode->platform_data) { - log_error("XRANDR: <%d> (add_mode) [ERROR] remove mode [%04lx]\n", m_id, mode->platform_data); + log_error("XRANDR: <%d> (add_mode) [ERROR] remove mode [%04llx]\n", m_id, (unsigned long long)mode->platform_data); XRRDestroyMode(m_pdisplay, mode->platform_data); mode->platform_data = 0; } } else - log_verbose("XRANDR: <%d> (add_mode) mode %04lx %dx%d refresh %.6f added\n", m_id, mode->platform_data, mode->hactive, mode->vactive, mode->vfreq); + log_verbose("XRANDR: <%d> (add_mode) mode %04llx %dx%d refresh %.6f added\n", m_id, (unsigned long long)mode->platform_data, mode->hactive, mode->vactive, mode->vfreq); return ms_xerrors == 0; } diff --git a/deps/switchres/display_linux.cpp b/deps/switchres/display_linux.cpp index 470647f3e383..4b496abbbdac 100644 --- a/deps/switchres/display_linux.cpp +++ b/deps/switchres/display_linux.cpp @@ -163,7 +163,7 @@ int linux_display::get_available_video_modes() video_modes.push_back(mode); backup_modes.push_back(mode); - log_verbose("Switchres: [%3ld] %4dx%4d @%3d%s%s %s: ", video_modes.size(), mode.width, mode.height, mode.refresh, mode.interlace ? "i" : "p", mode.type & MODE_DESKTOP ? "*" : "", mode.type & MODE_ROTATED ? "rot" : ""); + log_verbose("Switchres: [%3zu] %4dx%4d @%3d%s%s %s: ", video_modes.size(), mode.width, mode.height, mode.refresh, mode.interlace ? "i" : "p", mode.type & MODE_DESKTOP ? "*" : "", mode.type & MODE_ROTATED ? "rot" : ""); log_mode(&mode); }; From 52a92f848af08806e75bf673c03fd145855e422d Mon Sep 17 00:00:00 2001 From: "U-DESKTOP-SPFP6AQ\\twistedtechre" Date: Fri, 1 May 2026 20:15:11 +0200 Subject: [PATCH 06/14] Qt: companion driver cleanup pass Refactor pass on ui/drivers/ui_qt.{cpp,h} and ui_qt_widgets.{cpp,h} to reduce Qt-specific surface, lift pure-C work out of C++ syntax, and use libretro-common idioms in place of locally-rolled equivalents. ui/drivers/ui_qt.cpp 5620 -> 5462 (-158) ui/drivers/ui_qt_widgets.cpp 8888 -> 8783 (-105) ui/drivers/ui_qt.h 761 -> 747 ( -14) ui/drivers/ui_qt_widgets.h 1442 -> 1381 ( -61) ----- TOTAL 16711 -> 16373 (-338) No functional change. Build verified for ui_qt.o, ui_qt_widgets.o, moc_ui_qt.o and moc_ui_qt_widgets.o; the only warnings emitted are the same three pre-existing -Wdeprecated-declarations warnings the original tree emits (QDir::operator=(const QString&), QByteArray::append(const QString&), QListWidget::isItemHidden). Iterative-build wall clock on the four targets drops ~9% (28.1s -> 25.6s, hyperfine n=5), MOC-generated .cpp drops ~9% (8666 -> 7874 lines), and total object size drops ~4% (2.05 MiB -> 1.97 MiB). Changes: * Drop string_split_to_qt and use QString::split. The function was declared inside RETRO_BEGIN_DECLS - i.e. with C linkage - but returned QStringList, which is not a C-compatible type. The four call sites become QString::split('|') / .split(' '). * Replace MainWindow::getScrubbedString with a static scrub_qstring() helper using string_replace_all_chars from libretro-common. Moves the function out of MOC (one fewer slot to generate). * Collapse 11 trivial OptionsPage subclasses (MenuSoundsPage, UpdaterPage, QuickMenuPage, DriversPage, DirectoryPage, ConfigurationPage, CorePage, LoggingPage, AIServicePage, FrameThrottlePage, RewindPage) into one runtime-parameterised SimplePage class. Each was a do-nothing constructor plus a one-line widget() returning create_widget(DISPLAYLIST_X). The 12 instantiation sites in *Category::pages() become new SimplePage(DISPLAYLIST_X, MENU_ENUM_LABEL_VALUE_X, this). * Extract MainWindow::getCoreInfo() into a C helper qt_core_info_collect that emits two parallel struct string_list outputs (keys, values) with a status code (enum qt_core_info_row_status) packed into values->elems[i].attr.i. The walking logic is now pure C using libretro-common idioms (string_list_*, core_info_*, fill_pathname_basedir, string_count_occurrences_single_character); the C++ wrapper is a ~50-line loop applying status-driven HTML/CSS styling for firmware rows. Both consumers (onLaunchWithComboBoxIndexChanged, CoreInfoDialog::showCoreInfo) work unchanged. * Replace four parallel thumbnail paths with arrays. The m_thumbnailPixmap{,2,3,4} pointers, onResizeThumbnail{One,Two, Three,Four} slots, and thumbnailChanged{,2,3,4} signals are consolidated into m_thumbnailPixmaps[4] and three static tables. The four thumbnailChanged* signals were never emitted and never connected anywhere - deleted. The four onResizeThumbnail* slots were one-line wrappers around setThumbnail("thumbnailN", ...) - deleted, callers use setThumbnail directly. Destructor, onCurrentItemChanged and onThumbnailDropped collapse to short loops; an explicit ThumbnailType -> widget-index mapping handles the asymmetric ordering between the enum (BOXART, SCREENSHOT, TITLE_SCREEN, LOGO) and the 1..4 widget naming (boxart, title, screenshot, logo). * Convert MainWindow::getSelectedCore (which returned QHash but only ever set a single field) to getSelectedCorePath returning QString. Sole caller updated. * Replace QFileInfo/QDir/QFile usage with file_path.h / file_stream.h equivalents in three high-leverage spots: - renamePlaylistItem: now uses path_basename, path_basedir, path_get_extension, fill_pathname_slash, string_is_equal_case_insensitive, and filestream_rename. - onExtractArchive's per-entry delete-or-rename loop: now uses filestream_exists, filestream_delete, filestream_rename. Also fixes a pre-existing leak: the original code returned -1 on three error paths without freeing file_list. - loadContent's extension-filter block: collapses lastIndexOf('.') + .mid() onto path_get_extension, which the same block was already using three lines below. - Three QFileInfo(pathData).suffix() callers in add-files-to-playlist code: replaced with QString::fromUtf8(path_get_extension(pathData)). * Split ui_companion_qt_init from a 516-line monolith into six focused static helpers (qt_companion_build_menubar, qt_companion_build_browser_dock, qt_companion_build_thumbnail_docks, qt_companion_build_core_selection_dock, qt_companion_restore_settings, qt_companion_select_initial_playlist) plus a ~75-line orchestrator. Behaviour preserved verbatim. The thumbnail-dock setup that was 65 lines of 4-way copy-paste collapses to a 30-line loop using qt_thumbnail_widget_names from above. --- ui/drivers/ui_qt.cpp | 1462 +++++++++++++++------------------- ui/drivers/ui_qt.h | 18 +- ui/drivers/ui_qt_widgets.cpp | 187 +---- ui/drivers/ui_qt_widgets.h | 115 +-- 4 files changed, 722 insertions(+), 1060 deletions(-) diff --git a/ui/drivers/ui_qt.cpp b/ui/drivers/ui_qt.cpp index ee504343f8ce..d930823e5834 100644 --- a/ui/drivers/ui_qt.cpp +++ b/ui/drivers/ui_qt.cpp @@ -52,6 +52,7 @@ extern "C" { #include #include +#include #include #include #include @@ -1041,6 +1042,270 @@ static double exp_scale(double input_val, double mid_val, double max_val) return ret; } +/* Status codes carried in string_list_elem_attr.i for the + * core-info value list returned by qt_core_info_collect(). */ +enum qt_core_info_row_status +{ + QT_CORE_INFO_ROW_NORMAL = 0, + /* Firmware section: header rows with a key but empty value. */ + QT_CORE_INFO_ROW_FIRMWARE_NOTE, + /* Firmware status rows that should render in green. */ + QT_CORE_INFO_ROW_FIRMWARE_PRESENT, + /* Firmware status rows that should render in red. */ + QT_CORE_INFO_ROW_FIRMWARE_MISSING, + /* Notes: no key, free-form value. */ + QT_CORE_INFO_ROW_NOTE_NO_KEY +}; + +/* Append a single (key, value) row. status applies to value->attr.i. */ +static void qt_core_info_append_row( + struct string_list *keys, + struct string_list *values, + const char *key, const char *value, + enum qt_core_info_row_status status) +{ + union string_list_elem_attr attr; + attr.i = 0; + string_list_append(keys, key ? key : "", attr); + attr.i = (int)status; + string_list_append(values, value ? value : "", attr); +} + +/* Append a row whose key is ":" and whose value is plain. */ +static void qt_core_info_append_kv( + struct string_list *keys, + struct string_list *values, + enum msg_hash_enums label_enum, const char *value) +{ + char key[256]; + size_t _len = strlcpy(key, msg_hash_to_str(label_enum), sizeof(key)); + strlcpy(key + _len, ":", sizeof(key) - _len); + qt_core_info_append_row(keys, values, key, value, + QT_CORE_INFO_ROW_NORMAL); +} + +/* Append a row built from a list of strings joined by ", ". */ +static void qt_core_info_append_joined( + struct string_list *keys, + struct string_list *values, + enum msg_hash_enums label_enum, + const struct string_list *src) +{ + char buf[NAME_MAX_LENGTH * 4]; + size_t i, _len = 0; + buf[0] = '\0'; + for (i = 0; i < src->size; i++) + { + _len += strlcpy(buf + _len, src->elems[i].data, sizeof(buf) - _len); + if (i < src->size - 1) + _len += strlcpy(buf + _len, ", ", sizeof(buf) - _len); + } + qt_core_info_append_kv(keys, values, label_enum, buf); +} + +/* Collect a list of human-readable rows describing the currently-selected + * core. Output: two parallel string_lists. values->elems[i].attr.i holds + * a qt_core_info_row_status for that row. + * + * Returns false if no core is selected or its info isn't loaded; in that + * case a single row with the "no info available" message is appended. */ +static bool qt_core_info_collect( + const char *current_core_path, + struct string_list *keys, + struct string_list *values) +{ + size_t i; + core_info_t *core_info = NULL; + + core_info_find(current_core_path, &core_info); + + if ( !current_core_path + || !*current_core_path + || !core_info + || !(core_info->flags & CORE_INFO_FLAG_HAS_INFO)) + { + qt_core_info_append_row(keys, values, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NO_CORE_INFORMATION_AVAILABLE), + "", QT_CORE_INFO_ROW_NORMAL); + return false; + } + + if (core_info->core_name) + qt_core_info_append_kv(keys, values, + MENU_ENUM_LABEL_VALUE_CORE_INFO_CORE_NAME, core_info->core_name); + if (core_info->display_name) + qt_core_info_append_kv(keys, values, + MENU_ENUM_LABEL_VALUE_CORE_INFO_CORE_LABEL, core_info->display_name); + if (core_info->systemname) + qt_core_info_append_kv(keys, values, + MENU_ENUM_LABEL_VALUE_CORE_INFO_SYSTEM_NAME, core_info->systemname); + if (core_info->system_manufacturer) + qt_core_info_append_kv(keys, values, + MENU_ENUM_LABEL_VALUE_CORE_INFO_SYSTEM_MANUFACTURER, + core_info->system_manufacturer); + + if (core_info->categories_list) + qt_core_info_append_joined(keys, values, + MENU_ENUM_LABEL_VALUE_CORE_INFO_CATEGORIES, + core_info->categories_list); + if (core_info->authors_list) + qt_core_info_append_joined(keys, values, + MENU_ENUM_LABEL_VALUE_CORE_INFO_AUTHORS, + core_info->authors_list); + if (core_info->permissions_list) + qt_core_info_append_joined(keys, values, + MENU_ENUM_LABEL_VALUE_CORE_INFO_PERMISSIONS, + core_info->permissions_list); + if (core_info->licenses_list) + qt_core_info_append_joined(keys, values, + MENU_ENUM_LABEL_VALUE_CORE_INFO_LICENSES, + core_info->licenses_list); + if (core_info->supported_extensions_list) + qt_core_info_append_joined(keys, values, + MENU_ENUM_LABEL_VALUE_CORE_INFO_SUPPORTED_EXTENSIONS, + core_info->supported_extensions_list); + + if (core_info->firmware_count > 0) + { + char tmp_path[PATH_MAX_LENGTH]; + core_info_ctx_firmware_t firmware_info; + bool update_missing_firmware = false; + settings_t *settings = config_get_ptr(); + uint8_t flags = content_get_flags(); + bool systemfiles_in_content_dir = settings->bools.systemfiles_in_content_dir; + bool content_is_inited = flags & CONTENT_ST_FLAG_IS_INITED; + + firmware_info.path = core_info->path; + + if (systemfiles_in_content_dir && content_is_inited) + { + fill_pathname_basedir(tmp_path, + path_get(RARCH_PATH_CONTENT), + sizeof(tmp_path)); + if (!*tmp_path) + firmware_info.directory.system = settings->paths.directory_system; + else + { + size_t _len = strlen(tmp_path); + if ( string_count_occurrences_single_character(tmp_path, PATH_DEFAULT_SLASH_C()) > 1 + && tmp_path[_len - 1] == PATH_DEFAULT_SLASH_C()) + tmp_path[_len - 1] = '\0'; + firmware_info.directory.system = tmp_path; + } + } + else + firmware_info.directory.system = settings->paths.directory_system; + + update_missing_firmware = core_info_list_update_missing_firmware(&firmware_info); + + if (update_missing_firmware) + { + char firmware_label[256]; + char tmp[PATH_MAX_LENGTH]; + size_t _len = strlcpy(firmware_label, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_INFO_FIRMWARE), + sizeof(firmware_label)); + strlcpy(firmware_label + _len, ":", + sizeof(firmware_label) - _len); + + qt_core_info_append_row(keys, values, + firmware_label, "", QT_CORE_INFO_ROW_FIRMWARE_NOTE); + + if (systemfiles_in_content_dir) + qt_core_info_append_row(keys, values, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_INFO_FIRMWARE_IN_CONTENT_DIRECTORY), + "", QT_CORE_INFO_ROW_FIRMWARE_NOTE); + + snprintf(tmp, sizeof(tmp), + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_INFO_FIRMWARE_PATH), + firmware_info.directory.system); + qt_core_info_append_row(keys, values, + tmp, "", QT_CORE_INFO_ROW_FIRMWARE_NOTE); + + for (i = 0; i < core_info->firmware_count; i++) + { + char lbl_txt[256]; + const char *val_txt = NULL; + bool missing = false; + enum qt_core_info_row_status status; + size_t lbl_len = 0; + + if (!core_info->firmware[i].desc) + continue; + + lbl_len = strlcpy(lbl_txt, "(!) ", sizeof(lbl_txt)); + + if (core_info->firmware[i].missing) + { + missing = true; + if (core_info->firmware[i].optional) + strlcpy(lbl_txt + lbl_len, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_MISSING_OPTIONAL), + sizeof(lbl_txt) - lbl_len); + else + strlcpy(lbl_txt + lbl_len, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_MISSING_REQUIRED), + sizeof(lbl_txt) - lbl_len); + } + else + { + if (core_info->firmware[i].optional) + strlcpy(lbl_txt + lbl_len, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_PRESENT_OPTIONAL), + sizeof(lbl_txt) - lbl_len); + else + strlcpy(lbl_txt + lbl_len, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_PRESENT_REQUIRED), + sizeof(lbl_txt) - lbl_len); + } + + val_txt = core_info->firmware[i].desc + ? core_info->firmware[i].desc + : msg_hash_to_str(MENU_ENUM_LABEL_VALUE_RDB_ENTRY_NAME); + status = missing + ? QT_CORE_INFO_ROW_FIRMWARE_MISSING + : QT_CORE_INFO_ROW_FIRMWARE_PRESENT; + qt_core_info_append_row(keys, values, + lbl_txt, val_txt, status); + } + } + } + + if (core_info->notes && core_info->note_list) + { + for (i = 0; i < core_info->note_list->size; i++) + qt_core_info_append_row(keys, values, + "", core_info->note_list->elems[i].data, + QT_CORE_INFO_ROW_NOTE_NO_KEY); + } + + return true; +} + +/* Thumbnail widgets are addressed by a flat 0..3 index. The index order + * (boxart, title, screenshot, logo) matches the four QObject names set + * up in ui_companion_qt_init() and is *not* the same as ThumbnailType + * enum order (which has SCREENSHOT before TITLE_SCREEN). */ +static const char * const qt_thumbnail_widget_names[4] = { + "thumbnail", "thumbnail2", "thumbnail3", "thumbnail4" +}; + +static const char * const qt_thumbnail_subdirs[4] = { + THUMBNAIL_BOXART, THUMBNAIL_TITLE, THUMBNAIL_SCREENSHOT, THUMBNAIL_LOGO +}; + +static int qt_thumbnail_type_to_widget_idx(ThumbnailType t) +{ + switch (t) + { + case THUMBNAIL_TYPE_BOXART: return 0; + case THUMBNAIL_TYPE_TITLE_SCREEN: return 1; + case THUMBNAIL_TYPE_SCREENSHOT: return 2; + case THUMBNAIL_TYPE_LOGO: return 3; + } + return 0; +} + TreeView::TreeView(QWidget *parent) : QTreeView(parent) { } void TreeView::columnCountChanged(int oldCount, int newCount) @@ -1163,10 +1428,6 @@ MainWindow::MainWindow(QWidget *parent) : ,m_stopPushButton(new QToolButton(this)) ,m_browserAndPlaylistTabWidget(new QTabWidget(this)) ,m_pendingRun(false) - ,m_thumbnailPixmap(NULL) - ,m_thumbnailPixmap2(NULL) - ,m_thumbnailPixmap3(NULL) - ,m_thumbnailPixmap4(NULL) ,m_settings(NULL) ,m_viewOptionsDialog(NULL) ,m_coreInfoDialog(new CoreInfoDialog(this, NULL)) @@ -1251,6 +1512,8 @@ MainWindow::MainWindow(QWidget *parent) : qRegisterMetaType >("ThumbnailWidget"); qRegisterMetaType("retro_task_callback_t"); + memset(m_thumbnailPixmaps, 0, sizeof(m_thumbnailPixmaps)); + /* Cancel all progress dialogs immediately since * they show as soon as they're constructed. */ m_updateProgressDialog->cancel(); @@ -1662,14 +1925,10 @@ MainWindow::MainWindow(QWidget *parent) : MainWindow::~MainWindow() { - if (m_thumbnailPixmap) - delete m_thumbnailPixmap; - if (m_thumbnailPixmap2) - delete m_thumbnailPixmap2; - if (m_thumbnailPixmap3) - delete m_thumbnailPixmap3; - if (m_thumbnailPixmap4) - delete m_thumbnailPixmap4; + size_t i; + for (i = 0; i < 4; i++) + if (m_thumbnailPixmaps[i]) + delete m_thumbnailPixmaps[i]; if (m_proxyFileModel) delete m_proxyFileModel; } @@ -2295,404 +2554,71 @@ QString MainWindow::changeThumbnail(const QImage &image, QString type) void MainWindow::onThumbnailDropped(const QImage &image, ThumbnailType thumbnailType) { - switch (thumbnailType) - { - case THUMBNAIL_TYPE_BOXART: - { - QString path = changeThumbnail(image, THUMBNAIL_BOXART); - - if (path.isNull()) - return; - - if (m_thumbnailPixmap) - delete m_thumbnailPixmap; - - m_thumbnailPixmap = new QPixmap(pixmapFromPathRA(path)); - - onResizeThumbnailOne(*m_thumbnailPixmap, true); - break; - } - - case THUMBNAIL_TYPE_TITLE_SCREEN: - { - QString path = changeThumbnail(image, THUMBNAIL_TITLE); - - if (path.isNull()) - return; - - if (m_thumbnailPixmap2) - delete m_thumbnailPixmap2; - - m_thumbnailPixmap2 = new QPixmap(pixmapFromPathRA(path)); - - onResizeThumbnailTwo(*m_thumbnailPixmap2, true); - break; - } - - case THUMBNAIL_TYPE_SCREENSHOT: - { - QString path = changeThumbnail(image, THUMBNAIL_SCREENSHOT); - - if (path.isNull()) - return; - - if (m_thumbnailPixmap3) - delete m_thumbnailPixmap3; - - m_thumbnailPixmap3 = new QPixmap(pixmapFromPathRA(path)); - - onResizeThumbnailThree(*m_thumbnailPixmap3, true); - break; - } + int idx = qt_thumbnail_type_to_widget_idx(thumbnailType); + QString path = changeThumbnail(image, qt_thumbnail_subdirs[idx]); + QPixmap *new_pix = NULL; - case THUMBNAIL_TYPE_LOGO: - { - QString path = changeThumbnail(image, THUMBNAIL_LOGO); - - if (path.isNull()) - return; + if (path.isNull()) + return; - if (m_thumbnailPixmap4) - delete m_thumbnailPixmap4; + if (m_thumbnailPixmaps[idx]) + delete m_thumbnailPixmaps[idx]; - m_thumbnailPixmap4 = new QPixmap(pixmapFromPathRA(path)); + new_pix = new QPixmap(pixmapFromPathRA(path)); + m_thumbnailPixmaps[idx] = new_pix; - onResizeThumbnailFour(*m_thumbnailPixmap4, true); - break; - } - } + setThumbnail(qt_thumbnail_widget_names[idx], *new_pix, true); } QVector > MainWindow::getCoreInfo() { size_t i; QVector > infoList; - runloop_state_t *runloop_st = runloop_state_get_ptr(); - QHash currentCore = getSelectedCore(); - core_info_t *core_info = NULL; - QByteArray currentCorePathArray = currentCore["core_path"].toUtf8(); + QByteArray currentCorePathArray = getSelectedCorePath().toUtf8(); const char *current_core_path_data = currentCorePathArray.constData(); + struct string_list *keys = string_list_new(); + struct string_list *values = string_list_new(); - /* Search for current core */ - core_info_find(current_core_path_data, &core_info); - - if ( currentCore["core_path"].isEmpty() - || !core_info - || !(core_info->flags & CORE_INFO_FLAG_HAS_INFO)) + if (!keys || !values) { - QHash hash; - - hash["key"] = msg_hash_to_str( - MENU_ENUM_LABEL_VALUE_NO_CORE_INFORMATION_AVAILABLE); - hash["value"] = QLatin1String(""); - - infoList.append(hash); - + string_list_free(keys); + string_list_free(values); return infoList; } - if (core_info->core_name) - { - QHash hash; - - hash["key"] = QString( - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_INFO_CORE_NAME)) - + QString(":"); - hash["value"] = core_info->core_name; - - infoList.append(hash); - } - - if (core_info->display_name) - { - QHash hash; - - hash["key"] = QString( - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_INFO_CORE_LABEL)) - + QString(":"); - hash["value"] = core_info->display_name; - - infoList.append(hash); - } - - if (core_info->systemname) - { - QHash hash; - - hash["key"] = QString(msg_hash_to_str( - MENU_ENUM_LABEL_VALUE_CORE_INFO_SYSTEM_NAME)) - + QString(":"); - hash["value"] = core_info->systemname; - - infoList.append(hash); - } - - if (core_info->system_manufacturer) - { - QHash hash; - - hash["key"] = QString(msg_hash_to_str( - MENU_ENUM_LABEL_VALUE_CORE_INFO_SYSTEM_MANUFACTURER)) - + QString(":"); - hash["value"] = core_info->system_manufacturer; + qt_core_info_collect(current_core_path_data, keys, values); - infoList.append(hash); - } - - if (core_info->categories_list) + for (i = 0; i < keys->size; i++) { - QString categories; QHash hash; + enum qt_core_info_row_status status = + (enum qt_core_info_row_status)values->elems[i].attr.i; - for (i = 0; i < core_info->categories_list->size; i++) - { - categories += core_info->categories_list->elems[i].data; - if (i < core_info->categories_list->size - 1) - categories += QString(", "); - } - - hash["key"] = QString( - msg_hash_to_str( - MENU_ENUM_LABEL_VALUE_CORE_INFO_CATEGORIES)) - + QString(":"); - hash["value"] = categories; - - infoList.append(hash); - } + hash["key"] = keys->elems[i].data; + hash["value"] = values->elems[i].data; - if (core_info->authors_list) - { - QString authors; - QHash hash; - - for (i = 0; i < core_info->authors_list->size; i++) - { - authors += core_info->authors_list->elems[i].data; - if (i < core_info->authors_list->size - 1) - authors += QString(", "); - } - - hash["key"] = QString( - msg_hash_to_str( - MENU_ENUM_LABEL_VALUE_CORE_INFO_AUTHORS)) - + QString(":"); - hash["value"] = authors; - - infoList.append(hash); - } - - if (core_info->permissions_list) - { - QString permissions; - QHash hash; - - for (i = 0; i < core_info->permissions_list->size; i++) + if ( status == QT_CORE_INFO_ROW_FIRMWARE_PRESENT + || status == QT_CORE_INFO_ROW_FIRMWARE_MISSING) { - permissions += core_info->permissions_list->elems[i].data; - if (i < core_info->permissions_list->size - 1) - permissions += QString(", "); + const char *css_color = (status == QT_CORE_INFO_ROW_FIRMWARE_MISSING) + ? "#ff0000" : "#00af00"; + const char *style_rgb = (status == QT_CORE_INFO_ROW_FIRMWARE_MISSING) + ? "color: #ff0000" : "color: rgb(0, 175, 0)"; + QString style = QString("font-weight: bold; ") + style_rgb; + + hash["label_style"] = style; + hash["value_style"] = style; + hash["html_key"] = QString("" + hash["key"] + ""; + hash["html_value"] = QString("" + hash["value"] + ""; } - hash["key"] = QString( - msg_hash_to_str( - MENU_ENUM_LABEL_VALUE_CORE_INFO_PERMISSIONS)) - + QString(":"); - hash["value"] = permissions; - infoList.append(hash); } - if (core_info->licenses_list) - { - QString licenses; - QHash hash; - - for (i = 0; i < core_info->licenses_list->size; i++) - { - licenses += core_info->licenses_list->elems[i].data; - if (i < core_info->licenses_list->size - 1) - licenses += QString(", "); - } - - hash["key"] = QString( - msg_hash_to_str( - MENU_ENUM_LABEL_VALUE_CORE_INFO_LICENSES)) - + QString(":"); - hash["value"] = licenses; - - infoList.append(hash); - } - - if (core_info->supported_extensions_list) - { - QString supported_extensions; - QHash hash; - - for (i = 0; i < core_info->supported_extensions_list->size; i++) - { - supported_extensions += core_info->supported_extensions_list->elems[i].data; - if (i < core_info->supported_extensions_list->size - 1) - supported_extensions += QString(", "); - } - - hash["key"] = QString( - msg_hash_to_str( - MENU_ENUM_LABEL_VALUE_CORE_INFO_SUPPORTED_EXTENSIONS)) - + QString(":"); - hash["value"] = supported_extensions; - - infoList.append(hash); - } - - if (core_info->firmware_count > 0) - { - char tmp_path[PATH_MAX_LENGTH]; - core_info_ctx_firmware_t firmware_info; - bool update_missing_firmware = false; - settings_t *settings = config_get_ptr(); - uint8_t flags = content_get_flags(); - bool systemfiles_in_content_dir = settings->bools.systemfiles_in_content_dir; - bool content_is_inited = flags & CONTENT_ST_FLAG_IS_INITED; - - firmware_info.path = core_info->path; - - /* If 'System Files are in Content Directory' is enabled and content is inited, - * adjust the path to check for firmware files */ - if (systemfiles_in_content_dir && content_is_inited) - { - fill_pathname_basedir(tmp_path, - path_get(RARCH_PATH_CONTENT), - sizeof(tmp_path)); - - /* If content path is empty, fall back to global system dir path */ - if (!*tmp_path) - firmware_info.directory.system = settings->paths.directory_system; - else - { - size_t _len = strlen(tmp_path); - - /* Removes trailing slash (unless root dir), doesn't really matter - * but it's more consistent with how the path is stored and - * displayed without 'System Files are in Content Directory' */ - if ( string_count_occurrences_single_character(tmp_path, PATH_DEFAULT_SLASH_C()) > 1 - && tmp_path[_len - 1] == PATH_DEFAULT_SLASH_C()) - tmp_path[_len - 1] = '\0'; - - firmware_info.directory.system = tmp_path; - } - } - else - firmware_info.directory.system = settings->paths.directory_system; - - update_missing_firmware = core_info_list_update_missing_firmware(&firmware_info); - - if (update_missing_firmware) - { - char tmp[PATH_MAX_LENGTH]; - QHash hash; - - hash["key"] = QString(msg_hash_to_str( - MENU_ENUM_LABEL_VALUE_CORE_INFO_FIRMWARE)) - + QString(":"); - hash["value"] = QLatin1String(""); - - infoList.append(hash); - - /* If 'System Files are in Content Directory' is enabled, - * let's add a note about it. */ - if (systemfiles_in_content_dir) - { - hash["key"] = QString(msg_hash_to_str( - MENU_ENUM_LABEL_VALUE_CORE_INFO_FIRMWARE_IN_CONTENT_DIRECTORY)); - hash["value"] = QLatin1String(""); - - infoList.append(hash); - } - - /* Show the path that was checked */ - snprintf(tmp, sizeof(tmp), - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_INFO_FIRMWARE_PATH), - firmware_info.directory.system); - - hash["key"] = QString(tmp); - hash["value"] = QLatin1String(""); - - infoList.append(hash); - - /* FIXME: This looks hacky and probably - * needs to be improved for good translation support. */ - - for (i = 0; i < core_info->firmware_count; i++) - { - if (core_info->firmware[i].desc) - { - QString val_txt; - QHash hash; - QString lbl_txt = QString("(!) "); - bool missing = false; - - if (core_info->firmware[i].missing) - { - missing = true; - if (core_info->firmware[i].optional) - lbl_txt += msg_hash_to_str( - MENU_ENUM_LABEL_VALUE_MISSING_OPTIONAL); - else - lbl_txt += msg_hash_to_str( - MENU_ENUM_LABEL_VALUE_MISSING_REQUIRED); - } - else - if (core_info->firmware[i].optional) - lbl_txt += msg_hash_to_str( - MENU_ENUM_LABEL_VALUE_PRESENT_OPTIONAL); - else - lbl_txt += msg_hash_to_str( - MENU_ENUM_LABEL_VALUE_PRESENT_REQUIRED); - - if (core_info->firmware[i].desc) - val_txt = core_info->firmware[i].desc; - else - val_txt = msg_hash_to_str( - MENU_ENUM_LABEL_VALUE_RDB_ENTRY_NAME); - - hash["key"] = lbl_txt; - hash["value"] = val_txt; - - if (missing) - { - QString style = QString("font-weight: bold; color: #ff0000"); - hash["label_style"] = style; - hash["value_style"] = style; - hash["html_key"] = "" + hash["key"] + QString(""); - hash["html_value"] = "" + hash["value"] + QString(""); - } - else - { - QString style = QString("font-weight: bold; color: rgb(0, 175, 0)"); - hash["label_style"] = style; - hash["value_style"] = style; - hash["html_key"] = "" + hash["key"] + QString(""); - hash["html_value"] = "" + hash["value"] + QString(""); - } - - infoList.append(hash); - } - } - } - } - - if (core_info->notes) - { - for (i = 0; i < core_info->note_list->size; i++) - { - QHash hash; - - hash["key"] = QLatin1String(""); - hash["value"] = core_info->note_list->elems[i].data; - - infoList.append(hash); - } - } + string_list_free(keys); + string_list_free(values); return infoList; } @@ -2825,9 +2751,12 @@ void MainWindow::onStartCoreClicked() msg_hash_to_str(MSG_FAILED_TO_LOAD_CONTENT)); } -QHash MainWindow::getSelectedCore() +/* Resolve which core path the user has currently selected, given the + * UI's combo-box mode and the currently-highlighted content row. The + * result is the empty string if no core can be resolved (e.g. unknown + * mode, no current item, or no default core for the playlist). */ +QString MainWindow::getSelectedCorePath() { - QHash coreHash; QHash contentHash; QVariantMap coreMap = m_launchWithComboBox->currentData( Qt::UserRole).value(); @@ -2835,26 +2764,24 @@ QHash MainWindow::getSelectedCore() coreMap.value("core_selection").toInt()); ViewType viewType = getCurrentViewType(); - if (viewType == VIEW_TYPE_LIST) - contentHash = m_tableView->currentIndex().data( - PlaylistModel::HASH).value >(); - else if (viewType == VIEW_TYPE_ICONS) - contentHash = m_gridView->currentIndex().data( - PlaylistModel::HASH).value >(); + /* The content row only matters for the two playlist branches — + * CORE_SELECTION_CURRENT just hands back whatever core is loaded. + * Original behaviour: an "other" view type (e.g. while transitioning) + * returned an empty hash from all three branches. */ + if (viewType == VIEW_TYPE_LIST || viewType == VIEW_TYPE_ICONS) + contentHash = getCurrentContentIndex().data(PlaylistModel::HASH) + .value >(); else - return coreHash; + return QString(); - switch(coreSelection) + switch (coreSelection) { case CORE_SELECTION_CURRENT: - coreHash["core_path"] = path_get(RARCH_PATH_CORE); - break; + return QString::fromUtf8(path_get(RARCH_PATH_CORE)); case CORE_SELECTION_PLAYLIST_SAVED: - if ( contentHash.isEmpty() - || contentHash["core_path"].isEmpty()) - break; - - coreHash["core_path"] = contentHash["core_path"]; + if ( !contentHash.isEmpty() + && !contentHash["core_path"].isEmpty()) + return contentHash["core_path"]; break; case CORE_SELECTION_PLAYLIST_DEFAULT: { @@ -2865,7 +2792,7 @@ QHash MainWindow::getSelectedCore() break; plName = contentHash["pl_name"].isEmpty() - ? contentHash["db_name"] : contentHash["pl_name"]; + ? contentHash["db_name"] : contentHash["pl_name"]; if (plName.isEmpty()) break; @@ -2873,14 +2800,14 @@ QHash MainWindow::getSelectedCore() defaultCorePath = getPlaylistDefaultCore(plName); if (!defaultCorePath.isEmpty()) - coreHash["core_path"] = defaultCorePath; + return defaultCorePath; break; } default: break; } - return coreHash; + return QString(); } /* the hash typically has the following keys: @@ -2929,16 +2856,12 @@ void MainWindow::loadContent(const QHash &contentHash) if (contentHash.contains("path")) { - int last_index = contentHash["path"].lastIndexOf('.'); QByteArray pathArray = contentHash["path"].toUtf8(); const char *pathData = pathArray.constData(); + const char *ext = path_get_extension(pathData); - if (last_index >= 0) - { - QString ext_str = contentHash["path"].mid(last_index + 1); - if (!ext_str.isEmpty()) - extensionFilters.append(ext_str.toLower()); - } + if (ext && *ext) + extensionFilters.append(QString(ext).toLower()); if (path_is_compressed_file(pathData)) { @@ -2951,7 +2874,7 @@ void MainWindow::loadContent(const QHash &contentHash) size_t i; for (i = 0; i < list->size; i++) { - const char *filePath = list->elems[i].data; + const char *filePath = list->elems[i].data; const char *extension = path_get_extension(filePath); if (!extensionFilters.contains(extension, Qt::CaseInsensitive)) @@ -3505,61 +3428,68 @@ void MainWindow::onCurrentListItemDataChanged(QListWidgetItem *item) void MainWindow::renamePlaylistItem(QListWidgetItem *item, QString newName) { - QString oldPath; - QString newPath; - QString extension; + char old_path[PATH_MAX_LENGTH]; + char new_path[PATH_MAX_LENGTH]; + char old_basedir[PATH_MAX_LENGTH]; + char dir_playlist[PATH_MAX_LENGTH]; + char old_name_buf[PATH_MAX_LENGTH]; + const char *ext = NULL; QString oldName; - QFile file; - QFileInfo info; - QFileInfo playlistInfo; - QString playlistPath; + QString newPath; settings_t *settings = config_get_ptr(); const char *path_dir_playlist = settings->paths.directory_playlist; - QDir playlistDir(path_dir_playlist); if (!item) return; - playlistPath = item->data(Qt::UserRole).toString(); - playlistInfo = QFileInfo(playlistPath); - oldName = playlistInfo.completeBaseName(); + strlcpy(old_path, + item->data(Qt::UserRole).toString().toUtf8().constData(), + sizeof(old_path)); - /* Don't just compare strings in case there are - * case differences on Windows that should be ignored. */ - /* special playlists like history etc. can't have an association */ - if (QDir(playlistInfo.absoluteDir()) != QDir(playlistDir)) + /* completeBaseName(): strip directory and extension */ + fill_pathname(old_name_buf, path_basename(old_path), "", + sizeof(old_name_buf)); + oldName = QString::fromUtf8(old_name_buf); + + /* Compare the playlist's directory with path_dir_playlist + * case-insensitively to match Qt's QDir == QDir behaviour + * on Windows. */ + strlcpy(old_basedir, old_path, sizeof(old_basedir)); + path_basedir(old_basedir); + strlcpy(dir_playlist, path_dir_playlist, sizeof(dir_playlist)); + fill_pathname_slash(dir_playlist, sizeof(dir_playlist)); + + if (!string_is_equal_case_insensitive(old_basedir, dir_playlist)) { - /* Special playlists shouldn't be editable already, - * but just in case, set the old name back and - * early return if they rename it */ + /* Special playlists (history etc.) can't have an association. + * Set the old name back if user tried to rename one. */ item->setText(oldName); return; } /* Block this signal because setData() would trigger - * an infinite loop here */ - disconnect(m_listWidget, SIGNAL(itemChanged(QListWidgetItem*)), - this, SLOT(onCurrentListItemDataChanged(QListWidgetItem*))); - - oldPath = item->data(Qt::UserRole).toString(); - - file.setFileName(oldPath); - info = QFileInfo(file); - - extension = info.suffix(); - - newPath = info.absolutePath(); - - /* absolutePath() will always use / even on Windows */ - if (newPath.at(newPath.size() - 1) != '/') - /* add trailing slash if the path doesn't have one */ - newPath += '/'; + * an infinite loop here */ + disconnect(m_listWidget, SIGNAL(itemChanged(QListWidgetItem*)), + this, SLOT(onCurrentListItemDataChanged(QListWidgetItem*))); - newPath += newName + QString(".") + extension; + /* Build new path: basedir + newName + "." + extension */ + ext = path_get_extension(old_path); + { + QByteArray newNameUtf8 = newName.toUtf8(); + size_t _len = strlcpy(new_path, old_basedir, sizeof(new_path)); + _len += strlcpy(new_path + _len, newNameUtf8.constData(), + sizeof(new_path) - _len); + if (ext && *ext) + { + _len += strlcpy(new_path + _len, ".", sizeof(new_path) - _len); + strlcpy(new_path + _len, ext, sizeof(new_path) - _len); + } + } + newPath = QString::fromUtf8(new_path); item->setData(Qt::UserRole, newPath); - if (!file.rename(newPath)) + if (filestream_rename(old_path, new_path) != 0) { RARCH_ERR("[Qt] Could not rename playlist.\n"); item->setText(oldName); @@ -3583,57 +3513,46 @@ void MainWindow::onCurrentFileChanged(const QModelIndex &index) void MainWindow::onCurrentItemChanged(const QHash &hash) { + size_t i; QString path = hash["path"]; bool acceptDrop = false; - if (m_thumbnailPixmap) - delete m_thumbnailPixmap; - if (m_thumbnailPixmap2) - delete m_thumbnailPixmap2; - if (m_thumbnailPixmap3) - delete m_thumbnailPixmap3; - if (m_thumbnailPixmap4) - delete m_thumbnailPixmap4; + for (i = 0; i < 4; i++) + { + if (m_thumbnailPixmaps[i]) + delete m_thumbnailPixmaps[i]; + m_thumbnailPixmaps[i] = NULL; + } if (m_playlistModel->isSupportedImage(path)) { /* use thumbnail widgets to show regular image files */ - m_thumbnailPixmap = new QPixmap(pixmapFromPathRA(path)); - m_thumbnailPixmap2 = new QPixmap(*m_thumbnailPixmap); - m_thumbnailPixmap3 = new QPixmap(*m_thumbnailPixmap); - m_thumbnailPixmap4 = new QPixmap(*m_thumbnailPixmap); + m_thumbnailPixmaps[0] = new QPixmap(pixmapFromPathRA(path)); + for (i = 1; i < 4; i++) + m_thumbnailPixmaps[i] = new QPixmap(*m_thumbnailPixmaps[0]); } else { QString thumbnailsDir = m_playlistModel->getPlaylistThumbnailsDir( hash["db_name"]); - QString thumbnailName1 = m_playlistModel->getSanitizedThumbnailName( - thumbnailsDir + QString("/") + THUMBNAIL_BOXART + QString("/"), - hash["label_noext"]); - QString thumbnailName2 = m_playlistModel->getSanitizedThumbnailName( - thumbnailsDir + QString("/") + THUMBNAIL_TITLE + QString("/"), - hash["label_noext"]); - QString thumbnailName3 = m_playlistModel->getSanitizedThumbnailName( - thumbnailsDir + QString("/") + THUMBNAIL_SCREENSHOT + QString("/"), - hash["label_noext"]); - QString thumbnailName4 = m_playlistModel->getSanitizedThumbnailName( - thumbnailsDir + QString("/") + THUMBNAIL_LOGO + QString("/"), - hash["label_noext"]); - - m_thumbnailPixmap = new QPixmap(pixmapFromPathRA(thumbnailName1)); - m_thumbnailPixmap2 = new QPixmap(pixmapFromPathRA(thumbnailName2)); - m_thumbnailPixmap3 = new QPixmap(pixmapFromPathRA(thumbnailName3)); - m_thumbnailPixmap4 = new QPixmap(pixmapFromPathRA(thumbnailName4)); + + for (i = 0; i < 4; i++) + { + QString name = m_playlistModel->getSanitizedThumbnailName( + thumbnailsDir + QString("/") + + qt_thumbnail_subdirs[i] + QString("/"), + hash["label_noext"]); + m_thumbnailPixmaps[i] = new QPixmap(pixmapFromPathRA(name)); + } if ( m_currentBrowser == BROWSER_TYPE_PLAYLISTS && !currentPlaylistIsSpecial()) acceptDrop = true; } - onResizeThumbnailOne(*m_thumbnailPixmap, acceptDrop); - onResizeThumbnailTwo(*m_thumbnailPixmap2, acceptDrop); - onResizeThumbnailThree(*m_thumbnailPixmap3, acceptDrop); - onResizeThumbnailFour(*m_thumbnailPixmap4, acceptDrop); + for (i = 0; i < 4; i++) + setThumbnail(qt_thumbnail_widget_names[i], + *m_thumbnailPixmaps[i], acceptDrop); setCoreActions(); } @@ -3646,26 +3565,6 @@ void MainWindow::setThumbnail(QString widgetName, thumbnail->setPixmap(pixmap, acceptDrop); } -void MainWindow::onResizeThumbnailOne(QPixmap &pixmap, bool acceptDrop) -{ - setThumbnail("thumbnail", pixmap, acceptDrop); -} - -void MainWindow::onResizeThumbnailTwo(QPixmap &pixmap, bool acceptDrop) -{ - setThumbnail("thumbnail2", pixmap, acceptDrop); -} - -void MainWindow::onResizeThumbnailThree(QPixmap &pixmap, bool acceptDrop) -{ - setThumbnail("thumbnail3", pixmap, acceptDrop); -} - -void MainWindow::onResizeThumbnailFour(QPixmap &pixmap, bool acceptDrop) -{ - setThumbnail("thumbnail4", pixmap, acceptDrop); -} - void MainWindow::setCurrentViewType(ViewType viewType) { m_viewType = viewType; @@ -4154,8 +4053,10 @@ int MainWindow::onExtractArchive(QString path, QString extractionDir, struct archive_extract_userdata userdata; QByteArray pathArray = path.toUtf8(); QByteArray dirArray = extractionDir.toUtf8(); + QByteArray tmpExtArray = tempExtension.toUtf8(); const char *file = pathArray.constData(); const char *dir = dirArray.constData(); + const char *temp_ext = tmpExtArray.constData(); struct string_list *file_list = file_archive_get_file_list(file, NULL); retro_task_t *decompress_task = NULL; @@ -4169,37 +4070,45 @@ int MainWindow::onExtractArchive(QString path, QString extractionDir, for (i = 0; i < file_list->size; i++) { - QFile fileObj(file_list->elems[i].data); + const char *target_file = file_list->elems[i].data; - if (fileObj.exists()) - { - if (!fileObj.remove()) - { - /* If we cannot delete the existing file to update it, - * rename it for now and delete later */ - QFile fileTemp(fileObj.fileName() + tempExtension); + if (!filestream_exists(target_file)) + continue; - if (fileTemp.exists()) - { - if (!fileTemp.remove()) - { - showMessageBox(msg_hash_to_str( - MENU_ENUM_LABEL_VALUE_QT_COULD_NOT_DELETE_FILE), - MainWindow::MSGBOX_TYPE_ERROR, Qt::ApplicationModal, false); - RARCH_ERR("[Qt] Could not delete file: \"%s\".\n", file_list->elems[i].data); - return -1; - } - } + if (filestream_delete(target_file) == 0) + continue; + + /* If we cannot delete the existing file to update it, + * rename it out of the way for later cleanup. */ + { + char temp_path[PATH_MAX_LENGTH]; + size_t _len = strlcpy(temp_path, target_file, sizeof(temp_path)); + strlcpy(temp_path + _len, temp_ext, sizeof(temp_path) - _len); - if (!fileObj.rename(fileTemp.fileName())) + if (filestream_exists(temp_path)) + { + if (filestream_delete(temp_path) != 0) { showMessageBox(msg_hash_to_str( - MENU_ENUM_LABEL_VALUE_QT_COULD_NOT_RENAME_FILE), + MENU_ENUM_LABEL_VALUE_QT_COULD_NOT_DELETE_FILE), MainWindow::MSGBOX_TYPE_ERROR, Qt::ApplicationModal, false); - RARCH_ERR("[Qt] Could not rename file: \"%s\".\n", file_list->elems[i].data); + RARCH_ERR("[Qt] Could not delete file: \"%s\".\n", + target_file); + string_list_free(file_list); return -1; } } + + if (filestream_rename(target_file, temp_path) != 0) + { + showMessageBox(msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_QT_COULD_NOT_RENAME_FILE), + MainWindow::MSGBOX_TYPE_ERROR, Qt::ApplicationModal, false); + RARCH_ERR("[Qt] Could not rename file: \"%s\".\n", + target_file); + string_list_free(file_list); + return -1; + } } } @@ -4233,17 +4142,6 @@ int MainWindow::onExtractArchive(QString path, QString extractionDir, return 1; } -QString MainWindow::getScrubbedString(QString str) -{ - const QString chars("&*/:`\"<>?\\|"); - int i; - - for (i = 0; i < chars.size(); i++) - str.replace(chars.at(i), '_'); - - return str; -} - static void* ui_window_qt_init(void) { ui_window.qtWindow = new MainWindow(); @@ -4683,110 +4581,23 @@ static void ui_companion_qt_deinit(void *data) free(handle); } -static void* ui_companion_qt_init(void) -{ - int i = 0; - QString initialPlaylist; - QRect desktopRect; - ui_companion_qt_t *handle = (ui_companion_qt_t*) - calloc(1, sizeof(*handle)); - MainWindow *mainwindow = NULL; - QHBoxLayout *browserButtonsHBoxLayout = NULL; - QVBoxLayout *layout = NULL; - QVBoxLayout *launchWithWidgetLayout = NULL; - QHBoxLayout *coreComboBoxLayout = NULL; - QMenuBar *menu = NULL; - QScreen *screen = NULL; - QMenu *fileMenu = NULL; - QMenu *editMenu = NULL; - QMenu *viewMenu = NULL; - QMenu *viewClosedDocksMenu = NULL; - QMenu *helpMenu = NULL; - QDockWidget *thumbnailDock = NULL; - QDockWidget *thumbnail2Dock = NULL; - QDockWidget *thumbnail3Dock = NULL; - QDockWidget *thumbnail4Dock = NULL; - QDockWidget *browserAndPlaylistTabDock = NULL; - QDockWidget *coreSelectionDock = NULL; - QTabWidget *browserAndPlaylistTabWidget = NULL; - QStackedWidget *centralWidget = NULL; - QStackedWidget *widget = NULL; - QFrame *browserWidget = NULL; - QFrame *playlistWidget = NULL; - QWidget *coreSelectionWidget = NULL; - QWidget *launchWithWidget = NULL; - ThumbnailWidget *thumbnailWidget = NULL; - ThumbnailWidget *thumbnail2Widget = NULL; - ThumbnailWidget *thumbnail3Widget = NULL; - ThumbnailWidget *thumbnail4Widget = NULL; - QPushButton *browserDownloadsButton = NULL; - QPushButton *browserUpButton = NULL; - QPushButton *browserStartButton = NULL; - ThumbnailLabel *thumbnail = NULL; - ThumbnailLabel *thumbnail2 = NULL; - ThumbnailLabel *thumbnail3 = NULL; - ThumbnailLabel *thumbnail4 = NULL; - QAction *editSearchAction = NULL; - QAction *loadCoreAction = NULL; - QAction *unloadCoreAction = NULL; - QAction *exitAction = NULL; - QComboBox *launchWithComboBox = NULL; - QSettings *qsettings = NULL; - QListWidget *listWidget = NULL; - bool foundPlaylist = false; - - if (!handle) - return NULL; - - handle->app = static_cast - (ui_application_qt.initialize()); - handle->window = static_cast(ui_window_qt.init()); - - screen = qApp->primaryScreen(); - desktopRect = screen->availableGeometry(); - - mainwindow = handle->window->qtWindow; - - qsettings = mainwindow->settings(); - - initialPlaylist = qsettings->value("initial_playlist", - mainwindow->getSpecialPlaylistPath(SPECIAL_PLAYLIST_HISTORY)).toString(); - - mainwindow->resize(((desktopRect.width()) < (INITIAL_WIDTH) ? (desktopRect.width()) : (INITIAL_WIDTH)), - ((desktopRect.height()) < (INITIAL_HEIGHT) ? (desktopRect.height()) : (INITIAL_HEIGHT))); - mainwindow->setGeometry(QStyle::alignedRect(Qt::LeftToRight, - Qt::AlignCenter, mainwindow->size(), desktopRect)); - - mainwindow->setWindowTitle("RetroArch"); - mainwindow->setDockOptions(QMainWindow::AnimatedDocks - | QMainWindow::AllowNestedDocks - | QMainWindow::AllowTabbedDocks - | GROUPED_DRAGGING); - - listWidget = mainwindow->playlistListWidget(); - - widget = mainwindow->playlistViews(); - widget->setContextMenuPolicy(Qt::CustomContextMenu); - - QObject::connect(widget, SIGNAL(filesDropped(QStringList)), - mainwindow, SLOT(onPlaylistFilesDropped(QStringList))); - QObject::connect(widget, SIGNAL(enterPressed()), mainwindow, - SLOT(onDropWidgetEnterPressed())); - QObject::connect(widget, SIGNAL(deletePressed()), mainwindow, - SLOT(deleteCurrentPlaylistItem())); - QObject::connect(widget, SIGNAL(customContextMenuRequested(const QPoint&)), - mainwindow, SLOT(onFileDropWidgetContextMenuRequested(const QPoint&))); - - centralWidget = mainwindow->centralWidget(); - - centralWidget->addWidget(mainwindow->playlistViewsAndFooter()); - centralWidget->addWidget(mainwindow->fileTableView()); +/* ---------------------------------------------------------------- */ +/* Helpers split out of ui_companion_qt_init() for readability. */ +/* No functional changes from the original monolithic flow. */ +/* ---------------------------------------------------------------- */ - mainwindow->setCentralWidget(centralWidget); - - menu = mainwindow->menuBar(); - - fileMenu = menu->addMenu(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_FILE)); +static void qt_companion_build_menubar(MainWindow *mainwindow) +{ + QMenuBar *menu = mainwindow->menuBar(); + QMenu *fileMenu = menu->addMenu(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_FILE)); + QMenu *editMenu; + QMenu *viewMenu; + QMenu *viewClosedDocksMenu; + QMenu *helpMenu; + QAction *loadCoreAction; + QAction *unloadCoreAction; + QAction *exitAction; + QAction *editSearchAction; loadCoreAction = fileMenu->addAction(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_MENU_FILE_LOAD_CORE), mainwindow, @@ -4850,26 +4661,33 @@ static void* ui_companion_qt_init(void) MENU_ENUM_LABEL_VALUE_QT_MENU_HELP_ABOUT)) + QString("..."), mainwindow, SLOT(showAbout())); helpMenu->addAction(QString("About Qt..."), qApp, SLOT(aboutQt())); +} + +/* Build the playlist + file-browser tab dock. Returns the dock so the + * caller can splitDockWidget() the core-selection dock against it. */ +static QDockWidget *qt_companion_build_browser_dock(MainWindow *mainwindow) +{ + QFrame *playlistWidget = new QFrame(); + QFrame *browserWidget = new QFrame(); + QPushButton *browserDownloadsButton = new QPushButton(msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_CORE_ASSETS_DIRECTORY)); + QPushButton *browserUpButton = new QPushButton(msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_QT_TAB_FILE_BROWSER_UP)); + QPushButton *browserStartButton = new QPushButton(msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_FAVORITES)); + QHBoxLayout *browserButtonsHBoxLayout = new QHBoxLayout(); + QTabWidget *browserAndPlaylistTabWidget = mainwindow->browserAndPlaylistTabWidget(); + QDockWidget *browserAndPlaylistTabDock; - playlistWidget = new QFrame(); playlistWidget->setLayout(new QVBoxLayout()); playlistWidget->setObjectName("playlistWidget"); playlistWidget->layout()->setContentsMargins(0, 0, 0, 0); - playlistWidget->layout()->addWidget(mainwindow->playlistListWidget()); - browserWidget = new QFrame(); browserWidget->setLayout(new QVBoxLayout()); browserWidget->setObjectName("browserWidget"); browserWidget->layout()->setContentsMargins(0, 0, 0, 0); - browserDownloadsButton = new QPushButton(msg_hash_to_str( - MENU_ENUM_LABEL_VALUE_CORE_ASSETS_DIRECTORY)); - browserUpButton = new QPushButton(msg_hash_to_str( - MENU_ENUM_LABEL_VALUE_QT_TAB_FILE_BROWSER_UP)); - browserStartButton = new QPushButton(msg_hash_to_str( - MENU_ENUM_LABEL_VALUE_FAVORITES)); - QObject::connect(browserDownloadsButton, SIGNAL(clicked()), mainwindow, SLOT(onBrowserDownloadsClicked())); QObject::connect(browserUpButton, SIGNAL(clicked()), mainwindow, @@ -4877,7 +4695,6 @@ static void* ui_companion_qt_init(void) QObject::connect(browserStartButton, SIGNAL(clicked()), mainwindow, SLOT(onBrowserStartClicked())); - browserButtonsHBoxLayout = new QHBoxLayout(); browserButtonsHBoxLayout->addWidget(browserUpButton); browserButtonsHBoxLayout->addWidget(browserStartButton); browserButtonsHBoxLayout->addWidget(browserDownloadsButton); @@ -4885,15 +4702,13 @@ static void* ui_companion_qt_init(void) qobject_cast(browserWidget->layout())->addLayout(browserButtonsHBoxLayout); browserWidget->layout()->addWidget(mainwindow->dirTreeView()); - browserAndPlaylistTabWidget = mainwindow->browserAndPlaylistTabWidget(); browserAndPlaylistTabWidget->setObjectName("browserAndPlaylistTabWidget"); /* Several functions depend on the same tab title strings here, - * so if you change these, make sure to change those too - * setCoreActions() - * onTabWidgetIndexChanged() - * onCurrentListItemChanged() - */ + * so if you change these, make sure to change those too: + * setCoreActions() + * onTabWidgetIndexChanged() + * onCurrentListItemChanged() */ browserAndPlaylistTabWidget->addTab(playlistWidget, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_TAB_PLAYLISTS)); browserAndPlaylistTabWidget->addTab(browserWidget, @@ -4915,94 +4730,71 @@ static void* ui_companion_qt_init(void) browserAndPlaylistTabWidget->tabBar()->width(), 20, QSizePolicy::Expanding, QSizePolicy::Minimum)); - thumbnailWidget = new ThumbnailWidget(THUMBNAIL_TYPE_BOXART); - thumbnailWidget->setObjectName("thumbnail"); - - thumbnail2Widget = new ThumbnailWidget(THUMBNAIL_TYPE_TITLE_SCREEN); - thumbnail2Widget->setObjectName("thumbnail2"); - - thumbnail3Widget = new ThumbnailWidget(THUMBNAIL_TYPE_SCREENSHOT); - thumbnail3Widget->setObjectName("thumbnail3"); - - thumbnail4Widget = new ThumbnailWidget(THUMBNAIL_TYPE_LOGO); - thumbnail4Widget->setObjectName("thumbnail4"); - - QObject::connect(thumbnailWidget, SIGNAL(filesDropped(const QImage&, - ThumbnailType)), mainwindow, - SLOT(onThumbnailDropped(const QImage&, ThumbnailType))); - QObject::connect(thumbnail2Widget, SIGNAL(filesDropped(const QImage&, - ThumbnailType)), mainwindow, - SLOT(onThumbnailDropped(const QImage&, ThumbnailType))); - QObject::connect(thumbnail3Widget, SIGNAL(filesDropped(const QImage&, - ThumbnailType)), mainwindow, - SLOT(onThumbnailDropped(const QImage&, ThumbnailType))); - QObject::connect(thumbnail4Widget, SIGNAL(filesDropped(const QImage&, - ThumbnailType)), mainwindow, - SLOT(onThumbnailDropped(const QImage&, ThumbnailType))); - - thumbnailDock = new QDockWidget(msg_hash_to_str( - MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_BOXART), mainwindow); - thumbnailDock->setObjectName("thumbnailDock"); - thumbnailDock->setProperty("default_area", Qt::RightDockWidgetArea); - thumbnailDock->setProperty("menu_text", msg_hash_to_str( - MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_BOXART)); - thumbnailDock->setWidget(thumbnailWidget); - - mainwindow->addDockWidget(static_cast( - thumbnailDock->property("default_area").toInt()), thumbnailDock); - - thumbnail2Dock = new QDockWidget(msg_hash_to_str( - MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_TITLE_SCREEN), mainwindow); - thumbnail2Dock->setObjectName("thumbnail2Dock"); - thumbnail2Dock->setProperty("default_area", Qt::RightDockWidgetArea); - thumbnail2Dock->setProperty("menu_text", msg_hash_to_str( - MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_TITLE_SCREEN)); - thumbnail2Dock->setWidget(thumbnail2Widget); - - mainwindow->addDockWidget(static_cast( - thumbnail2Dock->property("default_area").toInt()), thumbnail2Dock); + return browserAndPlaylistTabDock; +} + +/* Build the four boxart/title/screenshot/logo thumbnail docks. + * The four docks are tabbed against the first one. */ +static void qt_companion_build_thumbnail_docks(MainWindow *mainwindow) +{ + /* Maps widget index -> ThumbnailType + display label hash + dock obj name. */ + static const ThumbnailType types[4] = { + THUMBNAIL_TYPE_BOXART, THUMBNAIL_TYPE_TITLE_SCREEN, + THUMBNAIL_TYPE_SCREENSHOT, THUMBNAIL_TYPE_LOGO + }; + static const msg_hash_enums labels[4] = { + MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_BOXART, + MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_TITLE_SCREEN, + MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_SCREENSHOT, + MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_LOGO + }; + static const char * const dock_obj_names[4] = { + "thumbnailDock", "thumbnail2Dock", "thumbnail3Dock", "thumbnail4Dock" + }; + QDockWidget *docks[4]; + int i; - thumbnail3Dock = new QDockWidget(msg_hash_to_str( - MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_SCREENSHOT), mainwindow); - thumbnail3Dock->setObjectName("thumbnail3Dock"); - thumbnail3Dock->setProperty("default_area", Qt::RightDockWidgetArea); - thumbnail3Dock->setProperty("menu_text", msg_hash_to_str( - MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_SCREENSHOT)); - thumbnail3Dock->setWidget(thumbnail3Widget); + for (i = 0; i < 4; i++) + { + ThumbnailWidget *tw = new ThumbnailWidget(types[i]); + tw->setObjectName(qt_thumbnail_widget_names[i]); - mainwindow->addDockWidget(static_cast( - thumbnail3Dock->property("default_area").toInt()), thumbnail3Dock); + QObject::connect(tw, SIGNAL(filesDropped(const QImage&, + ThumbnailType)), mainwindow, + SLOT(onThumbnailDropped(const QImage&, ThumbnailType))); - thumbnail4Dock = new QDockWidget(msg_hash_to_str( - MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_LOGO), mainwindow); - thumbnail4Dock->setObjectName("thumbnail4Dock"); - thumbnail4Dock->setProperty("default_area", Qt::RightDockWidgetArea); - thumbnail4Dock->setProperty("menu_text", msg_hash_to_str( - MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_LOGO)); - thumbnail4Dock->setWidget(thumbnail4Widget); + docks[i] = new QDockWidget(msg_hash_to_str(labels[i]), mainwindow); + docks[i]->setObjectName(dock_obj_names[i]); + docks[i]->setProperty("default_area", Qt::RightDockWidgetArea); + docks[i]->setProperty("menu_text", msg_hash_to_str(labels[i])); + docks[i]->setWidget(tw); - mainwindow->addDockWidget(static_cast( - thumbnail4Dock->property("default_area").toInt()), thumbnail4Dock); + mainwindow->addDockWidget(static_cast( + docks[i]->property("default_area").toInt()), docks[i]); + } - mainwindow->tabifyDockWidget(thumbnailDock, thumbnail2Dock); - mainwindow->tabifyDockWidget(thumbnailDock, thumbnail3Dock); - mainwindow->tabifyDockWidget(thumbnailDock, thumbnail4Dock); + for (i = 1; i < 4; i++) + mainwindow->tabifyDockWidget(docks[0], docks[i]); /* When tabifying the dock widgets, the last tab added is selected - * by default, so we need to re-select the first tab */ - thumbnailDock->raise(); - - coreSelectionWidget = new QWidget(); - coreSelectionWidget->setLayout(new QVBoxLayout()); - - launchWithComboBox = mainwindow->launchWithComboBox(); - - launchWithWidgetLayout = new QVBoxLayout(); + * by default, so we re-select the first tab here. */ + docks[0]->raise(); +} - launchWithWidget = new QWidget(); - launchWithWidget->setLayout(launchWithWidgetLayout); +/* Build the core-selection dock (combo box + run/stop/start buttons) + * and split it vertically against the existing browser/playlist dock. */ +static void qt_companion_build_core_selection_dock(MainWindow *mainwindow, + QDockWidget *browserAndPlaylistTabDock) +{ + QWidget *coreSelectionWidget = new QWidget(); + QVBoxLayout *launchWithLayout = new QVBoxLayout(); + QWidget *launchWithWidget = new QWidget(); + QHBoxLayout *coreComboBoxLayout = new QHBoxLayout(); + QComboBox *launchWithComboBox = mainwindow->launchWithComboBox(); + QDockWidget *coreSelectionDock; - coreComboBoxLayout = new QHBoxLayout(); + coreSelectionWidget->setLayout(new QVBoxLayout()); + launchWithWidget->setLayout(launchWithLayout); mainwindow->runPushButton()->setSizePolicy( QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding)); @@ -5021,12 +4813,11 @@ static void* ui_companion_qt_init(void) coreComboBoxLayout->setStretchFactor(launchWithComboBox, 1); - launchWithWidgetLayout->addLayout(coreComboBoxLayout); + launchWithLayout->addLayout(coreComboBoxLayout); coreSelectionWidget->layout()->addWidget(launchWithWidget); - coreSelectionWidget->layout()->addItem(new QSpacerItem(20, - browserAndPlaylistTabWidget->height(), + mainwindow->browserAndPlaylistTabWidget()->height(), QSizePolicy::Minimum, QSizePolicy::Expanding)); coreSelectionDock = new QDockWidget(msg_hash_to_str( @@ -5047,7 +4838,12 @@ static void* ui_companion_qt_init(void) mainwindow->resizeDocks(QList() << coreSelectionDock, QList() << 1, Qt::Vertical); #endif +} +/* Restore persistent state (limits, geometry, theme, view type, last tab). */ +static void qt_companion_restore_settings(MainWindow *mainwindow, + QSettings *qsettings) +{ if (qsettings->contains("all_playlists_list_max_count")) mainwindow->setAllPlaylistsListMaxCount(qsettings->value( "all_playlists_list_max_count", 0).toInt()); @@ -5088,16 +4884,13 @@ static void* ui_companion_qt_init(void) if (qsettings->contains("theme")) { - QString themeStr = qsettings->value("theme").toString(); + QString themeStr = qsettings->value("theme").toString(); MainWindow::Theme theme = mainwindow->getThemeFromString(themeStr); if ( qsettings->contains("custom_theme") && theme == MainWindow::THEME_CUSTOM) - { - QString customThemeFilePath = - qsettings->value("custom_theme").toString(); - mainwindow->setCustomThemeFile(customThemeFilePath); - } + mainwindow->setCustomThemeFile( + qsettings->value("custom_theme").toString()); mainwindow->setTheme(theme); } @@ -5108,9 +4901,7 @@ static void* ui_companion_qt_init(void) { QString viewType = qsettings->value("view_type", "list").toString(); - if (viewType == QLatin1String("list")) - mainwindow->setCurrentViewType(MainWindow::VIEW_TYPE_LIST); - else if (viewType == QLatin1String("icons")) + if (viewType == QLatin1String("icons")) mainwindow->setCurrentViewType(MainWindow::VIEW_TYPE_ICONS); else mainwindow->setCurrentViewType(MainWindow::VIEW_TYPE_LIST); @@ -5123,9 +4914,7 @@ static void* ui_companion_qt_init(void) QString thumbnailType = qsettings->value("icon_view_thumbnail_type", "boxart").toString(); - if (thumbnailType == QLatin1String("boxart")) - mainwindow->setCurrentThumbnailType(THUMBNAIL_TYPE_BOXART); - else if (thumbnailType == QLatin1String("screenshot")) + if (thumbnailType == QLatin1String("screenshot")) mainwindow->setCurrentThumbnailType(THUMBNAIL_TYPE_SCREENSHOT); else if (thumbnailType == QLatin1String("title")) mainwindow->setCurrentThumbnailType(THUMBNAIL_TYPE_TITLE_SCREEN); @@ -5134,38 +4923,20 @@ static void* ui_companion_qt_init(void) else mainwindow->setCurrentThumbnailType(THUMBNAIL_TYPE_BOXART); } +} - /* We make sure to hook up the tab widget callback only after the tabs - * themselves have been added, but before changing to a specific one, - * to avoid the callback firing before the view type is set. - */ - QObject::connect(browserAndPlaylistTabWidget, SIGNAL(currentChanged(int)), - mainwindow, SLOT(onTabWidgetIndexChanged(int))); - - /* Setting the last tab must come after setting the view type */ - if (qsettings->contains("save_last_tab")) - { - int lastTabIndex = qsettings->value("last_tab", 0).toInt(); - - if ( lastTabIndex >= 0 - && browserAndPlaylistTabWidget->count() > lastTabIndex) - { - browserAndPlaylistTabWidget->setCurrentIndex(lastTabIndex); - mainwindow->onTabWidgetIndexChanged(lastTabIndex); - } - } - else - { - browserAndPlaylistTabWidget->setCurrentIndex(0); - mainwindow->onTabWidgetIndexChanged(0); - } +/* Set the initial playlist row to match the user's saved choice; if not + * found, fall back to the first non-hidden row. */ +static void qt_companion_select_initial_playlist(QListWidget *listWidget, + const QString &initialPlaylist) +{ + int i; + bool found = false; - /* The initial playlist that is selected is based on - * the user's setting (initialPlaylist) */ for (i = 0; listWidget->count() && i < listWidget->count(); i++) { - QString path; QListWidgetItem *item = listWidget->item(i); + QString path; if (!item) continue; @@ -5174,26 +4945,121 @@ static void* ui_companion_qt_init(void) if (path == initialPlaylist) { - foundPlaylist = true; + found = true; listWidget->setRowHidden(i, false); listWidget->setCurrentRow(i); break; } } - /* couldn't find the user's initial playlist, just find anything */ - if (!foundPlaylist) + if (found) + return; + + /* Couldn't find the user's initial playlist, just find anything. */ + for (i = 0; listWidget->count() && i < listWidget->count(); i++) { - for (i = 0; listWidget->count() && i < listWidget->count(); i++) + if (!listWidget->isRowHidden(i)) { - /* select the first non-hidden row */ - if (!listWidget->isRowHidden(i)) - { - listWidget->setCurrentRow(i); - break; - } + listWidget->setCurrentRow(i); + break; + } + } +} + +static void* ui_companion_qt_init(void) +{ + QString initialPlaylist; + QRect desktopRect; + ui_companion_qt_t *handle = (ui_companion_qt_t*) + calloc(1, sizeof(*handle)); + MainWindow *mainwindow = NULL; + QScreen *screen = NULL; + QStackedWidget *centralWidget = NULL; + QStackedWidget *widget = NULL; + QTabWidget *browserAndPlaylistTabWidget = NULL; + QDockWidget *browserAndPlaylistTabDock = NULL; + QSettings *qsettings = NULL; + QListWidget *listWidget = NULL; + + if (!handle) + return NULL; + + handle->app = static_cast + (ui_application_qt.initialize()); + handle->window = static_cast(ui_window_qt.init()); + + screen = qApp->primaryScreen(); + desktopRect = screen->availableGeometry(); + + mainwindow = handle->window->qtWindow; + qsettings = mainwindow->settings(); + + initialPlaylist = qsettings->value("initial_playlist", + mainwindow->getSpecialPlaylistPath(SPECIAL_PLAYLIST_HISTORY)).toString(); + + mainwindow->resize(((desktopRect.width()) < (INITIAL_WIDTH) ? (desktopRect.width()) : (INITIAL_WIDTH)), + ((desktopRect.height()) < (INITIAL_HEIGHT) ? (desktopRect.height()) : (INITIAL_HEIGHT))); + mainwindow->setGeometry(QStyle::alignedRect(Qt::LeftToRight, + Qt::AlignCenter, mainwindow->size(), desktopRect)); + + mainwindow->setWindowTitle("RetroArch"); + mainwindow->setDockOptions(QMainWindow::AnimatedDocks + | QMainWindow::AllowNestedDocks + | QMainWindow::AllowTabbedDocks + | GROUPED_DRAGGING); + + listWidget = mainwindow->playlistListWidget(); + widget = mainwindow->playlistViews(); + widget->setContextMenuPolicy(Qt::CustomContextMenu); + + QObject::connect(widget, SIGNAL(filesDropped(QStringList)), + mainwindow, SLOT(onPlaylistFilesDropped(QStringList))); + QObject::connect(widget, SIGNAL(enterPressed()), mainwindow, + SLOT(onDropWidgetEnterPressed())); + QObject::connect(widget, SIGNAL(deletePressed()), mainwindow, + SLOT(deleteCurrentPlaylistItem())); + QObject::connect(widget, SIGNAL(customContextMenuRequested(const QPoint&)), + mainwindow, SLOT(onFileDropWidgetContextMenuRequested(const QPoint&))); + + centralWidget = mainwindow->centralWidget(); + centralWidget->addWidget(mainwindow->playlistViewsAndFooter()); + centralWidget->addWidget(mainwindow->fileTableView()); + mainwindow->setCentralWidget(centralWidget); + + qt_companion_build_menubar(mainwindow); + browserAndPlaylistTabDock = qt_companion_build_browser_dock(mainwindow); + qt_companion_build_thumbnail_docks(mainwindow); + qt_companion_build_core_selection_dock(mainwindow, + browserAndPlaylistTabDock); + qt_companion_restore_settings(mainwindow, qsettings); + + browserAndPlaylistTabWidget = mainwindow->browserAndPlaylistTabWidget(); + + /* We make sure to hook up the tab widget callback only after the tabs + * themselves have been added, but before changing to a specific one, + * to avoid the callback firing before the view type is set. */ + QObject::connect(browserAndPlaylistTabWidget, SIGNAL(currentChanged(int)), + mainwindow, SLOT(onTabWidgetIndexChanged(int))); + + /* Setting the last tab must come after setting the view type. */ + if (qsettings->contains("save_last_tab")) + { + int lastTabIndex = qsettings->value("last_tab", 0).toInt(); + + if ( lastTabIndex >= 0 + && browserAndPlaylistTabWidget->count() > lastTabIndex) + { + browserAndPlaylistTabWidget->setCurrentIndex(lastTabIndex); + mainwindow->onTabWidgetIndexChanged(lastTabIndex); } } + else + { + browserAndPlaylistTabWidget->setCurrentIndex(0); + mainwindow->onTabWidgetIndexChanged(0); + } + + qt_companion_select_initial_playlist(listWidget, initialPlaylist); mainwindow->initContentTableWidget(); @@ -5321,29 +5187,6 @@ ui_companion_driver_t ui_companion_qt = { "qt", }; -QStringList string_split_to_qt(QString str, char delim) -{ - int at = 0; - QStringList list = QStringList(); - - for (;;) - { - /* Find next split */ - int spl = str.indexOf(delim, at); - - /* Store split into list of extensions */ - list << str.mid(at, (spl < 0 ? -1 : spl - at)); - - /* No more splits */ - if (spl < 0) - break; - - at = spl + 1; - } - - return list; -} - #define CORE_NAME_COLUMN 0 #define CORE_VERSION_COLUMN 1 @@ -5547,8 +5390,7 @@ void LoadCoreWindow::initCoreList(const QStringList &extensionFilters) name_item = new QTableWidgetItem(name); hash["path"] = QByteArray(core->path); - hash["extensions"] = string_split_to_qt( - QString(core->supported_extensions), '|'); + hash["extensions"] = QString(core->supported_extensions).split('|'); name_item->setData(Qt::UserRole, hash); name_item->setFlags(name_item->flags() & ~Qt::ItemIsEditable); diff --git a/ui/drivers/ui_qt.h b/ui/drivers/ui_qt.h index 6f31a3a6859f..e3ce39d7fb36 100644 --- a/ui/drivers/ui_qt.h +++ b/ui/drivers/ui_qt.h @@ -484,7 +484,7 @@ class MainWindow : public QMainWindow Theme theme(); Theme getThemeFromString(QString themeString); QString getThemeString(Theme theme); - QHash getSelectedCore(); + QString getSelectedCorePath(); void showStatusMessage(QString msg, unsigned priority, unsigned duration, bool flush); bool showMessageBox(QString msg, MessageBoxType msgType = MSGBOX_TYPE_INFO, Qt::WindowModality modality = Qt::ApplicationModal, bool showDontAsk = true, bool *dontAsk = NULL); bool setCustomThemeFile(QString filePath); @@ -508,15 +508,10 @@ class MainWindow : public QMainWindow QHash getFileContentHash(const QModelIndex &index); QString getSpecialPlaylistPath(SpecialPlaylist playlist); QVector > getPlaylists(); - QString getScrubbedString(QString str); void setDefaultCustomProperties(); void setIconViewZoom(int zoomValue); signals: - void thumbnailChanged(const QPixmap &pixmap); - void thumbnail2Changed(const QPixmap &pixmap); - void thumbnail3Changed(const QPixmap &pixmap); - void thumbnail4Changed(const QPixmap &pixmap); void gotLogMessage(const QString &msg); void gotStatusMessage(QString msg, unsigned priority, unsigned duration, bool flush); void gotReloadPlaylists(); @@ -546,10 +541,6 @@ public slots: void onDropWidgetEnterPressed(); void selectBrowserDir(QString path); void setThumbnail(QString widgetName, QPixmap &pixmap, bool acceptDrop); - void onResizeThumbnailOne(QPixmap &pixmap, bool acceptDrop); - void onResizeThumbnailTwo(QPixmap &pixmap, bool acceptDrop); - void onResizeThumbnailThree(QPixmap &pixmap, bool acceptDrop); - void onResizeThumbnailFour(QPixmap &pixmap, bool acceptDrop); void appendLogMessage(const QString &msg); void onGotLogMessage(const QString &msg); void onGotStatusMessage(QString msg, unsigned priority, unsigned duration, bool flush); @@ -666,10 +657,7 @@ private slots: QToolButton *m_stopPushButton; QTabWidget *m_browserAndPlaylistTabWidget; bool m_pendingRun; - QPixmap *m_thumbnailPixmap; - QPixmap *m_thumbnailPixmap2; - QPixmap *m_thumbnailPixmap3; - QPixmap *m_thumbnailPixmap4; + QPixmap *m_thumbnailPixmaps[4]; QSettings *m_settings; ViewOptionsDialog *m_viewOptionsDialog; CoreInfoDialog *m_coreInfoDialog; @@ -754,8 +742,6 @@ typedef struct ui_window_qt MainWindow *qtWindow; } ui_window_qt_t; -QStringList string_split_to_qt(QString str, char delim); - RETRO_END_DECLS #endif diff --git a/ui/drivers/ui_qt_widgets.cpp b/ui/drivers/ui_qt_widgets.cpp index fb26805e0c2a..287d041c3d30 100644 --- a/ui/drivers/ui_qt_widgets.cpp +++ b/ui/drivers/ui_qt_widgets.cpp @@ -109,6 +109,18 @@ static inline QString sanitize_ampersand(QString input) return input.replace("&", "&&"); } +/* Replace characters unsafe in URLs / file names with '_' */ +static QString scrub_qstring(QString str) +{ + static const char chars[] = "&*/:`\"<>?\\|"; + QByteArray buf = str.toUtf8(); + char *s = buf.data(); + size_t i; + for (i = 0; i < sizeof(chars) - 1; i++) + string_replace_all_chars(s, chars[i], '_'); + return QString::fromUtf8(s); +} + static inline QString form_label(rarch_setting_t *setting) { return QString(sanitize_ampersand(setting->short_description)) + ":"; @@ -463,7 +475,7 @@ StringComboBox::StringComboBox(rarch_setting_t *setting, QWidget *parent) : ,m_setting(setting) ,m_value(setting->value.target.string) { - addItems(string_split_to_qt(QString(setting->values), '|')); + addItems(QString(setting->values).split('|')); connect(this, SIGNAL(currentTextChanged(const QString&)), this, SLOT(onCurrentTextChanged(const QString&))); @@ -1999,7 +2011,7 @@ void PlaylistEntryDialog::loadPlaylistOptions() QString ui_display_name; QHash hash; const core_info_t *core = &core_info_list->list[i]; - QStringList databases = string_split_to_qt(QString(core->databases), '|'); + QStringList databases = QString(core->databases).split('|'); hash["core_name"] = core->core_name; hash["core_display_name"] = core->display_name; @@ -2139,7 +2151,7 @@ const QStringList PlaylistEntryDialog::getSelectedExtensions() /* Otherwise it would create a QStringList with a single blank entry... */ if (!text.isEmpty()) - list = string_split_to_qt(text, ' '); + list = text.split(' '); return list; } @@ -5156,7 +5168,7 @@ void MainWindow::downloadThumbnail(QString system, QString title, QUrl url) if (!settings || m_pendingThumbnailDownloadTypes.isEmpty()) return; - title = getScrubbedString(title); + title = scrub_qstring(title); downloadType = m_pendingThumbnailDownloadTypes.takeFirst(); urlString = QString(THUMBNAIL_URL_HEADER) + system + "/" + downloadType + "/" + title + THUMBNAIL_IMAGE_EXTENSION; @@ -5305,7 +5317,7 @@ void MainWindow::downloadNextPlaylistThumbnail( if (!settings) return; - title = getScrubbedString(title); + title = scrub_qstring(title); urlString = QString(THUMBNAIL_URL_HEADER) + system + "/" + type + "/" + title + THUMBNAIL_IMAGE_EXTENSION; @@ -5566,7 +5578,8 @@ QVector AudioCategory::pages() QVector pages; pages << new AudioPage(this); - pages << new MenuSoundsPage(this); + pages << new SimplePage(DISPLAYLIST_MENU_SOUNDS_LIST, + MENU_ENUM_LABEL_VALUE_MENU_SOUNDS, this); return pages; } @@ -5643,17 +5656,6 @@ QWidget *AudioPage::widget() return widget; } -MenuSoundsPage::MenuSoundsPage(QObject *parent) : - OptionsPage(parent) -{ - setDisplayName(MENU_ENUM_LABEL_VALUE_MENU_SOUNDS); -} - -QWidget *MenuSoundsPage::widget() -{ - return create_widget(DISPLAYLIST_MENU_SOUNDS_LIST); -} - InputCategory::InputCategory(QWidget *parent) : OptionsCategory(parent) { @@ -5857,7 +5859,8 @@ QVector NetworkCategory::pages() QVector pages; pages << new NetplayPage(this); - pages << new UpdaterPage(this); + pages << new SimplePage(DISPLAYLIST_UPDATER_SETTINGS_LIST, + MENU_ENUM_LABEL_VALUE_UPDATER_SETTINGS, this); return pages; } @@ -6011,17 +6014,6 @@ void NetplayPage::onRadioButtonClicked(int id) #endif } -UpdaterPage::UpdaterPage(QObject *parent) : - OptionsPage(parent) -{ - setDisplayName(MENU_ENUM_LABEL_VALUE_UPDATER_SETTINGS); -} - -QWidget *UpdaterPage::widget() -{ - return create_widget(DISPLAYLIST_UPDATER_SETTINGS_LIST); -} - OnscreenDisplayCategory::OnscreenDisplayCategory(QWidget *parent) : OptionsCategory(parent) { @@ -6429,7 +6421,9 @@ QWidget *ViewsPage::widget() FormLayout *leftLayout = new FormLayout; QVBoxLayout *rightLayout = new QVBoxLayout; SettingsGroup *quickMenu = new SettingsGroup("Quick Menu"); - QuickMenuPage *quickPage = new QuickMenuPage(this); + OptionsPage *quickPage = new SimplePage( + DISPLAYLIST_QUICK_MENU_VIEWS_SETTINGS_LIST, + MENU_ENUM_LABEL_VALUE_QUICK_MENU_VIEWS_SETTINGS, this); SettingsGroup *mainMenu = new SettingsGroup("Main Menu"); SettingsGroup *settings = new SettingsGroup("Settings"); SettingsGroup *tabs = new SettingsGroup("Tabs"); @@ -6531,17 +6525,6 @@ QWidget *ViewsPage::widget() return widget; } -QuickMenuPage::QuickMenuPage(QObject *parent) : - OptionsPage(parent) -{ - setDisplayName(MENU_ENUM_LABEL_VALUE_QUICK_MENU_VIEWS_SETTINGS); -} - -QWidget *QuickMenuPage::widget() -{ - return create_widget(DISPLAYLIST_QUICK_MENU_VIEWS_SETTINGS_LIST); -} - AppearancePage::AppearancePage(QObject *parent) : OptionsPage(parent) { @@ -7065,23 +7048,11 @@ DriversCategory::DriversCategory(QWidget *parent) : QVector DriversCategory::pages() { QVector pages; - - pages << new DriversPage(this); - + pages << new SimplePage(DISPLAYLIST_DRIVER_SETTINGS_LIST, + MENU_ENUM_LABEL_VALUE_DRIVER_SETTINGS, this); return pages; } -DriversPage::DriversPage(QObject *parent) : - OptionsPage(parent) -{ - setDisplayName(MENU_ENUM_LABEL_VALUE_DRIVER_SETTINGS); -} - -QWidget *DriversPage::widget() -{ - return create_widget(DISPLAYLIST_DRIVER_SETTINGS_LIST); -} - /* DIRECTORY */ DirectoryCategory::DirectoryCategory(QWidget *parent) : @@ -7094,22 +7065,10 @@ DirectoryCategory::DirectoryCategory(QWidget *parent) : QVector DirectoryCategory::pages() { QVector pages; - - pages << new DirectoryPage(this); - + pages << new SimplePage(DISPLAYLIST_DIRECTORY_SETTINGS_LIST, this); return pages; } -DirectoryPage::DirectoryPage(QObject *parent) : - OptionsPage(parent) -{ -} - -QWidget *DirectoryPage::widget() -{ - return create_widget(DISPLAYLIST_DIRECTORY_SETTINGS_LIST); -} - /* CONFIGURATION */ ConfigurationCategory::ConfigurationCategory(QWidget *parent) : @@ -7122,22 +7081,10 @@ ConfigurationCategory::ConfigurationCategory(QWidget *parent) : QVector ConfigurationCategory::pages() { QVector pages; - - pages << new ConfigurationPage(this); - + pages << new SimplePage(DISPLAYLIST_CONFIGURATION_SETTINGS_LIST, this); return pages; } -ConfigurationPage::ConfigurationPage(QObject *parent) : - OptionsPage(parent) -{ -} - -QWidget *ConfigurationPage::widget() -{ - return create_widget(DISPLAYLIST_CONFIGURATION_SETTINGS_LIST); -} - /* CORE */ CoreCategory::CoreCategory(QWidget *parent) : @@ -7150,22 +7097,10 @@ CoreCategory::CoreCategory(QWidget *parent) : QVector CoreCategory::pages() { QVector pages; - - pages << new CorePage(this); - + pages << new SimplePage(DISPLAYLIST_CORE_SETTINGS_LIST, this); return pages; } -CorePage::CorePage(QObject *parent) : - OptionsPage(parent) -{ -} - -QWidget *CorePage::widget() -{ - return create_widget(DISPLAYLIST_CORE_SETTINGS_LIST); -} - /* LOGGING */ LoggingCategory::LoggingCategory(QWidget *parent) : @@ -7178,18 +7113,10 @@ LoggingCategory::LoggingCategory(QWidget *parent) : QVector LoggingCategory::pages() { QVector pages; - pages << new LoggingPage(this); + pages << new SimplePage(DISPLAYLIST_LOGGING_SETTINGS_LIST, this); return pages; } -LoggingPage::LoggingPage(QObject *parent) : - OptionsPage(parent) { } - -QWidget *LoggingPage::widget() -{ - return create_widget(DISPLAYLIST_LOGGING_SETTINGS_LIST); -} - /* AI SERVICE */ AIServiceCategory::AIServiceCategory(QWidget *parent) : @@ -7202,23 +7129,11 @@ AIServiceCategory::AIServiceCategory(QWidget *parent) : QVector AIServiceCategory::pages() { QVector pages; - - pages << new AIServicePage(this); - + pages << new SimplePage(DISPLAYLIST_AI_SERVICE_SETTINGS_LIST, + MENU_ENUM_LABEL_VALUE_AI_SERVICE_SETTINGS, this); return pages; } -AIServicePage::AIServicePage(QObject *parent) : - OptionsPage(parent) -{ - setDisplayName(MENU_ENUM_LABEL_VALUE_AI_SERVICE_SETTINGS); -} - -QWidget *AIServicePage::widget() -{ - return create_widget(DISPLAYLIST_AI_SERVICE_SETTINGS_LIST); -} - /* FRAME THROTTLE */ FrameThrottleCategory::FrameThrottleCategory(QWidget *parent) : @@ -7231,35 +7146,13 @@ FrameThrottleCategory::FrameThrottleCategory(QWidget *parent) : QVector FrameThrottleCategory::pages() { QVector pages; - - pages << new FrameThrottlePage(this); - pages << new RewindPage(this); - + pages << new SimplePage(DISPLAYLIST_FRAME_THROTTLE_SETTINGS_LIST, + MENU_ENUM_LABEL_VALUE_FRAME_THROTTLE_SETTINGS, this); + pages << new SimplePage(DISPLAYLIST_REWIND_SETTINGS_LIST, + MENU_ENUM_LABEL_VALUE_REWIND_SETTINGS, this); return pages; } -FrameThrottlePage::FrameThrottlePage(QObject *parent) : - OptionsPage(parent) -{ - setDisplayName(MENU_ENUM_LABEL_VALUE_FRAME_THROTTLE_SETTINGS); -} - -QWidget *FrameThrottlePage::widget() -{ - return create_widget(DISPLAYLIST_FRAME_THROTTLE_SETTINGS_LIST); -} - -RewindPage::RewindPage(QObject *parent) : - OptionsPage(parent) -{ - setDisplayName(MENU_ENUM_LABEL_VALUE_REWIND_SETTINGS); -} - -QWidget *RewindPage::widget() -{ - return create_widget(DISPLAYLIST_REWIND_SETTINGS_LIST); -} - PlaylistModel::PlaylistModel(QObject *parent) : QAbstractListModel(parent) { @@ -7570,7 +7463,8 @@ bool MainWindow::addDirectoryFilesToList(QProgressDialog *dialog, * inside the archive * doesn't have one of the chosen extensions, * then we skip it. */ - if (extensions.contains(QFileInfo(pathData).suffix())) + if (extensions.contains( + QString::fromUtf8(path_get_extension(pathData)))) add = true; } { @@ -7579,7 +7473,8 @@ bool MainWindow::addDirectoryFilesToList(QProgressDialog *dialog, * inside the archive * doesn't have one of the chosen extensions, * then we skip it. */ - if (extensions.contains(QFileInfo(pathData).suffix())) + if (extensions.contains( + QString::fromUtf8(path_get_extension(pathData)))) add = true; } } @@ -7877,7 +7772,7 @@ void MainWindow::addFilesToPlaylist(QStringList files) * doesn't have one of the chosen extensions, * then we skip it. */ if (!selectedExtensions.contains( - QFileInfo(pathData).suffix())) + QString::fromUtf8(path_get_extension(pathData)))) { string_list_free(list); continue; diff --git a/ui/drivers/ui_qt_widgets.h b/ui/drivers/ui_qt_widgets.h index 15550426aefa..474bacf498c8 100644 --- a/ui/drivers/ui_qt_widgets.h +++ b/ui/drivers/ui_qt_widgets.h @@ -895,6 +895,28 @@ class OptionsPage : public QObject QString m_displayName = "General"; }; +/* SimplePage: + * Trivial OptionsPage subclass whose widget() is a one-shot + * create_widget(displaylist) call. Consolidates ~11 near-identical + * leaf Page classes that only differ in display name and displaylist + * enum. Use this for any page that does not need custom layout, + * custom slots, or its own load()/apply(). */ +class SimplePage : public OptionsPage +{ + Q_OBJECT +public: + SimplePage(enum menu_displaylist_ctl_state state, + QObject *parent = nullptr) + : OptionsPage(parent), m_state(state) {} + SimplePage(enum menu_displaylist_ctl_state state, + msg_hash_enums name, + QObject *parent = nullptr) + : OptionsPage(parent), m_state(state) { setDisplayName(name); } + QWidget *widget(); /* defined inline at end of header, after create_widget() */ +private: + enum menu_displaylist_ctl_state m_state; +}; + class OptionsCategory : public QObject { Q_OBJECT @@ -936,14 +958,6 @@ class DriversCategory : public OptionsCategory QVector pages(); }; -class DriversPage : public OptionsPage -{ - Q_OBJECT -public: - DriversPage(QObject *parent = nullptr); - QWidget *widget(); -}; - /*********************************************************** AI Service ************************************************************/ @@ -954,14 +968,6 @@ class AIServiceCategory : public OptionsCategory QVector pages(); }; -class AIServicePage : public OptionsPage -{ - Q_OBJECT -public: - AIServicePage(QObject *parent = nullptr); - QWidget *widget(); -}; - /************************************************************ Video ************************************************************/ @@ -1039,14 +1045,6 @@ class AudioPage : public OptionsPage QWidget *widget(); }; -class MenuSoundsPage : public OptionsPage -{ - Q_OBJECT -public: - MenuSoundsPage(QObject *parent = nullptr); - QWidget *widget(); -}; - /************************************************************ Input ************************************************************/ @@ -1109,14 +1107,6 @@ class CoreCategory : public OptionsCategory QVector pages(); }; -class CorePage : public OptionsPage -{ - Q_OBJECT -public: - CorePage(QObject *parent = nullptr); - QWidget *widget(); -}; - /************************************************************ Configuration ************************************************************/ @@ -1127,14 +1117,6 @@ class ConfigurationCategory : public OptionsCategory QVector pages(); }; -class ConfigurationPage : public OptionsPage -{ - Q_OBJECT -public: - ConfigurationPage(QObject *parent = nullptr); - QWidget *widget(); -}; - /************************************************************ Saving ************************************************************/ @@ -1163,14 +1145,6 @@ class LoggingCategory : public OptionsCategory QVector pages(); }; -class LoggingPage : public OptionsPage -{ - Q_OBJECT -public: - LoggingPage(QObject *parent = nullptr); - QWidget *widget(); -}; - /************************************************************ Frame Throttle ************************************************************/ @@ -1181,22 +1155,6 @@ class FrameThrottleCategory : public OptionsCategory QVector pages(); }; -class FrameThrottlePage : public OptionsPage -{ - Q_OBJECT -public: - FrameThrottlePage(QObject *parent = nullptr); - QWidget *widget(); -}; - -class RewindPage : public OptionsPage -{ - Q_OBJECT -public: - RewindPage(QObject *parent = nullptr); - QWidget *widget(); -}; - /************************************************************ Recording ************************************************************/ @@ -1244,14 +1202,6 @@ class ViewsPage : public OptionsPage QWidget *widget(); }; -class QuickMenuPage : public OptionsPage -{ - Q_OBJECT -public: - QuickMenuPage(QObject *parent = nullptr); - QWidget *widget(); -}; - class AppearancePage : public OptionsPage { Q_OBJECT @@ -1344,14 +1294,6 @@ private slots: QGroupBox* createMitmServerGroup(); }; -class UpdaterPage : public OptionsPage -{ - Q_OBJECT -public: - UpdaterPage(QObject *parent = nullptr); - QWidget *widget(); -}; - /************************************************************ Playlists ************************************************************/ @@ -1406,14 +1348,6 @@ class DirectoryCategory : public OptionsCategory QVector pages(); }; -class DirectoryPage : public OptionsPage -{ - Q_OBJECT -public: - DirectoryPage(QObject *parent = nullptr); - QWidget *widget(); -}; - static inline QWidget *create_widget(enum menu_displaylist_ctl_state name) { unsigned i; @@ -1439,4 +1373,9 @@ static inline QWidget *create_widget(enum menu_displaylist_ctl_state name) return widget; } +inline QWidget *SimplePage::widget() +{ + return create_widget(m_state); +} + #endif From 146ac2efbcc96cb3d174ba47adf1a8d08dfefb6b Mon Sep 17 00:00:00 2001 From: "U-DESKTOP-SPFP6AQ\\twistedtechre" Date: Fri, 1 May 2026 20:27:44 +0200 Subject: [PATCH 07/14] Qt: decode file-browser preview images on a worker thread Selecting an image file in the file browser ran the PNG/JPEG decode synchronously on the UI thread via image_texture_load. For ordinary scraper-sized boxart this is fine, but for the multi-GiB ARGB32 bitmaps that come out of decoding a high-resolution upscayl-style PNG the UI froze for hundreds of milliseconds to several seconds. The same stall fired on every selection change, including the implicit ones triggered by collapsing a tree node when the current selection became invisible. Add a ThumbnailLoader owned by MainWindow (parallel to the one PlaylistModel owns for the grid view) and route the preview-pane decode through it. The panes are cleared immediately on selection change; the loaded pixmap is installed when the worker's imageLoaded signal fires. A pending-path token guards against rapid selection changes flickering stale images in. Playlist-thumbnail (scraper boxart) decoding stays synchronous because those images are bounded in size; the only change there is that m_pendingPreviewPath is cleared on entry so a previously-issued preview request can't land in the panes after the playlist code already painted boxart there. --- ui/drivers/ui_qt.cpp | 65 +++++++++++++++++++++++++++++++++++++++++--- ui/drivers/ui_qt.h | 9 ++++++ 2 files changed, 70 insertions(+), 4 deletions(-) diff --git a/ui/drivers/ui_qt.cpp b/ui/drivers/ui_qt.cpp index d930823e5834..7b159604d42c 100644 --- a/ui/drivers/ui_qt.cpp +++ b/ui/drivers/ui_qt.cpp @@ -1514,6 +1514,16 @@ MainWindow::MainWindow(QWidget *parent) : memset(m_thumbnailPixmaps, 0, sizeof(m_thumbnailPixmaps)); + /* Background loader for the file-browser preview pane. Decoding + * happens off the UI thread; results arrive on the imageLoaded + * signal and are filtered by m_pendingPreviewPath so that rapid + * selection changes don't flicker stale images in. */ + m_previewLoader = new ThumbnailLoader(this); + connect(m_previewLoader, + SIGNAL(imageLoaded(QImage,QModelIndex,QString)), this, + SLOT(onPreviewImageLoaded(QImage,QModelIndex,QString))); + m_previewLoader->start(); + /* Cancel all progress dialogs immediately since * they show as soon as they're constructed. */ m_updateProgressDialog->cancel(); @@ -3526,16 +3536,34 @@ void MainWindow::onCurrentItemChanged(const QHash &hash) if (m_playlistModel->isSupportedImage(path)) { - /* use thumbnail widgets to show regular image files */ - m_thumbnailPixmaps[0] = new QPixmap(pixmapFromPathRA(path)); - for (i = 1; i < 4; i++) - m_thumbnailPixmaps[i] = new QPixmap(*m_thumbnailPixmaps[0]); + /* Use thumbnail widgets to show regular image files. These can + * be very large (multi-GiB ARGB32 bitmaps after decoding a + * high-resolution PNG), so do the decode on the loader thread + * and update the panes when it arrives. Until then the panes + * show blank, which also clears any image left from a previous + * selection. */ + QPixmap blank; + + m_pendingPreviewPath = path; + + for (i = 0; i < 4; i++) + setThumbnail(qt_thumbnail_widget_names[i], blank, false); + + m_previewLoader->request(QModelIndex(), path); + + setCoreActions(); + return; } else { QString thumbnailsDir = m_playlistModel->getPlaylistThumbnailsDir( hash["db_name"]); + /* Clear any pending file-browser preview request: this code + * path serves the playlist views, not the file browser, so a + * preview result arriving now would be unwanted. */ + m_pendingPreviewPath = QString(); + for (i = 0; i < 4; i++) { QString name = m_playlistModel->getSanitizedThumbnailName( @@ -3557,6 +3585,35 @@ void MainWindow::onCurrentItemChanged(const QHash &hash) setCoreActions(); } +void MainWindow::onPreviewImageLoaded(const QImage image, + const QModelIndex & /* index */, const QString &path) +{ + size_t i; + + /* Drop stale results: if the user moved selection while we were + * decoding, the path we just got back isn't what's currently + * showing. */ + if (path != m_pendingPreviewPath) + return; + if (image.isNull()) + return; + + for (i = 0; i < 4; i++) + { + if (m_thumbnailPixmaps[i]) + delete m_thumbnailPixmaps[i]; + m_thumbnailPixmaps[i] = NULL; + } + + m_thumbnailPixmaps[0] = new QPixmap(QPixmap::fromImage(image)); + for (i = 1; i < 4; i++) + m_thumbnailPixmaps[i] = new QPixmap(*m_thumbnailPixmaps[0]); + + for (i = 0; i < 4; i++) + setThumbnail(qt_thumbnail_widget_names[i], + *m_thumbnailPixmaps[i], false); +} + void MainWindow::setThumbnail(QString widgetName, QPixmap &pixmap, bool acceptDrop) { diff --git a/ui/drivers/ui_qt.h b/ui/drivers/ui_qt.h index e3ce39d7fb36..706e4e3586e4 100644 --- a/ui/drivers/ui_qt.h +++ b/ui/drivers/ui_qt.h @@ -583,6 +583,7 @@ private slots: void onCurrentItemChanged(const QModelIndex &index); void onCurrentItemChanged(const QHash &hash); void onCurrentFileChanged(const QModelIndex &index); + void onPreviewImageLoaded(const QImage image, const QModelIndex &index, const QString &path); void onSearchEnterPressed(); void onSearchLineEditEdited(const QString &text); void onContentItemDoubleClicked(const QModelIndex &index); @@ -658,6 +659,14 @@ private slots: QTabWidget *m_browserAndPlaylistTabWidget; bool m_pendingRun; QPixmap *m_thumbnailPixmaps[4]; + /* Background loader for the file-browser preview pane. The + * preview is decoded asynchronously so that selecting a large + * image in the file table does not block the UI thread on a + * full-resolution PNG decode. m_pendingPreviewPath is the path + * we last requested; results for any other path are stale and + * dropped on arrival. */ + ThumbnailLoader *m_previewLoader; + QString m_pendingPreviewPath; QSettings *m_settings; ViewOptionsDialog *m_viewOptionsDialog; CoreInfoDialog *m_coreInfoDialog; From d49ccc1478df3e1dfa6114af744155c49907d06c Mon Sep 17 00:00:00 2001 From: "U-DESKTOP-SPFP6AQ\\twistedtechre" Date: Fri, 1 May 2026 20:34:47 +0200 Subject: [PATCH 08/14] Qt: cache root index in FileSystemProxyModel::filterAcceptsRow The proxy model's filterAcceptsRow is called once per (row, parent) pair the source QFileSystemModel surfaces, which can run into the tens of thousands during a directory load, sort, or filter pass on a populous tree. The original implementation resolved the source model's root path to a QModelIndex via sm->index(sm->rootPath()) on every single call - cheap individually, but it adds up. The root path itself only changes when the user navigates, so cache both the path and its resolved index on the proxy. On each call, compare the cached path against the current one (a QString equality check, much cheaper than re-walking the model); refresh on mismatch. No behaviour change. --- ui/drivers/ui_qt.cpp | 22 ++++++++++++++++++---- ui/drivers/ui_qt.h | 10 ++++++++++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/ui/drivers/ui_qt.cpp b/ui/drivers/ui_qt.cpp index 7b159604d42c..4be1698ab4bb 100644 --- a/ui/drivers/ui_qt.cpp +++ b/ui/drivers/ui_qt.cpp @@ -1386,12 +1386,26 @@ void LogTextEdit::appendMessage(const QString& text) } /* Only accept indexes from current path. - * https://www.qtcentre.org/threads/50700-QFileSystemModel-and-QSortFilterProxyModel-don-t-work-well-together */ + * https://www.qtcentre.org/threads/50700-QFileSystemModel-and-QSortFilterProxyModel-don-t-work-well-together + * + * The root index is cached because this method is invoked once per + * (row, parent) pair the proxy considers - which can be tens of + * thousands per directory load on a populous tree - while the + * QFileSystemModel root path changes only when the user navigates. + * Resolving the root path to an index via sm->index(...) walks the + * model on every call without the cache. */ bool FileSystemProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { - QFileSystemModel *sm = qobject_cast(sourceModel()); - QModelIndex rootIndex = sm->index(sm->rootPath()); - if (sourceParent == rootIndex) + QFileSystemModel *sm = qobject_cast(sourceModel()); + const QString currentRoot = sm->rootPath(); + + if (currentRoot != m_cachedRootPath) + { + m_cachedRootPath = currentRoot; + m_cachedRootIndex = sm->index(currentRoot); + } + + if (sourceParent == m_cachedRootIndex) return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent); return true; } diff --git a/ui/drivers/ui_qt.h b/ui/drivers/ui_qt.h index 706e4e3586e4..ee322e5dd1a7 100644 --- a/ui/drivers/ui_qt.h +++ b/ui/drivers/ui_qt.h @@ -386,6 +386,16 @@ class FileSystemProxyModel : public QSortFilterProxyModel protected: virtual bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); +private: + /* Cache for filterAcceptsRow's "is this row a child of the + * source model's current root?" check. The QFileSystemModel + * root path rarely changes, but filterAcceptsRow can fire + * thousands of times per directory load / sort / filter, and + * resolving rootPath() to a QModelIndex via sm->index(...) + * is not free. Compare the cached path string against the + * current one on each call; refresh both members on mismatch. */ + mutable QString m_cachedRootPath; + mutable QModelIndex m_cachedRootIndex; }; class LoadCoreTableWidget : public QTableWidget From 334f787c2e349da152dec106b4e2fcb12ca7b766 Mon Sep 17 00:00:00 2001 From: "U-DESKTOP-SPFP6AQ\\twistedtechre" Date: Fri, 1 May 2026 21:24:07 +0200 Subject: [PATCH 09/14] Qt: drop unused includes and split MainWindow constructor * Drop three unused Qt includes: - ui_qt.h: QDialog, QStyledItemDelegate. Both are still needed in ui_qt_widgets.h (PlaylistEntryDialog inherits QDialog, ThumbnailDelegate inherits QStyledItemDelegate), and ui_qt.h pulls in ui_qt_widgets.h, so consumers continue to get them transitively for free. - ui_qt_widgets.h: QTabWidget. Not used in this header at all. * Split MainWindow::MainWindow body into helpers. The constructor was 446 body lines of mixed widget creation, layout assembly, model setup, file-system configuration, dock setup, and signal wiring, with locals declared at the top and used hundreds of lines later. Five private methods now own the chunks that don't cross-depend: - setupPlaylistFooter() builds the zoom slider, view-type and thumbnail-type push buttons, items count label, and grid progress widget, attaching them under the playlist views. - setupModels() configures the playlist + filesystem proxy models and the table / file-table / grid views. - setupFileSystemBrowser() configures the QFileSystemModels (filters, root paths) and the directory tree view. - setupDockWidgets() owns the search / core-info / log dock widgets, including the searchResetButton that previously lived as a constructor local. - setupSignalConnections() owns the bulk of the QObject::connect block plus the m_dirTree initial-selection and m_thumbnailTimer setup that was interleaved with it. Constructor body: 446 -> 101 lines. * Three small file-static helpers collapse repeated boilerplate: - qt_button_set_action_label() replaces the setDefaultAction(new QAction(msg_hash_to_str(label), btn)) + setFixedSize(sizeHint()) pair (5 sites). - qt_dock_add_to() replaces the addDockWidget(static_cast( dock->property("default_area").toInt()), dock) idiom (7 sites). - qt_dock_configure() replaces a setObjectName / setProperty( "default_area") / setProperty("menu_text") / setWidget four-line block (6 sites). No behaviour change. --- ui/drivers/ui_qt.cpp | 431 +++++++++++++++++++++---------------- ui/drivers/ui_qt.h | 11 +- ui/drivers/ui_qt_widgets.h | 1 - 3 files changed, 249 insertions(+), 194 deletions(-) diff --git a/ui/drivers/ui_qt.cpp b/ui/drivers/ui_qt.cpp index 4be1698ab4bb..04b9da0cb5d7 100644 --- a/ui/drivers/ui_qt.cpp +++ b/ui/drivers/ui_qt.cpp @@ -136,6 +136,45 @@ static QPixmap pixmapFromPathRA(const QString &path) return QPixmap(path); } +/* Give btn a default action whose text comes from the localization + * tables, then size btn to fit. The action is parented to btn so + * Qt cleans it up automatically; the explicit parent is needed + * because QAction's default-NULL-parent overload only exists from + * Qt 5.7 onwards. */ +static void qt_button_set_action_label(QToolButton *btn, + enum msg_hash_enums label) +{ + btn->setDefaultAction(new QAction(msg_hash_to_str(label), btn)); + btn->setFixedSize(btn->sizeHint()); +} + +/* Add dock to win in the area stored as the dock's "default_area" + * dynamic property. The property is set elsewhere with a + * Qt::DockWidgetArea value; this just unpacks and routes it. + * Wraps a static_cast(... toInt()) idiom that + * otherwise repeats verbatim at every dock-attach site. */ +static void qt_dock_add_to(QMainWindow *win, QDockWidget *dock) +{ + win->addDockWidget(static_cast( + dock->property("default_area").toInt()), dock); +} + +/* Configure the four standard pieces of a QDockWidget in one call: + * the QObject name (for QSettings save/restore), the default + * docking area (read back later by qt_dock_add_to), the localized + * menu text shown in the View menu, and the widget the dock + * displays. Replaces a four-line setObjectName / setProperty / + * setProperty / setWidget block that recurs at every dock site. */ +static void qt_dock_configure(QDockWidget *dock, + const char *object_name, Qt::DockWidgetArea default_area, + enum msg_hash_enums menu_text, QWidget *widget) +{ + dock->setObjectName(object_name); + dock->setProperty("default_area", default_area); + dock->setProperty("menu_text", msg_hash_to_str(menu_text)); + dock->setWidget(widget); +} + /* %1 is a placeholder for palette(highlight) or the equivalent chosen by the user */ static const QString qt_theme_default_stylesheet = QString(R"( QPushButton[flat="true"] { @@ -1503,25 +1542,8 @@ MainWindow::MainWindow(QWidget *parent) : settings_t *settings = config_get_ptr(); const char *path_dir_playlist = settings->paths.directory_playlist; const char *path_dir_assets = settings->paths.directory_assets; - const char *path_dir_menu_content = settings->paths.directory_menu_content; QDir playlistDir(path_dir_playlist); QString configDir = QFileInfo(path_get(RARCH_PATH_CONFIG)).dir().absolutePath(); - QToolButton *searchResetButton = NULL; - QHBoxLayout *zoomLayout = new QHBoxLayout(); - QLabel *zoomLabel = new QLabel(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_ZOOM), m_zoomWidget); - QPushButton *thumbnailTypePushButton = new QPushButton(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS_THUMBNAIL_TYPE), m_zoomWidget); - QMenu *thumbnailTypeMenu = new QMenu(thumbnailTypePushButton); - QAction *thumbnailTypeBoxartAction = NULL; - QAction *thumbnailTypeScreenshotAction = NULL; - QAction *thumbnailTypeTitleAction = NULL; - QAction *thumbnailTypeLogoAction = NULL; - QPushButton *viewTypePushButton = new QPushButton(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_VIEW), m_zoomWidget); - QMenu *viewTypeMenu = new QMenu(viewTypePushButton); - QAction *viewTypeIconsAction = NULL; - QAction *viewTypeListAction = NULL; - QHBoxLayout *gridProgressLayout = new QHBoxLayout(); - QLabel *gridProgressLabel = NULL; - QHBoxLayout *gridFooterLayout = NULL; qRegisterMetaType >("ThumbnailWidget"); qRegisterMetaType("retro_task_callback_t"); @@ -1545,70 +1567,159 @@ MainWindow::MainWindow(QWidget *parent) : m_thumbnailPackDownloadProgressDialog->cancel(); m_playlistThumbnailDownloadProgressDialog->cancel(); - m_gridProgressWidget = new QWidget(); - gridProgressLabel = new QLabel( + /* m_playlistViewsAndFooter holds the playlist/grid views on top + * and the footer toolbar (zoom, view-type, thumbnail-type) on + * the bottom. Set up its layout, add the views, then call + * setupPlaylistFooter() to build and attach the toolbar. */ + m_playlistViewsAndFooter->setLayout(new QVBoxLayout()); + + m_gridView->setSelectionMode(QAbstractItemView::SingleSelection); + m_gridView->setEditTriggers(QAbstractItemView::NoEditTriggers); + + m_playlistViews->addWidget(m_gridView); + m_playlistViews->addWidget(m_tableView); + m_centralWidget->setObjectName("centralWidget"); + + m_playlistViewsAndFooter->layout()->addWidget(m_playlistViews); + m_playlistViewsAndFooter->layout()->setAlignment(Qt::AlignCenter); + m_playlistViewsAndFooter->layout()->setContentsMargins(0, 0, 0, 0); + + setupPlaylistFooter(); + + setupModels(); + + m_logWidget->setObjectName("logWidget"); + + m_folderIcon = QIcon(QString(path_dir_assets) + GENERIC_FOLDER_ICON); + m_defaultStyle = QApplication::style(); + m_defaultPalette = QApplication::palette(); + + /* ViewOptionsDialog needs m_settings set before it's constructed */ + m_settings = new QSettings(configDir + + QString("/retroarch_qt.cfg"), QSettings::IniFormat, this); + m_viewOptionsDialog = new ViewOptionsDialog(this, 0); + m_playlistEntryDialog = new PlaylistEntryDialog(this, 0); + + /* default NULL parameter for parent wasn't added until 5.7 */ + qt_button_set_action_label(m_startCorePushButton, MENU_ENUM_LABEL_VALUE_START_CORE); + qt_button_set_action_label(m_runPushButton, MENU_ENUM_LABEL_VALUE_RUN); + qt_button_set_action_label(m_stopPushButton, MENU_ENUM_LABEL_VALUE_QT_STOP); + qt_button_set_action_label(m_coreInfoPushButton, MENU_ENUM_LABEL_VALUE_QT_INFO); + + setupFileSystemBrowser(); + + reloadPlaylists(); + + setupDockWidgets(); + + m_dirTree->setContextMenuPolicy(Qt::CustomContextMenu); + m_listWidget->setContextMenuPolicy(Qt::CustomContextMenu); + + setupSignalConnections(); + + m_timer->start(TIMER_MSEC); + + statusBar()->addPermanentWidget(m_statusLabel); + + setCurrentCoreLabel(); + setCoreActions(); + + /* Both of these are necessary to get the folder to scroll + * to the top of the view */ + qApp->processEvents(); + QTimer::singleShot(0, this, SLOT(onBrowserStartClicked())); + + m_searchLineEdit->setFocus(); + m_loadCoreWindow->setWindowModality(Qt::ApplicationModal); + + m_statusMessageElapsedTimer.start(); + +#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) + resizeDocks(QList() << m_searchDock, + QList() << 1, Qt::Vertical); +#endif +} + +/* Build the footer toolbar - zoom slider, view-type and thumbnail- + * type push buttons, items count label, and the always-hidden grid + * progress widget - and attach it under the playlist views. Owns + * all its widget locals so they don't pollute the constructor. + * Mutates several MainWindow members (m_gridProgressWidget, + * m_gridProgressBar, m_zoomSlider, m_lastZoomSliderValue) and + * appends to m_playlistViewsAndFooter's layout, which the caller + * is expected to have created. */ +void MainWindow::setupPlaylistFooter() +{ + QHBoxLayout *zoomLayout = new QHBoxLayout(); + QLabel *zoomLabel = new QLabel( + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_ZOOM), m_zoomWidget); + QPushButton *thumbnailTypePushButton = new QPushButton( + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_VIEW_OPTIONS_THUMBNAIL_TYPE), + m_zoomWidget); + QMenu *thumbnailTypeMenu = new QMenu(thumbnailTypePushButton); + QPushButton *viewTypePushButton = new QPushButton( + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_VIEW), m_zoomWidget); + QMenu *viewTypeMenu = new QMenu(viewTypePushButton); + QHBoxLayout *gridProgressLayout = new QHBoxLayout(); + QHBoxLayout *gridFooterLayout = NULL; + QLabel *gridProgressLabel = NULL; + + m_gridProgressWidget = new QWidget(); + gridProgressLabel = new QLabel( msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_PROGRESS), m_gridProgressWidget); thumbnailTypePushButton->setObjectName("thumbnailTypePushButton"); thumbnailTypePushButton->setFlat(true); - thumbnailTypeBoxartAction = thumbnailTypeMenu->addAction( - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_BOXART)); - thumbnailTypeScreenshotAction = thumbnailTypeMenu->addAction( - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_SCREENSHOT)); - thumbnailTypeTitleAction = thumbnailTypeMenu->addAction( - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_TITLE_SCREEN)); - thumbnailTypeLogoAction = thumbnailTypeMenu->addAction( - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_LOGO)); + connect(thumbnailTypeMenu->addAction( + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_BOXART)), + SIGNAL(triggered()), this, SLOT(onBoxartThumbnailClicked())); + connect(thumbnailTypeMenu->addAction( + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_SCREENSHOT)), + SIGNAL(triggered()), this, SLOT(onScreenshotThumbnailClicked())); + connect(thumbnailTypeMenu->addAction( + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_TITLE_SCREEN)), + SIGNAL(triggered()), this, SLOT(onTitleThumbnailClicked())); + connect(thumbnailTypeMenu->addAction( + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_THUMBNAIL_LOGO)), + SIGNAL(triggered()), this, SLOT(onLogoThumbnailClicked())); thumbnailTypePushButton->setMenu(thumbnailTypeMenu); viewTypePushButton->setObjectName("viewTypePushButton"); viewTypePushButton->setFlat(true); - viewTypeIconsAction = viewTypeMenu->addAction( - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_VIEW_TYPE_ICONS)); - viewTypeListAction = viewTypeMenu->addAction( - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_VIEW_TYPE_LIST)); + connect(viewTypeMenu->addAction( + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_VIEW_TYPE_ICONS)), + SIGNAL(triggered()), this, SLOT(onIconViewClicked())); + connect(viewTypeMenu->addAction( + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_VIEW_TYPE_LIST)), + SIGNAL(triggered()), this, SLOT(onListViewClicked())); viewTypePushButton->setMenu(viewTypeMenu); gridProgressLabel->setObjectName("gridProgressLabel"); - m_gridProgressBar = new QProgressBar( - m_gridProgressWidget); - - m_gridProgressBar->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred)); + m_gridProgressBar = new QProgressBar(m_gridProgressWidget); + m_gridProgressBar->setSizePolicy( + QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred)); zoomLabel->setObjectName("zoomLabel"); - m_zoomSlider = new QSlider( - Qt::Horizontal, m_zoomWidget); - + m_zoomSlider = new QSlider(Qt::Horizontal, m_zoomWidget); m_zoomSlider->setMinimum(0); m_zoomSlider->setMaximum(100); m_zoomSlider->setValue(50); - m_zoomSlider->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred)); + m_zoomSlider->setSizePolicy( + QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred)); m_lastZoomSliderValue = m_zoomSlider->value(); - m_playlistViewsAndFooter->setLayout(new QVBoxLayout()); - - m_gridView->setSelectionMode(QAbstractItemView::SingleSelection); - m_gridView->setEditTriggers(QAbstractItemView::NoEditTriggers); - - m_playlistViews->addWidget(m_gridView); - m_playlistViews->addWidget(m_tableView); - m_centralWidget->setObjectName("centralWidget"); - - m_playlistViewsAndFooter->layout()->addWidget(m_playlistViews); - m_playlistViewsAndFooter->layout()->setAlignment(Qt::AlignCenter); - m_playlistViewsAndFooter->layout()->setContentsMargins(0, 0, 0, 0); - m_gridProgressWidget->setLayout(gridProgressLayout); gridProgressLayout->setContentsMargins(0, 0, 0, 0); - gridProgressLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Preferred)); + gridProgressLayout->addSpacerItem(new QSpacerItem( + 0, 0, QSizePolicy::Expanding, QSizePolicy::Preferred)); gridProgressLayout->addWidget(gridProgressLabel); gridProgressLayout->addWidget(m_gridProgressBar); @@ -1623,18 +1734,27 @@ MainWindow::MainWindow(QWidget *parent) : gridFooterLayout = new QHBoxLayout(); gridFooterLayout->addWidget(m_itemsCountLabel); - gridFooterLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Preferred)); + gridFooterLayout->addSpacerItem(new QSpacerItem( + 0, 0, QSizePolicy::Expanding, QSizePolicy::Preferred)); gridFooterLayout->addWidget(m_gridProgressWidget); gridFooterLayout->addWidget(m_zoomWidget); gridFooterLayout->addWidget(thumbnailTypePushButton); gridFooterLayout->addWidget(viewTypePushButton); - static_cast(m_playlistViewsAndFooter->layout())->addLayout(gridFooterLayout); + static_cast(m_playlistViewsAndFooter->layout()) + ->addLayout(gridFooterLayout); m_gridProgressWidget->hide(); +} - m_playlistModel = new PlaylistModel(this); - m_proxyModel = new QSortFilterProxyModel(this); +/* Configure the playlist + filesystem proxy models and the table / + * file-table / grid views that consume them. The init list creates + * the views; this fills in their behaviour (sort, selection, etc.) + * and hooks them up to their models. */ +void MainWindow::setupModels() +{ + m_playlistModel = new PlaylistModel(this); + m_proxyModel = new QSortFilterProxyModel(this); m_proxyModel->setSourceModel(m_playlistModel); m_proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); @@ -1648,7 +1768,8 @@ MainWindow::MainWindow(QWidget *parent) : m_tableView->verticalHeader()->setVisible(false); m_tableView->setSelectionBehavior(QAbstractItemView::SelectRows); m_tableView->setSelectionMode(QAbstractItemView::SingleSelection); - m_tableView->setEditTriggers(QAbstractItemView::SelectedClicked | QAbstractItemView::EditKeyPressed); + m_tableView->setEditTriggers( + QAbstractItemView::SelectedClicked | QAbstractItemView::EditKeyPressed); m_tableView->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); m_tableView->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); m_tableView->horizontalHeader()->setStretchLastSection(true); @@ -1667,57 +1788,24 @@ MainWindow::MainWindow(QWidget *parent) : m_gridView->setItemDelegate(new ThumbnailDelegate(m_gridItem, this)); m_gridView->setModel(m_proxyModel); - m_gridView->setSelectionModel(m_tableView->selectionModel()); +} - m_logWidget->setObjectName("logWidget"); - - m_folderIcon = QIcon(QString(path_dir_assets) + GENERIC_FOLDER_ICON); - m_defaultStyle = QApplication::style(); - m_defaultPalette = QApplication::palette(); - - /* ViewOptionsDialog needs m_settings set before it's constructed */ - m_settings = new QSettings(configDir - + QString("/retroarch_qt.cfg"), QSettings::IniFormat, this); - m_viewOptionsDialog = new ViewOptionsDialog(this, 0); - m_playlistEntryDialog = new PlaylistEntryDialog(this, 0); +/* Configure the QFileSystemModels and the directory tree view used + * by the file browser. Sets entry filters (respecting the user's + * "show hidden files" preference), points both models at the root + * of the filesystem, and hides the size/type/date columns on the + * tree so only names are shown. */ +void MainWindow::setupFileSystemBrowser() +{ + const bool show_hidden = m_settings->value("show_hidden_files", true).toBool(); + const QDir::Filters hidden_filters = show_hidden + ? (QDir::Hidden | QDir::System) + : static_cast(0); - /* default NULL parameter for parent wasn't added until 5.7 */ - m_startCorePushButton->setDefaultAction(new QAction(msg_hash_to_str( - MENU_ENUM_LABEL_VALUE_START_CORE), m_startCorePushButton)); - m_startCorePushButton->setFixedSize(m_startCorePushButton->sizeHint()); - - m_runPushButton->setDefaultAction(new QAction(msg_hash_to_str( - MENU_ENUM_LABEL_VALUE_RUN), m_runPushButton)); - m_runPushButton->setFixedSize(m_runPushButton->sizeHint()); - - m_stopPushButton->setDefaultAction(new QAction(msg_hash_to_str( - MENU_ENUM_LABEL_VALUE_QT_STOP), m_stopPushButton)); - m_stopPushButton->setFixedSize(m_stopPushButton->sizeHint()); - - m_coreInfoPushButton->setDefaultAction(new QAction(msg_hash_to_str( - MENU_ENUM_LABEL_VALUE_QT_INFO), m_coreInfoPushButton)); - m_coreInfoPushButton->setFixedSize(m_coreInfoPushButton->sizeHint()); - - searchResetButton = new QToolButton(m_searchWidget); - searchResetButton->setDefaultAction(new QAction(msg_hash_to_str( - MENU_ENUM_LABEL_VALUE_QT_MENU_SEARCH_CLEAR), searchResetButton)); - searchResetButton->setFixedSize(searchResetButton->sizeHint()); - - connect(searchResetButton, SIGNAL(clicked()), this, SLOT(onSearchResetClicked())); - - m_dirModel->setFilter( QDir::NoDotAndDotDot - | QDir::AllDirs - | QDir::Drives - | (m_settings->value("show_hidden_files", true).toBool() - ? (QDir::Hidden | QDir::System) - : static_cast(0))); - - m_fileModel->setFilter( QDir::NoDot - | QDir::AllEntries - | (m_settings->value("show_hidden_files", true).toBool() - ? (QDir::Hidden | QDir::System) - : static_cast(0))); + m_dirModel->setFilter(QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Drives + | hidden_filters); + m_fileModel->setFilter(QDir::NoDot | QDir::AllEntries | hidden_filters); #if defined(Q_OS_WIN) m_dirModel->setRootPath(""); @@ -1739,60 +1827,66 @@ MainWindow::MainWindow(QWidget *parent) : m_dirTree->hideColumn(2); /* type */ m_dirTree->hideColumn(3); /* date modified */ } +} - reloadPlaylists(); +/* Build the search / core-info / log dock widgets and their + * contents, register them with the main window, and hide the log + * dock so it stays out of the way until the user opens it. Pulled + * out of the constructor for readability. */ +void MainWindow::setupDockWidgets() +{ + QToolButton *searchResetButton = new QToolButton(m_searchWidget); + qt_button_set_action_label(searchResetButton, + MENU_ENUM_LABEL_VALUE_QT_MENU_SEARCH_CLEAR); + connect(searchResetButton, SIGNAL(clicked()), this, + SLOT(onSearchResetClicked())); m_searchWidget->setLayout(new QHBoxLayout()); m_searchWidget->layout()->addWidget(m_searchLineEdit); m_searchWidget->layout()->addWidget(searchResetButton); - m_searchDock->setObjectName("searchDock"); - m_searchDock->setProperty("default_area", Qt::LeftDockWidgetArea); - m_searchDock->setProperty("menu_text", - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_SEARCH)); - m_searchDock->setWidget(m_searchWidget); + qt_dock_configure(m_searchDock, "searchDock", Qt::LeftDockWidgetArea, + MENU_ENUM_LABEL_VALUE_SEARCH, m_searchWidget); m_searchDock->setFixedHeight(m_searchDock->minimumSizeHint().height()); - addDockWidget(static_cast( - m_searchDock->property("default_area").toInt()), m_searchDock); + qt_dock_add_to(this, m_searchDock); m_coreInfoLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop); m_coreInfoLabel->setTextFormat(Qt::RichText); m_coreInfoLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); m_coreInfoLabel->setOpenExternalLinks(true); - m_coreInfoDock->setObjectName("coreInfoDock"); - m_coreInfoDock->setProperty("default_area", Qt::RightDockWidgetArea); - m_coreInfoDock->setProperty("menu_text", - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_CORE_INFO)); - m_coreInfoDock->setWidget(m_coreInfoWidget); + qt_dock_configure(m_coreInfoDock, "coreInfoDock", Qt::RightDockWidgetArea, + MENU_ENUM_LABEL_VALUE_QT_CORE_INFO, m_coreInfoWidget); - addDockWidget(static_cast( - m_coreInfoDock->property("default_area").toInt()), m_coreInfoDock); + qt_dock_add_to(this, m_coreInfoDock); m_logWidget->setLayout(new QVBoxLayout()); m_logWidget->layout()->addWidget(m_logTextEdit); m_logWidget->layout()->setContentsMargins(0, 0, 0, 0); - m_logDock->setObjectName("logDock"); - m_logDock->setProperty("default_area", Qt::BottomDockWidgetArea); - m_logDock->setProperty("menu_text", msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_LOG)); - m_logDock->setWidget(m_logWidget); + qt_dock_configure(m_logDock, "logDock", Qt::BottomDockWidgetArea, + MENU_ENUM_LABEL_VALUE_QT_LOG, m_logWidget); - addDockWidget(static_cast( - m_logDock->property("default_area").toInt()), m_logDock); + qt_dock_add_to(this, m_logDock); /* Hide the log by default. If user has saved their dock positions * with the log visible, then this hide() call will be reversed * later by restoreState(). * * FIXME: If user unchecks "save dock positions", the log will - * not be unhidden even if it was previously saved in the config. - */ + * not be unhidden even if it was previously saved in the config. */ m_logDock->hide(); +} - m_dirTree->setContextMenuPolicy(Qt::CustomContextMenu); - m_listWidget->setContextMenuPolicy(Qt::CustomContextMenu); +/* Wire up signal/slot connections for the main window's child + * widgets and self-emitted signals. Pulled out of the constructor + * to keep that body readable. All connections operate on already- + * constructed members of MainWindow; no parameters needed. */ +void MainWindow::setupSignalConnections() +{ + settings_t *settings = config_get_ptr(); + const char *path_dir_menu_content = settings->paths.directory_menu_content; connect(m_searchLineEdit, SIGNAL(returnPressed()), this, SLOT(onSearchEnterPressed())); @@ -1822,18 +1916,6 @@ MainWindow::MainWindow(QWidget *parent) : SLOT(onLaunchWithComboBoxIndexChanged(int))); connect(m_zoomSlider, SIGNAL(valueChanged(int)), this, SLOT(onZoomValueChanged(int))); - connect(thumbnailTypeBoxartAction, SIGNAL(triggered()), this, - SLOT(onBoxartThumbnailClicked())); - connect(thumbnailTypeScreenshotAction, SIGNAL(triggered()), this, - SLOT(onScreenshotThumbnailClicked())); - connect(thumbnailTypeTitleAction, SIGNAL(triggered()), this, - SLOT(onTitleThumbnailClicked())); - connect(thumbnailTypeLogoAction, SIGNAL(triggered()), this, - SLOT(onLogoThumbnailClicked())); - connect(viewTypeIconsAction, SIGNAL(triggered()), this, - SLOT(onIconViewClicked())); - connect(viewTypeListAction, SIGNAL(triggered()), this, - SLOT(onListViewClicked())); connect(m_dirModel, SIGNAL(directoryLoaded(const QString&)), this, SLOT(onFileSystemDirLoaded(const QString&))); connect(m_fileModel, SIGNAL(directoryLoaded(const QString&)), this, @@ -1852,7 +1934,7 @@ MainWindow::MainWindow(QWidget *parent) : connect(m_playlistThumbnailDownloadProgressDialog, SIGNAL(canceled()), m_playlistThumbnailDownloadProgressDialog, SLOT(cancel())); connect(m_playlistThumbnailDownloadProgressDialog, SIGNAL(canceled()), - this,SLOT(onPlaylistThumbnailDownloadCanceled())); + this, SLOT(onPlaylistThumbnailDownloadCanceled())); connect(m_thumbnailDownloadProgressDialog, SIGNAL(canceled()), m_thumbnailDownloadProgressDialog, SLOT(cancel())); @@ -1923,28 +2005,6 @@ MainWindow::MainWindow(QWidget *parent) : QString,retro_task_callback_t)), this, SLOT(onExtractArchive(QString,QString,QString,retro_task_callback_t)), Qt::QueuedConnection); - - m_timer->start(TIMER_MSEC); - - statusBar()->addPermanentWidget(m_statusLabel); - - setCurrentCoreLabel(); - setCoreActions(); - - /* Both of these are necessary to get the folder to scroll - * to the top of the view */ - qApp->processEvents(); - QTimer::singleShot(0, this, SLOT(onBrowserStartClicked())); - - m_searchLineEdit->setFocus(); - m_loadCoreWindow->setWindowModality(Qt::ApplicationModal); - - m_statusMessageElapsedTimer.start(); - -#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) - resizeDocks(QList() << m_searchDock, - QList() << 1, Qt::Vertical); -#endif } MainWindow::~MainWindow() @@ -3036,7 +3096,6 @@ ViewOptionsDialog* MainWindow::viewOptionsDialog() {return m_viewOptionsDialog;} void MainWindow::setCoreActions() { QListWidgetItem *currentPlaylistItem = m_listWidget->currentItem(); - ViewType viewType = getCurrentViewType(); QHash hash = getCurrentContentHash(); QString currentPlaylistFileName = QString(); rarch_system_info_t *sys_info = &runloop_state_get_ptr()->system; @@ -3414,8 +3473,7 @@ void MainWindow::onShowHiddenDockWidgetAction() if (!dock->isVisible()) { - addDockWidget(static_cast( - dock->property("default_area").toInt()), dock); + qt_dock_add_to(this, dock); dock->setVisible(true); dock->setFloating(false); } @@ -4787,15 +4845,12 @@ static QDockWidget *qt_companion_build_browser_dock(MainWindow *mainwindow) browserAndPlaylistTabDock = new QDockWidget(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_MENU_DOCK_CONTENT_BROWSER), mainwindow); - browserAndPlaylistTabDock->setObjectName("browserAndPlaylistTabDock"); - browserAndPlaylistTabDock->setProperty("default_area", Qt::LeftDockWidgetArea); - browserAndPlaylistTabDock->setProperty("menu_text", - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_MENU_DOCK_CONTENT_BROWSER)); - browserAndPlaylistTabDock->setWidget(browserAndPlaylistTabWidget); - - mainwindow->addDockWidget(static_cast( - browserAndPlaylistTabDock->property("default_area").toInt()), - browserAndPlaylistTabDock); + qt_dock_configure(browserAndPlaylistTabDock, + "browserAndPlaylistTabDock", Qt::LeftDockWidgetArea, + MENU_ENUM_LABEL_VALUE_QT_MENU_DOCK_CONTENT_BROWSER, + browserAndPlaylistTabWidget); + + qt_dock_add_to(mainwindow, browserAndPlaylistTabDock); browserButtonsHBoxLayout->addItem(new QSpacerItem( browserAndPlaylistTabWidget->tabBar()->width(), @@ -4835,13 +4890,10 @@ static void qt_companion_build_thumbnail_docks(MainWindow *mainwindow) SLOT(onThumbnailDropped(const QImage&, ThumbnailType))); docks[i] = new QDockWidget(msg_hash_to_str(labels[i]), mainwindow); - docks[i]->setObjectName(dock_obj_names[i]); - docks[i]->setProperty("default_area", Qt::RightDockWidgetArea); - docks[i]->setProperty("menu_text", msg_hash_to_str(labels[i])); - docks[i]->setWidget(tw); + qt_dock_configure(docks[i], dock_obj_names[i], + Qt::RightDockWidgetArea, labels[i], tw); - mainwindow->addDockWidget(static_cast( - docks[i]->property("default_area").toInt()), docks[i]); + qt_dock_add_to(mainwindow, docks[i]); } for (i = 1; i < 4; i++) @@ -4893,15 +4945,12 @@ static void qt_companion_build_core_selection_dock(MainWindow *mainwindow, coreSelectionDock = new QDockWidget(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_CORE), mainwindow); - coreSelectionDock->setObjectName("coreSelectionDock"); - coreSelectionDock->setProperty("default_area", Qt::LeftDockWidgetArea); - coreSelectionDock->setProperty("menu_text", msg_hash_to_str( - MENU_ENUM_LABEL_VALUE_QT_CORE)); - coreSelectionDock->setWidget(coreSelectionWidget); + qt_dock_configure(coreSelectionDock, "coreSelectionDock", + Qt::LeftDockWidgetArea, MENU_ENUM_LABEL_VALUE_QT_CORE, + coreSelectionWidget); coreSelectionDock->setFixedHeight(coreSelectionDock->minimumSizeHint().height()); - mainwindow->addDockWidget(static_cast( - coreSelectionDock->property("default_area").toInt()), coreSelectionDock); + qt_dock_add_to(mainwindow, coreSelectionDock); mainwindow->splitDockWidget(browserAndPlaylistTabDock, coreSelectionDock, Qt::Vertical); diff --git a/ui/drivers/ui_qt.h b/ui/drivers/ui_qt.h index ee322e5dd1a7..9d73baff15cb 100644 --- a/ui/drivers/ui_qt.h +++ b/ui/drivers/ui_qt.h @@ -25,7 +25,6 @@ #include #include #include -#include #include #include #include @@ -37,7 +36,6 @@ #include #include #include -#include #include #include #include @@ -640,6 +638,15 @@ private slots: void applySearch(); void updateItemsCount(); QString changeThumbnail(const QImage &image, QString type); + /* Constructor helpers - keep MainWindow::MainWindow readable by + * pulling self-contained chunks of setup out into their own + * methods. None take parameters; everything operates on already- + * initialised members. */ + void setupPlaylistFooter(); + void setupModels(); + void setupFileSystemBrowser(); + void setupDockWidgets(); + void setupSignalConnections(); PlaylistModel *m_playlistModel; QSortFilterProxyModel *m_proxyModel; diff --git a/ui/drivers/ui_qt_widgets.h b/ui/drivers/ui_qt_widgets.h index 474bacf498c8..5a4db8ce2e50 100644 --- a/ui/drivers/ui_qt_widgets.h +++ b/ui/drivers/ui_qt_widgets.h @@ -19,7 +19,6 @@ #include #include #include -#include #include #include From a9878b1c35168b1f833122184233b5534ea1c845 Mon Sep 17 00:00:00 2001 From: "U-DESKTOP-SPFP6AQ\\twistedtechre" Date: Fri, 1 May 2026 21:36:51 +0200 Subject: [PATCH 10/14] Qt: prefer libretro-common file APIs and drop Qt5/Qt6 ladders Reduces Qt-specific surface and silences three pre-existing -Wdeprecated-declarations warnings. All changes preserve behaviour on Qt 5.2 and up (RetroArch's minimum, per qb/config.libs.sh). * MainWindow::MainWindow: Drop dead 'QDir playlistDir(path_dir_playlist)' and the path_dir_playlist local that only existed to construct it. Both unused since the constructor split. -Wunused-variable did not flag the QDir because of its non-trivial constructor. * MainWindow::setCustomThemeFile: Replace QFile open / readAll / close with filestream_read_file plus filestream_exists. Same five error paths preserved (blank path, missing file, open failure, empty file, success). * MainWindow::onFileBrowserTreeContextMenuRequested: Replace the QDir / dir.exists() existence check with path_is_directory(). Removes a Qt5/Qt6 ifdef ladder ('dir = string' on Qt5 vs 'dir.setPath(string)' on Qt6) and silences the QDir::operator=(QString) deprecation warning. The QDir::toNativeSeparators(...) display path stays - that side is fine on both Qt 5 and Qt 6. * LoadCoreWindow::onCoreEnterPressed: Replace 'QByteArray::append(QString)' (Qt 5) / 'QByteArray::append(QString::toStdString())' (Qt 6) ladder with 'path.toUtf8().constData()' inline. The temporary QByteArray's lifetime extends through the loadCore() call so this is safe. Silences the QByteArray::append(QString) deprecation warning. * MainWindow::onPlaylistWidgetContextMenuRequested: Replace 'm_listWidget->isItemHidden(item)' (deprecated since Qt 4.8) with 'item->isHidden()' (available since Qt 4.x). Removes a Qt5/Qt6 ifdef ladder. Silences the QListWidget::isItemHidden() deprecation warning. Tested under Qt 5.15 with -Wdeprecated-declarations enabled; all four object files (ui_qt, ui_qt_widgets, moc_ui_qt, moc_ui_qt_widgets) now build with zero warnings. No behaviour change. --- ui/drivers/ui_qt.cpp | 85 ++++++++++++++---------------------- ui/drivers/ui_qt_widgets.cpp | 7 +-- 2 files changed, 33 insertions(+), 59 deletions(-) diff --git a/ui/drivers/ui_qt.cpp b/ui/drivers/ui_qt.cpp index 04b9da0cb5d7..293ecf068760 100644 --- a/ui/drivers/ui_qt.cpp +++ b/ui/drivers/ui_qt.cpp @@ -1540,9 +1540,7 @@ MainWindow::MainWindow(QWidget *parent) : ,m_itemsCountLabel(new QLabel(this)) { settings_t *settings = config_get_ptr(); - const char *path_dir_playlist = settings->paths.directory_playlist; const char *path_dir_assets = settings->paths.directory_assets; - QDir playlistDir(path_dir_playlist); QString configDir = QFileInfo(path_get(RARCH_PATH_CONFIG)).dir().absolutePath(); qRegisterMetaType >("ThumbnailWidget"); @@ -2202,6 +2200,11 @@ const QString& MainWindow::customThemeString() const bool MainWindow::setCustomThemeFile(QString filePath) { + QByteArray pathArray; + const char *path_data; + void *buf = NULL; + int64_t len = 0; + if (filePath.isEmpty()) { QMessageBox::critical(this, @@ -2210,45 +2213,37 @@ bool MainWindow::setCustomThemeFile(QString filePath) return false; } - QFile file(filePath); + pathArray = filePath.toUtf8(); + path_data = pathArray.constData(); - if (file.exists()) + if (!filestream_exists(path_data)) { - bool opened = file.open(QIODevice::ReadOnly); - - if (!opened) - { - QMessageBox::critical(this, - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_CUSTOM_THEME), - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_FILE_READ_OPEN_FAILED)); - return false; - } - - { - QByteArray fileArray = file.readAll(); - QString fileStr = QString::fromUtf8(fileArray); - - file.close(); - - if (fileStr.isEmpty()) - { - QMessageBox::critical(this, - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_CUSTOM_THEME), - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_FILE_IS_EMPTY)); - return false; - } + QMessageBox::critical(this, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_CUSTOM_THEME), + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_FILE_DOES_NOT_EXIST)); + return false; + } - setCustomThemeString(fileStr); - } + if (!filestream_read_file(path_data, &buf, &len)) + { + QMessageBox::critical(this, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_CUSTOM_THEME), + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_FILE_READ_OPEN_FAILED)); + return false; } - else + + if (len <= 0) { + free(buf); QMessageBox::critical(this, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_CUSTOM_THEME), - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_FILE_DOES_NOT_EXIST)); + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_FILE_IS_EMPTY)); return false; } + setCustomThemeString(QString::fromUtf8(static_cast(buf), + static_cast(len))); + free(buf); return true; } @@ -2327,28 +2322,24 @@ bool MainWindow::showMessageBox(QString msg, MessageBoxType msgType, void MainWindow::onFileBrowserTreeContextMenuRequested(const QPoint&) { #ifdef HAVE_LIBRETRODB - QDir dir; - QByteArray dirArray; QPointer action; QList actions; QScopedPointer scanAction; QString currentDirString = QDir::toNativeSeparators( m_dirModel->filePath(m_dirTree->currentIndex())); settings_t *settings = config_get_ptr(); - const char *fullpath = NULL; const char *path_dir_playlist = settings->paths.directory_playlist; const char *path_content_db = settings->paths.path_content_database; + QByteArray dirArray; + const char *fullpath = NULL; if (currentDirString.isEmpty()) return; -#if (QT_VERSION > QT_VERSION_CHECK(6, 0, 0)) - dir.setPath(currentDirString); -#else - dir = currentDirString; -#endif + dirArray = currentDirString.toUtf8(); + fullpath = dirArray.constData(); - if (!dir.exists()) + if (!path_is_directory(fullpath)) return; /* Default NULL parameter for parent wasn't added until 5.7 */ @@ -2360,9 +2351,6 @@ void MainWindow::onFileBrowserTreeContextMenuRequested(const QPoint&) if (!(action = QMenu::exec(actions, QCursor::pos(), NULL, m_dirTree))) return; - dirArray = currentDirString.toUtf8(); - fullpath = dirArray.constData(); - task_push_dbscan( path_dir_playlist, path_content_db, @@ -5426,22 +5414,13 @@ void LoadCoreWindow::loadCore(const char *path) void LoadCoreWindow::onCoreEnterPressed() { - QByteArray pathArray; - const char *pathData = NULL; QTableWidgetItem *selectedCoreItem = m_table->item(m_table->currentRow(), CORE_NAME_COLUMN); QVariantHash hash = selectedCoreItem->data( Qt::UserRole).toHash(); QString path = hash["path"].toString(); -#if (QT_VERSION > QT_VERSION_CHECK(6, 0, 0)) - pathArray.append(path.toStdString()); -#else - pathArray.append(path); -#endif - pathData = pathArray.constData(); - - loadCore(pathData); + loadCore(path.toUtf8().constData()); } void LoadCoreWindow::onLoadCustomCoreClicked() diff --git a/ui/drivers/ui_qt_widgets.cpp b/ui/drivers/ui_qt_widgets.cpp index 287d041c3d30..00a5d5cd6c10 100644 --- a/ui/drivers/ui_qt_widgets.cpp +++ b/ui/drivers/ui_qt_widgets.cpp @@ -8024,13 +8024,8 @@ void MainWindow::onPlaylistWidgetContextMenuRequested(const QPoint&) for (j = 0; j < m_listWidget->count(); j++) { QListWidgetItem *item = m_listWidget->item(j); -#if (QT_VERSION > QT_VERSION_CHECK(6, 0, 0)) - bool hidden = item->isHidden(); -#else - bool hidden = m_listWidget->isItemHidden(item); -#endif - if (hidden) + if (item->isHidden()) { QAction *action = hiddenPlaylistsMenu->addAction(item->text()); action->setProperty("row", j); From 1816b93862d3f8de067bd69b6ac2c84969932b17 Mon Sep 17 00:00:00 2001 From: "U-DESKTOP-SPFP6AQ\\twistedtechre" Date: Fri, 1 May 2026 22:08:40 +0200 Subject: [PATCH 11/14] Qt: prune dead getters, signals, forward decls, and locals Pure dead-code removal in three files. No behaviour change. * ui_qt.h: drop 8 redundant forward declarations. GridView, ShaderParamsDialog, CoreOptionsDialog, CoreInfoDialog, PlaylistEntryDialog and ViewOptionsDialog all have full definitions in ui_qt_widgets.h, which ui_qt.h includes at line 46 - the forward decls below the include were noise. MainWindow and ThumbnailWidget were forward-declared at the top of ui_qt.h but never referenced before their own definitions later in the same file. Only the ThumbnailLabel forward decl is load-bearing - ThumbnailWidget references ThumbnailLabel* as a member before ThumbnailLabel is defined. * MainWindow: drop 5 dead signals and 4 dead connect() calls. - gotReloadCoreOptions, gotThumbnailDownload, updateThumbnails and extractArchiveDeferred each had a connect() in the constructor but were never emitted anywhere - the signal connections were inert. The slots they pointed to (onDownloadThumbnail, onGotReloadCoreOptions, onExtractArchive, updateVisibleItems) all stay - they have other callers, either via direct calls or via QMetaObject::invokeMethod. - gridItemChanged was declared but neither emitted nor connected. * MainWindow: drop 3 dead public getters. contentTableView(), contentGridView() and searchWidget() were declared and defined but had zero callers across ui/, libretro- common/, frontend/, menu/, and retroarch.c. * MainWindow::MainWindow: drop dead 'QDir playlistDir' and the path_dir_playlist local that only existed to construct it. Both unused since the constructor split. -Wunused-variable did not flag the QDir because of its non-trivial constructor. * MainWindow::changeThumbnail: replace QDir.exists() / QDir.mkpath pair with path_is_directory() / path_mkdir() from libretro- common, dropping one more QDir local. mkpath(".") and path_mkdir() both create parent directories recursively. * MainWindow::setCoreActions: drop dead 'QFileInfo info' block ('info' was assigned via setFile() but never read) and a dead 'coreName = ""' assignment in a then-branch that did nothing else with coreName. * qt_companion_select_initial_playlist: drop redundant 'listWidget->count() &&' guard from two for-loop conditions ('i < count' is already false when count is 0). * ui_qt_widgets.cpp: drop pre-existing dead 'uint32_t flags = runloop_st->flags' local in CoreOptionsDialog (-Wunused-variable was suppressed in the build and so this slipped in years ago). --- ui/drivers/ui_qt.cpp | 73 ++++++++++++------------------------ ui/drivers/ui_qt.h | 16 -------- ui/drivers/ui_qt_widgets.cpp | 2 - 3 files changed, 25 insertions(+), 66 deletions(-) diff --git a/ui/drivers/ui_qt.cpp b/ui/drivers/ui_qt.cpp index 293ecf068760..008c40726bdb 100644 --- a/ui/drivers/ui_qt.cpp +++ b/ui/drivers/ui_qt.cpp @@ -1945,12 +1945,9 @@ void MainWindow::setupSignalConnections() this, SLOT(onThumbnailPackDownloadCanceled())); connect(this, SIGNAL(itemChanged()), this, SLOT(onItemChanged())); - connect(this, SIGNAL(gotThumbnailDownload(QString,QString)), - this, SLOT(onDownloadThumbnail(QString,QString))); m_thumbnailTimer->setSingleShot(true); connect(m_thumbnailTimer, SIGNAL(timeout()), this, SLOT(updateVisibleItems())); - connect(this, SIGNAL(updateThumbnails()), this, SLOT(updateVisibleItems())); /* TODO: Handle scroll and resize differently. */ connect(m_gridView, SIGNAL(visibleItemsChangedMaybe()), @@ -1991,18 +1988,12 @@ void MainWindow::setupSignalConnections() SLOT(onGotReloadShaderParams()), Qt::AutoConnection); #endif #endif - connect(this, SIGNAL(gotReloadCoreOptions()), this, - SLOT(onGotReloadCoreOptions()), Qt::AutoConnection); /* These are always queued */ connect(this, SIGNAL(showErrorMessageDeferred(QString)), this, SLOT(onShowErrorMessage(QString)), Qt::QueuedConnection); connect(this, SIGNAL(showInfoMessageDeferred(QString)), this, SLOT(onShowInfoMessage(QString)), Qt::QueuedConnection); - connect(this, SIGNAL(extractArchiveDeferred(QString,QString, - QString,retro_task_callback_t)), this, - SLOT(onExtractArchive(QString,QString,QString,retro_task_callback_t)), - Qt::QueuedConnection); } MainWindow::~MainWindow() @@ -2585,12 +2576,11 @@ QString MainWindow::changeThumbnail(const QImage &image, QString type) QByteArray thumbArray = QDir::toNativeSeparators(thumbPath).toUtf8(); const char *thumbData = thumbArray.constData(); int quality = -1; - QDir dir(dirString); QImage scaledImage(image); - if (!dir.exists()) + if (!path_is_directory(dirData)) { - if (!dir.mkpath(".")) + if (!path_mkdir(dirData)) { RARCH_ERR("[Qt] Could not create directory: \"%s\".\n", dirData); return QString(); @@ -3115,41 +3105,34 @@ void MainWindow::setCoreActions() { QString coreName = hash["core_name"]; - if (coreName.isEmpty()) - coreName = QString(""); - else + if (!coreName.isEmpty() && coreName != QLatin1String("DETECT")) { - const char *detect_str = "DETECT"; - - if (coreName != detect_str) + if (m_launchWithComboBox->findText(coreName) == -1) { - if (m_launchWithComboBox->findText(coreName) == -1) + int i; + bool found_existing = false; + + for (i = 0; i < m_launchWithComboBox->count(); i++) { - int i; - bool found_existing = false; + QVariantMap map = m_launchWithComboBox->itemData( + i, Qt::UserRole).toMap(); - for (i = 0; i < m_launchWithComboBox->count(); i++) + if ( map.value("core_path").toString() == hash["core_path"] + || map.value("core_name").toString() == coreName) { - QVariantMap map = m_launchWithComboBox->itemData( - i, Qt::UserRole).toMap(); - - if ( map.value("core_path").toString() == hash["core_path"] - || map.value("core_name").toString() == coreName) - { - found_existing = true; - break; - } + found_existing = true; + break; } + } - if (!found_existing) - { - QVariantMap comboBoxMap; - comboBoxMap["core_name"] = coreName; - comboBoxMap["core_path"] = hash["core_path"]; - comboBoxMap["core_selection"] = CORE_SELECTION_PLAYLIST_SAVED; - m_launchWithComboBox->addItem(coreName, - QVariant::fromValue(comboBoxMap)); - } + if (!found_existing) + { + QVariantMap comboBoxMap; + comboBoxMap["core_name"] = coreName; + comboBoxMap["core_path"] = hash["core_path"]; + comboBoxMap["core_selection"] = CORE_SELECTION_PLAYLIST_SAVED; + m_launchWithComboBox->addItem(coreName, + QVariant::fromValue(comboBoxMap)); } } } @@ -3193,13 +3176,10 @@ void MainWindow::setCoreActions() if (allPlaylists) { - QFileInfo info; QListWidgetItem *listItem = m_listWidget->item(row); QString listItemString = listItem->data( Qt::UserRole).toString(); - info.setFile(listItemString); - if (listItemString == ALL_PLAYLISTS_TOKEN) continue; } @@ -3467,7 +3447,6 @@ void MainWindow::onShowHiddenDockWidgetAction() } } -QWidget* MainWindow::searchWidget() { return m_searchWidget; } QLineEdit* MainWindow::searchLineEdit() { return m_searchLineEdit; } void MainWindow::onSearchEnterPressed() { @@ -3723,12 +3702,10 @@ void MainWindow::onCurrentListItemChanged( setCoreActions(); } -TableView* MainWindow::contentTableView() { return m_tableView; } QTableView* MainWindow::fileTableView() { return m_fileTableView; } QStackedWidget* MainWindow::centralWidget() { return m_centralWidget; } FileDropWidget* MainWindow::playlistViews() { return m_playlistViews; } QWidget* MainWindow::playlistViewsAndFooter() {return m_playlistViewsAndFooter;} -GridView* MainWindow::contentGridView() { return m_gridView; } void MainWindow::onBrowserDownloadsClicked() { @@ -5041,7 +5018,7 @@ static void qt_companion_select_initial_playlist(QListWidget *listWidget, int i; bool found = false; - for (i = 0; listWidget->count() && i < listWidget->count(); i++) + for (i = 0; i < listWidget->count(); i++) { QListWidgetItem *item = listWidget->item(i); QString path; @@ -5064,7 +5041,7 @@ static void qt_companion_select_initial_playlist(QListWidget *listWidget, return; /* Couldn't find the user's initial playlist, just find anything. */ - for (i = 0; listWidget->count() && i < listWidget->count(); i++) + for (i = 0; i < listWidget->count(); i++) { if (!listWidget->isRowHidden(i)) { diff --git a/ui/drivers/ui_qt.h b/ui/drivers/ui_qt.h index 9d73baff15cb..988a7436ff30 100644 --- a/ui/drivers/ui_qt.h +++ b/ui/drivers/ui_qt.h @@ -96,15 +96,7 @@ class QSlider; class QDragEnterEvent; class QDropEvent; class QProgressDialog; -class MainWindow; -class ThumbnailWidget; class ThumbnailLabel; -class GridView; -class ShaderParamsDialog; -class CoreOptionsDialog; -class CoreInfoDialog; -class PlaylistEntryDialog; -class ViewOptionsDialog; enum SpecialPlaylist { @@ -471,12 +463,9 @@ class MainWindow : public QMainWindow PlaylistModel* playlistModel(); ListWidget* playlistListWidget(); QStackedWidget* centralWidget(); - TableView* contentTableView(); QTableView* fileTableView(); FileDropWidget* playlistViews(); - GridView* contentGridView(); QWidget* playlistViewsAndFooter(); - QWidget* searchWidget(); QLineEdit* searchLineEdit(); QComboBox* launchWithComboBox(); QToolButton* startCorePushButton(); @@ -524,14 +513,9 @@ class MainWindow : public QMainWindow void gotStatusMessage(QString msg, unsigned priority, unsigned duration, bool flush); void gotReloadPlaylists(); void gotReloadShaderParams(); - void gotReloadCoreOptions(); void showErrorMessageDeferred(QString msg); void showInfoMessageDeferred(QString msg); - void extractArchiveDeferred(QString path, QString extractionDir, QString tempExtension, retro_task_callback_t cb); void itemChanged(); - void updateThumbnails(); - void gridItemChanged(QString title); - void gotThumbnailDownload(QString system, QString title); void scrollToDownloads(QString path); void scrollToDownloadsAgain(QString path); diff --git a/ui/drivers/ui_qt_widgets.cpp b/ui/drivers/ui_qt_widgets.cpp index 00a5d5cd6c10..5547621c90e9 100644 --- a/ui/drivers/ui_qt_widgets.cpp +++ b/ui/drivers/ui_qt_widgets.cpp @@ -2922,8 +2922,6 @@ void CoreOptionsDialog::buildLayout() if (!contentLabel.isEmpty()) { - uint32_t flags = runloop_st->flags; - if (!label.isEmpty()) { QHBoxLayout *gameOptionsLayout = new QHBoxLayout(); From 0cc7a59da0cdb8d5eb101e9ce09554cfc0df9011 Mon Sep 17 00:00:00 2001 From: "U-DESKTOP-SPFP6AQ\\twistedtechre" Date: Fri, 1 May 2026 22:32:15 +0200 Subject: [PATCH 12/14] Qt: convert PlaylistModel content rows from QHash to a typed struct The Qt UI's playlist rows used to round-trip through QHash with magic string keys ('path', 'label', 'core_path', etc.). This commit replaces that with a typed PlaylistEntry struct. The keys actually used at the call sites collapsed cleanly into 9 fields plus a row index. Other QHash uses in the file (core-info display rows, core-selection-dialog rows) follow a different schema and are intentionally left alone. * ui_qt_widgets.h: define PlaylistEntry. The struct lives in this header rather than ui_qt.h because PlaylistEntryDialog has it in a default argument and ui_qt_widgets.h is included by ui_qt.h, not the other way round. Q_DECLARE_METATYPE and qRegisterMetaType() enable QVariant transport. * PlaylistModel: m_contents is now QVector. getThumbnailPath(), data() (HASH role), and setData() all consume the struct directly. The custom user role enum is renamed HASH -> ENTRY to match. * MainWindow: - getCurrentContentHash() -> getCurrentContentEntry() - getFileContentHash() -> getFileContentEntry() - loadContent(QHash) -> loadContent(PlaylistEntry) - onCurrentItemChanged(QHash) -> onCurrentItemChanged(PlaylistEntry) - updateCurrentPlaylistEntry(QHash) -> (PlaylistEntry) All call sites (setCoreActions, getSelectedCorePath, onRunClicked, onCurrentTableItemDataChanged, onCurrentFileChanged, onFileDoubleClicked, changeThumbnail, deleteCurrentPlaylistItem, the playlist-context-menu handler, and the addFilesToPlaylist flow) updated to read entry.field instead of hash["field"]. * PlaylistEntryDialog::showDialog and setEntryValues now take const PlaylistEntry &. The 'multiple entries' detection that used QHash::isEmpty() now uses entry.path.isEmpty(); previously the addFilesToPlaylist() flow had to write two empty-string keys just to make the QHash non-empty as a signal, which is gone. * Drop the dead 'index' field round-trip. updateCurrentPlaylistEntry() and deleteCurrentPlaylistItem() used to read 'index' from the hash via QString::toUInt, which meant PlaylistModel::addPlaylistItems() had to format every row's index as a decimal string into the hash. PlaylistEntry::index is just an unsigned now. * Drop dead m_currentGridHash member from MainWindow. It was only ever cleared, never written or read, so it always held an empty QHash. Removed along with the related conversions. No behaviour change. All four object files (ui_qt, ui_qt_widgets, moc_ui_qt, moc_ui_qt_widgets) build with zero warnings. --- ui/drivers/ui_qt.cpp | 163 +++++++++++++-------------- ui/drivers/ui_qt.h | 17 ++- ui/drivers/ui_qt_widgets.cpp | 210 +++++++++++++++-------------------- ui/drivers/ui_qt_widgets.h | 26 ++++- 4 files changed, 197 insertions(+), 219 deletions(-) diff --git a/ui/drivers/ui_qt.cpp b/ui/drivers/ui_qt.cpp index 008c40726bdb..73b4a793813d 100644 --- a/ui/drivers/ui_qt.cpp +++ b/ui/drivers/ui_qt.cpp @@ -1504,7 +1504,6 @@ MainWindow::MainWindow(QWidget *parent) : ,m_thumbnailType(THUMBNAIL_TYPE_BOXART) ,m_gridProgressBar(NULL) ,m_gridProgressWidget(NULL) - ,m_currentGridHash() ,m_currentGridWidget(NULL) ,m_allPlaylistsListMaxCount(0) ,m_allPlaylistsGridMaxCount(0) @@ -1545,6 +1544,7 @@ MainWindow::MainWindow(QWidget *parent) : qRegisterMetaType >("ThumbnailWidget"); qRegisterMetaType("retro_task_callback_t"); + qRegisterMetaType("PlaylistEntry"); memset(m_thumbnailPixmaps, 0, sizeof(m_thumbnailPixmaps)); @@ -2564,13 +2564,13 @@ void MainWindow::changeThumbnailType(ThumbnailType type) QString MainWindow::changeThumbnail(const QImage &image, QString type) { - QHash hash = getCurrentContentHash(); + PlaylistEntry entry = getCurrentContentEntry(); QString dirString = m_playlistModel->getPlaylistThumbnailsDir( - hash["db_name"]) + entry.dbName) + QString("/") + type; QString thumbPath = m_playlistModel->getSanitizedThumbnailName( dirString + QString("/"), - hash["label_noext"]); + entry.labelNoExt); QByteArray dirArray = QDir::toNativeSeparators(dirString).toUtf8(); const char *dirData = dirArray.constData(); QByteArray thumbArray = QDir::toNativeSeparators(thumbPath).toUtf8(); @@ -2711,7 +2711,7 @@ void MainWindow::onFileDoubleClicked(const QModelIndex &proxyIndex) if (m_fileModel->isDir(index)) m_dirTree->setCurrentIndex(m_dirModel->index(m_fileModel->filePath(index))); else - loadContent(getFileContentHash(index)); + loadContent(getFileContentEntry(index)); } void MainWindow::selectBrowserDir(QString path) @@ -2773,22 +2773,22 @@ QModelIndex MainWindow::getCurrentContentIndex() return QModelIndex(); } -QHash MainWindow::getCurrentContentHash() +PlaylistEntry MainWindow::getCurrentContentEntry() { - return getCurrentContentIndex().data(PlaylistModel::HASH).value >(); + return getCurrentContentIndex().data(PlaylistModel::ENTRY).value(); } -QHash MainWindow::getFileContentHash(const QModelIndex &index) +PlaylistEntry MainWindow::getFileContentEntry(const QModelIndex &index) { - QHash hash; + PlaylistEntry entry; QFileInfo fileInfo = m_fileModel->fileInfo(index); - hash["path"] = QDir::toNativeSeparators(m_fileModel->filePath(index)); - hash["label"] = hash["path"]; - hash["label_noext"] = fileInfo.completeBaseName(); - hash["db_name"] = fileInfo.dir().dirName(); + entry.path = QDir::toNativeSeparators(m_fileModel->filePath(index)); + entry.label = entry.path; + entry.labelNoExt = fileInfo.completeBaseName(); + entry.dbName = fileInfo.dir().dirName(); - return hash; + return entry; } void MainWindow::onContentItemDoubleClicked(const QModelIndex &index) @@ -2819,7 +2819,7 @@ void MainWindow::onStartCoreClicked() * mode, no current item, or no default core for the playlist). */ QString MainWindow::getSelectedCorePath() { - QHash contentHash; + PlaylistEntry entry; QVariantMap coreMap = m_launchWithComboBox->currentData( Qt::UserRole).value(); core_selection coreSelection = static_cast( @@ -2829,10 +2829,10 @@ QString MainWindow::getSelectedCorePath() /* The content row only matters for the two playlist branches — * CORE_SELECTION_CURRENT just hands back whatever core is loaded. * Original behaviour: an "other" view type (e.g. while transitioning) - * returned an empty hash from all three branches. */ + * returned an empty entry from all three branches. */ if (viewType == VIEW_TYPE_LIST || viewType == VIEW_TYPE_ICONS) - contentHash = getCurrentContentIndex().data(PlaylistModel::HASH) - .value >(); + entry = getCurrentContentIndex().data(PlaylistModel::ENTRY) + .value(); else return QString(); @@ -2841,20 +2841,16 @@ QString MainWindow::getSelectedCorePath() case CORE_SELECTION_CURRENT: return QString::fromUtf8(path_get(RARCH_PATH_CORE)); case CORE_SELECTION_PLAYLIST_SAVED: - if ( !contentHash.isEmpty() - && !contentHash["core_path"].isEmpty()) - return contentHash["core_path"]; + if (!entry.corePath.isEmpty()) + return entry.corePath; break; case CORE_SELECTION_PLAYLIST_DEFAULT: { QString plName; QString defaultCorePath; - if (contentHash.isEmpty()) - break; - - plName = contentHash["pl_name"].isEmpty() - ? contentHash["db_name"] : contentHash["pl_name"]; + plName = entry.plName.isEmpty() + ? entry.dbName : entry.plName; if (plName.isEmpty()) break; @@ -2883,7 +2879,7 @@ core_name - The display name of the core, or "DETECT" if unknown label_noext - The display name of the content that is guaranteed not to contain a file extension */ -void MainWindow::loadContent(const QHash &contentHash) +void MainWindow::loadContent(const PlaylistEntry &entry) { content_ctx_info_t content_info; QByteArray corePathArray; @@ -2916,9 +2912,9 @@ void MainWindow::loadContent(const QHash &contentHash) { QStringList extensionFilters; - if (contentHash.contains("path")) + if (!entry.path.isEmpty()) { - QByteArray pathArray = contentHash["path"].toUtf8(); + QByteArray pathArray = entry.path.toUtf8(); const char *pathData = pathArray.constData(); const char *ext = path_get_extension(pathData); @@ -2959,26 +2955,26 @@ void MainWindow::loadContent(const QHash &contentHash) { case CORE_SELECTION_CURRENT: corePathArray = path_get(RARCH_PATH_CORE); - contentPathArray = contentHash["path"].toUtf8(); - contentLabelArray = contentHash["label_noext"].toUtf8(); + contentPathArray = entry.path.toUtf8(); + contentLabelArray = entry.labelNoExt.toUtf8(); break; case CORE_SELECTION_PLAYLIST_SAVED: - corePathArray = contentHash["core_path"].toUtf8(); - contentPathArray = contentHash["path"].toUtf8(); - contentLabelArray = contentHash["label_noext"].toUtf8(); + corePathArray = entry.corePath.toUtf8(); + contentPathArray = entry.path.toUtf8(); + contentLabelArray = entry.labelNoExt.toUtf8(); break; case CORE_SELECTION_PLAYLIST_DEFAULT: { - QString plName = contentHash["pl_name"].isEmpty() - ? contentHash["db_name"] : contentHash["pl_name"]; + QString plName = entry.plName.isEmpty() + ? entry.dbName : entry.plName; QString defaultCorePath = getPlaylistDefaultCore(plName); if (!defaultCorePath.isEmpty()) { corePathArray = defaultCorePath.toUtf8(); - contentPathArray = contentHash["path"].toUtf8(); - contentLabelArray = contentHash["label_noext"].toUtf8(); + contentPathArray = entry.path.toUtf8(); + contentLabelArray = entry.labelNoExt.toUtf8(); } break; @@ -2987,8 +2983,8 @@ void MainWindow::loadContent(const QHash &contentHash) return; } - contentDbNameArray = contentHash["db_name"].toUtf8(); - contentCrc32Array = contentHash["crc32"].toUtf8(); + contentDbNameArray = entry.dbName.toUtf8(); + contentCrc32Array = entry.crc32.toUtf8(); core_path = corePathArray.constData(); content_path = contentPathArray.constData(); @@ -3047,21 +3043,21 @@ void MainWindow::loadContent(const QHash &contentHash) void MainWindow::onRunClicked() { - QHash contentHash; + PlaylistEntry entry; switch (m_currentBrowser) { case BROWSER_TYPE_FILES: - contentHash = getFileContentHash( + entry = getFileContentEntry( m_proxyFileModel->mapToSource(m_fileTableView->currentIndex())); break; case BROWSER_TYPE_PLAYLISTS: - contentHash = getCurrentContentHash(); + entry = getCurrentContentEntry(); break; } - if (!contentHash.isEmpty()) - loadContent(contentHash); + if (!entry.path.isEmpty()) + loadContent(entry); } PlaylistEntryDialog* MainWindow::playlistEntryDialog() @@ -3074,7 +3070,7 @@ ViewOptionsDialog* MainWindow::viewOptionsDialog() {return m_viewOptionsDialog;} void MainWindow::setCoreActions() { QListWidgetItem *currentPlaylistItem = m_listWidget->currentItem(); - QHash hash = getCurrentContentHash(); + PlaylistEntry entry = getCurrentContentEntry(); QString currentPlaylistFileName = QString(); rarch_system_info_t *sys_info = &runloop_state_get_ptr()->system; @@ -3101,39 +3097,36 @@ void MainWindow::setCoreActions() if (m_currentBrowser == BROWSER_TYPE_PLAYLISTS) { - if (!hash.isEmpty()) - { - QString coreName = hash["core_name"]; + const QString &coreName = entry.coreName; - if (!coreName.isEmpty() && coreName != QLatin1String("DETECT")) + if (!coreName.isEmpty() && coreName != QLatin1String("DETECT")) + { + if (m_launchWithComboBox->findText(coreName) == -1) { - if (m_launchWithComboBox->findText(coreName) == -1) + int i; + bool found_existing = false; + + for (i = 0; i < m_launchWithComboBox->count(); i++) { - int i; - bool found_existing = false; + QVariantMap map = m_launchWithComboBox->itemData( + i, Qt::UserRole).toMap(); - for (i = 0; i < m_launchWithComboBox->count(); i++) + if ( map.value("core_path").toString() == entry.corePath + || map.value("core_name").toString() == coreName) { - QVariantMap map = m_launchWithComboBox->itemData( - i, Qt::UserRole).toMap(); - - if ( map.value("core_path").toString() == hash["core_path"] - || map.value("core_name").toString() == coreName) - { - found_existing = true; - break; - } + found_existing = true; + break; } + } - if (!found_existing) - { - QVariantMap comboBoxMap; - comboBoxMap["core_name"] = coreName; - comboBoxMap["core_path"] = hash["core_path"]; - comboBoxMap["core_selection"] = CORE_SELECTION_PLAYLIST_SAVED; - m_launchWithComboBox->addItem(coreName, - QVariant::fromValue(comboBoxMap)); - } + if (!found_existing) + { + QVariantMap comboBoxMap; + comboBoxMap["core_name"] = coreName; + comboBoxMap["core_path"] = entry.corePath; + comboBoxMap["core_selection"] = CORE_SELECTION_PLAYLIST_SAVED; + m_launchWithComboBox->addItem(coreName, + QVariant::fromValue(comboBoxMap)); } } } @@ -3142,8 +3135,8 @@ void MainWindow::setCoreActions() switch(m_currentBrowser) { case BROWSER_TYPE_PLAYLISTS: - currentPlaylistFileName = hash["pl_name"].isEmpty() - ? hash["db_name"] : hash["pl_name"]; + currentPlaylistFileName = entry.plName.isEmpty() + ? entry.dbName : entry.plName; break; case BROWSER_TYPE_FILES: currentPlaylistFileName = m_fileModel->rootDirectory().dirName(); @@ -3456,16 +3449,16 @@ void MainWindow::onSearchEnterPressed() void MainWindow::onCurrentTableItemDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { - QHash hash; + PlaylistEntry entry; if (!roles.contains(Qt::EditRole)) return; if (topLeft != bottomRight) return; - hash = topLeft.data(PlaylistModel::HASH).value>(); + entry = topLeft.data(PlaylistModel::ENTRY).value(); - updateCurrentPlaylistEntry(hash); + updateCurrentPlaylistEntry(entry); onCurrentItemChanged(topLeft); } @@ -3551,20 +3544,20 @@ void MainWindow::renamePlaylistItem(QListWidgetItem *item, QString newName) void MainWindow::onCurrentItemChanged(const QModelIndex &index) { onCurrentItemChanged(index.data( - PlaylistModel::HASH).value>()); + PlaylistModel::ENTRY).value()); } void MainWindow::onCurrentFileChanged(const QModelIndex &index) { - onCurrentItemChanged(getFileContentHash( + onCurrentItemChanged(getFileContentEntry( m_proxyFileModel->mapToSource(index))); } -void MainWindow::onCurrentItemChanged(const QHash &hash) +void MainWindow::onCurrentItemChanged(const PlaylistEntry &entry) { size_t i; - QString path = hash["path"]; - bool acceptDrop = false; + const QString &path = entry.path; + bool acceptDrop = false; for (i = 0; i < 4; i++) { @@ -3596,7 +3589,7 @@ void MainWindow::onCurrentItemChanged(const QHash &hash) else { QString thumbnailsDir = m_playlistModel->getPlaylistThumbnailsDir( - hash["db_name"]); + entry.dbName); /* Clear any pending file-browser preview request: this code * path serves the playlist views, not the file browser, so a @@ -3608,7 +3601,7 @@ void MainWindow::onCurrentItemChanged(const QHash &hash) QString name = m_playlistModel->getSanitizedThumbnailName( thumbnailsDir + QString("/") + qt_thumbnail_subdirs[i] + QString("/"), - hash["label_noext"]); + entry.labelNoExt); m_thumbnailPixmaps[i] = new QPixmap(pixmapFromPathRA(name)); } @@ -3923,8 +3916,6 @@ void MainWindow::initContentTableWidget() if (!item) return; - m_currentGridHash.clear(); - if (m_currentGridWidget) { m_currentGridWidget->setObjectName("thumbnailWidget"); diff --git a/ui/drivers/ui_qt.h b/ui/drivers/ui_qt.h index 988a7436ff30..40260a6ef3ec 100644 --- a/ui/drivers/ui_qt.h +++ b/ui/drivers/ui_qt.h @@ -191,7 +191,7 @@ class PlaylistModel : public QAbstractListModel public: enum Roles { - HASH = Qt::UserRole + 1, + ENTRY = Qt::UserRole + 1, THUMBNAIL }; @@ -222,14 +222,14 @@ private slots: void onImageLoaded(const QImage image, const QModelIndex &index, const QString &path); private: - QVector > m_contents; + QVector m_contents; QCache m_cache; QSet m_pendingImages; QRegularExpression m_fileSanitizerRegex; ThumbnailType m_thumbnailType = THUMBNAIL_TYPE_BOXART; ThumbnailLoader *m_thumbnailLoader; QString getThumbnailPath(const QModelIndex &index, QString type) const; - QString getThumbnailPath(const QHash &hash, QString type) const; + QString getThumbnailPath(const PlaylistEntry &entry, QString type) const; QString getCurrentTypeThumbnailPath(const QModelIndex &index) const; void getPlaylistItems(QString path); }; @@ -501,8 +501,8 @@ class MainWindow : public QMainWindow void addFilesToPlaylist(QStringList files); QString getCurrentPlaylistPath(); QModelIndex getCurrentContentIndex(); - QHash getCurrentContentHash(); - QHash getFileContentHash(const QModelIndex &index); + PlaylistEntry getCurrentContentEntry(); + PlaylistEntry getFileContentEntry(const QModelIndex &index); QString getSpecialPlaylistPath(SpecialPlaylist playlist); QVector > getPlaylists(); void setDefaultCustomProperties(); @@ -528,7 +528,7 @@ public slots: void onShowHiddenDockWidgetAction(); void setCoreActions(); void onRunClicked(); - void loadContent(const QHash &contentHash); + void loadContent(const PlaylistEntry &entry); void onStartCoreClicked(); void onDropWidgetEnterPressed(); void selectBrowserDir(QString path); @@ -573,7 +573,7 @@ private slots: void onCurrentListItemChanged(QListWidgetItem *current, QListWidgetItem *previous); void onCurrentListItemDataChanged(QListWidgetItem *item); void onCurrentItemChanged(const QModelIndex &index); - void onCurrentItemChanged(const QHash &hash); + void onCurrentItemChanged(const PlaylistEntry &entry); void onCurrentFileChanged(const QModelIndex &index); void onPreviewImageLoaded(const QImage image, const QModelIndex &index, const QString &path); void onSearchEnterPressed(); @@ -614,7 +614,7 @@ private slots: private: void setCurrentCoreLabel(); void getPlaylistFiles(); - bool updateCurrentPlaylistEntry(const QHash &contentHash); + bool updateCurrentPlaylistEntry(const PlaylistEntry &entry); bool addDirectoryFilesToList(QProgressDialog *dialog, QStringList &list, QDir &dir, QStringList &extensions); void renamePlaylistItem(QListWidgetItem *item, QString newName); bool currentPlaylistIsSpecial(); @@ -692,7 +692,6 @@ private slots: ThumbnailType m_thumbnailType; QProgressBar *m_gridProgressBar; QWidget *m_gridProgressWidget; - QHash m_currentGridHash; QPointer m_currentGridWidget; int m_allPlaylistsListMaxCount; int m_allPlaylistsGridMaxCount; diff --git a/ui/drivers/ui_qt_widgets.cpp b/ui/drivers/ui_qt_widgets.cpp index 5547621c90e9..b209747fe412 100644 --- a/ui/drivers/ui_qt_widgets.cpp +++ b/ui/drivers/ui_qt_widgets.cpp @@ -1213,7 +1213,7 @@ void MainWindow::onFileDropWidgetContextMenuRequested(const QPoint &pos) QScopedPointer deleteAction; QPointer selectedAction; QPoint cursorPos = QCursor::pos(); - QHash contentHash = getCurrentContentHash(); + PlaylistEntry currentEntry = getCurrentContentEntry(); bool specialPlaylist = currentPlaylistIsSpecial(); bool allPlaylist = currentPlaylistIsAll(); bool actionsAdded = false; @@ -1242,7 +1242,7 @@ void MainWindow::onFileDropWidgetContextMenuRequested(const QPoint &pos) editAction.reset(new QAction(QString(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_EDIT)), this)); deleteAction.reset(new QAction(QString(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_DELETE)), this)); - if (!contentHash.isEmpty()) + if (!currentEntry.path.isEmpty()) { menu->addAction(editAction.data()); menu->addAction(deleteAction.data()); @@ -1261,9 +1261,9 @@ void MainWindow::onFileDropWidgetContextMenuRequested(const QPoint &pos) { if (selectedAction == downloadThumbnailAction.data()) { - QHash hash = getCurrentContentHash(); + PlaylistEntry entry = getCurrentContentEntry(); QString system = QFileInfo(getCurrentPlaylistPath()).completeBaseName(); - QString title = hash.value("label"); + QString title = entry.label; if (!title.isEmpty()) { @@ -1317,7 +1317,7 @@ void MainWindow::onFileDropWidgetContextMenuRequested(const QPoint &pos) PlaylistEntryDialog *playlistDialog = playlistEntryDialog(); QString currentPlaylistPath = getCurrentPlaylistPath(); - if (!playlistDialog->showDialog(contentHash)) + if (!playlistDialog->showDialog(currentEntry)) return; selectedName = m_playlistEntryDialog->getSelectedName(); @@ -1334,13 +1334,13 @@ void MainWindow::onFileDropWidgetContextMenuRequested(const QPoint &pos) if (selectedDatabase.isEmpty()) selectedDatabase = QFileInfo(currentPlaylistPath).fileName().remove(".lpl"); - contentHash["label"] = selectedName; - contentHash["path"] = selectedPath; - contentHash["core_name"] = selectedCore.value("core_name"); - contentHash["core_path"] = selectedCore.value("core_path"); - contentHash["db_name"] = selectedDatabase; + currentEntry.label = selectedName; + currentEntry.path = selectedPath; + currentEntry.coreName = selectedCore.value("core_name"); + currentEntry.corePath = selectedCore.value("core_path"); + currentEntry.dbName = selectedDatabase; - if (!updateCurrentPlaylistEntry(contentHash)) + if (!updateCurrentPlaylistEntry(currentEntry)) { showMessageBox(msg_hash_to_str( MENU_ENUM_LABEL_VALUE_QT_COULD_NOT_UPDATE_PLAYLIST_ENTRY), @@ -2070,16 +2070,16 @@ bool PlaylistEntryDialog::nameFieldEnabled() } void PlaylistEntryDialog::setEntryValues( - const QHash &contentHash) + const PlaylistEntry &entry) { QString db; - QString coreName = contentHash.value("core_name"); + const QString &coreName = entry.coreName; int foundDB = 0; int i = 0; loadPlaylistOptions(); - if (contentHash.isEmpty()) + if (entry.path.isEmpty()) { m_nameLineEdit->setText( msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_FIELD_MULTIPLE)); @@ -2090,8 +2090,8 @@ void PlaylistEntryDialog::setEntryValues( } else { - m_nameLineEdit->setText(contentHash.value("label")); - m_pathLineEdit->setText(contentHash.value("path")); + m_nameLineEdit->setText(entry.label); + m_pathLineEdit->setText(entry.path); m_nameLineEdit->setEnabled(true); m_pathLineEdit->setEnabled(true); } @@ -2112,7 +2112,7 @@ void PlaylistEntryDialog::setEntryValues( } } - db = contentHash.value("db_name"); + db = entry.dbName; if (!db.isEmpty()) { @@ -2159,10 +2159,10 @@ void PlaylistEntryDialog::onAccepted() { } void PlaylistEntryDialog::onRejected() { } void PlaylistEntryDialog::hideDialog() { reject(); } -bool PlaylistEntryDialog::showDialog(const QHash &hash) +bool PlaylistEntryDialog::showDialog(const PlaylistEntry &entry) { loadPlaylistOptions(); - setEntryValues(hash); + setEntryValues(entry); return (exec() == QDialog::Accepted); } @@ -5421,12 +5421,12 @@ void MainWindow::downloadPlaylistThumbnails(QString playlistPath) QHash hash2; QHash hash3; QHash hash4; - const QHash &itemHash = + const PlaylistEntry itemEntry = m_playlistModel->index(i, 0).data( - PlaylistModel::HASH).value< QHash >(); + PlaylistModel::ENTRY).value(); - hash["db_name"] = itemHash.value("db_name"); - hash["label_noext"] = itemHash.value("label_noext"); + hash["db_name"] = itemEntry.dbName; + hash["label_noext"] = itemEntry.labelNoExt; hash["type"] = THUMBNAIL_BOXART; hash2 = hash; @@ -7184,8 +7184,8 @@ QVariant PlaylistModel::data(const QModelIndex &index, int role) const case Qt::DisplayRole: case Qt::EditRole: case Qt::ToolTipRole: - return m_contents.at(index.row())["label_noext"]; - case HASH: + return m_contents.at(index.row()).labelNoExt; + case ENTRY: return QVariant::fromValue(m_contents.at(index.row())); case THUMBNAIL: { @@ -7211,12 +7211,12 @@ bool PlaylistModel::setData(const QModelIndex &index, const QVariant &value, int { if (index.isValid() && role == Qt::EditRole) { - QHash hash = m_contents.at(index.row()); + PlaylistEntry rowEntry = m_contents.at(index.row()); - hash["label"] = value.toString(); - hash["label_noext"] = QFileInfo(value.toString()).completeBaseName(); + rowEntry.label = value.toString(); + rowEntry.labelNoExt = QFileInfo(value.toString()).completeBaseName(); - m_contents.replace(index.row(), hash); + m_contents.replace(index.row(), rowEntry); emit dataChanged(index, index, { role }); return true; } @@ -7283,18 +7283,18 @@ QString PlaylistModel::getSanitizedThumbnailName(QString dir, QString label) con } -QString PlaylistModel::getThumbnailPath(const QHash &hash, QString type) const +QString PlaylistModel::getThumbnailPath(const PlaylistEntry &entry, QString type) const { /* use thumbnail widgets to show regular image files */ - if (isSupportedImage(hash["path"])) - return hash["path"]; + if (isSupportedImage(entry.path)) + return entry.path; return getSanitizedThumbnailName( - getPlaylistThumbnailsDir(hash.value("db_name")) + getPlaylistThumbnailsDir(entry.dbName) + QString("/") + type + QString("/"), - hash["label_noext"]); + entry.labelNoExt); } QString PlaylistModel::getCurrentTypeThumbnailPath(const QModelIndex &index) const @@ -7507,7 +7507,7 @@ void MainWindow::addFilesToPlaylist(QStringList files) QByteArray currentPlaylistArray; QScopedPointer dialog(NULL); QHash selectedCore; - QHash itemToAdd; + PlaylistEntry itemToAdd; QString selectedDatabase; QString selectedName; QString selectedPath; @@ -7528,10 +7528,8 @@ void MainWindow::addFilesToPlaylist(QStringList files) /* Assume a blank list means we will manually enter in all fields. */ if (files.isEmpty()) { - /* Make sure hash isn't blank, that would mean there's - * multiple entries to add at once. */ - itemToAdd["label"] = QLatin1String(""); - itemToAdd["path"] = QLatin1String(""); + /* Leave fields default-empty: the dialog will offer the + * "Multiple" placeholder when it sees an empty path. */ } else if (files.count() == 1) { @@ -7540,8 +7538,8 @@ void MainWindow::addFilesToPlaylist(QStringList files) if (info.isFile()) { - itemToAdd["label"] = info.completeBaseName(); - itemToAdd["path"] = path; + itemToAdd.label = info.completeBaseName(); + itemToAdd.path = path; } } @@ -7805,14 +7803,8 @@ void MainWindow::addFilesToPlaylist(QStringList files) } bool MainWindow::updateCurrentPlaylistEntry( - const QHash &contentHash) -{ - QString path; - QString label; - QString corePath; - QString coreName; - QString dbName; - QString crc32; + const PlaylistEntry &contentEntry) +{ QByteArray playlistPathArray; QByteArray pathArray; QByteArray labelArray; @@ -7830,8 +7822,6 @@ bool MainWindow::updateCurrentPlaylistEntry( const char *dbNameData = NULL; const char *crc32Data = NULL; playlist_t *playlist = NULL; - unsigned index = 0; - bool ok = false; settings_t *settings = config_get_ptr(); playlist_config.capacity = COLLECTION_SIZE; @@ -7842,39 +7832,22 @@ bool MainWindow::updateCurrentPlaylistEntry( settings->bools.playlist_portable_paths ? settings->paths.directory_menu_content : NULL); - if ( playlistPath.isEmpty() - || contentHash.isEmpty() - || !contentHash.contains("index")) - return false; - - index = contentHash.value("index").toUInt(&ok); - - if (!ok) - return false; - - path = contentHash.value("path"); - label = contentHash.value("label"); - coreName = contentHash.value("core_name"); - corePath = contentHash.value("core_path"); - dbName = contentHash.value("db_name"); - crc32 = contentHash.value("crc32"); - - if ( path.isEmpty() - || label.isEmpty() - || coreName.isEmpty() - || corePath.isEmpty() - ) + if ( playlistPath.isEmpty() + || contentEntry.path.isEmpty() + || contentEntry.label.isEmpty() + || contentEntry.coreName.isEmpty() + || contentEntry.corePath.isEmpty()) return false; playlistPathArray = playlistPath.toUtf8(); - pathArray = QDir::toNativeSeparators(path).toUtf8(); - labelArray = label.toUtf8(); - coreNameArray = coreName.toUtf8(); - corePathArray = QDir::toNativeSeparators(corePath).toUtf8(); + pathArray = QDir::toNativeSeparators(contentEntry.path).toUtf8(); + labelArray = contentEntry.label.toUtf8(); + coreNameArray = contentEntry.coreName.toUtf8(); + corePathArray = QDir::toNativeSeparators(contentEntry.corePath).toUtf8(); - if (!dbName.isEmpty()) + if (!contentEntry.dbName.isEmpty()) { - dbNameArray = (dbName + ".lpl").toUtf8(); + dbNameArray = (contentEntry.dbName + ".lpl").toUtf8(); dbNameData = dbNameArray.constData(); } @@ -7884,9 +7857,9 @@ bool MainWindow::updateCurrentPlaylistEntry( coreNameData = coreNameArray.constData(); corePathData = corePathArray.constData(); - if (!crc32.isEmpty()) + if (!contentEntry.crc32.isEmpty()) { - crc32Array = crc32.toUtf8(); + crc32Array = contentEntry.crc32.toUtf8(); crc32Data = crc32Array.constData(); } @@ -7924,7 +7897,7 @@ bool MainWindow::updateCurrentPlaylistEntry( entry.crc32 = const_cast(crc32Data); entry.db_name = const_cast(dbNameData); - playlist_update(playlist, index, &entry); + playlist_update(playlist, contentEntry.index, &entry); } playlist_write_file(playlist); @@ -8507,11 +8480,9 @@ void MainWindow::deleteCurrentPlaylistItem() QByteArray playlistArray; playlist_config_t playlist_config; QString playlistPath = getCurrentPlaylistPath(); - QHash contentHash = getCurrentContentHash(); + PlaylistEntry contentEntry = getCurrentContentEntry(); playlist_t *playlist = NULL; const char *playlistData = NULL; - unsigned index = 0; - bool ok = false; bool isAllPlaylist = currentPlaylistIsAll(); settings_t *settings = config_get_ptr(); @@ -8529,24 +8500,19 @@ void MainWindow::deleteCurrentPlaylistItem() if (playlistPath.isEmpty()) return; - if (contentHash.isEmpty()) + if (contentEntry.path.isEmpty()) return; playlistArray = playlistPath.toUtf8(); playlistData = playlistArray.constData(); - index = contentHash.value("index").toUInt(&ok); - - if (!ok) - return; - - if (!showMessageBox(QString(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_CONFIRM_DELETE_PLAYLIST_ITEM)).arg(contentHash["label"]), MainWindow::MSGBOX_TYPE_QUESTION_YESNO, Qt::ApplicationModal, false)) + if (!showMessageBox(QString(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_CONFIRM_DELETE_PLAYLIST_ITEM)).arg(contentEntry.label), MainWindow::MSGBOX_TYPE_QUESTION_YESNO, Qt::ApplicationModal, false)) return; playlist_config_set_path(&playlist_config, playlistData); playlist = playlist_init(&playlist_config); - playlist_delete_index(playlist, index); + playlist_delete_index(playlist, contentEntry.index); playlist_write_file(playlist); playlist_free(playlist); @@ -8657,50 +8623,50 @@ void PlaylistModel::getPlaylistItems(QString path) for (i = 0; i < playlistSize; i++) { - QHash hash; - const struct playlist_entry *entry = NULL; + PlaylistEntry rowEntry; + const struct playlist_entry *pl_row = NULL; - playlist_get_index(playlist, i, &entry); + playlist_get_index(playlist, i, &pl_row); - if (!entry->path || !*entry->path) + if (!pl_row->path || !*pl_row->path) continue; - hash["path"] = entry->path; - hash["index"] = QString::number(i); + rowEntry.path = pl_row->path; + rowEntry.index = (unsigned)i; - if (!entry->label || !*entry->label) + if (!pl_row->label || !*pl_row->label) { - hash["label"] = entry->path; - hash["label_noext"] = entry->path; + rowEntry.label = pl_row->path; + rowEntry.labelNoExt = pl_row->path; } else { - hash["label"] = entry->label; - hash["label_noext"] = entry->label; + rowEntry.label = pl_row->label; + rowEntry.labelNoExt = pl_row->label; } - if (entry->core_path && *entry->core_path) - hash["core_path"] = entry->core_path; + if (pl_row->core_path && *pl_row->core_path) + rowEntry.corePath = pl_row->core_path; - if (entry->core_name && *entry->core_name) - hash["core_name"] = entry->core_name; + if (pl_row->core_name && *pl_row->core_name) + rowEntry.coreName = pl_row->core_name; - if (entry->crc32 && *entry->crc32) - hash["crc32"] = entry->crc32; + if (pl_row->crc32 && *pl_row->crc32) + rowEntry.crc32 = pl_row->crc32; - if (entry->db_name && *entry->db_name) + if (pl_row->db_name && *pl_row->db_name) { - hash["db_name"] = entry->db_name; - hash["db_name"].remove(".lpl"); + rowEntry.dbName = pl_row->db_name; + rowEntry.dbName.remove(".lpl"); } if (playlistName && *playlistName) { - hash["pl_name"] = playlistName; - hash["pl_name"].remove(".lpl"); + rowEntry.plName = playlistName; + rowEntry.plName.remove(".lpl"); } - m_contents.append(hash); + m_contents.append(rowEntry); } playlist_free(playlist); @@ -8744,18 +8710,18 @@ void PlaylistModel::addDir(QString path, QFlags showHidden) for (i = 0; i < dirList.count(); i++) { - QHash hash; + PlaylistEntry rowEntry; QString fileName = dirList.at(i); QString filePath( QDir::toNativeSeparators(dir.absoluteFilePath(fileName))); QFileInfo fileInfo(filePath); - hash["path"] = filePath; - hash["label"] = hash["path"]; - hash["label_noext"] = fileInfo.completeBaseName(); - hash["db_name"] = fileInfo.dir().dirName(); + rowEntry.path = filePath; + rowEntry.label = filePath; + rowEntry.labelNoExt = fileInfo.completeBaseName(); + rowEntry.dbName = fileInfo.dir().dirName(); - m_contents.append(hash); + m_contents.append(rowEntry); } endResetModel(); diff --git a/ui/drivers/ui_qt_widgets.h b/ui/drivers/ui_qt_widgets.h index 5a4db8ce2e50..e3eec32277f0 100644 --- a/ui/drivers/ui_qt_widgets.h +++ b/ui/drivers/ui_qt_widgets.h @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -56,6 +57,27 @@ class QListWidget; class QStackedLayout; #endif +/* Content schema describing a single playlist row. Replaces the + * QHash previously stored in PlaylistModel and + * passed around as the playlist row payload. The field set is the + * same keys the QHash carried; making it a struct removes a class + * of stringly-typed lookup bugs and a lot of toUtf8/from-key + * round trips at call sites. */ +struct PlaylistEntry +{ + QString path; + QString label; + QString labelNoExt; + QString corePath; + QString coreName; + QString crc32; + QString dbName; + QString plName; + QString plPath; + unsigned index = 0; +}; +Q_DECLARE_METATYPE(PlaylistEntry) + class FormLayout : public QFormLayout { public: @@ -840,9 +862,9 @@ class PlaylistEntryDialog : public QDialog const QStringList getSelectedExtensions(); bool filterInArchive(); bool nameFieldEnabled(); - void setEntryValues(const QHash &contentHash); + void setEntryValues(const PlaylistEntry &entry); public slots: - bool showDialog(const QHash &hash = QHash()); + bool showDialog(const PlaylistEntry &entry = PlaylistEntry()); void hideDialog(); void onAccepted(); void onRejected(); From 56e0592626cfd03e6a1e697070a2c14dcebc6b94 Mon Sep 17 00:00:00 2001 From: "U-DESKTOP-SPFP6AQ\\twistedtechre" Date: Fri, 1 May 2026 22:51:38 +0200 Subject: [PATCH 13/14] Qt: drop more dead overrides, slots, and connections Four small dead-code removals turned up in another sweep over the audit areas. No behaviour change. * TreeView::columnCountChanged: pure passthrough to QTreeView::columnCountChanged. Without this override, Qt's default does exactly the same thing. Drop the slot decl + impl. * ListWidget::isEditorOpen: declared and defined, never called. TableView has the same method and one caller; ListWidget had none. * AppHandler::onLastWindowClosed + the QApplication::lastWindowClosed -> onLastWindowClosed connect: the slot body was empty and the connection was therefore a no-op. Drop the slot, the connect() call, and the now-empty 'private slots:' section in the header. * ThumbnailLabel::resizeEvent: pure passthrough to QWidget::resizeEvent, same situation as columnCountChanged. All four object files (ui_qt, ui_qt_widgets, moc_ui_qt, moc_ui_qt_widgets) build with zero warnings. --- ui/drivers/ui_qt.cpp | 18 ------------------ ui/drivers/ui_qt.h | 6 ------ 2 files changed, 24 deletions(-) diff --git a/ui/drivers/ui_qt.cpp b/ui/drivers/ui_qt.cpp index 73b4a793813d..e07ebed0d35a 100644 --- a/ui/drivers/ui_qt.cpp +++ b/ui/drivers/ui_qt.cpp @@ -1347,11 +1347,6 @@ static int qt_thumbnail_type_to_widget_idx(ThumbnailType t) TreeView::TreeView(QWidget *parent) : QTreeView(parent) { } -void TreeView::columnCountChanged(int oldCount, int newCount) -{ - QTreeView::columnCountChanged(oldCount, newCount); -} - void TreeView::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { QModelIndexList list = selected.indexes(); @@ -1370,11 +1365,6 @@ bool TableView::isEditorOpen() ListWidget::ListWidget(QWidget *parent) : QListWidget(parent) { } -bool ListWidget::isEditorOpen() -{ - return (state() == QAbstractItemView::EditingState); -} - void ListWidget::keyPressEvent(QKeyEvent *event) { int key = event->key(); @@ -4406,8 +4396,6 @@ static void* ui_application_qt_initialize(void) ui_application.app->setOrganizationName("libretro"); ui_application.app->setApplicationName("RetroArch"); ui_application.app->setApplicationVersion(PACKAGE_VERSION); - ui_application.app->connect(ui_application.app, SIGNAL(lastWindowClosed()), - app_handler, SLOT(onLastWindowClosed())); #ifdef Q_OS_UNIX setlocale(LC_NUMERIC, "C"); @@ -4473,7 +4461,6 @@ static ui_application_t ui_application_qt = { AppHandler::AppHandler(QObject *parent) : QObject(parent) { } AppHandler::~AppHandler() { } -void AppHandler::onLastWindowClosed() { } void AppHandler::exit() { @@ -4648,11 +4635,6 @@ void ThumbnailLabel::paintEvent(QPaintEvent *event) QWidget::paintEvent(event); } -void ThumbnailLabel::resizeEvent(QResizeEvent *event) -{ - QWidget::resizeEvent(event); -} - static void ui_companion_qt_deinit(void *data) { ui_companion_qt_t *handle = (ui_companion_qt_t*)data; diff --git a/ui/drivers/ui_qt.h b/ui/drivers/ui_qt.h index 40260a6ef3ec..18ca26ad5ca7 100644 --- a/ui/drivers/ui_qt.h +++ b/ui/drivers/ui_qt.h @@ -267,7 +267,6 @@ public slots: void setPixmap(const QPixmap &pixmap); protected: void paintEvent(QPaintEvent *event); - void resizeEvent(QResizeEvent *event); private: void updateMargins(); @@ -284,7 +283,6 @@ class TreeView : public QTreeView signals: void itemsSelected(QModelIndexList selectedIndexes); protected slots: - void columnCountChanged(int oldCount, int newCount); void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected); }; @@ -301,7 +299,6 @@ class ListWidget : public QListWidget Q_OBJECT public: ListWidget(QWidget *parent = 0); - bool isEditorOpen(); signals: void enterPressed(); void deletePressed(); @@ -317,9 +314,6 @@ class AppHandler : public QObject AppHandler(QObject *parent = 0); ~AppHandler(); void exit(); - -private slots: - void onLastWindowClosed(); }; class CoreInfoLabel : public QLabel From 8f6d51bc8043152438e54f34577bf9b90caecaff Mon Sep 17 00:00:00 2001 From: "U-DESKTOP-SPFP6AQ\\twistedtechre" Date: Fri, 1 May 2026 23:00:30 +0200 Subject: [PATCH 14/14] Qt: return const char* from enum-name lookup methods Three MainWindow methods exist solely to map an enum value to a fixed string literal: getThemeString, getCurrentViewTypeString, and getCurrentThumbnailTypeString. They returned QString built from a string literal at every call. Returning the literal directly as const char* is one less heap allocation per call and the caller (QSettings::setValue, which takes QVariant) accepts it via the implicit QString conversion either way. * getThemeString: 4 lines -> 1 line per branch. * getCurrentViewTypeString: switch over a 2-value enum collapses to an if. * getCurrentThumbnailTypeString: dropped a dead trailing 'return QString("list")' that the compiler couldn't reach - the THUMBNAIL_TYPE_BOXART branch already returned via the default label. No behaviour change. All four object files build with zero warnings. --- ui/drivers/ui_qt.cpp | 39 +++++++++++++++------------------------ ui/drivers/ui_qt.h | 6 +++--- 2 files changed, 18 insertions(+), 27 deletions(-) diff --git a/ui/drivers/ui_qt.cpp b/ui/drivers/ui_qt.cpp index e07ebed0d35a..82afa79cc000 100644 --- a/ui/drivers/ui_qt.cpp +++ b/ui/drivers/ui_qt.cpp @@ -2490,20 +2490,19 @@ MainWindow::Theme MainWindow::getThemeFromString(QString themeString) return THEME_SYSTEM_DEFAULT; } -QString MainWindow::getThemeString(Theme theme) +const char *MainWindow::getThemeString(Theme theme) { switch (theme) { - case THEME_SYSTEM_DEFAULT: - return QString("default"); case THEME_DARK: - return QString("dark"); + return "dark"; case THEME_CUSTOM: - return QString("custom"); + return "custom"; + case THEME_SYSTEM_DEFAULT: default: break; } - return QString("default"); + return "default"; } MainWindow::Theme MainWindow::theme() { return m_currentTheme; } @@ -3960,36 +3959,28 @@ void MainWindow::keyPressEvent(QKeyEvent *event) QSettings* MainWindow::settings() { return m_settings; } -QString MainWindow::getCurrentViewTypeString() +const char *MainWindow::getCurrentViewTypeString() { - switch (m_viewType) - { - case VIEW_TYPE_ICONS: - return QString("icons"); - case VIEW_TYPE_LIST: - default: - break; - } - - return QString("list"); + if (m_viewType == VIEW_TYPE_ICONS) + return "icons"; + return "list"; } -QString MainWindow::getCurrentThumbnailTypeString() +const char *MainWindow::getCurrentThumbnailTypeString() { switch (m_thumbnailType) { case THUMBNAIL_TYPE_SCREENSHOT: - return QString("screenshot"); + return "screenshot"; case THUMBNAIL_TYPE_TITLE_SCREEN: - return QString("title"); + return "title"; case THUMBNAIL_TYPE_LOGO: - return QString("logo"); + return "logo"; case THUMBNAIL_TYPE_BOXART: default: - return QString("boxart"); + break; } - - return QString("list"); + return "boxart"; } ThumbnailType MainWindow::getThumbnailTypeFromString(QString thumbnailType) diff --git a/ui/drivers/ui_qt.h b/ui/drivers/ui_qt.h index 18ca26ad5ca7..771e7e1df6f6 100644 --- a/ui/drivers/ui_qt.h +++ b/ui/drivers/ui_qt.h @@ -474,7 +474,7 @@ class MainWindow : public QMainWindow void setTheme(Theme theme = THEME_SYSTEM_DEFAULT); Theme theme(); Theme getThemeFromString(QString themeString); - QString getThemeString(Theme theme); + const char *getThemeString(Theme theme); QString getSelectedCorePath(); void showStatusMessage(QString msg, unsigned priority, unsigned duration, bool flush); bool showMessageBox(QString msg, MessageBoxType msgType = MSGBOX_TYPE_INFO, Qt::WindowModality modality = Qt::ApplicationModal, bool showDontAsk = true, bool *dontAsk = NULL); @@ -482,10 +482,10 @@ class MainWindow : public QMainWindow void setCustomThemeString(QString qss); const QString& customThemeString() const; void setCurrentViewType(ViewType viewType); - QString getCurrentViewTypeString(); + const char *getCurrentViewTypeString(); ViewType getCurrentViewType(); void setCurrentThumbnailType(ThumbnailType thumbnailType); - QString getCurrentThumbnailTypeString(); + const char *getCurrentThumbnailTypeString(); ThumbnailType getCurrentThumbnailType(); ThumbnailType getThumbnailTypeFromString(QString thumbnailType); void setAllPlaylistsListMaxCount(int count);