Commit graph

198 commits

Author SHA1 Message Date
Lukas Brübach
b715e18dfc [Client] Route investigate() through the trail helper 2026-11-07 22:28:38 +01:00
Lukas Brübach
0a2d91076d [Client] Fix alt badge and trail review findings
- Drop the dead VPN warning branch: rowVisible() already hides IP rows
  above the threshold, so setVpnThreshold(), its member and the spin-box
  connect never took effect.
- Dim IP rows at shared_count > 2 instead of > 1: the count already
  includes the investigated account, so every IP row was dimmed.
- Truncate backStack when a breadcrumb is clicked instead of pushing the
  current user again, so the trail keeps a single path and Back works.
- Word-wrap the trail label so a long chain cannot widen the tab.
- Extract the status/match pill painting into widgets/utility/pill_painter
  and use it from both delegates instead of duplicating it.
2026-11-07 22:28:38 +01:00
Lukas Brübach
25df7634fb [Client] Alt badges, breadcrumb trail, investigate button
AltTableDelegate paints palette-tinted match badges with paint-through selection, style warning/critical icons on nonzero counts, and font/elision handling. TabAlts gains a clickable breadcrumb trail, an Investigate-this-user button, and bold usernames. Async alts responses are guarded against stale investigations.
2026-11-07 22:28:38 +01:00
Lukas Brübach
fd2858fb53 Fix remaining review threads on the report queue dashboard
- Read the raw report status from Qt::UserRole in DlgMyReports, so regular
  users can comment on their own open reports again.
- Exclude the server's depth-0 seed row from the related-alt count.
- Dim only IP matches shared by more than the pair, not every IP match.
- Activate alts rows on double-click or Enter instead of `activated`, which
  fires on a single click on single-click platforms.
- Fold the reported-user bolding pass into the fill loop.
2026-11-07 22:28:38 +01:00
Lukas Brübach
e7b0f397af [Client] Report queue dashboard and status pills
TabReport becomes a dashboard: selected-report identity header (display-size user, bold status, category/time/room subline), Description/Chat/Comments/Reported-user tabbed sections with tab memory, always-visible user context with empty cues, and bold reported users. ReportStatusDelegate paints palette-tinted status pills with paint-through selection, translated status strings, and font/elision handling. report_utils gains formatReportStatus plus an opt-out status-color flag so the delegate owns color in the mod queue while the user dialog keeps its colors. Async responses stay guarded; retranslation re-applies header and comment states.
2026-11-07 22:28:38 +01:00
Lukas Brübach
bf1857f28a [Client] Moderation and alts dashboards
Turn the two staff tables into dashboards without behavior changes: TabModeration gets an identity header (display-size name, status, registered/last-login subline), Reports/Warnings/Bans/Alts count cards with an Open-graph deep link, full-width admin notes, and Alts/Sessions/Staff tabbed sections with tab memory. TabAlts gets toggle chips with match counts, bold usernames, palette-token dimming of shared-IP rows, and a resizable side card for the selected-user timeline with selection restore. Row activation works via Enter as well as double-click; async responses are guarded against stale investigations and retranslation re-applies cached dashboard data.
2026-11-07 22:28:38 +01:00
Lukas Brübach
8e03e295cd [Client] Alt tab filters and unified user timeline
TabAlts gains client-side match filters (Email / Client ID / IP toggles plus a hide-IPs-shared-by-more-than-N-users threshold for VPN noise) with a shown/hidden status count, and a Selected user panel that loads the row account's counts, admin notes, warning history, and ban history in one place. Detail requests are guarded against stale selections.
2026-11-07 22:28:38 +01:00
Lukas Brübach
221881a4ce [Client] Alt investigation entry points and in-tab navigation
TabModeration gains an Open alt graph button plus alts-table double-click, both routing to the User Alts tab. UserContextMenu gains an Investigate alts action. TabAlts gains row double-click navigation with a Back history stack so moderators can walk the alt graph without retyping names.
2026-11-07 22:28:38 +01:00
Lukas Brübach
66807f543b Fix remaining review threads on the user alts tab
- Switch to the User Alts tab when it is created, so the first investigate
  click does not open it in the background.
- Treat an empty RespOk as "user not found" and use a generic message for
  error codes, matching what the server actually returns.
- Exclude the depth-0 seed row from the related-account count.
- Guard the reply with the requested user name so a stale response cannot
  overwrite a newer one.
