* [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>
* [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>
* [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>
Unticking 'Save password' only disabled auto-connect: the dialog still handed
the stored verifier to the connection, so a password typed at the same time was
ignored, and a server that still has the legacy hash refused the login before it
could upgrade the account.
Clear the verifier in effect on untick, restore it on re-tick unless a password
has been typed, and drop the stale 'Password saved' hint. The downgrade abort
can now point at unticking the box and logging in with the password, without the
extra reconnect round-trip.
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* [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>
Timeline seeks only expose whole timer intervals, so jumping to an event could leave that event unprocessed until the next tick. Add an event-time seek that also applies events at the target timestamp.
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* [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>
* Fixed decrementCard for tokens not using the token zone
* Removed dead function actDecrementCard
Updated documentation
* Fixed no-op, added documentation
* Removed stray blank line
* Allow finding card nodes even if no database entry exists for the given card
* Added DeckStateManager tests
* Added tokens on mainboard test
* Added if checks and sorted sources
Added if checks
* Library include fix
* Renamed zone to targetZone
* Revert "Allow finding card nodes even if no database entry exists for the given card"
This reverts commit 88578cd0d9.
* Combined loader_local_matching_test and deck_state_manager_test settings sources
* Added settings sandbox, refactor
* Format
* Renamed return variable
* Flattened if statement
* Style fix
* 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
* [Client] Add option to load decks from ManaBox
* [Client] Fix PR comments
* [Client] Fix UI and transaltion requests
* [Client] Fix for UI and parser not doing what expected when loading maybe board
* Add ManaBox support to docs
As pointed out in review comments
* [Client] Fix final PR comments
---------
Co-authored-by: tooomm <tooomm@users.noreply.github.com>
* [VisualDatabase] Keep the card grid scrolling on macOS
The view ignored the wheel whenever the vertical scroll bar was not
visible. On macOS, scroll bars are transient: once the fade-out finishes,
QScrollbarStyleAnimation hides the scroll bar widget, so isVisible() then
reports false even though the grid has more rows to scroll. Scrolling was
dead until something else brought the bar back.
Gate on the scroll bar's range instead: it says whether there is anything
to scroll, while visibility is only about presentation. An event with no
range to scroll is still ignored, so an enclosing scroll area can take it.
* Update card_database_visual_view.cpp
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* [VisualDatabase] Draw the card grid with a model/view instead of a widget per card
The visual card database built one CardInfoPictureWithTextOverlayWidget per
card, 100 per page, and tore them down on every search, resize and scroll
page. It also looked each card up again by name from the model row, kept a
shadow QList<ExactCard>, and carried paging state plus a 5 s timer that only
existed to defer the first page.
Replace that with a real view on a flattened model:
- CardDatabaseDisplayModel gains CardInfoRole and cardInfoAt(), so a view can
get the CardInfoPtr behind a proxy row without a name round trip.
- New CardDatabaseVisualDisplayModel (libcockatrice_models) flattens the
filtered and sorted proxy into one entry per displayed card picture: the
preferred printing normally, every printing of the selected sets while set
filters are active. Proxy changes rebuild it on a 30 ms coalescing timer.
- New CardDatabaseVisualView (QListView IconMode) paints only visible rows
through the new CardPictureDelegate, keeps the old interaction contract
(left/right/middle press -> cardClicked, hover -> cardHovered, Ctrl+wheel
resize, no selection, no focus stealing, scroll to top on a new result) and
repaints when CardPictureLoader reports an async image.
- CardSizeWidget::enableCtrlScrollResize() also watches scroll-area viewports,
which is what makes Ctrl+wheel work over a QListView.
- VisualDatabaseDisplayWidget drops the paging, the shadow list, the dead
members and the 5 s timer.
Behaviour-preserving: no gameplay change, no new user-facing strings.
* [VisualDatabase] Fix the card grid wheel step
QListView hands the delegate's item height to the scroll bar as its single step under ScrollPerPixel, so QAbstractSlider moved wheelScrollLines() whole card rows per notch. Handle the wheel in the view instead: one line is an eighth of the grid cell, so a notch moves about a third of a card row at any card size and still follows the OS line count.
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
The server list used hand-numbered saveName%1/server%1 keys and an O(n)
shift loop to delete entries. Store the profiles with beginReadArray and
beginWriteArray instead, and migrate the legacy keys once in the
constructor so existing profiles and saved passwords survive.
SettingsManager::batchWrite no longer disables atomic sync, so a batch is
still written through QSettings' atomic save. Add tests for add, update,
remove, password updates and the legacy migration.
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
Qt6::Test was linked into every test but never used. Add a small RAII
QAbstractItemModelTester helper and apply it to the deck list model, the card
database model and display proxy, and the sets model. Add QSignalSpy coverage
for the card-added and download-settings signals, and assert explicitly that a
card row is a leaf with no child columns (the tester does not check that).
The contract checker immediately found four model bugs, fixed here:
- DeckListModel allowed children below non-zero columns.
- CardDatabaseModel reported every card as a child of every card row.
- CardDatabaseModel reported six columns for a valid (leaf) card index.
- CardDatabaseDisplayModel claimed more rows were fetchable while rowCount
already exposed them all, so fetchMore() inserted duplicates.
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
checkUserPassword ignored force_password_change on the challenge-response
path, so a reset password stayed valid. Returning PasswordChangeRequired
was not enough on its own: the session was dropped and the client told the
user to ask the administrator for the temporary password they had just used.
The pending session now keeps its identity without registering a user
session or announcing a join, may only ping and change its password, and
the client completes the change in-session before reconnecting.
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* [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>
* [Installer] Request quit for hidden Cockatrice instances and record holders in abort telemetry
* [Installer] Fix compiler warnings in holder probe and installer quit handler
* [VisualDeckStorage] Order the includes the way clang-format wants them
* [Installer] Ask a hidden instance to close, then offer to end it once
The previous attempt had the client answer a registered window message, which
cannot work for the update it was reported on: the instance being replaced is a
client from before that message exists, so it never answers, and the user is
left with "Cockatrice is still running" exactly as reported. Every step that can
free the files has to be the installer's, so the client side is reverted.
The probe now posts WM_CLOSE to the instance's own window whether that window is
on screen or only in the notification area: the request does not need a visible
window, an instance in the tray is the one this update has no other way to
reach, and the confirmation Cockatrice answers with is a window Qt shows itself,
so it is painted - unlike one restored from outside. A visible window is brought
forward first, and Qt's bookkeeping windows stay excluded, because a close to the
notification-area one terminates the instance without its confirmation.
A question of the instance's own no longer suppresses the close. Withholding it
protected nobody: the installer asked the user to close a program it had never
asked, and a current client refuses the close while its confirmation is pending
anyway, leaving the question where it is. The launcher mode keeps the
visible-only rule, because a launcher without a window has already answered its
own close and the wait ends it after a grace instead of asking a user who is not
there.
The guard asked the same question until the user gave up: after the automatic
close failed it offered to end the programs, then re-ran the probe and showed
the same dialog word for word when the answer was No, which is how an abort was
reported three times over. It now asks once - Yes ends the Cockatrice, Oracle
and Servatrice programs the payload installs and says what that costs, No
cancels - and what survives an end is named in the abort instead of being put to
the user as the same question again. The uninstaller's guard and the recovery
path after a failed purge follow the same shape.
Kept: the abort telemetry that records the holders, the close result and the end
result as event data.
---------
Co-authored-by: sentry[bot] <39604003+sentry[bot]@users.noreply.github.com>
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* [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>
* [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>
* 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>