* [Translation] Name languages without reading the translation files
The language selector derived every label from the language's own .qm file:
it loaded the file and translated the literal "English" in the "i18n"
context. lupdate could never extract that literal from the callers, which
pass the DEFAULT_LANG_NAME macro, so a dummy QT_TRANSLATE_NOOP in
cache_settings.cpp existed to give it something to extract. #7050 deleted
that dummy, the next lupdate push dropped the string from Transifex, and
the following pull rewrote the .ts files without it. lrelease only emits
translated messages, so 22 of 25 .qm files returned an empty name.
The names are data, not translations: a selector has to be readable before
any translation is loaded, and a label must not depend on the translation
of the language it names. TranslationLoader::languageDisplayName now holds
them next to availableLanguages, falling back to Qt's native language name
and then to the code, so a language added later still gets a label.
Also select the configured language by item data instead of by matching the
translated "English" string, which pointed at the wrong row whenever the
lookup came back empty, and restore that English entry's own name: en_US is
"English (United States)" so it can no longer be confused with the
untranslated default.
Oracle shared the code path, so its wizard language page is fixed too.
* [Translation] Name the default language selection once
Review follow-up: log the unresolvable language through the page's own
category, and move the en_US entry both selectors fall back to for an unset
setting into TranslationLoader::defaultLanguageCode.
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* Retranslate all open tabs on language change
* Address review: deeper Visual DB retranslation, index-based Archidekt size logic
In Qt 6, setItemText() on the current item emits currentTextChanged but not
currentIndexChanged, so connecting the Archidekt combos to currentTextChanged
fired a search on every language change. Both combos now use currentIndexChanged,
and buildSearchUrl() compares the size-logic combo by index so translated labels
cannot drop sizeLogic.
* Derive display mode button text in one place
* [Models] Use QCollator for natural, cached sorting
QString::localeAwareCompare rebuilds its collation data for every
comparison, and it compares numbers as text. Keep one QCollator per model,
enable numeric mode, and use it for the card name, set and user name sorts.
Add a test that sorts "Forest 1", "Forest 2", "Forest 10" through
DeckListSortFilterProxyModel and checks the natural order.
* [Models] Sort naturally under the C locale too
Numeric mode only takes effect through a real locale: under the C locale,
which headless CI processes run in, QCollator silently falls back to string
order and sorts "Forest 10" before "Forest 2". That made collator_sort_test
fail on every Linux leg while passing locally.
Add makeNaturalSortCollator(), which enables numeric mode and pins English
only when the process locale is the degenerate one, and build the models'
collators through it. Users with a real locale keep their own collation.
* [Models] Fix compile of natural sorting (QStringView mismatch)
* nit: initialize collators in member initializer lists
* nit: namespace makeNaturalSortCollator under NaturalSort
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
The local picture loader walked the whole custom picture tree every ten
seconds on the GUI thread. Watch the root and every subdirectory with
QFileSystemWatcher, debounce bursts of events into a single 500 ms refresh,
and keep a five-minute fallback poll for filesystems where watching fails.
Add an explicit-path constructor and indexedPaths() so the indexing can be
tested without SettingsCache, and emit indexChanged() after a rebuild.
Verification: cockatrice built.
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* [Network] Add a configured QNetworkAccessManager factory
Every HTTP caller built its own QNetworkAccessManager, so the system proxy
and a transfer timeout were easy to omit. Add NetworkManagerFactory::create()
as the single place that applies the process-wide proxy configuration and a
transfer timeout, and use it at all 14 construction sites.
Per-site settings such as disk caches and manual redirect policies stay with
their callers.
* [Tests] Link libcockatrice_network into the tests that compile its headers
release_channel.cpp and card_picture_loader_worker.cpp now include
libcockatrice/network/network_manager.h, but the test targets that compile
them only linked libcockatrice_settings, so the include directory never
reached them and every platform failed with "file not found":
loader_local_matching_test and picture_loader_benchmark_test.
libcockatrice_network is an INTERFACE library, so linking it costs these
targets nothing but hands over the include path.
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
Read the avatar with setAutoTransform(true) so camera orientation is
applied, and ask QImageReader for a scaled decode at MAX_AVATAR_DIMENSION
instead of decoding a full-resolution photo and scaling it afterwards.
Verification: cockatrice built.
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* [QtUtils] Add parent lookup, layout clearing, scroll area and monospace helpers
* [QtUtils] Adopt shared Qt helpers across the client
* [QtUtils] Add layout creation and relayout helpers
* Swap parameter order (spacing before margin) and remove static from relayout()
* [QtUtils] Use relayout() after rebuilding dynamic layouts
* [QtUtils] Use makeLayout() for zero-margin layouts
* Consolidate setSpacing() calls into makeLayout() using new parameter order
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
A palette preview repaints widgets through applyStyleAndPalette without running
themeChangedSlot, so generator-owned pixmaps stayed at the previous palette
until the next full theme switch. Clear them where the palette is applied.
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* Retranslate Home tab when the language changes
TabSupervisor::retranslateUi() never included the Home tab, and
HomeWidget set its button text only once, when the buttons were
created. Switching languages left the Home tab in the old language
until it was reopened.
Store the Home buttons as members, add HomeWidget::retranslateUi()
to set their text, and call it from TabHome and TabSupervisor. The
connect button text still only changes for the Connecting,
Disconnected and LoggedIn statuses, as before.
* Address review: plain ClientStatus, drop optional and placeholder
* Refactor display type toggler and add language change handling in VisualDeckDisplayOptionsWidget
* Address review: extract updateDisplayTypeButton(), document changeEvent
* Restore trailing newline in visual_deck_display_options_widget.cpp
* [CardDB] Avoid copying printing maps and property strings
Reading a card's sets was a measured hotspot: `SetToPrintingsMap setMap =
card->getSets();` copies the whole QMap, and then iterating it with a
non-const iterator detaches and deep-copies every PrintingInfo, including
its property hash, on first use. A micro-benchmark of that exact pattern
(copy plus non-const iteration) came out about 19x slower than a const
reference over the same data.
The querier, the cache writer, the XML writer, the picture loader and the
printing/set selection widgets now take `getSets()` by const reference and
only use const iterators. The tests that iterate generated data are
covered by the existing card database and picture loader suites.
String accessors were also returning by value where the value lives in a
hash that has already been materialized:
- CardInfo::getProperty() and the legacy getCardType()/getColors()/
getManaCost()/getCmc()/getPowTough()/getLoyalty() accessors,
- PrintingInfo::getProperty(), getUuid(), getFlavorName(), getArtist(),
and getSet(),
- ExactCard::getPrinting() and getCardPtr(),
- CardSet::getShortName(), getLongName() and getSetType().
getPrinting() in particular copied a PrintingInfo on every call, and a
copy of an unmaterialized PrintingInfo has to take the properties mutex.
Callers that need an owned copy still get one; the rest just read.
Other allocation removals in the same path:
- LazyPropertiesHash::ensureMaterialized() checks an atomic flag before
taking the mutex, so property reads after the first one do not lock.
The flag is release-stored after materialization and acquire-loaded by
readers/copies, preserving the existing locking for the first read.
- CardSet::getCorrectedShortName() no longer rebuilds its QSet of
reserved Windows file names on every call.
- CardSet::getIsKnownIgnored() no longer formats the release date into a
throwaway string just to check its length.
- The querier builds its card-type/format count caches without
`cards.values()` or a properties key list, reserves its result lists,
and picks a random card without materializing `cards.keys()`.
- The querier's pinned-printing path reuses the CardInfoPtr it already
has instead of looking the card up again by name.
- CardDatabaseCache/XML serialization copy the alt-name QSet by
reference.
- The picture loader compiles its !prop!/!set! regular expressions once
instead of per URL template, and walks the transform map with an
iterator instead of keys() plus operator[].
Full build and ctest (628 tests) pass.
* [CardDB] Address review comments
PrintingInfo is a value type: it is stored in QLists and returned by value
from the querier, so a const-ref getter can hand out a reference into a
temporary. Return the QStrings and the CardSetPtr by value again.
The constFind lookup stays, so a property read still never materializes the
lazy property hash - only the last shared-data refcount is paid.
* [CardDB] Undo the const-ref QString return changes
QString is implicitly shared, so returning by value costs only a refcount
and avoids handing out references into a lazily materialized hash. The
constFind lookups stay, so a property read still never materializes the
hash.
* [CardDB] Use QMap::asKeyValueRange() in transformUrl
The range is a view over the map's own iterators and yields references to
the stored key and value, so it keeps the no-allocation property of the
manual const iteration.
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* [VisualDatabase] Use scoped QSignalBlockers for filter updates
updateFilterModel() blocked signals on the filter model and its filter tree,\nthen unblocked each of them twice. The second pair was a no-op only because\nthe first pair had already restored the default. Use two scoped\nQSignalBlockers instead, so the models are unblocked exactly once before the\nexplicit changed()/layoutChanged() notifications.\n\nVerification: cockatrice built.
* Drop stale typo note from QSignalBlocker comment
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* [CrashReporting] Breakpad everywhere, session detection, symbol CI (inc 3)
Breakpad on all platforms (no crashpad packaging); remove the Windows
MiniDumpWithFullMemory filter and the installer WER LocalDumps full-dump
registration. QLockFile per-process session tracking replaces the
Windows-only .dmp scan, placed after parser.process and the instance
hand-off so help/version exits and handed-off secondaries never leak a
lock. CI: Release-only DSN bake from SENTRY_DSN secret, tag-only
sentry-cli symbol upload (org cockatrice, project native).
Tests: 533/533 ctest green, format clean, code gate PASS.
* [CrashReporting] Enable http_retry, harden symbol upload, fix NSIS context
Sets sentry_options_set_http_retry so the cache_keep-held crash
envelope in Ask mode is written with a retry filename and actually
flushes on giveConsent() instead of sitting in <db>/cache until
pruning.
Adds continue-on-error to both sentry-cli install steps so a Sentry
registry outage or broken Git Bash install cannot block release
assets.
Cleans the legacy WER CrashDumps folder under SetShellVarContext
current (under all it resolves to ProgramData, not the user's
AppData) in installer and uninstaller; registry delete works in
either context.
* [CrashReporting] Extract the startup crash-report flow from main() per review
* [CrashReporting] Break up the CrashReporter class doc per review
* [CrashReporting] Drop the invalid --release from the symbol upload
`debug-files upload` never accepted `--release` (checked against the pinned
sentry-cli 3.8.0: the subcommand declares --org/--project and a set of
type/id/scan options, and execute() reads no release; the only global flags are
--url/--header/--auth-token/--log-level/--quiet/--allow-failure). Clap rejects
the unknown flag before any upload happens, so every tag build has been shipping
without symbols, with continue-on-error keeping the release pipeline green.
Release association is also the wrong mechanism: debug information files are
matched to events by debug ID via the event's debug_meta. The Sentry release of
a native event comes from the event itself (COCKATRICE_SENTRY_RELEASE), not from
the DIF upload. Remove the flag and the now-unused SENTRY_RELEASE env from both
invocations (Linux, and Windows/macOS).
The symbol upload stays continue-on-error (a Sentry outage must not block the
release assets); revisit once it has proven itself on a tag.
* [CrashReporting] Split debug information out of packaged release binaries
-g is required so the CI symbol upload has DWARF to send, but it was left in
the shipped binaries too: the Linux Release client is 563 MiB, 552 MiB of it
debug info, and every distribution channel pays for that.
Keep the build tree unchanged (CI uploads symbols from build/) and strip only
the packaged copy. A CPack pre-build script extracts each ELF's debug info to
COCKATRICE_DEBUG_SYMBOL_DIR and then runs `strip --strip-debug --strip-unneeded`.
.eh_frame is allocatable and survives, so unwinding is unaffected, and the build
ID is identical in the stripped binary and the sidecar.
Measured on a local Release build (GCC -O2 -g, WITH_CRASH_REPORTING=ON),
bytes:
binary build tree packaged before packaged after .debug sidecar
cockatrice 590,722,560 590,722,560 28,086,480 579,623,576
oracle 53,432,608 53,432,608 1,649,888 52,075,344
tar.gz of both trees: 289,964,521 -> 19,018,058 (~15x smaller).
Only ELF (Linux/BSD) is handled: Windows already links /DEBUG with separate
PDBs, and macOS extracts a dSYM from the bundle. Opt-out via
COCKATRICE_SPLIT_DEBUG_SYMBOLS; skipped with a warning when objcopy/strip are
missing. CPACK_PRE_BUILD_SCRIPTS (not CPACK_INSTALL_SCRIPTS, which runs before
staging) invokes it, and the script refuses to run unless CPACK_TEMPORARY_DIRECTORY
is a real CPack staging tree so a missing variable can never turn its glob into
a filesystem-wide walk.
* [CrashReporting] Upload Windows PDBs from where Ninja actually puts them
The Windows branch of the symbol upload looked in build/<target>/Release, which
is the Visual Studio generator's layout. The Windows leg uses Ninja, which is
single-config: linker PDBs land in build/<target>/, next to the binaries — the
same paths the "[Windows] Upload PDBs" artifact step below globs with
if-no-files-found: error.
So paths was always empty on Windows, the step printed "No symbol paths found,
skipping symbol upload" and exited 0: a silent no-op behind continue-on-error.
Every Windows build would then report "A required debug information file was
missing" for cockatrice.exe with a PDB-style debug id (GUID-age), exactly as if
no upload step existed at all.
Use the artifact step's globs. Also turn the empty-path case into a failure in
both symbol steps: a silent skip is indistinguishable from a successful upload
(which is how the wrong path went unnoticed), and continue-on-error already
keeps a broken upload from failing the release.
Verified by extracting both steps' run blocks and executing the Windows branch
against a Ninja-style tree: it now passes the three linker PDBs to sentry-cli
(and not the CMakeFiles/*.pdb intermediates), and exits non-zero with an
::error:: annotation when there is nothing to upload.
* [CrashReporting] Archive PDBs on tag builds so a failed upload is recoverable
The "[Windows] Upload PDBs" artifact step skipped tag builds
(github.ref_type != 'tag'), so for a release the symbols existed only inside
the Sentry upload — which is continue-on-error. A failed upload therefore lost
the ability to symbolicate that release permanently: no rebuild produces the
same PDB, since the debug id is a per-build GUID+age. That is how every
Windows crash reported so far became unreadable.
Archive the PDBs for tag builds as well, and order the step after
"Upload to release" so a missing PDB can never block a release from being
published. Retention stays at the repository default; nothing consumes the
artifact automatically, it is the manual recovery path
(gh run download <run> -n <name>-PDBs, then sentry-cli debug-files upload).
Verified by re-parsing the workflow and asserting the step order
(release -> PDBs -> attest) and the broadened condition.
* [CrashReporting] Upload the Windows executables with their PDBs
Sentry needs two things per image on Windows: symbolication and stack
unwinding. The PDB covers symbolication, but for a 64-bit PE the Call Frame
Information (.pdata/.xdata) lives in the executable and is not copied into the
PDB, so uploading PDBs alone leaves the image at "Stack Unwinding: Missing".
That is exactly what the event's Images panel showed for cockatrice.exe, while
Sentry's own Microsoft symbol server resolved KERNELBASE.dll and ucrtbase.dll
from the same dump.
sentry-cli also needs the pair in the same invocation: only then does it
associate the PE's code identifier with the PDB's debug identifier, otherwise
"the identifiers diverge and Sentry might not be able to resolve the correct
file for symbolication" (docs: Debug Identifiers).
Do the same for macOS, where the dSYM carries the debug information but the
Mach-O carries the unwind information, and include the executables in the
symbol artifact so a re-upload still has the pair to offer.
Verified by extracting the step and running both branches against stub
harnesses: Windows passes the three exe/pdb pairs in a single sentry-cli
invocation and still fails loudly on an empty tree; macOS passes the dSYM and
the Mach-O together.
* [CrashReporting] Upload the shipped Qt runtime DLLs too
Qt ships no PDBs with its MSVC SDK, so every Qt frame in a crash report
resolved to nothing: an image with neither PDB nor PE has no unwind
information (on 64-bit Windows the Call Frame Information lives in the PE
and is not copied into the PDB) and no symbol source to fall back on.
The list is required-runtime.txt, the manifest .ci/compile.sh already
asserts the installer against, so what gets uploaded is exactly what an
install carries. Qt frames now get unwinding plus export-table names; line
numbers still need Qt's debug information files, which the SDK omits.
The guards are not cosmetic: the step runs under `bash -eo pipefail`, where
a sed over a missing file or an unmatched ls glob aborts the whole step, and
continue-on-error makes that look identical to a successful upload.
* [CrashReporting] Upload only the binaries, archive symbols only on tags
The Linux leg handed sentry-cli whole build directories: 471 candidate files,
of which 7 were object files that can never match a stack frame. Only the
three linked binaries carry the build-ids Sentry looks up; the packaged copy
is split and stripped but keeps that same build-id.
The Windows symbol archive ran on every packaging build even though only tag
builds upload to Sentry, so a PR build paid for an artifact nothing could
ever consume. Tags keep it as the durable copy for a failed upload.
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* [CrashReporting] Real send path with consent gating (DSN still placeholder)
require_user_consent + give/revoke around the Ask dialog, message event
with in-memory crash-log.txt bytes attachment, before_send scrubs
message/logentry/breadcrumbs/data of cockatrice:// secrets. Failure box
offers Save (shows path) / Discard (revokes + rejects) / Cancel (back
to dialog); reject() override so X/Escape cannot bypass revocation;
silent AlwaysSend path falls back to a local file. Empty DSN keeps the
previous mock behavior byte-identical.
Tests: 528/528 ctest green, format clean, code + UI gates PASS.
* [CrashReporting] Attach crash log and user note to the pending crash event
The redacted log used to ride a separate message event from the native
crash (minidump + stack), forcing manual correlation in Sentry. Fold it
into the pending crash envelope(s) instead by appending well-formed
attachment items before consent releases them: sentry-native replays
cached envelopes byte-for-byte (raw read, raw serialize in 0.17.3), so
the appended crash-log.txt and user-note.txt items upload with the very
event that already carries the dump and stack.
Covers both storage locations: Ask mode's consent-held files in
<database>/cache and AlwaysSend's transport-dumped files in
<database>/<run>.run, which must be attached before sentry_init replays
them. When no pending crash event exists (unclean exit without a native
crash) the standalone message event remains as fallback, now also
carrying the optional note.
The dialog gains an optional free-text field ('Here's what I was
doing...') that travels as user-note.txt; the mock JSON records it too.
* [CrashReporting] Send PROJECT_VERSION as the Sentry release
main() reported VERSION_STRING (PROJECT_VERSION_FRIENDLY) as the Sentry
release — that carries a build date, so every build of the same version
landed in its own release, and it never matched the installer, which
reports CPACK/PROJECT_VERSION. Bake COCKATRICE_SENTRY_RELEASE=
PROJECT_VERSION instead: date-free, identical between client and
installer, and derived from the git tag on tag builds, so events,
symbol uploads and the installer log all group under one release.
* [CrashReporting] Friendlier crash dialog copy
Reword the report dialog along a single friendly arc: headline ('Oops,
we crashed — and now we need your help to make Cockatrice better!'),
why helping is easy, exactly what gets collected (crash dump with stack
trace, app version, OS info, redacted log excerpt), the value of each
report ('better and less crashy for everyone'), and the consent choice
('Send and remember makes this automatic without interruption in the
future'). The checkbox itself stays direction-neutral ('Remember my
choice') because it also persists a 'Don't send' decision.
* [CrashReporting] Log where crash reports go and warn when they cannot
The client resolved COCKATRICE_SENTRY_DSN (environment over the baked-in
value) without ever saying which one won, and sentry-native refuses to build
a request for a DSN it considers invalid without reporting anything, so a
tester pointing the client at a scratch project had nothing to go on when a
crash never appeared.
Say which DSN source won, and log the destination (host and project id, never
the key) once the SDK is armed. Warn instead of staying quiet when no DSN is
configured at all, when a DSN is malformed - disabling reporting rather than
silently falling back to the project baked into the build, which is the rule
the installer already follows in cmake/CrashReporting.cmake - and when the
build was compiled without Sentry support. A deliberate Never-send stays at
info level: that is a choice, not a misconfiguration.
Also warn when a pending crash envelope was captured for a different
destination than the session that will upload it. sentry-native posts every
envelope with the DSN held in the transport state of the session doing the
sending, and envelopes restored from disk are raw, so the "dsn" header of a
cached envelope is never read: a crash captured for one project can be
uploaded to another with nothing in the log to show it.
Tests cover the DSN shape check (same unanchored pattern the build matches, so
a DSN this build accepted is never rejected here), destination description
without the key, and the mismatch detection, which stays quiet for a different
key on the same project, unreadable files, and headers without a dsn.
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* [CrashReporting] Mock dialog, sentry skeleton and CI prerequisites
Mock: DlgCrashReport preview dialog (no account needed, redacted log
preview), CrashReportingMode Ask/Always/Never setting in DebugSettings,
--simulate-crash flag and Help-menu preview entry. Send writes a local
mock JSON only.
Vendor sentry-native 0.17.3 via FetchContent behind
WITH_CRASH_REPORTING (breakpad everywhere, backend none under
sanitizers) plus a CrashReporter facade with early init and post-loop
shutdown. DSN is a placeholder: empty CMake default with
COCKATRICE_SENTRY_DSN env override, so SDK init is skipped and mock
behavior is unchanged until a real DSN is configured.
CI prerequisites riding here so every layer below builds green:
libcurl dev plus CA bundles in the Linux images and CodeQL env, sentry
Qt integration off (its own find_package cannot see the macOS thin Qt
layout), and the StrFunc Using construction makensis requires.
Tests: ctest green, format clean, code plus UI gates PASS.
* [CrashReporting] Forward mode changes to SDK and fix Linux CI opt-in
Connects PersonalSettings::crashReportingModeChanged to
giveConsent()/revokeConsent() so switching from AlwaysSend to
NeverSend (or Ask) mid-session reaches the SDK instead of leaving
consent given until restart.
Forwards WITH_CRASH_REPORTING into the docker container the way
CCACHE_DIR is, so the Linux package leg actually builds with crash
reporting ON instead of silently building OFF.
* [CrashReporting] Give crash-report logging its own category per review
* [CrashReporting] Drop unused DebugSettings constructor visibility per review
* [CrashReporting] Document reporting modes on the enum per review
* [CrashReporting] Match the dump scan to the writer's temp fallback
Review nit: when the handler's SHGetKnownFolderPath lookup fails it writes to
<temp>/../.., but the startup scan looked in QDir::tempPath(), so a dump
produced through the fallback would never be found and its crash would never
offer to be reported. Walk up the same two levels in the scan.
The path is Windows-only (#ifdef Q_OS_WIN); the statements were compiled and
run standalone against Qt 6 to confirm the API use.
* [CrashReporting] Complete the dump-scan fallback path
The previous commit walked the scan's fallback up two levels but dropped the
CrashDumps/Cockatrice suffix that the writer appends unconditionally, so a dump
written through the fallback was still invisible to the scan.
Evaluate the writer's expression verbatim rather than mirroring it with QDir:
cdUp() is existence-checked and silently leaves the path alone when the
directory is missing, parent_path() is not, and QDir::absoluteFilePath() joins
the shared suffix without risking a doubled separator when the walk lands on a
root. Verified by compiling the block verbatim against Qt 6 with -Wextra
-Werror and comparing it with the writer's join for both the normal and the
fallback case.
Also record that %LOCALAPPDATA% is the scan's stand-in for
FOLDERID_LocalAppData and why the two normally agree.
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* [Client] Settings: isolate the selected section when drilling down
Show only the selected section's group box. Restore the overview on
page rows. Filter search results to the control's containing section.
Reset the scroll position on every change.
Reworked to stay in the enlarged modal instead of the settings tab.
* [Client] Settings: re-clicking an isolated section restores the overview
* [Settings] Keep section row selected when drilling down per review
* [Settings] Stop stealing nav selection mid-gesture per review
* [Settings] Sample nav selection pre-press so re-click detection works per review
* Don't flash the section on drill-down
* [Client] Settings: clear the section filter when selecting the page row per review
onTabClicked() returned early for the already-current page before switchPageContent() could call applySectionFilter(index, nullptr), so clicking the page row while a section was isolated moved the selection but left the page filtered. Only skip the restore when no section is isolated.
* [Settings] Select the search result's section in the sidebar per review
Reaching a setting through the search isolated its section and flashed the
widget, but left the navigation selection on the page row, so the sidebar
looked like it had only jumped to the section's page. Extract the section-row
selection shared with drill-down into selectSectionRow() and use it for search
results too, keeping the gesture/re-click behavior untouched.
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
Add AbstractSettingsPage::getSections built from each page's group boxes.
Show each multi-group page's sections as child rows in the navigation
tree. Section rows show the parent page and scroll to and highlight the
group box. Start pages collapsed. Keep single-group pages flat.
Reworked to stay in the enlarged modal instead of the settings tab.
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* [Client] Settings: add top-right cogwheel shortcut
Add a subtle flat settings button to the tab bar corner that opens the
settings dialog via the existing menu action slot. Icon and tooltip
follow the menu action in retranslateUi.
* [Client] Settings: flat black-cogwheel corner entry per review
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* [Client] Settings: grouped left navigation for settings pages
Replace the top icon-button bar in SettingsView with a grouped left
QTreeWidget: Application, Cards and Data, Chat and Sound, Advanced. The
active page is the selected row (palette highlight); search mode hides
the tree; group headers expand but never select. Page tr() contexts
stay DlgSettings; new group names are SettingsView strings.
Reworked to stay in the enlarged modal instead of the settings tab.
* [Settings] Make groupedPages static const per review
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* [Client] Settings: enlarge the settings dialog
Keep settings modal per review: size to ~90% of the main window
(5% margins each side) with screen fallback, size grip and
750x700 minimum bounded to the screen. Drops the tab approach
while giving dense pages room without the tab-switch friction.
* [Client] Settings: clamp dialog width to 1280 per review
* [Client] Settings: extract dialog size calculation per review
Move the host/screen target math out of the DlgSettings constructor into a calculateDialogSize helper. Drop the leftover 'per review' wording from the clamp comment. No behavior change.
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* [Client] Settings: extract SettingsView from DlgSettings
Move the search bar, tab bar, stacked pages and search results out of
DlgSettings into a reusable SettingsView widget. The dialog becomes a
thin frame (title, OK button, close validation) delegating to the view.
No visual or behavior change; tr() contexts preserved as DlgSettings.
* [Client] Settings: match CI clang-format-18 line breaking
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* Translate oracle_en@source.ts in ru [Manual Sync]
71% of minimum 5% translated source file: 'oracle_en@source.ts'
on 'ru'.
Sync of partially translated files:
untranslated content is included with an empty translation
or source language content depending on file format
* Translate cockatrice_en@source.ts in ru [Manual Sync]
48% of minimum 5% translated source file: 'cockatrice_en@source.ts'
on 'ru'.
Sync of partially translated files:
untranslated content is included with an empty translation
or source language content depending on file format
---------
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
* Translate oracle_en@source.ts in yue [Manual Sync]
82% of minimum 5% translated source file: 'oracle_en@source.ts'
on 'yue'.
Sync of partially translated files:
untranslated content is included with an empty translation
or source language content depending on file format
* Translate cockatrice_en@source.ts in yue [Manual Sync]
43% of minimum 5% translated source file: 'cockatrice_en@source.ts'
on 'yue'.
Sync of partially translated files:
untranslated content is included with an empty translation
or source language content depending on file format
* Translate oracle_en@source.ts in yue [Manual Sync]
82% of minimum 5% translated source file: 'oracle_en@source.ts'
on 'yue'.
Sync of partially translated files:
untranslated content is included with an empty translation
or source language content depending on file format
---------
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
35% of minimum 5% translated source file: 'cockatrice_en@source.ts'
on 'zh-Hant'.
Sync of partially translated files:
untranslated content is included with an empty translation
or source language content depending on file format
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
16% of minimum 5% translated source file: 'cockatrice_en@source.ts'
on 'sv'.
Sync of partially translated files:
untranslated content is included with an empty translation
or source language content depending on file format
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
* Translate oracle_en@source.ts in sr [Manual Sync]
23% of minimum 5% translated source file: 'oracle_en@source.ts'
on 'sr'.
Sync of partially translated files:
untranslated content is included with an empty translation
or source language content depending on file format
* Translate cockatrice_en@source.ts in sr [Manual Sync]
7% of minimum 5% translated source file: 'cockatrice_en@source.ts'
on 'sr'.
Sync of partially translated files:
untranslated content is included with an empty translation
or source language content depending on file format
---------
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
* Translate oracle_en@source.ts in pl [Manual Sync]
82% of minimum 5% translated source file: 'oracle_en@source.ts'
on 'pl'.
Sync of partially translated files:
untranslated content is included with an empty translation
or source language content depending on file format
* Translate cockatrice_en@source.ts in pl [Manual Sync]
41% of minimum 5% translated source file: 'cockatrice_en@source.ts'
on 'pl'.
Sync of partially translated files:
untranslated content is included with an empty translation
or source language content depending on file format
---------
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
* Translate oracle_en@source.ts in en@pirate [Manual Sync]
7% of minimum 5% translated source file: 'oracle_en@source.ts'
on 'en@pirate'.
Sync of partially translated files:
untranslated content is included with an empty translation
or source language content depending on file format
* Translate cockatrice_en@source.ts in en@pirate [Manual Sync]
5% of minimum 5% translated source file: 'cockatrice_en@source.ts'
on 'en@pirate'.
Sync of partially translated files:
untranslated content is included with an empty translation
or source language content depending on file format
---------
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
13% of minimum 5% translated source file: 'cockatrice_en@source.ts'
on 'cs'.
Sync of partially translated files:
untranslated content is included with an empty translation
or source language content depending on file format
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
* Translate oracle_en@source.ts in en_US [Manual Sync]
93% of minimum 5% translated source file: 'oracle_en@source.ts'
on 'en_US'.
Sync of partially translated files:
untranslated content is included with an empty translation
or source language content depending on file format
* Translate cockatrice_en@source.ts in en_US [Manual Sync]
67% of minimum 5% translated source file: 'cockatrice_en@source.ts'
on 'en_US'.
Sync of partially translated files:
untranslated content is included with an empty translation
or source language content depending on file format
---------
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
* Translate oracle_en@source.ts in pt_BR [Manual Sync]
92% of minimum 5% translated source file: 'oracle_en@source.ts'
on 'pt_BR'.
Sync of partially translated files:
untranslated content is included with an empty translation
or source language content depending on file format
* Translate cockatrice_en@source.ts in pt_BR [Manual Sync]
52% of minimum 5% translated source file: 'cockatrice_en@source.ts'
on 'pt_BR'.
Sync of partially translated files:
untranslated content is included with an empty translation
or source language content depending on file format
---------
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
* Translate oracle_en@source.ts in pt [Manual Sync]
50% of minimum 5% translated source file: 'oracle_en@source.ts'
on 'pt'.
Sync of partially translated files:
untranslated content is included with an empty translation
or source language content depending on file format
* Translate cockatrice_en@source.ts in pt [Manual Sync]
21% of minimum 5% translated source file: 'cockatrice_en@source.ts'
on 'pt'.
Sync of partially translated files:
untranslated content is included with an empty translation
or source language content depending on file format
---------
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
* Translate oracle_en@source.ts in el [Manual Sync]
52% of minimum 5% translated source file: 'oracle_en@source.ts'
on 'el'.
Sync of partially translated files:
untranslated content is included with an empty translation
or source language content depending on file format
* Translate cockatrice_en@source.ts in el [Manual Sync]
10% of minimum 5% translated source file: 'cockatrice_en@source.ts'
on 'el'.
Sync of partially translated files:
untranslated content is included with an empty translation
or source language content depending on file format
---------
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
* Translate oracle_en@source.ts in et [Manual Sync]
80% of minimum 5% translated source file: 'oracle_en@source.ts'
on 'et'.
Sync of partially translated files:
untranslated content is included with an empty translation
or source language content depending on file format
* Translate cockatrice_en@source.ts in et [Manual Sync]
6% of minimum 5% translated source file: 'cockatrice_en@source.ts'
on 'et'.
Sync of partially translated files:
untranslated content is included with an empty translation
or source language content depending on file format
---------
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
* Translate oracle_en@source.ts in de [Manual Sync]
93% of minimum 5% translated source file: 'oracle_en@source.ts'
on 'de'.
Sync of partially translated files:
untranslated content is included with an empty translation
or source language content depending on file format
* Translate cockatrice_en@source.ts in de [Manual Sync]
67% of minimum 5% translated source file: 'cockatrice_en@source.ts'
on 'de'.
Sync of partially translated files:
untranslated content is included with an empty translation
or source language content depending on file format
---------
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
* Translate oracle_en@source.ts in es [Manual Sync]
93% of minimum 5% translated source file: 'oracle_en@source.ts'
on 'es'.
Sync of partially translated files:
untranslated content is included with an empty translation
or source language content depending on file format
* Translate cockatrice_en@source.ts in es [Manual Sync]
62% of minimum 5% translated source file: 'cockatrice_en@source.ts'
on 'es'.
Sync of partially translated files:
untranslated content is included with an empty translation
or source language content depending on file format
---------
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
* Translate oracle_en@source.ts in it [Manual Sync]
93% of minimum 5% translated source file: 'oracle_en@source.ts'
on 'it'.
Sync of partially translated files:
untranslated content is included with an empty translation
or source language content depending on file format
* Translate cockatrice_en@source.ts in it [Manual Sync]
67% of minimum 5% translated source file: 'cockatrice_en@source.ts'
on 'it'.
Sync of partially translated files:
untranslated content is included with an empty translation
or source language content depending on file format
---------
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
* Translate oracle_en@source.ts in ja [Manual Sync]
81% of minimum 5% translated source file: 'oracle_en@source.ts'
on 'ja'.
Sync of partially translated files:
untranslated content is included with an empty translation
or source language content depending on file format
* Translate cockatrice_en@source.ts in ja [Manual Sync]
41% of minimum 5% translated source file: 'cockatrice_en@source.ts'
on 'ja'.
Sync of partially translated files:
untranslated content is included with an empty translation
or source language content depending on file format
---------
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
* Translate oracle_en@source.ts in fi [Manual Sync]
82% of minimum 5% translated source file: 'oracle_en@source.ts'
on 'fi'.
Sync of partially translated files:
untranslated content is included with an empty translation
or source language content depending on file format
* Translate cockatrice_en@source.ts in fi [Manual Sync]
33% of minimum 5% translated source file: 'cockatrice_en@source.ts'
on 'fi'.
Sync of partially translated files:
untranslated content is included with an empty translation
or source language content depending on file format
---------
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>