- Move the duplicated formatEpoch body into widgets/utility/epoch_format.h.
2026-11-07 22:28:38 +01:00
Lukas Brübach
a14e1efe46 [Client] Transient User Alts investigation tab
New TabAlts tab with user search, depth selector (1-3), and an alt table showing per-row match evidence (Via, Depth, Shared fan-out) alongside the account data. Sends Command_GetUserAlts with the requested depth and degrades gracefully against old servers (missing evidence fields render as dashes). Wired as a transient single-reuse tab via TabSupervisor::openTabAlts with no menu action or persisted setting; closed on disconnect like the other staff tabs.
2026-11-07 22:28:38 +01:00
RickyRister
eab217d1bf
[DlgCreateGame] Revert dlg to use exec instead of open (#7795) 2026-11-07 13:44:40 +01:00
tooomm
2d625575f6
[Translations] Give translators context for separator-bearing tr() strings (#6487)
Build the card menu's related card actions from complete strings with %1
placeholders instead of appending translated fragments. This also fixes
tr(cond ? "Transform into " : "Attach to "), which lupdate cannot extract:
neither string appears in cockatrice_en@source.ts, so neither was ever
translated.

Give the EDHRec tab title a %1 placeholder so French can render
"EDHRec : %1" instead of concatenating into "EDHRec :CardName".

Keep the colon in tr("Token:") and the leading space in tr(" seconds"),
since that separator is language-specific, and attach //: translator
comments so they are no longer shown out of context on Transifex.
2026-11-06 21:12:07 +01:00
DawnFire42
17f583013b
[CardMenu] Free the card menus and submenus rebuilt on each right-click (#7093)
* Parent card menu submenus to the menu that owns them

QMenu::addMenu(QMenu *) does not take ownership of the submenu, and these
submenus were created without a parent. Since CardMenu is rebuilt from
scratch on every right-click, each rebuild leaked the MoveMenu, PtMenu,
card counters and related-cards submenus.

Pass the owning CardMenu as the QWidget parent so Qt's parent-child
mechanism frees them with it.

* Delete the replaced card menu in TabGame to fix menu rebuild leak

PlayerMenu creates a new CardMenu on every right-click and TabGame installs
it with QAction::setMenu(), which does not take ownership, so the previous
menu was never freed. TabGame now deletes the menu it replaces and reuses a
single empty placeholder menu instead of allocating one per update. The
installed menu is also released when the tab is destroyed.
2026-11-06 20:51:00 +01:00
sentry[bot]
64c7809332
[DlgCreateGame] Heap-allocate create game dialog to prevent crash on parent deletion (#7777)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / vcpkg baseline (push) Waiting to run
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 sanitize (push) Blocked by required conditions
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 cover (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 27 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / Prune caches (push) Waiting to run
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
* [DlgCreateGame] Heap-allocate create game dialog to prevent crash on parent deletion

* [Tests] Run the short-write cases without forking a death test

The three cases that force a write failure - CardDatabaseTest.Xml4ShortWriteKeepsInstalledFile,
OracleDatabaseWriter.ShortWriteKeepsPreviouslyInstalledDatabase and
SpoilerFileTest.ShortWriteLeavesExistingFileIntact - all forked a child, lowered
RLIMIT_FSIZE and ignored SIGXFSZ. That child is killed by SIGSEGV on the macOS
runners, so all three have been red on every macOS leg of master since they landed,
while the same cases pass on Linux.

Cap the soft limit in the test process instead: ctest already runs one case per
process, and the guard restores the limit and the SIGXFSZ handler, so a whole binary
keeps working. Same real EFBIG failure, no fork, and macOS keeps its coverage of the
writers.

* [CI] Derive DEBUG_TEST and RUN_TESTS in the nightly job

The shared steps gate on both, but build-linux-nightly never set them, so its
coverage leg skipped "Build debug and test" and then failed collecting a report
from an empty build/ directory on every nightly and tag run. continue-on-error
kept the run green, so the coverage artifact silently never existed.

Derive both exactly as the pull request job does.

---------

Co-authored-by: sentry[bot] <39604003+sentry[bot]@users.noreply.github.com>
Co-authored-by: Lukas Brübach <44814898+BruebachL@users.noreply.github.com>
2026-11-05 07:49:20 +01:00
BruebachL
627c7a83aa
[Icons] Migrate UI glyphs to palette-token SVGs (#7595)
* [Icons] Migrate UI glyphs to palette-token SVGs

Runtime inversion is not a theming system: it rotates hue, breaks duotone art
and mishandles alpha, and a QPixmap snapshot cannot follow a palette change.
Redraw the top-level UI glyph set on a 24x24 grid with `currentColor` as the
paint token, drop Inkscape/Sodipodi cruft, and delete the icons whose metaphor
was duplicated (cogwheel/gear -> settings, scale_balanced -> scales,
update.png -> reload).

Load the glyphs through `loadThemedIcon` so the engine resolves the token from
the palette on every request, and let `themePixmap` use the same resolution for
one-shot pixmaps. `loadColorAdjustedPixmap` is gone; every caller now keeps a
QIcon, which `SettingsButtonWidget::setButtonIcon` accepts instead of a QPixmap.

Content art (mana, counters, countries) keeps its authored colors.

Add a render test covering aspect ratio, palette flips, icon modes and
high-DPI pixmaps.

* [Tests] Keep the icon render test QApplication alive until exit

Creating it as a function-local static tears it down before Qt's library statics
at exit, and libQt6Network then runs QObject destructors against freed memory —
a SEGV the sanitizer leg reports after the tests have already passed. Leak the
application instead, the usual pattern for a test binary.

* [Icons] Render lock and dropdown glyphs through the icon engine

Both generators rendered the sentinel SVG to a fixed height x height pixmap
at DPR 1. The pre-migration path rendered larger and let QIcon scale it, so
on a 2x display the games-list lock (13px) and the banner dropdown arrow
(24px) came back crisp; the new path hands the widget a DPR-1 bitmap that
gets upscaled and blurred.

- The games list asks the themed engine for `icons/lock` directly, so the
  item view paints it at the device pixel ratio and the lock also follows
  HighlightedText on a selected row.
- The dropdown goes through `loadThemedIcon(...).pixmap(size, dpr)`, which
  keeps the glyph's aspect ratio and renders at the requested ratio.
- LockPixmapGenerator has no consumer left, so it and its cache are gone.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-11-04 18:39:41 +01:00
BruebachL
7b4827dadd
[Replay] Show the history sidebar in replay tabs (#7613)
Add the Game History dock to the left of replay tabs, wire it to the replay timeline, and register it with the View menu and layout reset.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-11-04 18:26:51 +01:00
BruebachL
5ea16c805f
[Server/Client/Protocol] Shuffle or reorder player seats before a game starts (#7398)
* [Protocol] Add shuffle players option and player order command/event

Adds a 'Shuffle players at start' option to game creation plus a local
player id on the game state so clients can reconcile seat changes, and a
SET_PLAYER_ORDER command (1206) with a PLAYER_ORDER_CHANGED event (2023)
so the game host can set or randomize the seating order.

Took 6 minutes

* [Server] Shuffle and reorder player seats before a game starts

Auto-shuffles the seats at game start when enabled (now for games with
two or more players instead of three or more) and handles the new
SET_PLAYER_ORDER command so the game host or a moderator can reorder or
randomize the seats before the game starts. Re-seating re-keys the
player ids while keeping the per-user game/seat routing and persistent
player records in sync, then broadcasts the new order to everyone.

* [Client] Add shuffle players option and turn order dialog

Adds the 'Shuffle players at start' checkbox to game creation and a
host-only 'Set turn order...' action that opens a dialog to reorder or
randomize the seats before the game starts. Seating changes are shown in
the game's message log.

* [Client] Move the turn order dialog strings into retranslateUi

Follows the DlgInviteToGame convention so the dialog can be retranslated
instead of freezing its strings at construction time.

* [Server] Keep player ids stable across seat shuffles

* [Client] Lay out table and turn order dialog in seat order

* [Server] Send game-joined player list in seat order

* [Client] Reword the shuffle option to match its behaviour

* [Client] Address the seat-order review comments

- Give the game setup checkboxes a row of their own so they no longer
  collide in the create-game dialog
- Rename DlgTurnOrder::randomize() to isRandomizeRequested()
- Document what the ids in GameState::seatOrder mean
- Keep "Set turn order..." in sync with the game: hidden for non-hosts
  and once the game has started, re-shown when the game ends or hosting
  moves to the local player (new GameState::hostChanged signal)

* [Client] Do not preview a randomized turn order

Randomizing is a request to the server, not a local shuffle. Shuffling the
list locally meant the dialog showed an order the server then discarded, and
let the host re-open it until a favourable order appeared. The dialog now
switches to a mode that says the server will randomize and toggles back to the
manual list on request.

* [Client] Let moderators set the turn order

The server accepts SET_PLAYER_ORDER from the host or a moderator, but the
client hid the action from everyone except the host, making the moderator
branch unreachable. Visibility and the handler now share canSetTurnOrder().

* [Server] Rely on the player order event instead of a full state resend

cmdSetPlayerOrder re-sent the whole game state after reseating, so every
client reloaded its deck view mid-lobby for no reason. The new seating reaches
clients through the Event_PlayerOrderChanged that reseatPlayers broadcasts.

* [Client] Show random turn order in the game selector

The restrictions column listed "open decklists" but not the new seating
option, so a joining player had no way to see that seats will be randomized
each game. Game info already shows the disabled checkbox.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-11-04 15:44:50 +01:00
BruebachL
a791389c6a
[Fonts] Restore DeckList Serif default and live-update all role readers (#7541)
* [Fonts] Restore DeckList Serif default and live-update all role readers

* [Fonts] Keep the DeckList fallback on the application font

DeckList is read by the deck editor headers, the filter tree and the deck
view too, not only by the two surfaces that hardcoded Serif. Resolving the
unset role to Serif in appFont() made every user without a fonts.toml see
Serif outside the deck list.

- appFont(DeckList) falls back to the application font again.
- ThemeManager::deckListFont() resolves the role and keeps the Serif
  fallback for the two historical sites: the deck view zone headers and
  the printed deck list.
- DeckView no longer repaints its viewport on fontsChanged; touching the
  scene items already schedules the repaint in every attached view.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-11-04 07:57:55 +01:00
BruebachL
3bcdfc3793
[Fonts] Migrate DeckList and InterfaceDefault surfaces to theme roles (#7539)
* [Fonts] Migrate DeckList and InterfaceDefault surfaces to theme roles

* [Fonts] Drop unused lambda captures in font-refresh handlers

* [Fonts] Fix pixel sizes and game overlay fonts from review

- Set the sizes the stylesheets carried with setPixelSize() rather than
  setPointSize(): home buttons (34), banner (24), home group box (20),
  the placeholder labels (14), the analytics drag handle (10), the EDHREC
  navigation buttons (24), the user info popup (11/14/11/10/11) and the
  game overlays (14/16/12). Points at 96 DPI were ~33% larger.
- The drag/total counters and tally columns keep the monospace family the
  stylesheets set, so digit widths stop changing as counts tick and
  white-space: pre stays aligned.
- applyDeckListFont() drops the cached tally rows before re-measuring, so
  previewing a larger DeckList font no longer leaves the tally clipped.
- Tally labels and the constructor/fontsChanged pairs in the shared-deck
  preview, quick setup, analytics panel, home box and public decks build
  their font once in a local lambda instead of twice.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-11-04 07:57:54 +01:00
BruebachL
396196b762
[Fonts] Migrate CardText surfaces to theme font role (#7538)
* [Fonts] Migrate CardText surfaces to theme font role

* [Fonts] Keep CardText call sites on the app font and fix px sizes

- CardText's unset fallback is qApp->font() again: only the three counter
  painters (paintNumberEllipse, GeneralCounter, PlayerCounter) were Serif
  before the role existed, and they now ask for it through
  ThemeManager::cardTextFont(). Card names, player names, counters, the
  overlay, the Archidekt and EDHREC labels and the completer delegate keep the
  app font they used before.
- Use setPixelSize for the EDHREC card name (20px) and commander name (28px)
  and details (16px) sizes: the stylesheets said px, and setPointSize made
  them about a third larger.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-11-04 07:57:54 +01:00
BruebachL
7a40ed9079
[Fonts] Migrate ChatLog surfaces to theme font role (#7537)
* [Fonts] Migrate ChatLog surfaces to theme font role

* [Fonts] Keep ChatView on the app font and share the report log font

- ChatLog's unset fallback is qApp->font() again: ChatView never set a font
  before the role existed, so an unset role must not switch chat and the game
  log to monospace at the system GeneralFont size.
- Move the monospace fallback the report dialogs used before into
  report_utils::reportLogFont(), which returns the theme's ChatLog role when it
  is set and the old monospace otherwise. tab_report, dlg_my_reports and
  dlg_report_user now share it, so they cannot drift and they honour a themed
  size instead of overriding it.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-11-04 07:57:53 +01:00
BruebachL
3fa8d78786
[Tests] Play card interactions through a Cockatrice event player (#7697)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-11-04 06:23:45 +01:00
BruebachL
34201122ec
[Tests] Name the tab, home and settings widgets the GUI tests address (#7696)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-11-04 06:23:44 +01:00
Bryce!
75d7764643
[Client] Keep Archidekt navigation tied to its active request (#7723)
* [Client] Keep Archidekt navigation tied to its active request [skip ci]

* [Client] Keep Archidekt listing pagination after a failed navigation

Only update list mode and the next-page link when a listing or deck
response is accepted, so a failed deck navigation leaves the existing
listing scrollable. Retry still re-issues the failed request.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-11-04 05:59:17 +01:00
Bryce!
624163916f
[Client] Keep Commander Spellbook requests bound to the active deck (#7722)
* [Client] Keep Commander Spellbook requests bound to the active deck [skip ci]

* [Client] Restore the bracket info tooltip when idle

retranslateUi() used to install the static "Why this bracket?" tooltip, but it now calls displayEstimate(), which returned early while no estimate existed. That left the info button without its idle tooltip.

Set the fallback where displayEstimate() owns the button's tooltip. An existing estimate still wins, and a language change refreshes the text.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-11-04 05:49:31 +01:00
Bryce!
7768416091
Place current deck-editor docks before restoring saved layouts [skip ci] (#7734) 2026-11-04 04:37:25 +01:00
Jurriaan
bfb53c5def
[Client] Visual Deck Editor: Added more mouse click modifiers to add/decrement cards to specific zones (#7589)
* Make visual database mouse click modifiers properly add/decrement different card zones

* Use event->modifiers(), refactor

* Removed QApplication include

* Added visual database display documentation page

* Minor fixes

* Added documentation for the mouse interaction of the table view
2026-11-03 10:35:58 +01:00
BruebachL
94e6281d87
[Logs] Browse room history without filters with paginated infinite scroll (#7531)
* [Logs] Browse room history without filters with paginated infinite scroll

* [Logs] Continue pagination when switching log tabs

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-11-02 07:36:49 +01:00
BruebachL
345b0e8ba9
[Refactor] Move game logic into libcockatrice_game (#7437)
* [Refactor] Move game logic into libcockatrice_game

* [Refactor] Include card_state.h in player_actions.h

* [Game] Inject settings providers into libcockatrice_game, drop Widgets

* [Game] Follow base rename to createCardRequested after rebase

* [Game] Drop unused CardItem forward declaration

* [Game] Fix includes after moving game logic into libcockatrice_game

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-31 18:44:15 +01:00
BruebachL
6f0c5dca12
[Refactor] Move LoadedDeck and DeckFileFormat data types into libcockatrice_deck_list (#7424)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-31 18:44:11 +01:00
BruebachL
73d5a2a0ab
[Refactor] Emit phase toolbar intent instead of protobuf commands (#7419)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-31 18:44:10 +01:00
sentry[bot]
aa42cfbccc
[VisualDeckEditor] Create menus before central frame to prevent Windows crash in setParent_sys (#7673)
Co-authored-by: sentry[bot] <39604003+sentry[bot]@users.noreply.github.com>
2026-10-27 09:12:37 +01:00
BruebachL
93309e01a2
[Server/Client/Protocol] Let moderators clear banner cards (#7546)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-25 13:19:20 +01:00
Neel Jogani
a4c4c67674
Fix tab retranslation (#7580)
* 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
2026-10-25 12:30:17 +01:00
BruebachL
518c7e6125
[Network] Add a configured QNetworkAccessManager factory (#7628)
* [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>
2026-10-25 00:33:43 +02:00
BruebachL
11e2bb0d2f
[QtUtils] Adopt window fade, path, bold font and confirmation helpers (#7622)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 cover (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Desktop / Prune caches (push) Waiting to run
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-24 21:43:50 +02:00
BruebachL
e806085681
[QtUtils] Use makeLayout() for zero-margin layouts (#7620)
* [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>
2026-10-24 19:06:18 +02:00
Neel Jogani
6e770eb7b2
Retranslate Home tab when the language changes (#7579)
* 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
2026-10-24 08:35:31 +02:00
BruebachL
ab13a6748f
[QtUtils] Adopt shared Qt helpers across the client (#7618)
* [QtUtils] Add parent lookup, layout clearing, scroll area and monospace helpers

* [QtUtils] Adopt shared Qt helpers across the client

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-24 08:26:21 +02:00
RickyRister
b849a57dab
[TabRoom] Fix compile failure due to unused lambda capture (#7577) 2026-10-18 11:10:26 +02:00
BruebachL
ce51e7147d
[Servatrice] Log room channel messages in audit log (#7479)
* [Servatrice] Log room channel messages and bump schema to 38

* Fixup: show room_channel audit rows and keep migration nullable

The log history query returns room_channel rows, but the log tab
dropped them. Render them in the room table with an extra Channel
column. Also drop the NOT NULL on target_type from the 37->38
migration so migrated schemas match the nullable servatrice.sql
definition (and strict-mode ALTERs cannot fail on legacy NULL rows).
Update the channel tests to the flag-based privilege model: the admin
fixture now carries IsModerator alongside IsAdmin, as the database
layer assigns.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-16 22:46:39 +02:00
BruebachL
c99113ab0e
[Client] Tabbed room channels with unread states and collapsible chat (#7478)
* [Client] Add room channel selector with per-channel chat views

* [Client] Tabbed room channels with unread states, collapse, read-only, banners

Reworks the room chat channel display: channels move out of the combobox
into a tab bar below the message input showing every channel available
to the user.

- Each tab shows its unread message count and is tinted when it holds
  unreads or a mention; both colors are user-configurable chat settings
  (channelUnreadColor, channelMentionColor) with a settings-page section.
- Clicking the active tab collapses the chat: message view and input
  hide and the splitter section minimizes to the tab strip; expanding
  restores the previous splitter sizes. Viewing a channel clears its
  unread state.
- Channels the user may read but not write (per write_access_level)
  show with the message input disabled and a read-only placeholder;
  sends are guarded locally, the server still enforces.
- Channel banner_message strings render at the top of their channel
  view on join, like the room join message on Main.
- Replayed channel history no longer inflates unread counts; moderation
  redaction now applies across all channel views.

* Fixup: keep chat tab bar visible when chat is collapsed

QSplitter children are collapsible by default, so setSizes({1, 0})
hid the whole chat group box including the channel tab bar.
Mark the chat widget non-collapsible so collapsing clamps to its
minimum size (title + tab bar) instead.

* [Room] Explicitly initialize MAIN_CHANNEL per review

* [Room] Address channel tab review follow-ups

- gate @all mentions on moderator/admin sender to match ChatView,
  whole-word matching for own name and highlight words
- hide single-channel tab bar so old servers do not show a lone Main
  tab that collapses chat on misclick
- capture target ChatView in sendMessage so errors land in the
  channel the message was sent from
- share ignore/unregistered filtering between RoomSay and
  RoomChannelSay via isRoomMessageIgnored

* [Client] Fix tab_room.cpp formatting per clang-format-18

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
Co-authored-by: BruebachL <bruebachl@cockatrice.example>
2026-10-16 22:46:39 +02:00
BruebachL
4c5a5e3e55
[Game] Invalidate view when hiding replaced card menu (#7453)
* [Game] Invalidate view when hiding replaced card menu

Hide any visible card menu before replacing and invalidate viewport.

* [Game] Fix viewport update compile error in setCardMenu

QWidget::update has no QRectF overload, and sceneRect is in scene
coordinates rather than viewport coordinates anyway, so invalidate the
whole viewport instead.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-11 15:36:11 +02:00
BruebachL
07eb9f0103
[Report] Prompt for a note on Resolve and drop the redundant option (#7445)
- Remove the third close option (Resolve with note...) so closing a
  report is one of two clear actions
- Make Resolve always prompt for the optional resolution note, matching
  Dismiss, and rename the button to Resolve...
- Halve the resolveReport signature now that every caller prompts

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-11 15:33:48 +02:00
BruebachL
38acddeede
[Report] Add context menu to report queue rows (#7444)
* [Report] Add report reopen action for closed reports

- New Command_ReportReopen moderator command (extension 1206) with server
  handler that reopens resolved/dismissed reports, resets resolution state
  and notified, and records the reopening note in the audit log
- Reopen... button in the report queue action bar, gated to resolved and
  dismissed reports, with optional note prompt and tooltip
- Success confirmations for Assign, Resolve/Dismiss and Reopen now survive
  the list refresh via a pending status message instead of being clobbered
- Dispatch/auth coverage in server_moderator_role_test

* [Report] Surface resolution audit info in report details

- Add resolved_by_name to ServerInfo_Report and join resolved_by users in
  the report list, my-list and details queries
- Render resolution note and resolved-at/by in the report detail pane
  instead of duplicating the description into the chat log context

* [Report] Add context menu to report queue rows

- Open the standard UserContextMenu on right-click/Menu key for the
  reported user, selecting the row first so the details panel matches
  the menu target
- Carry the reported user's level in ServerInfo_Report so the menu
  shows the same actions as the user list, with an IsUser fallback for
  deleted or unregistered users
- Wire the Private chat action through TabReport to TabSupervisor and
  retranslate the menu on live language switches

* [Server] Fix reported_user_level column indices in report queries

The SELECT lists resolved_by_name at index 10, shifting room_id to 11,
replay_id to 12, reported_user_id to 13 and reported_user_admin to 14.
The code read 12/13, so 'user exists' meant 'a replay exists' and the
admin flags came from the user id (e.g. id 5 -> admin+judge; users
without a replay reported as unregistered, hiding registered-only
actions). Fixed in cmdReportList and cmdReportMyList, plus the column
comment.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-11 15:33:47 +02:00
BruebachL
4a010bd17f
[Report] Add report reopen action for closed reports (#7442)
- New Command_ReportReopen moderator command (extension 1206) with server
  handler that reopens resolved/dismissed reports, resets resolution state
  and notified, and records the reopening note in the audit log
- Reopen... button in the report queue action bar, gated to resolved and
  dismissed reports, with optional note prompt and tooltip
- Success confirmations for Assign, Resolve/Dismiss and Reopen now survive
  the list refresh via a pending status message instead of being clobbered
- Dispatch/auth coverage in server_moderator_role_test

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-11 15:28:45 +02:00
BruebachL
d603a2bb6a
[Tooling] Apply include-what-you-use across the tree (#7396)
* [DeckList] Apply include-what-you-use to libcockatrice_deck_list

* [IWYU] Apply include-what-you-use across the tree

Run one whole-tree IWYU pass over the libcockatrice_* libraries, the
Cockatrice client, Oracle, Servatrice and the test suite, so each
translation unit includes what it uses directly and forward-declares
what it does not need.

IWYU only analyses real translation units, never the moc code CMake
generates from Q_OBJECT headers, so it strips includes that moc output
silently relies on. Where a type is passed by value (or nested in a
QList/QMap) in a signal or slot signature, the generated moc code needs
the complete type; those includes are kept with an explanatory
`// IWYU pragma: keep`. The same holds for Q_DECLARE_METATYPE targets
such as QSqlDatabase.

A handful of includes are added by hand where callers brace-initialize a
const-reference parameter, which also requires the complete type.

Applied as a single commit on purpose: stripping includes from the
libcockatrice_* headers is precisely what breaks the client, test and
server translation units, and the fixes for those live in the same
translation units. Splitting by component would therefore leave
intermediate commits that do not compile.

All 426 modified Q_OBJECT/Q_GADGET headers were verified to be
moc-self-contained by compiling each generated moc_*.cpp standalone;
several built only because of moc include ordering in
mocs_compilation.cpp before the keep-includes were added.

* [Tooling] Emit public Qt headers and fix IWYU fallout CI found

Three problems the local build could not see, all because a green local
build only proves the configuration it was run under.

Private Qt headers. IWYU emitted private, lowercase Qt headers, and
qtypes.h does not exist at all before Qt 6.9, so the Debian 12 and
Ubuntu 24.04 jobs on Qt 6.4.2 failed to compile. The generator only
emitted a mapping when the capital header name mirrored the private one
(QAbstractAnimation for qabstractanimation.h), so qtypes.h, which is
wrapped by QtTypes instead, got no mapping and kept the private
spelling. Map it explicitly to <QtTypes>, which is public since Qt 5 and
provides the same typedefs either way, and rewrite the 100 affected
includes.

Do not generalize this to "prefer any Qt* wrapper". The table is
generated from whichever Qt is installed, and the wrappers are not
equally old: QtLogging only appeared in Qt 6.8 and QtMinMax in 6.5, so
that heuristic trades one breakage for another. Every other lowercase
header still in the tree has shipped publicly since Qt 5, so those are
left alone.

Platform-guarded includes. IWYU only sees the preprocessor configuration
it is handed, so it dropped an include that only the macOS branch of
main.cpp needs, and the macOS jobs failed on an undeclared
UrlSchemeEventFilter. Restore the include. Force-compiling every file
that carries a platform guard under Q_OS_MAC, Q_OS_MACOS and __APPLE__
reports nothing else, so the macOS branches are covered rather than
assumed.

shellcheck. Drop a leftover variable and stop assigning the
include-what-you-use lookup, which was only ever an existence check.

* [Tooling] Map Qt 6.5+ split headers to QtGlobal for Qt 6.4 compatibility

IWYU emitted QtTypes, QtPreprocessorSupport, QtVersionChecks, QtAssert,
QtMinMax, QtClassHelperMacros, QtEnvironmentVariables and QtTranslation.
Qt 6.5 split those declarations out of qglobal.h into separate headers, so
neither the Qt* wrapper nor the q*.h private name exists on the oldest Qt we
support (6.4.2), and Debian 12 / Ubuntu 24.04 fail with "No such file or
directory". qtypes.h is worse still: it only appeared in Qt 6.9.

QtGlobal is the one spelling that works across the whole supported range. It
has been public since Qt 5, defines these declarations itself up to Qt 6.4, and
includes the split headers from Qt 6.5 on, so redirect all eight private
headers to it and collapse the affected includes in the tree.

Curated overrides now take precedence over the name-mirroring rule, which
cannot know about Qt version floors because the mapping is generated from
whatever Qt is installed. That rule was mapping qtversionchecks.h to
<QtVersionChecks> even with an override present, because the override was only
consulted as a fallback. A curated key that matches nothing is now a hard
error rather than a silent fallback, and the redirected headers are no longer
advertised as the home of a symbol.

Verified every Qt include in the tree resolves against the real
qt6-base-dev 6.4.2 package: 15 distinct includes, 0 missing.

* [Tooling] Map QOverload to QtGlobal and drop the QMetaMethodArgument include

Two more headers IWYU emitted do not exist on Qt 6.4.2:

QOverload was split out of qglobal.h in Qt 6.5, so on 6.4.2 it is declared in
qglobal.h directly and the standalone header is a hard error. QtGlobal covers
both, so map qoverload.h to it like the other 6.5+ split headers.

QMetaMethodArgument is never referenced anywhere in the tree. It is a bare
forwarder for qobjectdefs.h that our generator advertised as a class symbol,
which is how IWYU came to add the include at all. The symbol mappings are
generated from header names, so they cannot tell a forwarder that happens to be
capitalised from a real class; removing the unused include is the fix, and the
generic case stays a known limitation of the generator.

Extending the audit to every Q* header (an earlier pass only matched headers
starting with "Qt" or lowercase q*.h, so it missed all of them) against the real
qt6-base-dev, qt6-multimedia-dev, qt6-declarative-dev, qt6-svg-dev and
qt6-websockets-dev 6.4.2 packages: 341 distinct Qt includes, 0 missing.

* [Game] Give moc the complete CardItem type in player_actions.h

player_actions.h forward declares CardItem but passes QList<CardItem *> by
value in actPlay/actPlayFacedown/actHide. moc instantiates the metatype for
those parameters, and on Qt 6.4 that expands to sizeof(CardItem), so Ubuntu
24.04 fails with "invalid application of 'sizeof' to incomplete type
'CardItem'". Newer Qt tolerates the incomplete type, which is why only the
6.4.2 jobs failed.

Include the defining header with an IWYU pragma keep, matching how the other
MOC-required includes in this area are handled.

* [Game] Give moc the complete ServerInfo_Card type

player_logic.h, game_scene.h, view_zone.h and view_zone_widget.h forward
declare ServerInfo_Card but pass QList<const ServerInfo_Card *> through signal
and slot signatures. moc instantiates the metatype for those parameters, which
Qt 6.4 expands to sizeof(ServerInfo_Card), so the connect in game_scene.cpp
fails on Debian 12 and Ubuntu 24.04 with "invalid application of 'sizeof' to
incomplete type 'const ServerInfo_Card'". Newer Qt tolerates it, so only the
6.4.2 jobs failed.

Include the generated protobuf header with an IWYU pragma keep, following the
pattern already used for ServerInfo_Player and ServerInfo_User in
game_event_handler.h.

* [Client] Complete the types Qt 6.4 needs for connect and moc

Qt 6.4 expands QMetaType::fromType on the normalised argument list of a
PMF connect and on moc-generated metatypes, which requires sizeof on every
signal and slot parameter. Newer Qt tolerates forward declarations there,
so only the Debian 12 and Ubuntu 24.04 jobs failed, with
"invalid application of 'sizeof' to incomplete type" for QMouseEvent,
QPrinter and GameReplay.

Replace the forward declarations with the real includes:

- QMouseEvent in the nine widget headers that pass it through signals
- QPrinter in abstract_tab_deck_editor.cpp, for the actPrintDeck lambda
- game_replay.pb.h in tab_replays.h. GameReplay is a protobuf message, so
  the project's game/replay.h only forward declares it and cannot supply
  the complete type.

Found by recompiling all 1918 project TUs against the Debian Qt 6.4.2
headers, which reproduces those two CI jobs locally in one pass.

* [Client] Complete Release and QMenu for Qt 6.4 moc and connect

Continuing the Qt 6.4 complete-type sweep:

- window_main.cpp connects ClientUpdateChecker::finishedCheck to
  MainWindow::checkClientUpdatesFinished, whose Release * parameter made
  moc 6.4 instantiate the metatype. GameReplay turned out to be a protobuf
  message, so Release likewise needed release_channel.h in the three
  headers declaring those signals: window_main.h, dlg_update.h and
  client_update_checker.h.
- moc 6.4 also needs sizeof(QMenu) for the QList<QMenu *> carried by
  TabSupervisor::setMenu and MainWindow::updateTabMenu.

Verified by regenerating moc output with the Debian moc 6.4.2 for all 434
Q_OBJECT/Q_GADGET classes and compiling it, plus recompiling all 1612
source TUs, against the Qt 6.4.2 headers: no remaining failures.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-08 11:13:50 +02:00
BruebachL
d0991b68f6
[EDHRec] Update tab title when a card is searched (#7408)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
TabEdhRecMain never emitted tabTextChanged, so TabSupervisor::updateTabText
was never called and the tab kept the title it was created with.

addEdhrecMainTab() creates the tab without a card, so every search left the
title reading "EDHRec: " with no card name for the rest of the tab's life.

addEdhrecTab() sets the card before myAddTab(), which is why the older
TabEdhRec tab was never affected.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-07 14:55:25 +02:00
BruebachL
4ce2e8c3d3
[UserList] Fix join/spectate from the profile hover card doing nothing (#7383)
The games list in the profile hover card offered "Join game" and
"Spectate", but UserInfoPopup::joinGameRequested was only forwarded to an
identical UserListWidget::joinGameRequested signal that nothing connected
to. Clicking either entry sent no command, opened no tab and showed no
error.

Route the request to TabSupervisor, which owns the join flow. The popup's
games list spans every room the user is in, so the target room may not be
open yet; IntentJoinServerGame already joins the room first and retries,
which is why this goes through the intent rather than straight to
GameSelector.

Extract the intent construction that joinReportGame already had into a
shared joinGameByIntent helper instead of duplicating it.

Also stop disabling "Join game" for games that are full or already
running. GameSelector::joinGame() offers to join as a spectator in that
case, so a greyed-out entry read as a dead button; GameSelector's own
context menu never disables it either.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-02 18:57:07 +02:00
RickyRister
ffcb9094ca
[Replay] Fix cards accumulating in zone view when rewinding (#7357)
* [Replay] Fix cards accumulating in zone view when rewinding

* rename method
2026-10-02 17:17:34 +02:00