Compare commits

...

404 Commits

Author SHA1 Message Date
Danny Piper
7679546e30 Add Nix shell (#5362) 2024-12-28 23:52:57 +00:00
Zach H
45b11dc984 Add password reset button label (#5367) 2024-12-28 23:52:14 +00:00
RickyRister
25d21a3da6 refactor: remove ReleaseChannel keeping track of its own indexes (#5366) 2024-12-28 23:51:37 +00:00
Zach H
c8d49b5bf9 Support macOS-15 Builds (#5364) 2024-12-28 23:09:01 +00:00
RickyRister
f737d9a794 fix bug with release channel setting not being remembered (#5365) 2024-12-28 23:08:07 +00:00
RickyRister
df9c5ae53c Check for client updates on startup (#5359) 2024-12-28 21:29:59 +00:00
transifex-integration[bot]
e0829a75d2 Translate cockatrice/cockatrice_en@source.ts in it (#5363)
100% translated source file: 'cockatrice/cockatrice_en@source.ts'
on 'it'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-12-28 21:01:34 +00:00
Zach H
1f58f7e93d Support Mod/Admin Notes Section (#5361) 2024-12-28 18:05:49 +00:00
transifex-integration[bot]
14807ba036 Translate oracle/oracle_en@source.ts in pl (#5360)
100% translated source file: 'oracle/oracle_en@source.ts'
on 'pl'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-12-28 15:31:16 +00:00
transifex-integration[bot]
75fb3894a6 Translate oracle/oracle_en@source.ts in pt_BR (#5358)
100% translated source file: 'oracle/oracle_en@source.ts'
on 'pt_BR'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-12-28 12:08:58 +00:00
github-actions[bot]
18119bd11b Update translation source strings (#5357)
Co-authored-by: github-actions <github-actions@github.com>
2024-12-28 06:33:57 +00:00
RickyRister
4c7796537f Support folder download in deck storage tab (#5356)
* refactor

* support folder download
2024-12-28 06:31:18 +00:00
RickyRister
3452cb01d0 fix replay download not working if replay folder is empty (#5355)
* fix downloading single replays

* fix downloading replay folder
2024-12-28 06:15:25 +00:00
RickyRister
6a151ef97a Add button to open decks folder (#5354) 2024-12-28 05:09:46 +00:00
RickyRister
e3d651668c Add button to open replays folder (#5352) 2024-12-28 05:06:26 +00:00
Zach H
7a5704beaa Support Moderator/Admin force activating users (#5353) 2024-12-28 05:01:31 +00:00
RickyRister
37b78a9a4c change action's text to "unconcede" when player is conceded (#5351) 2024-12-28 00:01:36 +00:00
Zach H
8bc5a9d581 Merge pull request #5350 from Cockatrice/fix_1953
Allow Moderators/Admins to Grant Replay Access
2024-12-27 18:51:11 -05:00
ZeldaZach
57ed162b79 Fix Linter 2024-12-27 18:35:52 -05:00
ZeldaZach
3524231500 Allow Moderators/Admins to Grant Replay Access
- Only to themselves, at this time
- Automatically refreshes feed, no need to re-login
2024-12-27 18:32:39 -05:00
Zach H
5cfe2b4762 Merge pull request #5348 from Cockatrice/set_owner
Establish Card Ownership & Return on Player Leave
2024-12-27 18:26:45 -05:00
ZeldaZach
a8bac1e468 Return Tagged Cards to Owner, if possible, on concede/leave 2024-12-27 18:23:39 -05:00
ZeldaZach
4f798286af Establish Card Ownership Tag 2024-12-27 18:23:09 -05:00
transifex-integration[bot]
8a04b2d69d Translate cockatrice/cockatrice_en@source.ts in de (#5349)
100% translated source file: 'cockatrice/cockatrice_en@source.ts'
on 'de'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-12-27 22:26:03 +00:00
transifex-integration[bot]
17893d9747 Translate oracle/oracle_en@source.ts in it (#5347)
100% translated source file: 'oracle/oracle_en@source.ts'
on 'it'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-12-27 20:13:57 +00:00
ZeldaZach
8af49406cd Un-translate 'ms' 2024-12-27 14:18:44 -05:00
transifex-integration[bot]
3b068b79fe Translate webclient/src/i18n-default.json in fr (#5346)
100% translated source file: 'webclient/src/i18n-default.json'
on 'fr'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-12-27 08:36:06 +00:00
transifex-integration[bot]
ce14e83e78 Translate webclient/src/i18n-default.json in es (#5345)
100% translated source file: 'webclient/src/i18n-default.json'
on 'es'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-12-27 08:35:58 +00:00
transifex-integration[bot]
f213d6fda7 Translate cockatrice_en@source.ts in en_US (#5344)
100% translated source file: 'cockatrice_en@source.ts'
on 'en_US'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-12-27 08:35:48 +00:00
RickyRister
83db00d7a3 reduce vertical spacing in PrintingSelector (#5342) 2024-12-27 08:35:38 +00:00
ZeldaZach
7e9bd88eb4 Fix Shutdown Server 2024-12-27 01:38:21 -05:00
github-actions[bot]
ea716ca440 Update translation source strings (#5343)
Co-authored-by: github-actions <github-actions@github.com>
2024-12-27 06:19:12 +00:00
ZeldaZach
3cd7a04002 Change Settings from Min to Base Size 2024-12-26 23:54:24 -05:00
RickyRister
914002f846 use grid instead of FlowWidget for PrintingSelector display options (#5341)
* use grid instead of FlowWidget for PrintingSelector display options

* remove one level of nesting
2024-12-27 02:08:07 +00:00
Zach H
17b82a186f Add QSet for faster lookups in CardDatabase (#5332) 2024-12-26 19:52:18 -05:00
RickyRister
7a8e957476 allow playing cards directly transformed from hand (#5339) 2024-12-26 19:51:58 -05:00
Zach H
6dfd354973 Support starting games with fewer than max players (#5338) 2024-12-26 18:32:20 -05:00
RickyRister
956c12eb32 remove shortcut workaround; always add card menu to player (#5337)
* remove workaround

* make aCardMenu less innocuous

* make card menus active for all players
2024-12-26 16:49:17 -05:00
RickyRister
d5ae4eed26 Ctrl drag now adds/removes to selection (#5336)
* refactor: clean up to use for-each loop

* track cards in rect so far and toggle isSelected on change

* only clear selection if ctrl isn't held

* fix build errors
2024-12-26 15:08:20 -05:00
RickyRister
ca486e5ed9 Don't display unusable actions in opponent's card menus (#5335) 2024-12-26 10:23:54 -05:00
RickyRister
de63066b0b fix deck storage open deck not working at all when folder is in selection (#5333) 2024-12-26 00:25:30 -05:00
Zach H
c7ca55ceb5 Support Picking Select Art per Card Basis (#5329) 2024-12-25 23:12:06 -05:00
RickyRister
024bef7ded add local rename button to replays tab (#5331) 2024-12-25 22:34:24 -05:00
RickyRister
34d3d60f95 fix text missing from chat macro list's buttons when hidden (#5330)
* fix text missing from chat macro list's buttons when hidden

* turns out you don't need to set tooltip if you already set text
2024-12-25 22:32:53 -05:00
RickyRister
ed907d7c6f Support downloading replay folders (#5325)
* rename old get replay match method to get enclosing

* creat raw getReplayMatch method

* implement thing
2024-12-25 07:33:36 -05:00
RickyRister
9d7fd66546 fix text missing from download url list's buttons when hidden (#5326) 2024-12-25 07:29:55 -05:00
RickyRister
9934841950 make better use of space in download url settings window (#5327) 2024-12-25 07:29:27 -05:00
RickyRister
432fe1100b gitignore all cmake-build folders (#5328) 2024-12-25 07:28:41 -05:00
Zach H
d987628935 Reorder String options for Filtering (#5324) 2024-12-25 00:58:59 -05:00
RickyRister
4c3ceae0e4 open replays on double-click in replays tab (#5323) 2024-12-25 00:34:43 -05:00
RickyRister
2b9d7538bf open decks on double-click in deck storage tab (#5322) 2024-12-25 00:33:48 -05:00
RickyRister
4ca1fc083d add "open recent" menu option to deck editor tab (#5319)
* add "open recent" menu option to deck editor tab

* change texts

* also get it to work with loading from deck storage tab

* add error message when fail to open

* only update recents on successful open

* only update recents on successful open

* reword to "Clear"
2024-12-24 19:55:04 -05:00
BruebachL
e7585271fb The printingSelector should set the deckEditor modified flag on adding/removing cards. (#5321)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2024-12-24 10:28:58 -05:00
RickyRister
6e6824117d add new folder button to local deck storage tab (#5318)
* add new folder button to local deck storage tab

* allow delete button to delete folders
2024-12-24 00:26:11 -05:00
RickyRister
3e5f2fd8b2 add new folder button to game replays tab (#5317) 2024-12-24 00:23:13 -05:00
RickyRister
6e470d788e Support multi-select for remote decks in deck storage tab (#5315)
* enable multiselection

* support multi open deck

* support multi download

* support multi delete
2024-12-24 00:05:49 -05:00
RickyRister
a40d8092ce support multi-select for local decks in deck storage tab (#5314)
* allow multi-select

* support multi upload

* support multi open deck

* support multi delete deck
2024-12-23 20:41:15 -05:00
RickyRister
0234a70bfd fix bug with uploading unnamed decks ignoring the prompt (#5313) 2024-12-23 20:39:57 -05:00
RickyRister
705b1e0c2b support multi-select for remote replays in game replays tab (#5310) 2024-12-23 20:38:47 -05:00
RickyRister
69379334f9 support multi-select for local replay tab (#5309) 2024-12-23 20:31:58 -05:00
transifex-integration[bot]
12e50a1f2f Translate cockatrice_en@source.ts in en_US (#5308)
100% translated source file: 'cockatrice_en@source.ts'
on 'en_US'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-12-23 20:30:17 -05:00
RickyRister
ec17a477be shortcut search now displays all rows in section (#5307) 2024-12-23 20:29:52 -05:00
github-actions[bot]
205e1c7a59 Update translation source strings (#5305)
Co-authored-by: github-actions <github-actions@github.com>
2024-12-22 18:35:34 -05:00
transifex-integration[bot]
ffb60c06cb Translate oracle_en@source.ts in en@pirate [Manual Sync] (#5295)
4% of minimum 3% 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

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-12-22 18:01:02 -05:00
transifex-integration[bot]
2280f59ee6 Translate i18n-default.json in nl [Manual Sync] (#5297)
19% of minimum 3% translated source file: 'i18n-default.json'
on 'nl'.

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>
2024-12-22 18:00:44 -05:00
transifex-integration[bot]
0d4dd63edc Translate i18n-default.json in es [Manual Sync] (#5299)
99% of minimum 3% translated source file: 'i18n-default.json'
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>
2024-12-22 18:00:39 -05:00
transifex-integration[bot]
69f1f4c1a5 Translate i18n-default.json in fi [Manual Sync] (#5301)
13% of minimum 3% translated source file: 'i18n-default.json'
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>
2024-12-22 18:00:35 -05:00
transifex-integration[bot]
d930d9c237 Updates for project Cockatrice and language tr (#5296)
* Translate oracle_en@source.ts in tr [Manual Sync]

36% of minimum 3% translated source file: 'oracle_en@source.ts'
on 'tr'.

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 tr [Manual Sync]

6% of minimum 3% translated source file: 'cockatrice_en@source.ts'
on 'tr'.

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>
2024-12-22 18:00:31 -05:00
transifex-integration[bot]
9c782d130f Translate i18n-default.json in pt_BR [Manual Sync] (#5298)
100% translated source file: 'i18n-default.json'
on 'pt_BR'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-12-22 18:00:13 -05:00
transifex-integration[bot]
f12053f39d Translate i18n-default.json in de [Manual Sync] (#5300)
100% translated source file: 'i18n-default.json'
on 'de'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-12-22 18:00:05 -05:00
transifex-integration[bot]
bcf6ca4f87 Translate i18n-default.json in fr [Manual Sync] (#5302)
99% of minimum 3% translated source file: 'i18n-default.json'
on 'fr'.

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>
2024-12-22 17:59:55 -05:00
transifex-integration[bot]
46619bb425 Translate i18n-default.json in ru [Manual Sync] (#5303)
14% of minimum 3% translated source file: 'i18n-default.json'
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>
2024-12-22 17:59:46 -05:00
transifex-integration[bot]
cdd870a129 Translate i18n-default.json in en_US [Manual Sync] (#5304)
100% translated source file: 'i18n-default.json'
on 'en_US'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-12-22 17:59:35 -05:00
transifex-integration[bot]
7a1b7b9438 Updates for project Cockatrice and language it (#5294)
* Translate cockatrice_en@source.ts in it [Manual Sync]

99% of minimum 3% 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

* Translate i18n-default.json in it [Manual Sync]

100% translated source file: 'i18n-default.json'
on 'it'.

---------

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-12-22 17:59:24 -05:00
transifex-integration[bot]
2183ada1f2 Translate oracle_en@source.ts in cs [Manual Sync] (#5293)
3% of minimum 3% translated source file: 'oracle_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>
2024-12-22 17:58:35 -05:00
Zach H
1d9e64ec73 Fix settings dialog tr (#5292) 2024-12-22 17:39:43 -05:00
Zach H
5339be318e Fix "ghosting" of cards sticking on invalid moves (#5289) 2024-12-22 17:35:44 -05:00
Zach H
e1ba39c437 Fix multiple "Selected Cards" in Menu on MacOS (#5288) 2024-12-22 04:33:09 +00:00
BruebachL
07ee271478 Refactor codebase to new Qt Slot/Signal syntax - Pt1 (#5202)
---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
Co-authored-by: ZeldaZach <zahalpern+github@gmail.com>
2024-12-22 02:01:17 +00:00
RickyRister
4823cce622 Show conflicting shortcut in error message (#5287) 2024-12-22 01:58:55 +00:00
Zach H
23099f7e8b Fix token name highlight on open (#5286) 2024-12-22 01:43:00 +00:00
RickyRister
5bdbd51fa8 implement search bar in shortcuts menu (#5285)
* implement search bar in shortcuts menu

* remove unneeded imports

* use expandAll
2024-12-22 00:21:53 +00:00
BruebachL
a0e5871c6e Fix the image shrinking due to repeated scaling and FP precision loss. (#5284)
* Fix the image shrinking due to repeated scaling and FP precision loss.

* Add a setting for auto-rotating sideways layout cards.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2024-12-21 22:47:43 +00:00
RickyRister
3cf0904651 add action to select all cards in table row (#5280) 2024-12-21 18:52:19 +00:00
Zach H
2bd06ff0fd Add CrashDump support for Windows OS (#5282) 2024-12-21 18:52:07 +00:00
RickyRister
6ea333d0f1 move SearchLineEdit into custom_line_edit file (#5281) 2024-12-21 05:12:14 +00:00
Zach H
91d2485940 Update PegLib, Fix Database Searching CFG (#5244)
* Support C++20 Standard

* Update peglib.h

* Fix lambdas

* Move from for loops to std::any/all_of

* Support fixed CFG

* Fix Rarity Search to be more accurate
2024-12-21 03:37:08 +00:00
RickyRister
0d99b2bcf4 make unattach shortcut always active (#5278) 2024-12-20 05:56:48 +00:00
RickyRister
a54a424f84 add action to select all cards in column (#5277)
* add action to select all cards in column

* change default shortcut to Ctrl+Shift+C
2024-12-20 03:39:17 +00:00
RickyRister
3514699f5b check that target card is in play before attaching (#5275) 2024-12-19 23:55:04 +00:00
RickyRister
d196988cab allow attached cards to be moved to other zones (#5276) 2024-12-19 23:53:48 +00:00
BruebachL
03aff83135 Add the ability to define starting life total during game creation. (#5174)
* Have the server respect gameType info when setting up zones.

* ServerPlayer::setupZones is now passed the room->getGameTypes();
* ServerPlayer::setupZones now checks if the GameType String includes "Commander" and then sets the life total to 40 instead.

* Formatting.

* Remove debug logging imports.

* Move game option value declarations to dlg_create_game.

* Lint.

* Fix mocks.

* Add a default for backwards compatibility.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2024-12-19 23:52:47 +00:00
RickyRister
17e6bfaca6 fix bug with multi-attach sometimes only attaching one card (#5272) 2024-12-19 13:38:57 +00:00
Zach H
90281262be Revert "Revert "Rotate split cards (#5264)" (#5269)" (#5273)
This reverts commit d41aa30e10.
2024-12-19 13:25:54 +00:00
RickyRister
5bbc118920 fix bug introduced in #5267 (#5270)
* fix bug introduced in #5267

* remove default args to prevent similar bugs in the future

* add newInstance overload with default properties
2024-12-19 13:17:09 +00:00
ZeldaZach
dde2f8b9ad Bump Win Qt6 6.5.3->6.6.*
- Fix #4968
2024-12-19 01:09:04 -05:00
Zach H
d41aa30e10 Revert "Rotate split cards (#5264)" (#5269)
This reverts commit a5c509981b.
2024-12-19 06:03:48 +00:00
RickyRister
231d0380a7 reword "open deck in new tab" setting (#5268) 2024-12-19 05:52:40 +00:00
BruebachL
a5c509981b Rotate split cards (#5264) 2024-12-19 04:13:45 +00:00
Zach H
71b01e6110 ADD landscapeOrientation field (#5267) 2024-12-19 03:52:34 +00:00
RickyRister
c716f85962 refactor: remove cipt param from Player::playCard (#5266) 2024-12-19 02:46:14 +00:00
BruebachL
245d51caea New printing selector (#5182)
* Squashed Commits

Lint things.

Set focus back to deckView after selecting a card to enable keyboard navigation.

Bump scrollbar to top when selecting a new card.

Update card info on hover.

Layout cleanups

Add +- to buttons.

Merge buttons into card picture.

Cleanup size, min 2 cards by default in rows

Support layout settings config and set min to 525 so two cols are visible by default for printings, when opened

Move Printing Selector to before Deck, and visible true

Null safety for setCard.

Turn down the dropshadow a little.

Make PrintingSelector dockable, don't duplicate sets when bumping them to the front of the list.

When swapping cards between mainboard and sideboard, use preferred printing if no uuid is available (i.e. null).

Reorder includes...

Unwonk an include.

Give the card widget a snazzy drop shadow, appease the linter gods.

Handle jumping between segments

Remember scale factor when initializing new widgets.

Cleanup

Select Card works (Not M->SB tho)

Resize word-wrapped label properly.

Fix the layouting, mostly.

remove tx

Build Fix

Squashed Commits

Load and store redirects properly.

Layouting is fun :)

* Group PrintingSelectorCardDisplayWidgets into distinct containers for alignment purposes.

Override resizeEvent() properly.

Word wrap properly.

Keep widget sizes uniform for aesthetic reasons (grid pattern).

Label stuff, center card picture widget, allow cardSizeSlider to scale down.

Replace cards which have no uuid in the decklist when first selecting a printing.

Add buttons for previous and next card in DeckList.

Add a card size slider.

Move sort options initialization to implementation file.

Explicitly nullptr the parent for the PrintingSelector.

Address PR comments (minor cleanups).

Hook up to the rows removed signal to update card counts properly.

Include QDebug.

Add labels to the mainboard/sideboard button boxes.

Implement a search bar.

Expand node recursively when we add a new card.

Only create image widgets for the printing selector if it is visible in order to not slow down image loading.

Minor Tweaks

Invert decklist export logic to write out setName, collectorNumber, providerId value if it is NOT empty.

Linting.

Update CardCounts properly, update PrintingSelector on Database selection.

Initialize sideboard card count label properly.

Split mainboard/sideboard display and increment/decrement buttons.

Add button to sort all sortOptions by ascending or descending order.

Add option to sort by release date in ascending or descending order.

Add PrintingSelector to database view.

Display placeholder image before loading.

Fix deckEditor crash on mainboard/sideboard swap by correcting column index to providerId instead of shortName.

Include currentZoneName, fix the column when updating from DeckView indexChanged to be UUID and not setShortName so cards are properly fetched again.

The most minor linter change you've ever seen.

Null checks are important.

Linter again.

Linter and refactor to providerId.

Sort properly, (We don't need a map, we need a list, a map won't be ordered right [i.e. 1, 10, 11, 2, 3, 4, ..., 9])

Sort alphabetically or by preference.

Hook printingSelector up to the CardInfoFrameWidget.

Allow info from CardFrame to be retrieved, properly initialize PrintingSelector again.

Refactors to reflect CardInfoPicture becoming CardInfoPictureWidget.

Make PrintingSelector re-usable by introducing setCard().

Make PrintingSelector use the CardFrame, not the database index.

Add a new selector widget for printings.

* Support multiple <set> tags per card within the database

This will allow us to show off all different printings for cards that might appear multiple times in a set (alt arts, Secret Lairs, etc.)

* Support Flip Cards with related art

* Minor Cleanup

* Minor Cleanup

* Release Date DESC default

* Load widgets in batches.

* Refactor local batch variables to be class variables/defines.

* Clear timer on updateDisplay.

* Fix Timer & Builds on Qt5

* Not Override

* Yes Override

* Yes Override

* Lint

* Can't override in function definition.

* Resize setName to picture width on initialization.

Also add a new signal to card_info_picture_widget to emit when the scale factor changes.

Hook this up to the setName resizing method to ensure card size updates trigger it appropriately after initialization.

Clean up unused enter and resize methods that just delegated to base-class.

* Add ability to force preferred set art to be loaded for every card.

* Show related cards from printing selector by right-clicking card image.

* fix build

* Fix UST cards

* Inc QDebug

* Fix Qt5 Builds

* Fix Qt5 Builds

* Fix Qt5 Builds

* Fix Qt5 Builds

* Fix Qt5 Builds

* Fix cards being able to jump between side and mainboard

* Don't hide PrintingSelector button widgets if the deck contains a card from the set.

* Update PrintingSelector properly on DeckListModel::dataChanged

* Add option to disable bumping sets to the front of the list if the deck contains cards from the set.

* Linter behave.

* Linter behave.

* Fix mocks.

* Fix cards without providerIds being counted for all cards.

* Flip preference sort so descending means "Most to least preferred".

* Set the index correctly when removing a non-providerId printing for a providerId printing to avoid jumping to the next card.

* Move the "Next/Previous" card buttons to their own widget.

* Move the card size slider to its own widget.

* Lint the makelist.

* Linter

* Crash fix

* Move the sorting options to their own widget.

* Move the search bar to its own widget.

* Minor cleanup

* Minor cleanup

* Minor cleanup

* Only overwrite card in deck if UUID _and_ Number missing

* Adjust font size when adjusting card size.

* Clean up some imports.

* Pivot to a view options toolbar.

* Persist sort options and change default to 'preference'.

* Lint.

* Remember how many cards were originally in deck when replacing with uuid version.

* Relabel buttons for clarity.

* Fix tests.

* Fix tests properly.

* Fix dbconverter mock.

* Try to wrangle font sizes.

* Update mainboard/sideboard labels correctly.

* Initialize button sizes correctly.

* Label texts are supposed to be white.

* Adjust another deckModel->findCard call to include number parameter.

* Style buttons again.

* Negative currentSize means we don't render the widget yet, return a default value.

* Clean up debug statements.

* Boop the mainboard/sideboard label and the cardCount after a little bit of delay to make sure they initialize at the right size.

* Persist card size slider selection in SettingsCache.

* Good Lint Inc.

* updateCardCount to get white color in initializer

* Make the view display options functional.

* Comment ALL the things.

* Lint the things.

* Brief accidentally nuked some constants.

* Proper Qt slot for checkboxes.

* Don't use timers, Qt provides ShowEvent for anything necessary before the widget is shown.

* Cleanup from Reading

* Cleanup Lints

* Minor

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
Co-authored-by: Zach Halpern <zahalpern+github@gmail.com>
2024-12-19 02:40:34 +00:00
RickyRister
e588917f6c don't snap already-expanded cardview windows (#5265) 2024-12-19 02:27:40 +00:00
RickyRister
27e5d21b6b fix bug with scrollbar resizing (#5263) 2024-12-18 05:16:47 +00:00
RickyRister
b894b75e6a add clone action to card menu in all zones that it functions in (#5259) 2024-12-18 04:48:06 +00:00
RickyRister
116397cdb3 add option to auto-play "put top card on stack until" hits (#5258)
* rename variables

* implement feature

* readd null check
2024-12-18 04:47:49 +00:00
RickyRister
a6b5abf271 clicking to play can now play all selected (#5254)
* play action now applies to all selected cards

* check card zone before applying action

* fix bug with wonky play from deck

* refactor

* don't play card if it's already on table

* add new setting

* make actPlay and friends public

* implement thing

* refactor card_item
2024-12-18 04:43:17 +00:00
RickyRister
fd5a649246 fix clone shortcut not working on opponent's cards (#5251)
* implement fix

* fix nullptr bug

* also add the selectAll action to the always active shortcuts
2024-12-18 04:41:12 +00:00
RickyRister
e8e57989ba Reload card database action now also reloads the download urls (#5262)
* add sync method to SettingsManager

* sync download urls on reload card database
2024-12-18 04:39:58 +00:00
RickyRister
03db4ccce6 ability to directly attach from other zones (#5250)
* add attach and draw arrow actions to more card menus

* implement attaching from other zones

* disallow attaching from deck

* do nothing if target is already attached

* add null check
2024-12-18 04:38:22 +00:00
RickyRister
c9d5d5609c Double click to untap works when multi-zone select (#5253) 2024-12-17 03:58:25 +00:00
RickyRister
ac16206ddb Add action to select all cards in zone (#5246)
* rip shortcut for aDrawArrow

* implement thing

* add separator below hide

* shorten text by 1 word

* move shortcut to under Playing_Area

* rebind draw arrow shortcut to Alt+A

* remove auto hotkey

* shorten to "Select All"

* add back auto-hotkey
2024-12-16 03:55:47 +00:00
RickyRister
5f8bcbd02d Add keyboard shortcut for "hide" action (#5248)
* implement hide shortcut

* remove parens
2024-12-15 20:34:33 +00:00
RickyRister
a0f74134bb make card view window max initial height configurable (#5236) 2024-12-14 03:02:00 +00:00
Zach H
0463a6fd70 Support Windows Debug Builds (#5242) 2024-12-13 21:58:46 +00:00
RickyRister
a5de633c64 warn if "play top card until" filter expression doesn't match any card in database (#5243)
* make FilterString::check const

* implement thing
2024-12-13 21:58:29 +00:00
RickyRister
b2ad2acff3 improve FilterString validation error message (#5240) 2024-12-13 06:26:58 +00:00
dependabot[bot]
4ee6ff73e0 Bump nanoid from 3.3.4 to 3.3.8 in /webclient (#5239)
Bumps [nanoid](https://github.com/ai/nanoid) from 3.3.4 to 3.3.8.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/3.3.4...3.3.8)

---
updated-dependencies:
- dependency-name: nanoid
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-13 04:45:05 +00:00
RickyRister
628bdde939 hide action now applies to all selected cards (#5233)
* hide action now applies to all selected cards

* check card zone before applying action

so that we don't nuke cards from existence when we select across multiple zones

* small fixes

* remove redundant loop

* nullcheck view
2024-12-13 04:42:53 +00:00
RickyRister
e9b78c1c59 "Play top cards until" action now has option for number of hits (#5229)
* create new dlg window

* get thing to work

* move validation into dlg

* remove nodiscard

I'll revert this if someone else complains
2024-12-12 14:03:42 +01:00
RickyRister
315cbc0925 fix remaining wonkiness with rewind buffering in replays (#5235) 2024-12-11 10:54:36 +01:00
ebbit1q
69741d858c fix crash using uninitialised memory from #5228 (#5237) 2024-12-11 01:19:53 +00:00
tooomm
20d99a78b6 pretty print translation files (#5234) 2024-12-09 23:46:57 +00:00
RickyRister
2d68393e07 dynamically resize scrollbar when zone view window is resized (#5228) 2024-12-09 19:01:37 +00:00
RickyRister
8cb1470643 add option to show keyboard shortcuts in right click menu (#5225) 2024-12-09 18:58:37 +00:00
dependabot[bot]
8d9b27bf47 Bump path-to-regexp and express in /webclient (#5226)
Bumps [path-to-regexp](https://github.com/pillarjs/path-to-regexp) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together.

Updates `path-to-regexp` from 0.1.10 to 0.1.12
- [Release notes](https://github.com/pillarjs/path-to-regexp/releases)
- [Changelog](https://github.com/pillarjs/path-to-regexp/blob/master/History.md)
- [Commits](https://github.com/pillarjs/path-to-regexp/compare/v0.1.10...v0.1.12)

Updates `express` from 4.21.0 to 4.21.2
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.2/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.21.0...4.21.2)

---
updated-dependencies:
- dependency-name: path-to-regexp
  dependency-type: indirect
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-09 18:54:47 +00:00
transifex-integration[bot]
0c5d9f1a7d Translate webclient/src/i18n-default.json in de (#5222)
100% translated source file: 'webclient/src/i18n-default.json'
on 'de'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-12-09 18:54:32 +00:00
transifex-integration[bot]
a7d88c06c1 Translate webclient/src/i18n-default.json in it (#5223)
100% translated source file: 'webclient/src/i18n-default.json'
on 'it'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-12-09 18:54:24 +00:00
transifex-integration[bot]
2735000fcf Translate webclient/src/i18n-default.json in pt_BR (#5224)
100% translated source file: 'webclient/src/i18n-default.json'
on 'pt_BR'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-12-09 18:54:15 +00:00
RickyRister
a39de270cd make rewind buffering timeout configurable (#5227)
* update settingsCache

* implement thing

* add new setting to window

* rename setting

* make it compile on qt5

* fix typo

* somehow changing the order here fixes a bug?

The loaded value was getting clamped to 99
2024-12-09 02:25:10 +00:00
tooomm
10f11213d3 fix indentation again (#5230) 2024-12-09 01:23:45 +00:00
tooomm
3b49cbf73b Rename label (#5232) 2024-12-09 01:23:27 +00:00
Zach H
e4cfe08113 Address weird crash case (#5221)
* Address weird crash case

* Address weird crash case

* remove const
2024-12-05 12:18:41 +01:00
transifex-integration[bot]
fa02cb885c Updates for project Cockatrice and language nl (#5220)
* Translate cockatrice/cockatrice_en@source.ts in nl

100% translated source file: 'cockatrice/cockatrice_en@source.ts'
on 'nl'.

* Translate oracle/oracle_en@source.ts in nl

100% translated source file: 'oracle/oracle_en@source.ts'
on 'nl'.

---------

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-12-03 16:17:54 +01:00
transifex-integration[bot]
69b864fa02 Translate cockatrice_en@source.ts in en_US (#5217)
100% translated source file: 'cockatrice_en@source.ts'
on 'en_US'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-12-01 19:59:19 -05:00
J. Cameron McDonald
b9ed9a6c0b feat: set prioritization by set type (#5097)
* feat: prefer 'Core' and 'Expansion' sets for prioritization

* rework set prioritization

* clean up priority enum

* formatting

* revert changes to CockatriceXml3Parser

* re-add missing null check

* remove priority fallback ternary from CardSet model

* make defaultSort logic easier to follow

* revert changes to v3 card database xsd

* remove unused invisible priority col from sets dialog

* move draft innovation and duel deck sets to secondary prio

* minor fixes

* change PriorityFallback to 1

* make priority optional in xml

* remove PriorityUndefined and set PriorityFallback to 0

* set priority when not found to PriorityOther

in case a new set type is added it's unlikey we want it sorted first,
it'll probably be a new product so it's probably best to sort it with
the funny things

* simplify sort function

---------

Co-authored-by: tooomm <tooomm@users.noreply.github.com>
Co-authored-by: ebbit1q <ebbit1q@gmail.com>
2024-12-01 19:59:00 -05:00
RickyRister
5156495b47 add more sort options (#5214)
* distinguish between groupBy and sortBy options

* add more sort options
2024-11-30 22:32:39 -05:00
RickyRister
b92047bc3f rename and refactor some stuff in ZoneViewWidget (#5213)
* fix QComboBox creation order in retranslateUi

* move bottom row creation closer to where it's used

* rename QGraphicsLinearLayout variables

hFilterbox and hPilebox don't make much sense now

* add comment about #5204
2024-11-30 18:54:55 -05:00
RickyRister
70559d32df fix bug where card view window with single card is too short (#5211)
It was a divide by 0 bug lol
2024-11-30 08:53:30 -05:00
RickyRister
bb84b75db9 fix bug where card view window with pile view is too short (#5212) 2024-11-30 08:53:10 -05:00
transifex-integration[bot]
f634177973 Updates for project Cockatrice and language nl (#5170)
* Translate cockatrice/cockatrice_en@source.ts in nl

100% translated source file: 'cockatrice/cockatrice_en@source.ts'
on 'nl'.

* Translate cockatrice/cockatrice_en@source.ts in nl

100% translated source file: 'cockatrice/cockatrice_en@source.ts'
on 'nl'.

* Translate cockatrice/cockatrice_en@source.ts in nl

100% translated source file: 'cockatrice/cockatrice_en@source.ts'
on 'nl'.

---------

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-11-30 12:55:06 +01:00
Zach H
e33ff37c82 Pass QTime objects instead of references (#5209)
- References seem to go to 0 in newer Qt versions(?)

https://doc.qt.io/qt-6/qtime.html

> QTime objects should be passed by value rather than by reference to const; they simply package int.
2024-11-30 01:36:38 -05:00
RickyRister
d2bc7f6ac0 get retranslateUi to work with sort options (#5208) 2024-11-30 00:13:17 -05:00
RickyRister
5ef1ca06f5 store sort option in settings as QComboBox index instead of enum value (#5207)
* rename config property

* change default

* functional changes
2024-11-29 22:46:16 -05:00
Zach H
1d8651bc00 Fix Deck Popup Glitchy Rendering (#5204)
- QLabel sizes weren't taken into account until the widget is rendered
- Long QLabels can cause exacerbated issues
- Force refresh after 1ms to take QLabels into account
2024-11-29 12:53:19 -05:00
RickyRister
17eabf2004 add sort options to card view window (#5206)
* refactor to allow for sorting by property of choice

* implement thing

* prevent overlapping sort properties

* enable/disable pile view checkbox if groupBy is off

* fix compiler warnings

* check to disable pile view checkbox on init

* Fix builds on Qt5

* Fix builds on Qt5

---------

Co-authored-by: ZeldaZach <zahalpern+github@gmail.com>
2024-11-29 12:53:06 -05:00
RickyRister
37bb1367db refactor method for positioning cards in ZoneViewZone (#5203)
* refactor out method for positioning cards in zone view

* rename some variables

* use max/min

* some small formatting stuff
2024-11-28 14:59:31 -05:00
RickyRister
24b5dab456 leave some documentation on Zone classes (#5199)
* leave some documentation on Zone classes

* small refactor

* undo functional change from refactor and clean up comments

* move variables into if block
2024-11-28 14:40:49 -05:00
Zach H
c6bfc8b8ea Fix Qt5 Slot/Signals for QCheckBoxes (#5201) 2024-11-27 22:11:55 +00:00
RickyRister
f2b0fa164e add padding to right side of card reveal window (#5198) 2024-11-27 00:17:37 -05:00
RickyRister
0ca8bdb3a8 refactor CardList (#5197) 2024-11-27 00:15:35 -05:00
RickyRister
a8471f62bc clean up DownloadSettings (#5194)
* refactor DownloadSettings

* only reset to default on first run

* use c++ foreach

* use addItems

* move default urls to static const
2024-11-26 02:12:56 +00:00
tooomm
5f1c03682f macOS version fix + wording (#5189) 2024-11-26 00:05:09 +01:00
RickyRister
3255ed3ffb add menu option to reload card db (#5196) 2024-11-25 07:43:08 +00:00
RickyRister
c51b54c0c5 rename variables for url list layout (#5195) 2024-11-25 06:27:21 +00:00
RickyRister
a3f0807d47 fix error message (#5192)
`QObject::connect: No such slot UserInterfaceSettingsPage::setNotificationEnabled(Qt::CheckState) in /Users/Ricky/GitHub/Cockatrice/cockatrice/src/dialogs/dlg_settings.cpp:448`
2024-11-24 14:52:56 +00:00
RickyRister
27055944df skip tap animation when rewinding (#5168) 2024-11-23 10:40:37 -05:00
Zach H
7b1653034b Bump macos14 XCode to 15.4 (#5188) 2024-11-22 22:52:42 -05:00
dependabot[bot]
39d8ca050f Bump cross-spawn from 7.0.3 to 7.0.6 in /webclient (#5181)
Bumps [cross-spawn](https://github.com/moxystudio/node-cross-spawn) from 7.0.3 to 7.0.6.
- [Changelog](https://github.com/moxystudio/node-cross-spawn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/moxystudio/node-cross-spawn/compare/v7.0.3...v7.0.6)

---
updated-dependencies:
- dependency-name: cross-spawn
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-22 22:22:36 -05:00
Zach H
50274cb66d Change 'custom(VER)' to 'custom-VER' because Fedora mad (#5180) 2024-11-22 22:22:22 -05:00
RickyRister
bd60a9fd2e don't blink highlighted phase when backwards skipping in replays (#5185) 2024-11-22 22:21:54 -05:00
BruebachL
83409c32c4 Cache redirects properly by implementing our own QSettings cache for urls. (#5186)
* Cache redirects properly by implementing our own QSettings cache for urls.

* Load and store redirects properly.

* Set the maximum network cache size from settings value on PictureLoaderWorker instantiation.

* Address comments.

* Lint.

* Adjust debug statements to be in line with existing ones.

* Minor Tweaks

* Make redirect cache ttl a user-adjustable setting.

* Fix Build

* Minor Cleanup

* Minor Cleanup

* Build Fix

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
Co-authored-by: ZeldaZach <zahalpern+github@gmail.com>
2024-11-22 22:21:26 -05:00
RickyRister
1bc92623dc add "open in new tab" button to decklist confirmation dialogue (#5183)
* refactor to use confirmOpen

* implement extra button in confirmation

* use brackets in one-liner if statements

* refactor save confirmation window into function
2024-11-21 23:24:50 +01:00
BruebachL
f73196841a Multiple Printings per Deck (#5171)
* Refactor CardInfo Widgets to reside in their appropriate folder and to have a clearer naming structure.

* Added Zach's work on storing printing information in the DeckList (#1)

* Change CardInfo's PixmapCacheKey to be the UUID of the preferred set after database loading has finished. Otherwise, and if no UUID of a preferred set is available, default to the card name.

* Refactor CardDatabase *db global variable to singleton CardDatabaseManager.

This commit refactors the global variable CardDatabase *db into a singleton encapsulated by the DatabaseManager class, accessible via DatabaseManager::getInstance(). This change centralizes access to the database instance, improving code modularity and encapsulation, resolving dependencies on main.h for code that requires access to the database instance.

- Added DatabaseManager class with getInstance() method returning a pointer to the singleton CardDatabase.
- Removed global db variable and updated references across the codebase.
 - Thread-safe static initialization for the singleton.

Impact: This refactor should have no functional impact on the application, as it maintains the same interface for accessing the CardDatabase instance. However, the codebase now benefits from improved encapsulation, lifetime management, and thread-safety.

* fixed db issue an renamed sets to set in picture loader

* canibalized zach work and added it to the decklist builder

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>

* Reintroduce some changes lost in the merge.

* Introduce UUID attribute to abstract_card_item, card_item, deck_view_card, server_card and serverinfo_card.

* Have various game events respect the new UUID attribute on instantiation.

* Correct some calls to default to preferred printing.

* DeckList now tries to assign reasonable defaults for UUID and collectorNumber if none are found in loaded DeckLists.
Rename overloaded DeckListModel findChild() function to findCardChildByNameAndUUID() for clarity.

* canibalized zach work and added it to the decklist builder

* Change getPreferredPrintingForCard to getPreferredSetForCard to reflect refactor.

* Properly update and set the DeckEditor's CardFrame to fetch by name and UUID if a card was selected from the decklist.

* Mainboard/Sideboard swaps should respect the UUID from the old zone instead of just blindly adding preferredPrinting.

* If the card info is null, there's no point in trying to look for the sets.

* Don't define methods twice.

* Convenience method to fetch a specific CardInfoPerSet instance for a cardName and a UUID.

* Check if the uuid starts with card_ when comparing.

* Address pull request comments (nullptr checks and additional comments, mostly.)

* Reformat code so the linter will stop yelling at me.

* DeckList no longer pre-populates uuids.

* Update Event_MoveCard to include the card UUID.

* Update Player::MoveCard to include the card UUID.

* Set the uuid when we set the cardName, in terms of hidden zones.

* [TEST/RevertMe] Set the uuid everywhere to test.

* Don't inline setUUID and mimic setName for AbstractCardItem.

* Revert blindly setting uuid for testing.

* Address PR comments (AbstractCardItem).

* Combine if-statement.

* Re-order uuid to visually align with its field number.

* Remove unnecessary new uuid field from event_move_card.

* Remove unused imports.

* Include cardName in the PixmapCacheKey in order to not break double-faced cards.

* Refactor setCode to cardUUID and introduce new cardSetShortName field.

* Override

* Refactor UUID to be providerId and change QString comparisons with empty string to isEmpty().

* Update translations.

* Change parent to be the first argument.

* Pull Parent argument up for CardItem.

* Pull Parent argument up for CardItem.

* Linter.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
Co-authored-by: LunaticCat <39006478+LunyaticCat@users.noreply.github.com>
Co-authored-by: luna <yannbrun1507@outlook.fr>
Co-authored-by: ZeldaZach <zahalpern+github@gmail.com>
2024-11-18 21:56:44 -05:00
RickyRister
86a4b130ff don't open deck in new tab if current tab is blank (#5169) 2024-11-18 11:59:34 +01:00
tooomm
f4e2f117c3 Readme: Remove last Gitter hint (#5178) 2024-11-18 00:27:33 +01:00
BruebachL
8ef92d26c5 Add Utility Layouts and corresponding Widgets (#5177)
* Add FlowWidget class with flexible layout and scroll handling

- Implemented FlowWidget class to organize widgets in a flow layout with scrollable options.
- Integrated QScrollArea to handle overflow with configurable horizontal and vertical scroll policies.
- Incorporated dynamic layout selection (FlowLayout, HorizontalFlowLayout, VerticalFlowLayout) based on scroll policy.

* Add OverlapWidget and OverlapLayout for managing overlapping child widgets

- Implemented the OverlapWidget class to manage child widgets in an overlapping manner, supporting configurable overlap percentage, maximum columns, maximum rows, and layout direction.
- Introduced the OverlapLayout class, which arranges widgets with overlapping positions, allowing flexible stacking based on specified parameters.

* Add OverlapControlWidget.

* Delete FlowLayout items later to allow them to finish their event loop.

* Allow OverlapWidgets to adjust their rows/columns on resize.

* Clamp vertical FlowLayout to any available parent scrollAreas.

* Implement margins and spacing for FlowLayouts.

* Adjust/revert some things.

* Address pull request comments (nullptr checks and additional comments, mostly.)

* Reformat code so the linter will stop yelling at me.

* Remove undefined methods from FlowLayouts.

* Fix the build.

* Revert FlowLayout::takeAt to index check.

* Commits will continue until linter morale improves.

* Fix various warnings in FlowLayout.

* Fix various warnings in FlowLayout.h.

* Fix various warnings in the FlowLayout classes.

* Fix [[nodiscard]] warning.

* Fix more warnings.

* Final round of yellow squiggle fixing.

* Linter formatting.

* Refactor column/row calculation to be more readable.

* Code style.

* Address PR comments.

* Combine if-statements.

* Replace std::math functions with Qt equivalents.

* Fix non-consts and QtMath.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2024-11-17 13:32:31 -05:00
BruebachL
c8336df49d Refactor Card Info Widgets (#5176)
* Refactor CardInfo Widgets to reside in their appropriate folder and to have a clearer naming structure.

* Add optional HoverToZoom functionality to CardInfoPictureWidget (default: disabled) and utility class to display text over a CardInfoPictureWidget.

* Patch CardInfoWidgets to use the new CardDatabaseManager.

* Add HoverToZoom to CardInfoPictureWithTextOverlayWidget

* Refactors and new signals for CardInfoPictureWidgets.

* Address pull request comments (nullptr checks and additional comments, mostly.)

* Reformat code so the linter will stop yelling at me.

* Linting.

* Fix the build.

* Fix warnings.

* Formatting, const qualifiers.

* Sensibly call the base class's (QWidget) paint event.

* Address PR comments (card picture).

* QT Version check because enterEvent signature changed.

* Linting.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2024-11-17 10:49:22 -05:00
RickyRister
c2fe3cda35 Add option to open deck in new tab by default (#5143)
* add comments

* add new setting for openDeckInNewTab

* implement open deck in new tab

* rename setting

* fix typo

* set default to false
2024-11-10 18:16:50 -05:00
BruebachL
c54f47efbf Change CardInfo's PixmapCacheKey to be the UUID of the card in the preferred set after database loading has finished. Otherwise, and if no UUID of a preferred set is available, default to the card name. (#5158)
* Change CardInfo's PixmapCacheKey to be the UUID of the preferred set after database loading has finished. Otherwise, and if no UUID of a preferred set is available, default to the card name.

* Clean up some variable names, clarify preferred Set insertion for PictureLoader, use the new CardDatabaseManager.

* Code formatting.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2024-11-10 17:49:11 -05:00
lumadd
3c40cc4b7d [4191] fix: Move unfocusTextBox and aFocusChat shortcuts to Player group (#5079)
* [4191] fix: Move unfocusTextBox and aFocusChat shortcuts to Player family

* [4191] fix: fix formatting

* Revert "[4191] fix: fix formatting"

This reverts commit 86a4a675f3bc8118d4ba8dd45f408c4e8c348f33.

* Revert "[4191] fix: Move unfocusTextBox and aFocusChat shortcuts to Player family"

This reverts commit 3ec183628df81c48123a8a248d0416c529ee0c8e.

* [4191] fix: Textbox and tab_game shortcut groups cannot conflict with Player group

* Revert "[4191] fix: Textbox and tab_game shortcut groups cannot conflict with Player group"

This reverts commit 36800393339d997df1a932bb798f95d2d387399a.

* [4191] fix: Move unfocusTextBox and aFocusChat shortcuts to Player family

* [4191] fix: Migrate shortcuts if new version is detected

* [4191] fix: formatting

* [4191] fix: Maybe fix build issue on Windows7, Debian11, UbuntuBionic and UbuntuFocal
2024-11-09 19:56:42 +01:00
RickyRister
f0fb77bade move replay-related constants into ReplayTimelineWidget (#5166)
* move constants

* make the existing static const into a constexpr
2024-11-09 11:18:51 +01:00
RickyRister
e894e78346 Do not open card reveal windows when skipping in replays (#5157)
* create EventProcessingOption QFlag

* pass EventProcessingOption all the way down

* implement reveal skipping logic
2024-11-09 02:06:23 +01:00
SlightlyCircuitous
dd04c610ec Remove Fedora 39 Build and Add Fedora 41 Build (#5151)
* Remove Fedora 39 docker file

EOL

* Add Fedora 41 Dockerfile

new release

* Remove Fedora 39, Add Fedora 41 to release template

* Remove Fedora 39, Add Fedora 41 to desktop build

---------

Co-authored-by: tooomm <tooomm@users.noreply.github.com>
2024-11-08 11:32:54 +01:00
tooomm
2e674efe50 Pretty print translation source (#5107) 2024-11-05 22:26:54 +01:00
ebbit1q
4d394c31f9 fix the timezones used for the user info box and add comments (#5162) 2024-11-05 14:54:38 -05:00
tooomm
11d58abbc3 CI: Update build matrix & clean naming (#5040)
---------

Co-authored-by: ebbit1q <ebbit1q@gmail.com>
Co-authored-by: ZeldaZach <zahalpern+github@gmail.com>
2024-11-05 14:38:33 -05:00
BruebachL
5f4ad87a47 Refactor CardDatabase *db global variable to singleton CardDatabaseManager. (#5159)
* Refactor CardDatabase *db global variable to singleton CardDatabaseManager.

This commit refactors the global variable CardDatabase *db into a singleton encapsulated by the DatabaseManager class, accessible via DatabaseManager::getInstance(). This change centralizes access to the database instance, improving code modularity and encapsulation, resolving dependencies on main.h for code that requires access to the database instance.

- Added DatabaseManager class with getInstance() method returning a pointer to the singleton CardDatabase.
- Removed global db variable and updated references across the codebase.
 - Thread-safe static initialization for the singleton.

Impact: This refactor should have no functional impact on the application, as it maintains the same interface for accessing the CardDatabase instance. However, the codebase now benefits from improved encapsulation, lifetime management, and thread-safety.

* Refactor CardDatabase *db global variable to singleton CardDatabaseManager.

This commit refactors the global variable CardDatabase *db into a singleton encapsulated by the DatabaseManager class, accessible via DatabaseManager::getInstance(). This change centralizes access to the database instance, improving code modularity and encapsulation, resolving dependencies on main.h for code that requires access to the database instance.

- Added DatabaseManager class with getInstance() method returning a pointer to the singleton CardDatabase.
- Removed global db variable and updated references across the codebase.
 - Thread-safe static initialization for the singleton.

Impact: This refactor should have no functional impact on the application, as it maintains the same interface for accessing the CardDatabase instance. However, the codebase now benefits from improved encapsulation, lifetime management, and thread-safety.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2024-11-05 19:32:59 +01:00
RickyRister
e43a21866c Buffer rewinds from backward skips in replays (#5141)
* split event processing to own method

* implement rewind throttling

* don't throttle backward skips from clicks

* use the term 'buffering' instead

* remove initial backup logic; just always buffer shortcut backward skips

* prevent segfault

* turns out you can just reuse the same one-shot timer

* try scaling timeout based on event count

* rewrite timeout calculation code

* fix linker error
2024-11-05 18:45:42 +01:00
RickyRister
6652012f4c fix bug with phase highlighting in replays (#5161)
* fix bug with incorrectly highlighted phases

* fix new bug with phases continuously darkening

* use preincrement instead of postincrement

* simplify conditional
2024-11-05 18:23:01 +01:00
BruebachL
0c4e8ca290 CardDatabase gains a new signal void cardDatabaseLoadingFinished(), which (#5156)
it will emit in loadCardDatabases(), mirroring the else branch where cardDatabaseLoadingFailed() is emitted.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2024-11-03 23:45:19 +01:00
ebbit1q
230a2c5c62 fix crashes in local games because of using uninitialised pointers (#5147) 2024-10-26 21:03:32 +00:00
ebbit1q
590fb7f533 fix row colors swapping when using back button in replays (#5148)
only happens when there is an uneven amount of rows in the chat
2024-10-26 21:02:51 +00:00
github-actions[bot]
e8b88248f2 Update translation files (#5146) 2024-10-26 19:11:24 +02:00
transifex-integration[bot]
c6ba1b6a4e Translate cockatrice/cockatrice_en@source.ts in de (#5145)
100% translated source file: 'cockatrice/cockatrice_en@source.ts'
on 'de'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-10-26 14:11:18 +02:00
RickyRister
c4c52bd8c0 Add keyboard shortcuts for skipping forward/backward in replays (#5140)
* split skipToTime into own function

* implement shortcut

* fix shortcut warning bug

* check boundary conditions in skipToTime

* change default fast forward shortcut to .

* implement big skip shortcuts

* remove unnecessary arg in lambda

* change default fast forward shortcut to Ctrl+F

* rename constants

* change default fast forward shortcut to Ctrl+P

* use static const
2024-10-23 14:00:23 +02:00
tooomm
c633a792f5 bump version (#5099) 2024-10-21 18:57:47 -04:00
Zach H
8d5421d9da Add backwards support Qt6.7's checkStateChanged on QCheckBoxes (#5137) 2024-10-20 23:35:34 -04:00
github-actions[bot]
b041f4ace2 Update translation source strings (#5117)
Co-authored-by: github-actions <github-actions@github.com>
2024-10-20 16:18:32 -04:00
RickyRister
d26f96db9e Add keyboard shortcuts for replays (#5136)
* add keyboard shortcut for play/pause

* add keyboard shortcut for fast-forward

* make shortcuts rebindable

* run formatter
2024-10-20 16:41:59 +02:00
LunaticCat
fa999880ee Major Directory Refactoring (#5118)
* refactored cardzone.cpp, added doc and changed if to switch case

* started moving every files into different folders

* remove undercase to match with other files naming convention

* refactored dialog files

* ran format.sh

* refactored client/tabs folder

* refactored client/tabs folder

* refactored client/tabs folder

* refactored client folder

* refactored carddbparser

* refactored dialogs

* Create sonar-project.properties

temporary file for lint

* Create build.yml

temporary file for lint

* removed all files from root directory

* removed all files from root directory

* added current branch to workflow

* fixed most broken import

* fixed issues while renaming files

* fixed oracle importer

* fixed dbconverter

* updated translations

* made sub-folders for client

* removed linter

* removed linter folder

* fixed oracle import

* revert card_zone documentation

* renamed db parser files name and deck_view imports

* fixed dlg file issue

* ran format file and fixed test file

* fixed carddb test files

* moved player folder in game

* updated translations and format files

* fixed peglib import

* format cmake files

* removing vcpkg to try to add it back later

* tried fixing vcpkg file

* renamed filter to filters and moved database parser to cards folder

* reverted translation files

* reverted oracle translated

* Update cockatrice/src/dialogs/dlg_register.cpp

Co-authored-by: tooomm <tooomm@users.noreply.github.com>

* Update cockatrice/src/client/ui/window_main.cpp

Co-authored-by: tooomm <tooomm@users.noreply.github.com>

* removed empty line at file start

* removed useless include from tab_supervisor.cpp

* refactored cardzone.cpp, added doc and changed if to switch case

* started moving every files into different folders

* remove undercase to match with other files naming convention

* refactored dialog files

* ran format.sh

* refactored client/tabs folder

* refactored client folder

* refactored carddbparser

* refactored dialogs

* removed all files from root directory

* Create sonar-project.properties

temporary file for lint

* Create build.yml

temporary file for lint

* added current branch to workflow

* fixed most broken import

* fixed issues while renaming files

* fixed oracle importer

* fixed dbconverter

* updated translations

* made sub-folders for client

* removed linter

* removed linter folder

* fixed oracle import

* revert card_zone documentation

* renamed db parser files name and deck_view imports

* fixed dlg file issue

* ran format file and fixed test file

* fixed carddb test files

* moved player folder in game

* updated translations and format files

* fixed peglib import

* reverted translation files

* format cmake files

* removing vcpkg to try to add it back later

* tried fixing vcpkg file

* pre-updating of cockatrice changes

* removed empty line at file start

* reverted oracle translated

* Update cockatrice/src/dialogs/dlg_register.cpp

Co-authored-by: tooomm <tooomm@users.noreply.github.com>

* Update cockatrice/src/client/ui/window_main.cpp

Co-authored-by: tooomm <tooomm@users.noreply.github.com>

* removed useless include from tab_supervisor.cpp

---------

Co-authored-by: tooomm <tooomm@users.noreply.github.com>
2024-10-20 10:11:35 -04:00
RickyRister
d1e0f9dfc5 fix bug (#5133) 2024-10-19 20:18:35 -04:00
RickyRister
2d86938375 Consolidate play/pause buttons in replays (#5131)
* Consolidate play/pause buttons in replays

* always enable fast forward button

* run formatter
2024-10-19 20:18:01 -04:00
ebbit1q
4865269a73 don't delete "" (#5135) 2024-10-19 13:06:18 +02:00
dependabot[bot]
038ce3dcec Bump send and express in /webclient (#5123)
Bumps [send](https://github.com/pillarjs/send) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together.

Updates `send` from 0.18.0 to 0.19.0
- [Release notes](https://github.com/pillarjs/send/releases)
- [Changelog](https://github.com/pillarjs/send/blob/master/HISTORY.md)
- [Commits](https://github.com/pillarjs/send/compare/0.18.0...0.19.0)

Updates `express` from 4.18.2 to 4.21.0
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.0/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.18.2...4.21.0)

---
updated-dependencies:
- dependency-name: send
  dependency-type: indirect
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-11 04:28:41 +00:00
RickyRister
43b997fe40 follow symlinks when iterating through the custom image folder (#5126) 2024-10-11 04:27:24 +00:00
Polty
44e92f61ca #3945 deck list: Navigation keys (PageUp/Down, Home/End) (#5103)
* #3945 deck list: Navigation keys (PageUp/Down, Home/End) interact with the deck list.

* make Home/End work normally when there is text in the search textbox

* fix debug build, explicit cast from int to Qt::Key enum
2024-10-09 23:11:12 +02:00
Alexander Choi
b4bfa17cee In-game message macros available immediately in active games (#5113)
* In-game message macros available immediately in active games

* fix formatting

* init sayMenu actions with sayMenu as parent
2024-10-09 23:08:57 +02:00
tooomm
500b694cc6 CI: Fix logic in translation action after dependency update (#5124)
* Update translations-pull.yml

* Update translations-push.yml
2024-10-07 18:48:43 +00:00
dependabot[bot]
b998282304 Bump peter-evans/create-pull-request from 6 to 7 (#5110)
Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 6 to 7.
- [Release notes](https://github.com/peter-evans/create-pull-request/releases)
- [Commits](https://github.com/peter-evans/create-pull-request/compare/v6...v7)

---
updated-dependencies:
- dependency-name: peter-evans/create-pull-request
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-07 20:05:44 +02:00
Alexander Choi
b704216553 fix macro shortcuts so that Ctrl+1 is not double-assigned (#5112) 2024-09-15 16:55:30 -04:00
transifex-integration[bot]
03ec02a749 Translate oracle/oracle_en@source.ts in fi (#5100) 2024-08-30 13:16:22 +02:00
Zach H
248ea82573 Support Game Events (#5087) 2024-08-25 00:31:20 +00:00
ebbit1q
bbe125beee replace cipt check with regex (#5094) 2024-08-25 00:31:01 +00:00
tooomm
95cd1c6f87 CI: Update install-qt-action (#5096) 2024-08-22 01:22:57 +02:00
J. Cameron McDonald
1c2107ae8f docs: fix readme "get involved" links (#5098) 2024-08-17 23:46:16 +02:00
ebbit1q
e826e17c6c add qtimageformats module (#5092)
* add qtimageformats module

* add qt6-image-formats-plugins to apt depends in cmakelists

* too many quotes

* add qt6-qtimageformats to rpm deps
2024-08-16 22:32:22 -04:00
Joseph Insalaco
b111f0921c Admin persistence changes (#5086) 2024-08-16 22:31:57 -04:00
transifex-integration[bot]
090a48515c Updates for project Cockatrice and language en_US (#5088)
* Translate cockatrice_en@source.ts in en_US

100% translated source file: 'cockatrice_en@source.ts'
on 'en_US'.

* Translate oracle/oracle_en@source.ts in en_US

100% translated source file: 'oracle/oracle_en@source.ts'
on 'en_US'.

---------

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-08-03 14:29:28 +02:00
transifex-integration[bot]
b8555d8c42 Translate cockatrice/cockatrice_en@source.ts in nl (#5089)
100% translated source file: 'cockatrice/cockatrice_en@source.ts'
on 'nl'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-08-03 14:16:42 +02:00
Joseph Insalaco
cf1f4f12a9 Updating Session Persistence with all valid persistence calls (#5085)
* Updating Session Persistence with all valid persistence calls

* Spacing fixes

---------

Co-authored-by: Zach H <zahalpern+github@gmail.com>
2024-07-29 17:25:33 +00:00
ebbit1q
ef4413633a fix regression in #4762 to _fill_with_ template (#5083)
fixes #5062
2024-07-29 01:20:50 +00:00
Zach H
c5bb38e907 Add types for Moderator commands (#5084)
* Add types for Moderator commands

* Support User Priv Level & userLevel
2024-07-29 01:16:29 +00:00
github-actions[bot]
9f515fc804 Update translation files (#5080) 2024-07-23 11:53:07 +02:00
Zach H
245edcefdd Add openssl to windows reqs (#5074) 2024-07-13 12:38:43 -04:00
transifex-integration[bot]
153f73c308 Translate cockatrice/cockatrice_en@source.ts in de (#5073)
100% translated source file: 'cockatrice/cockatrice_en@source.ts'
on 'de'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-07-13 13:11:03 +00:00
github-actions[bot]
315837b267 Update translation source strings (#5069)
Co-authored-by: github-actions <github-actions@github.com>
2024-07-01 19:04:16 +02:00
Joseph Insalaco
ea8da24215 Webatrice: Adding joined game to persistence layer (#5068)
* Adding joined game to persistence layer

* Linting fixes
2024-06-27 02:06:47 +00:00
Joseph Insalaco
1ab723ca64 Webatrice: Adding game created to persistence layer (#5067) 2024-06-27 01:03:21 +00:00
Joseph Insalaco
f8bc6cf998 Adding remove messages to persistence layer (#5066) 2024-06-27 00:44:40 +00:00
Zach H
8687163cca Add few more interfaces (#5063) 2024-06-25 05:00:45 +00:00
Zach H
e261e16d99 Re-Add ability to share editable deck views (#5060)
- Rolls back 6811819161
- Follow up to adbb607700
2024-06-24 21:52:11 +00:00
Jeremy Letto
bdcd083eea refactor imports (#5058) 2024-06-17 01:00:23 -04:00
Zach H
c4bf9eb61c Cleanup (#5057)
* Add Types

* Add Types
2024-06-17 00:32:36 -04:00
Zach H
0994d10410 More stuff (#5056)
* Skeleton + RemoveMessages

* GameJoinedData
2024-06-16 23:26:03 -04:00
Zach H
291c535edb More web stuff (#5055)
* Add Response.gamesOfuser

* Cleanup and confirm all
2024-06-16 22:48:07 -04:00
ZeldaZach
f04702fdd1 Backwards Compatibility for rolling dice 2024-06-16 21:10:07 -04:00
Zach H
b7fbc12ac0 Allow Judges to see all information, regardless of room settings (#5053) 2024-06-16 19:12:37 -04:00
Zach H
e2ab8db958 Add some sessions (#5052)
* Add AccountEdit

* Add PasswordChange

* Cleanup

* Add SessionService.accountImage

* Add SessionService.message

* Add SessionService.getUserInfo

* Lint
2024-06-14 23:06:50 -04:00
Zach H
34d70980e8 Webatrice admin commands (#5051)
* Add AdminCommand.updateServerMessage

* Add AdminCommand.shutdownServer

* Add AdminCommand.reloadConfig

* Cleanup

* Add AdminCommand.adjustMod

* Lint

* Lint
2024-06-13 02:52:40 +00:00
Zach H
e45c4042fe Webatrice: Add all ModeratorCommands (#5049)
* Move viewLogHistory to Moderator commands

* Add Moderator.banFromServer

* Add Moderator.getBanHistory

* Add Moderator.getWarnHistory

* Add Moderator.warnUser

* Add Moderator.getWarnList
2024-06-13 02:15:14 +00:00
Zach H
ce8092318e Allow up to 100 dice to be rolled at a time (#5047)
* Allow up to 100 dice to be rolled at a time
- Fix #4276
2024-06-12 08:37:04 -04:00
transifex-integration[bot]
c95cc1dd9d Translate webclient/src/i18n-default.json in it (#5045)
100% translated source file: 'webclient/src/i18n-default.json'
on 'it'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-06-06 23:53:15 +00:00
Zach H
1f72877728 Drop MacOS 10.15/11 Support :( (#5033) 2024-05-31 09:10:54 -04:00
tooomm
93b40343d9 EnableCIServerMode (#5034) 2024-05-25 12:40:40 -04:00
transifex-integration[bot]
ba10108207 Translate cockatrice/cockatrice_en@source.ts in de (#5038) 2024-05-24 19:05:11 +02:00
github-actions[bot]
c28f66d673 Update translation source strings (#4973)
Co-authored-by: github-actions <github-actions@github.com>
2024-05-18 22:11:07 +02:00
Zach H
59f327f97a Pin XCode versions for Mac Builds (#5032) 2024-05-13 18:57:33 -04:00
tooomm
872c92a244 CI: Use windows-2022 image with Visual Studio 17 2022 (#4999)
Co-authored-by: ZeldaZach <zahalpern+github@gmail.com>
2024-05-13 17:41:50 -04:00
dependabot[bot]
2303880b87 Bump ejs from 3.1.8 to 3.1.10 in /webclient (#5027)
Bumps [ejs](https://github.com/mde/ejs) from 3.1.8 to 3.1.10.
- [Release notes](https://github.com/mde/ejs/releases)
- [Commits](https://github.com/mde/ejs/compare/v3.1.8...v3.1.10)

---
updated-dependencies:
- dependency-name: ejs
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-02 11:48:57 +00:00
github-actions[bot]
0e97cc1712 Update translation files (#5021)
Co-authored-by: github-actions <github-actions@github.com>
2024-04-24 19:37:31 -04:00
SlightlyCircuitous
d550e42441 Remove Fedora 38 Build and Add Fedora 40 Build (#5024)
* Remove Fedora 38 docker image

* Add Fedora 40 Dockerfile

* Remove Fedora 38, Add Fedora 40 to release template

* Remove Fedora 38, add Fedora 40 to desktop-build
2024-04-24 19:37:23 -04:00
SlightlyCircuitous
4279753030 Add Ubuntu 24.04 "Noble Numbat" Build (#5023)
* Create Ubuntu Noble Numbat dockerfile

* Add Noble Numbat to desktop_build

* Add Noble Numbat to release_template
2024-04-24 15:13:20 +02:00
Jeremy Letto
2f6c018b7a untangle updateStatus (#5018)
* untangle updateStatus

* fix test
2024-04-05 04:56:12 +00:00
Jeremy Letto
be5d42baba WebClient: refactor protobuf method structure (#5014) 2024-04-01 17:32:08 +00:00
ebbit1q
f174614496 assign new arrow id when arrow is moved to transformed card (#5012)
* add timeout to settingscache

* assign new arrow id when arrow is moved to transformed card

fixes bug introduced in #4907
fixes #5008
2024-03-27 14:47:00 +01:00
dependabot[bot]
e8c7fba8b0 Bump peter-evans/create-pull-request from 5 to 6 (#4997) 2024-03-19 19:42:08 +01:00
SlightlyCircuitous
5c49283023 Remove Ubuntu 23.04 Lunar Lobster Build (#5002)
* Delete .ci/UbuntuLunar directory

EOL

* Update release_template.md

Lunar is EOL

* Update desktop-build.yml

Lunar is EOL
2024-02-27 21:07:18 +01:00
ebbit1q
ad56b431a3 increase version number so we can create a beta (#5001) 2024-02-27 20:55:28 +01:00
SlightlyCircuitous
b0d8a33d5f Remove Fedora 37 Build, Add Fedora 39 Build (#5003) 2024-02-26 08:37:28 +01:00
dependabot[bot]
1715bcb216 Bump microsoft/setup-msbuild from 1 to 2 (#4996)
Bumps [microsoft/setup-msbuild](https://github.com/microsoft/setup-msbuild) from 1 to 2.
- [Release notes](https://github.com/microsoft/setup-msbuild/releases)
- [Changelog](https://github.com/microsoft/setup-msbuild/blob/main/building-release.md)
- [Commits](https://github.com/microsoft/setup-msbuild/compare/v1...v2)

---
updated-dependencies:
- dependency-name: microsoft/setup-msbuild
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-10 15:31:13 -05:00
tooomm
96caeaca72 Enable MTT over parallel flag (#4998) 2024-02-10 15:30:43 -05:00
tooomm
c8723ae935 Remove Gitter chat (#4995) 2024-02-01 18:25:04 +01:00
tooomm
94e39c044c update mv mapping (#4896) 2024-01-30 00:27:45 -05:00
ebbit1q
9776ea53c9 make translation update script more specific in line selection (#4993) 2024-01-25 23:28:23 +01:00
github-actions[bot]
675d07dac0 Update translation files (#4991)
Co-authored-by: github-actions <github-actions@github.com>
2024-01-23 22:29:40 +01:00
tooomm
1217820288 CI: GitHub Job Summary for Translation PRs (#4992) 2024-01-23 22:21:35 +01:00
tooomm
90e1a3cb76 Utilize new CPUs with more cores (#4988) 2024-01-22 23:15:20 +01:00
tooomm
7c1095ea50 CI: Fix ignore pattern & highlight status of translation automations (#4977)
* Add result of run to GHA summary

* Fix `paths-ignore`
2024-01-22 22:07:21 +01:00
dependabot[bot]
203e916a07 Bump actions/cache from 3 to 4 (#4990) 2024-01-22 19:32:27 +01:00
github-actions[bot]
7201e34b38 Update translation files (#4984)
Co-authored-by: github-actions <github-actions@github.com>
2024-01-18 20:50:28 +01:00
Basile Clement
6d032c378f Improve drag & drop behavior (#4963)
* Improve drag & drop behavior

This patch tweaks the drag & drop behavior (in particular, the grid
placement) to be more intuitive. More precisely, with this patch the
drag & drop will:

 - Only use the "hot spot" (i.e. position of the cursor on the card)
   for zones where the card is actually displayed around the cursor (in
   particular, not on the table where the card snaps to the grid).

 - Use better boundaries computed with respect to the center of the
   card (rather than its top left corner) for determining which grid
   cell a card should go to

 - Align behavior of the preview and the actual effect when overflow of
   the 3-card stacks occurs

 - Avoid visual glitches where the cursor ends up outside of the card or
   at incorrect offsets when moving the mouse too fast (which translates
   to overflows of the hot spot computation)

* Address review comments

 - Use simpler computation for restricting hotSpot range
 - Prevent dropping cards onto full 3-card slots
2024-01-01 16:51:36 -05:00
dependabot[bot]
badd8952f5 Bump @babel/traverse from 7.20.0 to 7.23.2 in /webclient (#4917)
Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.20.0 to 7.23.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse)

---
updated-dependencies:
- dependency-name: "@babel/traverse"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-20 21:31:12 -05:00
dependabot[bot]
7209eddb2d Bump crypto-js from 4.1.1 to 4.2.0 in /webclient (#4926)
Bumps [crypto-js](https://github.com/brix/crypto-js) from 4.1.1 to 4.2.0.
- [Commits](https://github.com/brix/crypto-js/compare/4.1.1...4.2.0)

---
updated-dependencies:
- dependency-name: crypto-js
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-20 21:31:04 -05:00
dependabot[bot]
a7ffd43b29 Bump actions/upload-artifact from 3 to 4 (#4969)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3 to 4.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-18 23:58:37 +01:00
tooomm
724ba69483 macOS 12 --> 13 (#4966) 2023-12-17 13:39:06 -05:00
ebbit1q
1716801437 make package on macos 13 (#4961) 2023-12-15 21:15:57 -05:00
ebbit1q
fa727524dc make cards on the stack slightly overlap to stress order (#4930)
* make cards on the stack slightly overlap to stress order

dragging cards to the stack now places the card at the location it is
dropped

* make default play action append to stack

* add vertical overlap to settings and vertical hand

* Update cockatrice/src/dlg_settings.cpp

Co-authored-by: tooomm <tooomm@users.noreply.github.com>

* Fix format

---------

Co-authored-by: tooomm <tooomm@users.noreply.github.com>
Co-authored-by: ZeldaZach <zahalpern+github@gmail.com>
2023-12-15 14:00:58 -05:00
ebbit1q
28f80e18a0 add ctrl enter as shortcut for ok when setting annotation (#4929) 2023-12-15 13:55:11 -05:00
ebbit1q
4acc8bfe80 put cards on top in a random order (#4960) 2023-12-15 13:51:21 -05:00
Zach H
9f86ed7887 Fix Fedora Builds (#4964) 2023-12-15 13:49:32 -05:00
Basile Clement
cb18a55338 Support fractional scaling when scaling card images (#4962)
Fixes #4880
2023-12-14 14:24:35 +01:00
dependabot[bot]
78a928464c Bump @adobe/css-tools from 4.0.1 to 4.3.2 in /webclient (#4953)
Bumps [@adobe/css-tools](https://github.com/adobe/css-tools) from 4.0.1 to 4.3.2.
- [Changelog](https://github.com/adobe/css-tools/blob/main/History.md)
- [Commits](https://github.com/adobe/css-tools/commits)

---
updated-dependencies:
- dependency-name: "@adobe/css-tools"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-09 00:53:10 -05:00
Zach H
07a8cd0a5f Fix #4903: Parse Email Addresses whenever used (#4932) 2023-12-09 00:52:47 -05:00
Zach H
b73ef58567 Support WebP format for Card Images (#4950)
- Fix #4939
2023-12-09 00:52:14 -05:00
Zach H
519531f3a0 Support escaping single and double quotes in Deck Editor Search Regex Strings (#4948)
- Fix #4946
2023-12-09 00:51:54 -05:00
tooomm
4b8e47d079 Update default theme name (#4944) 2023-11-20 01:01:42 +01:00
transifex-integration[bot]
ed170f7e07 Updates for project Cockatrice and language de (#4942)
* Translate cockatrice/cockatrice_en@source.ts in de

100% translated source file: 'cockatrice/cockatrice_en@source.ts'
on 'de'.

* Translate oracle/oracle_en@source.ts in de

100% translated source file: 'oracle/oracle_en@source.ts'
on 'de'.

---------

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2023-11-18 12:10:11 +01:00
transifex-integration[bot]
6bb559874c Translate cockatrice/cockatrice_en@source.ts in it (#4941)
100% translated source file: 'cockatrice/cockatrice_en@source.ts'
on 'it'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2023-11-18 08:44:10 +01:00
transifex-integration[bot]
9cd68e25b3 Translate cockatrice/cockatrice_en@source.ts in de (#4937)
100% translated source file: 'cockatrice/cockatrice_en@source.ts'
on 'de'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2023-11-12 19:22:58 +01:00
transifex-integration[bot]
72ac441598 Updates for project Cockatrice and language de (#4934)
* Translate cockatrice/cockatrice_en@source.ts in de

100% translated source file: 'cockatrice/cockatrice_en@source.ts'
on 'de'.

* Translate cockatrice/cockatrice_en@source.ts in de

100% translated source file: 'cockatrice/cockatrice_en@source.ts'
on 'de'.

---------

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2023-11-05 19:15:48 +01:00
dependabot[bot]
f5fe56c85d Bump actions/setup-node from 3 to 4 (#4928)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 3 to 4.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-31 00:48:39 +01:00
tooomm
d4fc1be2cc CI: Cleanup & updates (#4921)
* simplify ci after 64bit only

* hint on macos 10.15 with qt6.2

* Update desktop-build.yml

* Update desktop-build.yml

* update xcode

* 14.3 finds 14.3.1, but 14.0 doesn't find 14.0.1

* Update desktop-build.yml
2023-10-28 14:36:50 -04:00
ebbit1q
7b3617a273 clean up #4904 for consistency (#4927)
note, this is not a racetime issue, see #4907
2023-10-28 14:35:15 -04:00
tooomm
3e8adae3de Rename "stack until found" feature (#4871)
* Rename "stack until found" feature

* lint
2023-10-25 18:43:57 +02:00
tooomm
9943133d6d TOTD: Exchange Gitter for Discord, Fix date format (#4920)
* Update tips_of_the_day.xml

* Delete cockatrice/resources/tips/images/gitter.png

* Add Discord icon

* Update cockatrice.qrc

* date format
2023-10-24 14:26:38 +02:00
tooomm
a5706a47af Include missing strings for translation (#4919) 2023-10-19 21:58:38 +02:00
github-actions[bot]
b3b911c64d Update translation files (#4913)
Co-authored-by: github-actions <github-actions@github.com>
2023-10-18 20:41:14 +02:00
github-actions[bot]
05beb4fcaf Update translation source strings (#4918) 2023-10-18 00:29:56 +02:00
tooomm
324b50e381 Improve Transifex pull action (#4916) 2023-10-16 22:51:08 +02:00
Zach H
186f4289e9 Address /W4 compiler warnings for Windows (#4910) 2023-10-15 20:31:13 -04:00
Zach H
cb90a8356b Use proto21 on macos11 (#4914) 2023-10-15 20:30:50 -04:00
tooomm
e9c502ab32 CI: Add action for pulling new translations (#4911)
* Update and rename translations.yml to translations-push.yml

* Rename update_translations.sh to update_translation_source_strings.sh

* Update and rename update_translations_template.md to update_translation_source_strings_template.md

* Add translations-pull.yml

* Update config

* Update desktop-lint.yml

* Update desktop-build.yml

* correct env var naming

* names
2023-10-15 18:47:15 -04:00
tooomm
f728520e97 Update release_template.md (#4909) 2023-10-13 22:52:08 -04:00
Zach H
c1b0d50237 Handle Qt6.6 Deprecations (#4908) 2023-10-13 20:53:47 -04:00
tooomm
b9cfc29059 CI: Use concurrency group (#4902) 2023-10-13 19:01:37 -04:00
ebbit1q
6bf7c79891 copy arrows on transform (#4907) 2023-10-13 19:01:08 -04:00
Zach H
2bd0e58354 HotFix: Prevent crashing if a Zone is null with an arrow while a player concedes race time (#4904) 2023-10-13 19:00:53 -04:00
Zach H
ee674cb0cf Support MacOS 12 & 13. Support Protobuf 23. Deprecate MacOS 11. (#4884) 2023-10-13 14:45:22 -04:00
transifex-integration[bot]
dd1b354d48 Translate webclient/src/i18n-default.json in de (#4906)
100% translated source file: 'webclient/src/i18n-default.json'
on 'de'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2023-10-13 14:26:05 -04:00
tooomm
d3e96f4a99 Switch to rolling release (#4905) 2023-10-13 14:25:40 -04:00
tooomm
90e2eb3db9 Webclient: Fix translation file names (#4897) 2023-10-09 18:05:43 +02:00
transifex-integration[bot]
102be6a350 Translate cockatrice_en@source.ts in en_US [Manual Sync] (#4893)
98% of minimum 80% 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>
2023-10-06 22:56:03 +02:00
transifex-integration[bot]
be6152948c Updates for project Cockatrice and lanuage es on branch master (#4886)
* Translate i18n-default.json in es [Manual Sync]

99% of minimum 80% translated source file: 'i18n-default.json'
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]

98% of minimum 80% 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

* Translate i18n-default.json in es [Manual Sync]

99% of minimum 95% translated source file: 'i18n-default.json'
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]

98% of minimum 95% 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>
2023-10-06 22:52:37 +02:00
transifex-integration[bot]
f14bf4b205 Updates for project Cockatrice and lanuage it on branch master (#4887)
* Translate i18n-default.json in it [Manual Sync]

99% of minimum 80% translated source file: 'i18n-default.json'
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]

98% of minimum 80% 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

* Translate i18n-default.json in it [Manual Sync]

99% of minimum 95% translated source file: 'i18n-default.json'
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>
2023-10-06 21:37:59 +02:00
transifex-integration[bot]
123ac2ca25 Updates for project Cockatrice and lanuage fr on branch master (#4888)
* Translate cockatrice_en@source.ts in fr [Manual Sync]

98% of minimum 80% translated source file: 'cockatrice_en@source.ts'
on 'fr'.

Sync of partially translated files: 
untranslated content is included with an empty translation 
or source language content depending on file format

* Translate i18n-default.json in fr [Manual Sync]

99% of minimum 80% translated source file: 'i18n-default.json'
on 'fr'.

Sync of partially translated files: 
untranslated content is included with an empty translation 
or source language content depending on file format

* Translate i18n-default.json in fr [Manual Sync]

99% of minimum 95% translated source file: 'i18n-default.json'
on 'fr'.

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>
2023-10-06 21:36:56 +02:00
transifex-integration[bot]
7216b976ec Updates for project Cockatrice and lanuage pt_BR on branch master (#4890)
* Translate cockatrice_en@source.ts in pt_BR [Manual Sync]

98% of minimum 80% 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

* Translate webclient/src/i18n-default.json in pt_BR

100% translated source file: 'webclient/src/i18n-default.json'
on 'pt_BR'.

---------

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2023-10-06 21:36:16 +02:00
ebbit1q
7fb698cfbf update release template with #4883 deprecating 32 bit (#4885) 2023-10-02 14:09:19 -04:00
Zach H
b0470ab678 Move to OpenSSLv3 (& Drop 32-bit) (#4883) 2023-10-01 17:19:31 -04:00
Zach H
0deb037035 Address connect errors in logs (#4882) 2023-10-01 15:30:54 -04:00
ZeldaZach
064b362d60 Bump to 2.9.0 :) 2023-09-14 22:14:22 -04:00
tooomm
6bbe228a84 README: Cleanup translations widget and links (#4870)
* Fix translations widget and links

* Name webatrice
2023-09-11 22:13:52 -04:00
transifex-integration[bot]
a8ba8b6ab5 Updates for project Cockatrice and lanuage de on branch master (#4867)
* Translate cockatrice/cockatrice_en@source.ts in de

100% translated source file: 'cockatrice/cockatrice_en@source.ts'
on 'de'.

* Translate cockatrice/cockatrice_en@source.ts in de

100% translated source file: 'cockatrice/cockatrice_en@source.ts'
on 'de'.

* Translate cockatrice/cockatrice_en@source.ts in de

100% translated source file: 'cockatrice/cockatrice_en@source.ts'
on 'de'.

---------

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2023-09-11 21:24:57 +02:00
dependabot[bot]
e850f6c2a5 Bump actions/checkout from 3 to 4 (#4866) 2023-09-07 23:11:19 +02:00
github-actions[bot]
e9eb7d6db1 Update translation source strings (#4865)
Co-authored-by: github-actions <github-actions@github.com>
2023-09-01 20:20:19 -04:00
tooomm
56d21321d0 remove translation string (#4860) 2023-08-18 12:34:54 -04:00
transifex-integration[bot]
3888a74212 Translate oracle/oracle_en@source.ts in de (#4862)
100% translated source file: 'oracle/oracle_en@source.ts'
on 'de'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2023-08-18 12:34:39 -04:00
transifex-integration[bot]
0035e29f9e Translate webclient/src/i18n-default.json in pt_BR (#4863)
100% translated source file: 'webclient/src/i18n-default.json'
on 'pt_BR'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2023-08-18 12:34:30 -04:00
ebbit1q
90679d5669 fix issues with #4648 (#4864) 2023-08-18 12:34:17 -04:00
transifex-integration[bot]
ac5dc2578a Translate cockatrice/cockatrice_en@source.ts in de (#4861)
100% translated source file: 'cockatrice/cockatrice_en@source.ts'
on 'de'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2023-08-13 20:51:02 +02:00
tooomm
671e6823be cleanup vcpkg.json (#4859) 2023-08-11 13:35:59 +02:00
github-actions[bot]
0d76662311 Update translation source strings (#4843)
Co-authored-by: github-actions <github-actions@github.com>
2023-08-10 22:37:17 +02:00
tooomm
8dd59cf3cf CI: Bump GitHub actions + submodule (#4852)
* Bump used actions

* Update vcpkg submodule (#4857)

* Pause npm updates for webclient (#4853)

* Bump peter-evans/create-pull-request from 4 to 5 (#4846)

Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 4 to 5.
- [Release notes](https://github.com/peter-evans/create-pull-request/releases)
- [Commits](https://github.com/peter-evans/create-pull-request/compare/v4...v5)

---
updated-dependencies:
- dependency-name: peter-evans/create-pull-request
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Update vcpkg

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump vcpkg action to v11

* Update vcpkg cache setting

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-09 12:28:16 +02:00
dependabot[bot]
332d25dc00 Bump peter-evans/create-pull-request from 4 to 5 (#4846)
Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 4 to 5.
- [Release notes](https://github.com/peter-evans/create-pull-request/releases)
- [Commits](https://github.com/peter-evans/create-pull-request/compare/v4...v5)

---
updated-dependencies:
- dependency-name: peter-evans/create-pull-request
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-08 23:21:26 +02:00
tooomm
0fa81a77dc Pause npm updates for webclient (#4853) 2023-08-08 13:17:28 +02:00
tooomm
9a74d8f72d update link (#4845) 2023-08-06 22:46:02 -04:00
Zach H
8c539351e3 Fix Mac Builds (#4844) 2023-08-06 22:45:45 -04:00
ebbit1q
e3552fc0ae add more default shortcuts (#4349)
* add more default shortcuts

replace the ctrl a look at top of library shortcut with ctrl shift n
use ctl a for draw arrow
replace ctrl shift b for toggle sideboard lock
use ctrl b for move card to bottom of library
use ctrl shift l for start local game
add keyboard shortcuts for all 3 counter colors using , . / keys
use ctrl [ ] \ for the "other" counter
add ctrl = as an easy alternative to ctrl + for people without keypads
( on linux ctrl alt keypad + is a special key that is reserved in x
  it produces the XF86_Next_VMode keyboard event which isn't bindable )
use alt u for toggling untapping
use alt l for peeking at cards
use ctrl alt u for unattaching cards
use alt n for set annotation
use alt y for milling one card

* use alt f for flipping cards
2023-08-06 17:56:24 -04:00
tooomm
ca308636c3 CI: Add automatic PR creation for source string updates (#4544)
* wording

* add pr creation

* Update translations.yml

* Update translations.yml

* update translation workflow

* Update CONTRIBUTING.md

* Update CONTRIBUTING.md

* Update CONTRIBUTING.md

* skip ci update

* skip ci update

* update conditions

* remove empty line

* typo

* tee test

* cleanup

* pass data between steps

* opt for step output over env variable

* remove space

* create script

* wording

* fix fork protection, re-add pr run

* updates

* Update translations.yml

* adjust for new source paths

* update comment

* wording

Co-authored-by: ebbit1q <ebbit1q@gmail.com>

* wording

* reorder

* reorder

* fix deprecation of set-output

* fix version

---------

Co-authored-by: ebbit1q <ebbit1q@gmail.com>
2023-08-06 17:55:50 -04:00
ebbit1q
244cb847fb replace trayicon activation with menu actions (#4632) 2023-08-06 17:55:02 -04:00
tooomm
176c52daf2 Enable Dependabot (#4795)
* add dependabot file

* disable submodules for now
2023-08-06 17:54:17 -04:00
tooomm
ee3525ec64 Webclient: Brazilian translations are doubled (#4809)
* cleanup old brazilian translations

* pt-br --> pt_br

* pt-br --> pt_br

* pt-br --> pt_br
2023-08-06 17:53:55 -04:00
dependabot[bot]
adce921be7 Bump protobufjs from 7.1.2 to 7.2.4 in /webclient (#4827)
Bumps [protobufjs](https://github.com/protobufjs/protobuf.js) from 7.1.2 to 7.2.4.
- [Release notes](https://github.com/protobufjs/protobuf.js/releases)
- [Changelog](https://github.com/protobufjs/protobuf.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/protobufjs/protobuf.js/compare/protobufjs-v7.1.2...protobufjs-v7.2.4)

---
updated-dependencies:
- dependency-name: protobufjs
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-06 17:53:41 -04:00
dependabot[bot]
20ceb1c284 Bump semver from 6.3.0 to 6.3.1 in /webclient (#4829)
Bumps [semver](https://github.com/npm/node-semver) from 6.3.0 to 6.3.1.
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/v6.3.1/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v6.3.0...v6.3.1)

---
updated-dependencies:
- dependency-name: semver
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-06 17:53:32 -04:00
dependabot[bot]
db4364b8f8 Bump tough-cookie from 4.1.2 to 4.1.3 in /webclient (#4828)
Bumps [tough-cookie](https://github.com/salesforce/tough-cookie) from 4.1.2 to 4.1.3.
- [Release notes](https://github.com/salesforce/tough-cookie/releases)
- [Changelog](https://github.com/salesforce/tough-cookie/blob/master/CHANGELOG.md)
- [Commits](https://github.com/salesforce/tough-cookie/compare/v4.1.2...v4.1.3)

---
updated-dependencies:
- dependency-name: tough-cookie
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-06 17:53:25 -04:00
dependabot[bot]
48d6435e09 Bump word-wrap from 1.2.3 to 1.2.4 in /webclient (#4831)
Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.4.
- [Release notes](https://github.com/jonschlinkert/word-wrap/releases)
- [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.4)

---
updated-dependencies:
- dependency-name: word-wrap
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-06 17:53:18 -04:00
ebbit1q
7c20e9ab34 add move cards from top of library until dialog (#4648)
a bit of a hack, the client will use the play top card action and then
compare it with the propmpted expression, as if you were cascading
normally but really fast

the new keybind for this is ctrl shift y

I have ratelimited the action to 10 cards a second
2023-08-06 17:53:07 -04:00
ebbit1q
cb52605928 use a regex to remove all reserved characters from file names (#4804) 2023-08-06 17:46:22 -04:00
Guangcong Luo
bd3100dcda Update macOS Monterey app icon (#4805) 2023-08-06 17:46:03 -04:00
ebbit1q
afb7c35cfd add a way to replace the user facing list of disallowed words (#4807) 2023-08-06 17:45:37 -04:00
tooomm
5b694a55d2 CI: Remove fedora 36 (#4799)
* remove fedora 36

* remove fedora 36

* remove fedora 36
2023-08-04 02:02:44 +02:00
SlightlyCircuitous
f750a4cd72 Remove Ubuntu 22.10 Kinetic Kudu Build (#4826) 2023-07-25 21:34:18 -04:00
SlightlyCircuitous
eddeaaf52a Add Debian 12 "Bookworm" Build (#4812) 2023-06-14 20:28:12 -04:00
tooomm
2b42bee424 Webclient: lint (#4810)
* lint

* lint
2023-05-14 00:09:40 +02:00
SlightlyCircuitous
b9706c0cc1 Add indentation (#4806)
Improves readability
2023-05-09 17:08:06 -04:00
transifex-integration[bot]
800b21b000 Apply translations in pt_BR (#4801)
100% translated for the source file 'cockatrice/cockatrice_en@source.ts'
on the 'pt_BR' language.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2023-05-09 17:07:23 -04:00
transifex-integration[bot]
d1736a25bb Translate webclient/src/i18n-default.json in pt_BR (#4800)
100% translated for the source file 'webclient/src/i18n-default.json'
on the 'pt_BR' language.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2023-05-09 17:06:40 -04:00
ebbit1q
f269e5fe58 add fedora 38, scheduled to release next week (#4785) 2023-04-30 14:21:13 +02:00
tooomm
45a5296013 CI: Fix versions before deprecation (#4798)
* fix deprecation warning

* fix deprecation warning
2023-04-30 13:51:09 +02:00
SlightlyCircuitous
5f0ab2a177 CPack DMG Installer with Background Image (#4787)
* Set some CPack variables

WIP for DMG background image

* Use a .tif for background

* Add Background image and DS_Store script

Script is from https://www.kitware.com//creating-mac-os-x-packages-with-cmake/ with cmake.app changed to cockatrice.app

* Add position for all icons

* Adjust background size and icon position

This draws dbconverter off the screen as a hack to hide it, so hopefully it doesn't break.

* Add files via upload

* Change Icon Position

Icon position is probably relative to the window edge, not the edge of the screen so the numbers should be lower.

* Adjust Icon Position and Window Boundaries

Applescript seems to use the center of the icon rather than the left corner so the coordinates need to be adjusted by 64 px. 

The finder window counts the 22 px of the title bar in the window size so 22 must be added to the fourth coord of position to fit the entire image.

* Fix spelling of Servatrice

* Satifsfy Code Style

* Use vector format (eps)

* Delete background.tif

* Use vector format (eps)

* Use the right file extention in the script

* Point to svg

* Use svg

* Add svg for background image

* Delete background.eps

* Use plain SVG, adjust text

* Move applescript to /cmake

* Move background to /cmake

* Point CPack to /cmake

background and applescript now reside there

* Revert to .tif

* Use compressed .tif

* Use more specific file name

* Script expects .background:background.tif
2023-04-29 23:13:01 -04:00
ebbit1q
70ab02987a save sets dialog size (#4791)
* save sets dialog size

* reset sorting when restoring

* add to gitignore
2023-04-29 23:10:59 -04:00
ebbit1q
421da882d8 qt 6.5 compatibility (#4796)
* remove metatypes definitions

* deprecation of QApplication::setActiveWindow
2023-04-29 23:09:26 -04:00
SlightlyCircuitous
1a40102f71 Remove Debian 10 "Buster" Build (#4789)
* Drop Debian 10 Support

EOL September 10 2022

* Drop Debian 10 Support

EOL September 10, 2022

* Drop Debian 10 Support

EOL September 10, 2022
2023-04-19 23:04:35 +02:00
SlightlyCircuitous
1fbc10cd77 Add Ubuntu 23.04 Lunar Lobster Build (#4784)
* Create Lunar Dockerfile

Note that qt6-svg-dev and qt6-websockets-dev are renamed replacements for libqt6svg6-dev and libqt6websockets6-dev, respectively.

* Add Ubuntu Lunar

* Add Ubuntu Lunar to Template
2023-04-12 22:31:39 +02:00
skwerlman
87462398d8 show deck hash even when its invalid (#4595)
* show deck hash even when its invalid

* remove invalid deck hashes entirely

---------

Co-authored-by: ebbit1q <ebbit1q@gmail.com>
2023-04-10 22:29:29 +02:00
dependabot[bot]
b33246b29f Bump loader-utils from 2.0.3 to 2.0.4 in /webclient (#4707)
Bumps [loader-utils](https://github.com/webpack/loader-utils) from 2.0.3 to 2.0.4.
- [Release notes](https://github.com/webpack/loader-utils/releases)
- [Changelog](https://github.com/webpack/loader-utils/blob/v2.0.4/CHANGELOG.md)
- [Commits](https://github.com/webpack/loader-utils/compare/v2.0.3...v2.0.4)

---
updated-dependencies:
- dependency-name: loader-utils
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-01 22:31:44 -04:00
transifex-integration[bot]
dbffe30f63 Translate /webclient/src/i18n-default.json in es (#4657)
translation completed for the source file '/webclient/src/i18n-default.json'
on the 'es' language.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2023-04-01 22:30:59 -04:00
ebbit1q
9ce450d0b0 add _substr_[pos]_[len] to picture download templates (#4762) 2023-04-01 22:29:57 -04:00
ebbit1q
f5f8acf1fd save downloaded xmls (#4736) 2023-04-01 22:19:57 -04:00
tooomm
304ed3cd60 UI: User info dialog updates (#4533) 2023-04-01 21:50:49 -04:00
dependabot[bot]
07248692ce Bump webpack from 5.74.0 to 5.76.1 in /webclient (#4774)
Bumps [webpack](https://github.com/webpack/webpack) from 5.74.0 to 5.76.1.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.74.0...v5.76.1)

---
updated-dependencies:
- dependency-name: webpack
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-15 23:46:50 -04:00
Brent Clark
cef99cba71 Add left nav (#4705)
* Automated translation update ( bf08a04cda )

* Add Layout component wip

* finish layout implementation

* convert header to left nav

* better nav item spacing

* return source files to original glory

* lint fix

* Remove height limit on login screen

* fix top spacing on 3-panel layout

---------

Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Brent Clark <brent@backboneiq.com>
2023-03-15 23:45:55 -04:00
SlightlyCircuitous
cab5f29b57 Add 'persistent' as valid attribute of related (#4771) 2023-03-10 02:36:17 +01:00
Basile Clement
55a2f75d16 Make cards rounded (#4765)
* Make cards rounded

Magic cards have rounded corners, and playing cards tend to have rounded
corners as well, but Cockatrice currently displays rectangular cards.

This can cause visual glitches when using image scans where the border
does not extend in the corner, and for this reason Cockatrice always
draws a (rectangular) border around the card to try and make it look a
bit better.

In this patch I take a different approach: rather than try to make
rounded pegs, er, cards, go into a square hole, the hole is now rounded.
More precisely, the AbstractCardItem now has a rounded rectangular shape
(with a corner of 5% of the width of the card, identical to that of
modern M:TG physical cards).

As a side effect, the card drawing gets a bit simplified by getting rid
of transformPainter() when drawing the card outline and using the
QPainter::drawPixmap overloads that takes a target QRectF instead.  This
means we no longer have to bother about card rotation when painting
since that's taken care of by the Graphics View framework (which
transformPainter() undoes).

* format

* Also give PileZone rounded corners

* Forgot untap status + bits of CardDragItem

* fix deckviewcard calculations

* Rounded CardInfoPicture
2023-03-07 01:41:08 +01:00
ebbit1q
a416ee8f2b set target to sha in release creation (#4763) 2023-03-03 11:40:16 -05:00
Basile Clement
c14936c63c PictureLoader: Replace downloadedPics cache with QNetworkCache (#4756)
* PictureLoader: Replace downloadedPics cache with QNetworkCache

Currently when the "Download card pictures on the fly" option is
enabled, Cockatrice stores downloaded pictures into a downloadedPics
sub-folder, keyed on set and card name. If a picture is found in that
folder, we never try to download a picture for that card ever again
(until it is reprinted in a more recent set, I guess).

This has the unfortunate consequence that if you change the URLs for
downloading card images, the changes are not applied to cards that
already have their picture downloaded. In particular, if you use
localized card pictures (through !sflang!), you get a mix of cards in
different languages depending on the currently configured language at
the time each card was downloaded.

This patch removes that mechanism in favor of setting a
QNetworkDiskCache on the QNetworkAccessManager used by the PictureLoader
to download pictures. The QNetworkDiskCache caches the picture keyed on
their URL: if the URL changes, a new request will be made. In
particular, if you use picture URLs with !sflang! and change the
language, pictures for the current language will be downloaded even for
cards that already have a picture. The QNetworkDiskCache is configured
with a maximum size of 4GB, which should be enough to hold one
high-quality JPEG for each M:TG card in existence.

Note that this does not affect the existing mechanism for defining
custom card art, either through the CUSTOM directory or the set-based
one.  Cockatrice will still read existing cards in the downloadedPics
directory as before, it will just no longer write into that directory
(since pictures are cached by the QNetworkDiskCache instead). To fully
switch to the new cache, users should use the "Delete Downloaded Images"
button in the settings: it will clear the QNetworkDiskCache but also
remove the downloadedPics directory.

* Do not use system cache dir for portable installs

* Add settings for network cache size

* Delete corrupted cache entries

* Use old-style connect() syntax (Qt5 build failure)

* Add setNetworkCacheSizeInMB to test mocks

* setTransferTimeout was added in Qt 5.15

* Improve logging messages

We now have the following messages

 - "Trying to download picture from url: URL" before loading a picture
   when picture download is enabled
 - "Trying to load picture from cache: URL" before loading a picture
   when picture download is disabled (i.e. cache-only offline mode)
 - "Removing corrupted cache file for url URL and retrying (ERR)" when
   when we fail to load a picture from the cache. Usually, this should
   be due to the timeout, in which case ERR will be "Operation
   Canceled".
 - "Download failed for url URL (ERR)" when there was an error
   downloading a picture from the network (ERR is the error message)
 - "Following redirect to URL" and "Following cached redirect to URL"
   when following a redirect (from network/from cache)
 - "Image successfully downloaded from URL" and "Image successfully
   loaded from cached url at URL" on success
 - "Possible cached/downloaded picture at URL could not be loaded" on
   ImageReader error

* Clarify that network cache is on disk

Also migrate "Delete Downloaded Image" to a "Clear" button right next to
the network cache size.

* Remove qPrintable

* Move pixmap cache settings to card sources

* qDebug().nospace()

* some formatting on debug messages

* format

* inverted condition

---------

Co-authored-by: ebbit1q <ebbit1q@gmail.com>
2023-03-03 16:55:17 +01:00
Basile Clement
42e7a8b423 Better support Double-Faced Cards (#4753)
* Better support Double-Faced Cards

This patch allows cards to be (virtually) transformed into other cards
while preserving their state, essentially implemeting the MTG mechanic
of the same name.

On the server side, this is implemented by allowing cards to be "stashed
away". A card that is stashed away is not in any zone, but is instead
owned by another card. When a token is destroyed due to a zone change,
if it had a card stashed away, that card is placed in the target zone
instead of the token.

On the database side, `attach="transform"` is used on `<reverse>` and
`<reverse-related>` to indicate that the created token should be
transformed this way.

Old servers ignore the new field in `Command_CreateToken` and will
perform a regular attachment, as currently.

* Address review comments

* Prevent tokens from being stashed

* format.sh
2023-03-03 16:54:51 +01:00
ebbit1q
4558b1c7ef set the target in the created release (#4758)
the target needs to be the current short commit hash because it is being
compared to by the updater, the default is "master" which breaks the
updater.
2023-02-23 01:11:41 +01:00
SlightlyCircuitous
f444ba9665 Corrected edition search syntax (#4752)
* Replaced "e:lea,leb" with "e:lea or e:leb"
* Removed "e:lea,leb -(e:lea e:leb) (Cards that appear in Alpha or Beta but not in both editions)" as this does not produce results in Cockatrice (even when using "or" instead of "," as above)
2023-02-16 12:08:00 +01:00
ebbit1q
787c8d740b replace github release actions with the gh tool in bash (#4746)
* replace github release actions with the gh tool in bash

* set macos 10.15 qt version to 6.2 LTS
2023-02-10 03:33:48 -05:00
Basile Clement
ef38a8bb2b Re-add missing '/' separator in <pt> after b282df2e27 (#4747)
In b282df2e27 (#4728) the logic for
creating <pt> values was updated to avoid adding a final slash after an
existing power value and missing toughness value. This works by setting
the ptSeparator to an empty string when either the power or the
toughness is undefined. However, due to the ptSeparator variable being
scoped out of the `for` loop, this causes all remaining cards to have an
empty string as a separator, ending up with <pt> values of e.g. 21
instead of 2/1.

Moreover, the implementation from #4728 is ambiguous in the case of a
card having a toughness value but no power value: in that situation, it
creates a <pt> entry with the toughness value and no separator, which is
not a good idea since it is not possible to know if <pt>2</pt> means
power 2 and no toughness, or no power and toughness 2 (Cockatrice takes
the first interpretation).

To avoid ambiguities, the <pt> value is now one of:

 1. A regular P/T value when the card has power and toughness
 2. A simplified P value when the card has power but no toughness
 3. A simplified /T value when the card has toughness but no power
 4. Absent when the card has neither power nor toughness

Note that, as far as I can tell, Cockatrice seems to (incorrectly, IMO)
ignore the initial slash if present in Player::parsePT, and treat /T as
a power value. However that is a separate issue: this patch is concerned
with Oracle and ensuring proper values in cards.xml, not with how
Cockatrice interprets those values.
2023-02-08 19:59:14 +01:00
Jeremy Letto
b5d35d346a Add architecture image to webclient README (#4745) 2023-02-08 18:04:23 +01:00
Basile Clement
9a7b15d19b Allow revealing specific cards from hand and library (#4743)
Currently Cockatrice allows revealing the whole hand, or one card at
random from the hand. Sometimes, a player needs to reveal a specific
card from their hand instead, which is not supported. To achieve a
similar effect, players usually move the corresponding card (or cards)
to a public zone, then back to their hand. While this works, it is
unsatisfactory (compared to a regular reveal, you can't keep the
"revealed" window around, for one) and somewhat unintuitive.

This patch adds a "Reveal to..." menu to cards and card selections in
the player's hand or in custom zones (this includes looking at the
player's library). This menu allows revealing a card or set of cards to
any given player, or to all players.

To implement this functionality at the protocol level, the existing
RevealCards command is extended to support revealing multiple specific
cards. This is done by making `card_id` a non-packed repeated field in
the `Command_RevealCards` and `Event_RevealCards` protobufs.  Using a
non-packed repeated fields allows maintaining backwards compatibility:
an empty optional field is encoded the same way as an empty non-packed
list, an optional field with a value is encoded the same way as a
one-element non-packed list, and when decoding a multi-elements
non-packed list as an optional, only the last item in the list is read.

Since the RevealCards command already exists, and due to the compatible
encodings, a new client connecting to an old server can reveal a single
specific card from their hand. When trying to reveal multiple cards at
once, the old server will only see the request for one of the cards to
be revealed, and the player will have to reveal each card separately.

On the other hand, `Event_RevealedCards` already has an explicit list of
cards revealed by the server, and the `card_id` field is only used when
exactly one card has been revealed: thus, old and new clients will
behave identically when receiving a new `Event_RevealedCards`. In
particular, if a player using a new client reveals multiple cards from
their hand on a new server, another player using an old client will
correctly see all the revealed cards.

The approach used to build the "Reveal to..." menu is slightly different
from the approach used to build other player selection menus. Because
the "Reveal to..." menu is specific to each card, but must also be
updated whenever a player is added to or removed from the game, I chose
to re-create it on the fly whenever a card is clicked, as that seemed
the safest way to avoid both memory leaks and inconsistent state given
my understanding of the code.
2023-02-07 17:12:04 -05:00
Zach H
ba35a11e82 Find OpenSSL on Windows (#4730) 2023-02-07 16:47:50 -05:00
Basile Clement
00c9efe541 Enable buttons for current game when receiving server response (#4737)
* Enable buttons for current game when receiving server response

Previously, upon joining a game, we were unconditionally re-enabling the
"Join" button in the lobby, even if it was not enabled in the first
place, causing #4698. This could also lead to issues where if the user
selects a different game after joining (which they can do in case of
e.g. network connectivity issues), the "Join as spectator" button could
get incorrectly disabled.

This fixes #4698 by re-enabling the buttons based on the state of the
currently selected game at the time the response is received. This also
avoids inconsistencies if a different game has been selected in between
joining and receiving a response from the server.

* Typo: enable gameButton in enableButtons

The "create game" button was incorrectly being disabled in enableButtons
whereas (as the name indicates) it should have been enabled

* Remove misleading comment about race conditions
2023-02-06 13:49:45 +01:00
cajun
44d1ab348b Add Oracle support for persistent & Fix persistent on reverse-related (#4742)
* fix persistent reverse-related

* create relations from spellbook property

* run format.sh
2023-02-06 07:00:54 -05:00
Zach H
f25e4785ae FIX #4665: Address missing sound on Qt5 Builds (#4733)
* FIX #4665: Address missing sound on Qt5 Builds

* FIX #4665: Address missing sound on Qt5 Builds

* Include both engines
2023-02-05 22:05:47 -05:00
Zach H
4c290aec57 Fix #4706: don't replace ampersands when loading from plain text (#4734)
* Fix #4706: Exit linting early if a card with the exact name is found first

* Remove ampersand conversion

* put back

* Update tests

* Format

* don't use qsizetype

---------

Co-authored-by: ebbit1q <ebbit1q@gmail.com>
2023-01-30 00:37:28 +01:00
tooomm
42d1d66d9b CI: macOS-10.15 environment is deprecated (#4664)
* macOS-10.15 environment is deprecated

Build for 10.14 has to be dropped.
Build for 10.15 can be preserved via 11.

* update xcode versions

* Xcode 13.0 doesn't work for us on Big Sur

* [skip ci] update list of binaries
2023-01-29 12:47:42 -05:00
ebbit1q
06c25301a5 update build dockerfiles (#4732) 2023-01-23 18:14:35 -05:00
transifex-integration[bot]
43dbb45cc6 Translate /webclient/src/i18n-default.json in it (#4718)
translation completed updated for the source file '/webclient/src/i18n-default.json'
on the 'it' language.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2023-01-23 02:26:24 -05:00
dependabot[bot]
9fb62de5cb Bump json5 from 1.0.1 to 1.0.2 in /webclient (#4729)
Bumps [json5](https://github.com/json5/json5) from 1.0.1 to 1.0.2.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v1.0.1...v1.0.2)

---
updated-dependencies:
- dependency-name: json5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-23 02:26:12 -05:00
PakhomCh
b282df2e27 Fixed ptSeparation oracle issue (#4728)
* Fixed psTeparation oracle issue

* Update oracle/src/oracleimporter.cpp

Co-authored-by: PakhomCh <pakhomch@gmail.com>
2023-01-16 13:30:19 -05:00
ebbit1q
da8f57f397 remove link to google doc roadmap from readme (#4727) 2023-01-02 22:07:18 -05:00
SlightlyCircuitous
e9f1992c7f Add URL to explain message macros (#4712)
* Add URL Link to Explain Message Macros

* Add URL Link to Explain Message Macros

* Revert custom shortcuts wiki link

* Conform to formatting guidelines
2022-12-11 23:54:06 +01:00
transifex-integration[bot]
2c94a6a64e Apply translations in it (#4714)
translation completed for the source file '/cockatrice/cockatrice_en@source.ts'
on the 'it' language.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2022-12-11 23:42:51 +01:00
SlightlyCircuitous
6a5e0a8501 Display Welcome Message as Most Recent Message (#4720)
* Move Join Message Block

- Moves Join Message code block to after the for loop that gets old chat message, which makes the Join Message the most recent message in the chat box instead of the oldest
-Only the rc.enqueuePostResponseItem() line really needs to move for functionality, but I have moved the whole block for readability

* Comply with formatting guide

-Remove offending white space
2022-12-11 23:40:47 +01:00
Jeremy Letto
26d7fe2ff0 Webatrice: update deps (#4700)
* save work

* fix reset styling

* fix toast reducer

* update non-react deps

* update react libraries

* remove jquery, use sanitize-html instead

* add missing change

* fix deps and dev deps

* update workflow to target Node 16

* run @mui/codemod to remove @mui/styles

* add default body font size

* update react 17 to 18

* declare enum before use

* add rel attr to links

* fix font sizing issue

* trailing commas

* refactor deep destructuring

Co-authored-by: Jeremy Letto <jeremy.letto@datasite.com>
2022-11-01 12:41:42 -05:00
ebbit1q
5854a635ca fix deprecated usage of set-output (#4699) 2022-10-31 23:26:26 +01:00
ebbit1q
3d4858b840 use qt6 in arch builds (#4691)
* use qt6 in arch builds

* fix 6.4.0 deprecations
2022-10-31 23:26:13 +01:00
ebbit1q
dec2a252fa remove dependency on deprecated qt5 libraries for qt6 (#4692)
* remove dependency on deprecated qt5 libraries for qt6

removes the use of qt6-5compat for builds
replaces use of QRegExp with QRegularExpression
fixes incorrect usage of QRegExp
removes use of QTextCodec
fixes incorrect usage of QTextCodec
sets qtlinguist as a required component for qt6

* fix anchoredPattern not existing in qt 5.11
2022-10-31 23:24:11 +01:00
Jeremy Letto
f619ef23fd Upgrade to MUI 5 (#4606)
* save work

* fix perf issue on i18n rollup

* fix reset styling

* move body line-height from reset

Co-authored-by: Jeremy Letto <jeremy.letto@datasite.com>
2022-10-29 20:17:03 -05:00
Antoine Dahan
72743e834e Selecting game already open in a tab brings user to that tab. (#4653)
* When trying to join a game from GameSelector that's already been joined by you, navigate to its game tab.

* return immediately, do not change button states

Co-authored-by: ebbit1q <ebbit1q@gmail.com>
2022-10-29 15:46:50 +02:00
Tobyclark
8e4ddf366c Added hint when drawing 0 cards (#4697)
* Logging a player drawing 0 cards will now result in the message "player had no cards left to draw."

* Added hint when drawing when deck is empty

* Added hint when drawing when deck is empty

* Added hint when drawing when deck is empty

* Update cockatrice/src/messagelogwidget.cpp

update log message to present tense

Co-authored-by: ebbit1q <ebbit1q@gmail.com>

* added deckIsEmpty parameter to messagelogwidget::logDrawCards

* run format

Co-authored-by: ebbit1q <ebbit1q@gmail.com>
2022-10-29 15:46:29 +02:00
ebbit1q
b99bd0176a update deprecated workflow actions (#4690) 2022-10-17 23:55:07 -04:00
ebbit1q
a68b98b245 update vcpkg submodule (#4689) 2022-10-17 18:23:39 -04:00
transifex-integration[bot]
a69d6ff1b4 Translate /oracle/oracle_en@source.ts in it (#4688)
translation completed for the source file '/oracle/oracle_en@source.ts'
on the 'it' language.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2022-10-17 16:43:31 -04:00
transifex-integration[bot]
ec679e95fd Translate /webclient/src/i18n-default.json in fr (#4678)
translation completed updated for the source file '/webclient/src/i18n-default.json'
on the 'fr' language.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2022-10-17 16:43:14 -04:00
ebbit1q
3f78235a74 fix updater with changes in release name (#4666)
* fix updater with changes in release name

* clangify
2022-10-17 16:42:08 -04:00
ebbit1q
c8a2fd78b0 fix crash when right clicking a user's name in a replay (#4681)
this happened when viewing a replay with the "view replay" option in the
top menu, instead of using the replays tab while connected to a server.
this uses the local game player instead of the online one which does not
initialize the player info of the local spectating player, this causes a
crash when opening the context menu on another player in the replay from
one of their chat messages as it tries to check if you're a registered
user and could add them as a friend etc.
it now regards the uninitialized player info as an unregistered user and
will not show these options.
2022-10-17 16:40:27 -04:00
ebbit1q
45cf08111a fix crash when a cardmenu becomes an orphan (#4682)
* fix crash when a cardmenu becomes an orphan

when a cardmenu is closed the cursor on that card reverts to the open
hand, this crashed the client when that card would be destroyed or moved
the act of reverting to the open hand now happens as an emitted signal,
this way it just doesn't exist anymore when the card is deleted.

* simplify fix
2022-10-17 16:38:44 -04:00
ebbit1q
527ac36129 update card menu immediately on card counter event (#4686)
fixes #4658
2022-10-17 16:37:32 -04:00
ebbit1q
a7232513a7 remove cards being looked at from the count on drawing (#4671) 2022-10-17 16:35:54 -04:00
ebbit1q
90f187e885 fix 4679 (#4680) 2022-10-02 13:23:35 -04:00
705 changed files with 102705 additions and 54462 deletions

View File

@@ -8,9 +8,11 @@ RUN pacman --sync --refresh --sysupgrade --needed --noconfirm \
gtest \
mariadb-libs \
protobuf \
qt5-base \
qt5-multimedia \
qt5-svg \
qt5-tools \
qt5-websockets \
qt6-base \
qt6-imageformats \
qt6-multimedia \
qt6-svg \
qt6-tools \
qt6-translations \
qt6-websockets \
&& pacman --sync --clean --clean --noconfirm

View File

@@ -1,25 +0,0 @@
FROM debian:10
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
ccache \
clang-format \
cmake \
file \
g++ \
git \
liblzma-dev \
libmariadb-dev-compat \
libprotobuf-dev \
libqt5multimedia5-plugins \
libqt5sql5-mysql \
libqt5svg5-dev \
libqt5websockets5-dev \
protobuf-compiler \
qt5-default \
qtbase5-dev \
qtmultimedia5-dev \
qttools5-dev \
qttools5-dev-tools \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

View File

@@ -17,6 +17,7 @@ RUN apt-get update && \
libqt5svg5-dev \
libqt5websockets5-dev \
protobuf-compiler \
qt5-image-formats-plugins \
qtmultimedia5-dev \
qttools5-dev \
qttools5-dev-tools \

27
.ci/Debian12/Dockerfile Normal file
View File

@@ -0,0 +1,27 @@
FROM debian:12
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
build-essential \
ccache \
clang-format \
cmake \
file \
g++ \
git \
libgl-dev \
liblzma-dev \
libmariadb-dev-compat \
libprotobuf-dev \
libqt6multimedia6 \
libqt6sql6-mysql \
qt6-svg-dev \
qt6-websockets-dev \
protobuf-compiler \
qt6-image-formats-plugins \
qt6-l10n-tools \
qt6-multimedia-dev \
qt6-tools-dev \
qt6-tools-dev-tools \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

View File

@@ -1,21 +0,0 @@
FROM fedora:35
RUN dnf install -y \
@development-tools \
ccache \
cmake \
desktop-file-utils \
file \
gcc-c++ \
git \
hicolor-icon-theme \
libappstream-glib \
mariadb-devel \
protobuf-devel \
qt5-{qttools,qtsvg,qtmultimedia,qtwebsockets}-devel \
rpm-build \
sqlite-devel \
wget \
xz-devel \
zlib-devel \
&& dnf clean all

View File

@@ -1,4 +1,4 @@
FROM fedora:36
FROM fedora:40
RUN dnf install -y \
ccache \
@@ -7,7 +7,8 @@ RUN dnf install -y \
git \
mariadb-devel \
protobuf-devel \
qt6-{qttools,qtsvg,qtmultimedia,qtwebsockets,qt5compat}-devel \
qt6-{qttools,qtsvg,qtmultimedia,qtwebsockets}-devel \
qt6-qtimageformats \
rpm-build \
xz-devel \
zlib-devel \

15
.ci/Fedora41/Dockerfile Normal file
View File

@@ -0,0 +1,15 @@
FROM fedora:41
RUN dnf install -y \
ccache \
cmake \
gcc-c++ \
git \
mariadb-devel \
protobuf-devel \
qt6-{qttools,qtsvg,qtmultimedia,qtwebsockets}-devel \
qt6-qtimageformats \
rpm-build \
xz-devel \
zlib-devel \
&& dnf clean all

View File

@@ -1,4 +1,4 @@
FROM ubuntu:focal
FROM ubuntu:20.04
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
@@ -18,6 +18,7 @@ RUN apt-get update && \
libqt5websockets5-dev \
protobuf-compiler \
qt5-default \
qt5-image-formats-plugins \
qtmultimedia5-dev \
qttools5-dev \
qttools5-dev-tools \

View File

@@ -1,4 +1,4 @@
FROM ubuntu:jammy
FROM ubuntu:22.04
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
@@ -13,12 +13,12 @@ RUN apt-get update && \
liblzma-dev \
libmariadb-dev-compat \
libprotobuf-dev \
libqt6core5compat6-dev \
libqt6multimedia6 \
libqt6sql6-mysql \
libqt6svg6-dev \
libqt6websockets6-dev \
protobuf-compiler \
qt6-image-formats-plugins \
qt6-l10n-tools \
qt6-multimedia-dev \
qt6-tools-dev \

View File

@@ -0,0 +1,27 @@
FROM ubuntu:24.04
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
build-essential \
ccache \
clang-format \
cmake \
file \
g++ \
git \
libgl-dev \
liblzma-dev \
libmariadb-dev-compat \
libprotobuf-dev \
libqt6multimedia6 \
libqt6sql6-mysql \
qt6-svg-dev \
qt6-websockets-dev \
protobuf-compiler \
qt6-image-formats-plugins \
qt6-l10n-tools \
qt6-multimedia-dev \
qt6-tools-dev \
qt6-tools-dev-tools \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

View File

@@ -1,24 +0,0 @@
FROM ubuntu:bionic
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
ccache \
clang-format \
cmake \
file \
g++ \
git \
liblzma-dev \
libmariadb-dev-compat \
libprotobuf-dev \
libqt5multimedia5-plugins \
libqt5sql5-mysql \
libqt5svg5-dev \
libqt5websockets5-dev \
protobuf-compiler \
qt5-default \
qtmultimedia5-dev \
qttools5-dev \
qttools5-dev-tools \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

View File

@@ -127,7 +127,7 @@ fi
# Add cmake --build flags
buildflags=(--config "$BUILDTYPE")
if [[ $PARALLEL_COUNT ]]; then
if [[ $(cmake --build /not_a_dir --parallel |& head -1) =~ parallel ]]; then
if [[ $(cmake --build /not_a_dir --parallel 2>&1 | head -1) =~ parallel ]]; then
# workaround for bionic having an old cmake
echo "this version of cmake does not support --parallel, using native build tool -j instead"
buildflags+=(-- -j "$PARALLEL_COUNT")
@@ -160,7 +160,13 @@ cmake .. "${flags[@]}"
echo "::endgroup::"
echo "::group::Build project"
cmake --build . "${buildflags[@]}"
if [[ $RUNNER_OS == Windows ]]; then
# Enable MTT, see https://devblogs.microsoft.com/cppblog/improved-parallelism-in-msbuild/
# and https://devblogs.microsoft.com/cppblog/cpp-build-throughput-investigation-and-tune-up/#multitooltask-mtt
cmake --build . "${buildflags[@]}" -- -p:UseMultiToolTask=true -p:EnableClServerMode=true
else
cmake --build . "${buildflags[@]}"
fi
echo "::endgroup::"
if [[ $USE_CCACHE ]]; then
@@ -171,7 +177,7 @@ fi
if [[ $MAKE_TEST ]]; then
echo "::group::Run tests"
ctest -C "$BUILDTYPE"
ctest -C "$BUILDTYPE" --output-on-failure
echo "::endgroup::"
fi
@@ -183,6 +189,12 @@ fi
if [[ $MAKE_PACKAGE ]]; then
echo "::group::Create package"
if [[ $RUNNER_OS == macOS ]]; then
# Workaround https://github.com/actions/runner-images/issues/7522
echo "killing XProtectBehaviorService"; sudo pkill -9 XProtect >/dev/null || true;
echo "waiting for XProtectBehaviorService kill"; while pgrep "XProtect"; do sleep 3; done;
fi
cmake --build . --target package --config "$BUILDTYPE"
echo "::endgroup::"

View File

@@ -4,6 +4,7 @@
# where SUFFIX is either available in the environment or as the first arg
# if MAKE_ZIP is set instead a zip is made
# expected to be run in the build directory unless BUILD_DIR is set
# adds output to GITHUB_OUTPUT
builddir="${BUILD_DIR:=.}"
findrx="Cockatrice-*.*"
@@ -50,5 +51,5 @@ fi
cd "$oldpwd"
relative_path="$path/$filename"
ls -l "$relative_path"
echo "::set-output name=path::$relative_path"
echo "::set-output name=name::$filename"
echo "path=$relative_path" >>"$GITHUB_OUTPUT"
echo "name=$filename" >>"$GITHUB_OUTPUT"

View File

@@ -4,6 +4,7 @@
# the releases are first made as drafts and will be vetted by a human
# it just has to provide a template
# this requires the repo to be unshallowed
# adds output to GITHUB_OUTPUT
template_path=".ci/release_template.md"
body_path="/tmp/release.md"
beta_regex='beta'
@@ -22,22 +23,22 @@ fi
# create title
if [[ $TAG =~ $beta_regex ]]; then
echo "::set-output name=is_beta::yes"
echo "is_beta=yes" >>"$GITHUB_OUTPUT"
title="$TAG"
echo "creating beta release '$title'"
elif [[ ! $(cat CMakeLists.txt) =~ $name_regex ]]; then
echo "::error file=$0::could not find releasename in CMakeLists.txt"
exit 1
else
echo "::set-output name=is_beta::no"
echo "is_beta=no" >>"$GITHUB_OUTPUT"
name="${BASH_REMATCH[1]}"
version="${TAG##*-}"
title="Cockatrice $version: $name"
no_beta=1
echo "::set-output name=friendly_name::$name"
echo "friendly_name=$name" >>"$GITHUB_OUTPUT"
echo "creating full release '$title'"
fi
echo "::set-output name=title::$title"
echo "title=$title" >>"$GITHUB_OUTPUT"
# add release notes template
if [[ $no_beta ]]; then
@@ -60,9 +61,9 @@ fi
all_tags="
$(git tag)" # tags are ordered alphabetically
before="${all_tags%%
$TAG*}" # strip line with current tag an all lines after it
"$TAG"*}" # strip line with current tag an all lines after it
# note the extra newlines are needed to always have a last line
if [[ $all_tags == $before ]]; then
if [[ $all_tags == "$before" ]]; then
echo "::warning file=$0::could not find current tag"
else
while
@@ -74,7 +75,7 @@ else
beta_list+=" $previous" # add to list of skipped betas
next_before="${before%
*}" # strip the last line
if [[ $next_before == $before ]]; then
if [[ $next_before == "$before" ]]; then
unset previous
break
fi
@@ -108,5 +109,5 @@ else
fi
# write to file
echo "::set-output name=body_path::$body_path"
echo "body_path=$body_path" >>"$GITHUB_OUTPUT"
echo "$body" >"$body_path"

View File

@@ -4,25 +4,24 @@
git push -d origin --REPLACE-WITH-BETA-LIST--
-->
<!-- This list of binaries should be updated every time the ci is changed to
<!-- This list of binaries should be updated every time the CI is changed to
include different targets -->
<pre>
<b>Pre-compiled binaries we serve:</b>
- <kbd>Windows 7/8/10/11 (32-bit)</kbd>
- <kbd>Windows 7/8 (64-bit)</kbd>
- <kbd>Windows 10/11 (64-bit)</kbd>
- <kbd>macOS 10.14</kbd> ("Mojave")
- <kbd>macOS 10.15</kbd> ("Catalina")
- <kbd>macOS 11.0+</kbd> ("Big Sur")
- <kbd>Ubuntu 18.04</kbd> ("Bionic Beaver")
- <kbd>Ubuntu 20.04</kbd> ("Focal Fossa")
- <kbd>Ubuntu 22.04</kbd> ("Jammy Jellyfish")
- <kbd>Debian 10</kbd> ("Buster")
- <kbd>Windows 10+</kbd>
- <kbd>Windows 7+</kbd>
- <kbd>macOS 14+</kbd> ("Sonoma") / Apple M
- <kbd>macOS 13+</kbd> ("Ventura") / Intel
- <kbd>Ubuntu 24.04 LTS</kbd> ("Noble Numbat")
- <kbd>Ubuntu 22.04 LTS</kbd> ("Jammy Jellyfish")
- <kbd>Ubuntu 20.04 LTS</kbd> ("Focal Fossa")
- <kbd>Debian 12</kbd> ("Bookworm")
- <kbd>Debian 11</kbd> ("Bullseye")
- <kbd>Fedora 35</kbd>
- <kbd>Fedora 36</kbd>
<kbd>We are also packaged in Arch Linux's official community repository, courtesy of @FFY00</kbd></i>
<kbd>General linux support is available via a flatpak package (Flathub)</kbd></i>
- <kbd>Fedora 41</kbd>
- <kbd>Fedora 40</kbd>
<i>We are also packaged in <kbd>Arch Linux</kbd>'s official "extra" repository, courtesy of @FFY00</i>
<i>General Linux support is available via a <kbd>flatpak</kbd> package (Flathub)</i>
</pre>
@@ -30,22 +29,24 @@ include different targets -->
We're pleased to announce the newest official release: <kbd>--REPLACE-WITH-RELEASE-TITLE--</kbd>
We hope you enjoy the changes made and we have listed all changes, with their corresponding tickets, since the last version of Cockatrice was released for your convenience.
We hope you enjoy the changes made! All improvements with their corresponding tickets since the last version of Cockatrice are listed in the changelog below.
If you ever encounter a bug, have a suggestion or idea, or feel a need for a developer to look into something, please feel free to [open a ticket](https://github.com/Cockatrice/Cockatrice/issues). ([How to create a GitHub Ticket for Cockatrice](https://github.com/Cockatrice/Cockatrice/wiki/How-to-Create-a-GitHub-Ticket-Regarding-Cockatrice))
If you ever encounter a bug, have a suggestion or idea, or feel a need for a developer to look into something, please feel free to [open a ticket](https://github.com/Cockatrice/Cockatrice/issues). ([How to create a Ticket for Cockatrice](https://github.com/Cockatrice/Cockatrice/wiki/How-to-Create-a-GitHub-Ticket-Regarding-Cockatrice))
For any information relating to Cockatrice, please take a look at our official site: **https://cockatrice.github.io**
For basic information related to the app and getting started, please take a look at our official site: **https://cockatrice.github.io**
If you'd like to help contribute to Cockatrice in any way, check out our [README](https://github.com/Cockatrice/Cockatrice#get-involved-). We're always available to answer questions you may have on how the program works and how you can provide a meaningful contribution.
If you'd like to help and contribute to Cockatrice in any way, check out our [README](https://github.com/Cockatrice/Cockatrice#get-involved-).
We're always available to answer questions you may have on how the program works and how you can provide a meaningful contribution.
## Upgrading Cockatrice
<!-- this optional section puts a warning banner for problems with updating
> ⚠️ **With this release, we no longer provide a ready-to-install binary for:**
> [!IMPORTANT]
> **With this release, we no longer provide a ready-to-install binary for:**
> --DEPRECATED-OS-HERE--
-->
- Run the internal software updater: <kbd>Help → Check for Client Updates</kbd>
Run the internal software updater: <kbd>Help → Check for Client Updates</kbd>
Don't forget to update your card database right after! (<kbd>Help → Check for Card Updates...</kbd>)
@@ -62,14 +63,14 @@ Remove empty headers when done.
-->
<!-- Highlights of the release -->
### ⚠️ Important:
### 🔖 Highlights:
### ✨ New Features:
### 🐛 Fixed Bugs / Resolved issues:
### 🐛 Fixed Bugs / Resolved Issues:
<!-- Complete list of changes (foldable) -->
<details>
<summary>
📘 <b>Show all changes</b> (--REPLACE-WITH-COMMIT-COUNT-- commits)
<b>Show all changes</b> (--REPLACE-WITH-COMMIT-COUNT-- commits)
</summary>
### User Interface
@@ -90,5 +91,6 @@ Remove empty headers when done.
## Special Thanks
<!-- Personalise this a bit! -->
We continue to find it amazing that so many people contribute their time, knowledge, code, testing and more to the project. We'd like to thank the entire Cockatrice community for their efforts.
It's amazing that so many people contribute their time, knowledge, code, testing and more to the project.
We'd like to thank the entire Cockatrice community for their efforts! 🙏
<!-- We'd like to especially recognize @ZeldaZach, --ADD-CONTRIBUTORS-HERE-- for their help in preparing so many amazing new features for the user base. -->

View File

@@ -0,0 +1,58 @@
#!/bin/bash
# ci script to update translation files
# usage:
# $0 cockatrice/cockatrice_en@source.ts cockatrice/src common
# or
# FILE="cockatrice/cockatrice_en@source.ts"
# DIRS="cockatrice/src common"
# $0
# note: directories can't contain spaces
# check parameters
if [[ ! $FILE ]]; then
FILE="$1"
shift
fi
if [[ ! $FILE ]]; then
echo "no output file selected" >&2
exit 2;
fi
if [[ ! $DIRS ]]; then
DIRS="$*"
fi
if [[ ! $DIRS ]]; then
echo "no source directories selected to translate" >&2
exit 2;
fi
if [[ ! -e $FILE ]]; then
echo "output file does not exist at: $FILE" >&2
exit 3;
fi
# print version
if ! lupdate -version; then
echo "failed to run lupdate" >&2
exit 4;
fi
# run lupdate, duplicating the output in stderr and saving it
# for convenience we ignore that $DIRS will be split on spaces
# shellcheck disable=SC2086
if ! got="$(lupdate $DIRS -ts "$FILE" | tee /dev/stderr)"; then
echo "failed to update $FILE with $DIRS" >&2
exit 4;
fi
# trim output
# the line we are interested in is:
# Found xxx source text(s) (x new and xxx already existing)
output="${got##* source text(s) (}" # get stuff in between brackets
output="${output%%)*}" # trim everything after first )
if [[ $output == "$got" ]]; then
echo "could not parse generated output" >&2
exit 4;
fi
# write output to ci environment file
echo "output=$output" >> "$GITHUB_OUTPUT"

View File

@@ -0,0 +1,14 @@
Updated source strings for translations:
- {{ .cockatrice_output }} (Cockatrice)
- {{ .oracle_output }} (Oracle)
<br>
Last changes are based on commit {{ .commit }}.
---
*This PR is automatically generated and updated by the workflow at `.github/workflows/translations-push.yml`. Review [action runs][2].*<br>
*After merging, all changes to the source language are available for translation at [Transifex][1] shortly.*
[1]: https://app.transifex.com/cockatrice/cockatrice/
[2]: https://github.com/Cockatrice/Cockatrice/actions/workflows/translations-push.yml?query=branch%3Amaster

View File

@@ -290,20 +290,21 @@ be included in the next release 👍
Basic workflow for translations:
1. Developer adds a `tr("foo")` string in the code;
2. Every few days, a maintainer updates the `*_en@source.ts files` with the new strings;
3. Transifex picks up the new files from GitHub every 24 hours;
4. Translators translate the new untranslated strings on Transifex;
5. Before a release, a maintainer fetches the updated translations from Transifex.
2. CI updates the `*_en@source.ts files` regularly and creates a PR automatically;
3. Maintainer verifies and merges the change;
4. Transifex picks up the new files from GitHub automatically;
5. Translators translate the new untranslated strings on Transifex;
6. Before a release, a maintainer fetches the updated translations from Transifex.
### Using Translations (for developers) ###
All the user-interface strings inside Cockatrice's source code must be written
in English(US).
All user interface strings inside Cockatrice's source code must be written
in English (US).
Translations to other languages are managed using [Transifex](
https://www.transifex.com/projects/p/cockatrice/).
Adding a new string to translate is as easy as adding the string in the
'tr("")' function, the string will be picked up as translatable automatically
`tr("")` function, the string will be picked up as translatable automatically
and translated as needed.
For example, setting the text of a label in the way that the string
`"My name is:"` can be translated:
@@ -312,7 +313,7 @@ nameLabel.setText(tr("My name is:"));
```
To translate a string that would have plural forms you can add the amount to
the tr call, also you can add an extra string as a hint for translators:
the tr() call, also you can add an extra string as a hint for translators:
```c++
QString message = tr("Everyone draws %n cards", "pop up message", amount);
```
@@ -321,20 +322,46 @@ https://doc.qt.io/qt-5/i18n-source-translation.html#handling-plurals)
If you're about to propose a change that adds or modifies any translatable
string in the code, you don't need to take care of adding the new strings to
the translation files.
Every few days, or when a lot of new strings have been added, someone from the
development team will take care of extracting all the new strings and adding
them to the english translation files and making them available to translators
on Transifex.
the translation files.<br>
We have an automated process to update our language source files on a schedule
and provide the translators on Transifex with the new contents.<br>
Maintainers can also manually trigger this on demand.
### Maintaining Translations (for maintainers) ###
When new translatable strings have been added to the code, a maintainer should
make them available to translators on Transifex. Every few days, or when a lot
of new strings have been added, a maintainer should take care of extracting all
the new strings and add them to the english translation files.
When new translatable strings have been added to the code, a maintainer has to
make them available to translators on Transifex.
To update the english translation files, re-run cmake enabling the appropriate
To help with that, we have an automated CI workflow, that regularly looks at the
code in the master branch, extracts all strings and updates dedicated source string
files with any changes. These updates are not commited right away, the CI creates a
PR for reviewing instead.<br>
After approval, our translation tool automatically picks the changes up and deploys
them to our translators. Be mindful when merging only a few changes!
Once a release is planned, or when a lot of strings have been added or changed, a
maintainer can manually trigger a CI run to extract all strings on demand.
<details>
<summary><b>Manually trigger CI run (Workflow Dispatch)</b></summary>
Maintainers can always request the CI to run on demand if it's required.
Go to the `Actions` tab and select our dedicated translation workflow:
https://github.com/Cockatrice/Cockatrice/actions/workflows/translations.yml
You see a "This workflow has a workflow_dispatch event trigger." hint at the top of
the list.<br>
Select `Run workflow` on the right and trigger a run from master branch.
The CI will now check for changed strings and create a PR if there are any updates.
</details>
<details>
<summary><b>Manually update source strings locally</b></summary>
To update the english source files for translation, re-run cmake enabling the appropriate
parameter and then run make:
```sh
cd cockatrice/build
@@ -357,11 +384,13 @@ It is recommended to disable the parameter afterwards using:
```sh
cmake .. -DUPDATE_TRANSLATIONS=OFF
```
Now you are ready to propose your change.
Now you are ready to commit your changes and open a PR.
Once your change gets merged, Transifex will pick up the modified files
automatically (checked every 24 hours) and update the interface where
translators will be able to translate the new strings.
</details>
Once the changes get merged, Transifex will pick up the modified files
automatically (checked every few hours) and update their online editor where
translators will be able to translate the new strings right in the browser.
### Releasing Translations (for maintainers) ###

49
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,49 @@
# Configuration options: https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
# # Enable version updates for git submodules
# Not yet possible to bump only on tags or releases, see:
# https://github.com/dependabot/dependabot-core/issues/1639
# https://github.com/dependabot/dependabot-core/issues/2192
# Alternative: Action that updates submodule and can be manually run on demand (workflow_dispatch)
# - package-ecosystem: "gitsubmodule"
# # Look for `.gitmodules` in the `root` directory
# directory: "/"
# # Check for updates once a month
# schedule:
# interval: "monthly"
# # Limit the amout of open PR's (default = 5, disabled = 0, security updates are not impacted)
# open-pull-requests-limit: 1
# # Enable version updates for Docker
# Not yet possible to bump from one LTS version to the next and skip others, see:
# https://github.com/dependabot/dependabot-core/issues/2247
# - package-ecosystem: "docker"
# # Look for a `Dockerfile` in the `root` directory
# directory: "/"
# # Check for updates once a week
# schedule:
# interval: "weekly"
# # Limit the amout of open PR's (default = 5, disabled = 0, security updates are not impacted)
# open-pull-requests-limit: 1
# Enable version updates for GitHub Actions
- package-ecosystem: "github-actions"
# Directory must be set to "/" to check for workflow files in .github/workflows
directory: "/"
# Check for updates to GitHub Actions once a week
schedule:
interval: "weekly"
# Limit the amout of open PR's (default = 5, disabled = 0, security updates are not impacted)
open-pull-requests-limit: 2
# # Enable version updates for npm
# - package-ecosystem: "npm"
# # Look for `package.json` and `lock` files in the `webclient` subdirectory
# directory: "/webclient"
# # Check the npm registry for updates once a week
# schedule:
# interval: "weekly"
# # Limit the amout of open PR's (default = 5, disabled = 0, security updates are not impacted)
# open-pull-requests-limit: 5

View File

@@ -8,6 +8,7 @@ on:
- '**.md'
- 'webclient/**'
- '.github/workflows/web-*.yml'
- '.github/workflows/translations-*.yml'
tags:
- '*'
pull_request:
@@ -15,6 +16,12 @@ on:
- '**.md'
- 'webclient/**'
- '.github/workflows/web-*.yml'
- '.github/workflows/translations-*.yml'
# Cancel earlier, unfinished runs of this workflow on the same branch (unless on master)
concurrency:
group: "${{ github.workflow }} @ ${{ github.ref_name }}"
cancel-in-progress: ${{ github.ref_name != 'master' }}
jobs:
configure:
@@ -23,33 +30,27 @@ jobs:
outputs:
tag: ${{steps.configure.outputs.tag}}
sha: ${{steps.configure.outputs.sha}}
upload_url: ${{steps.create_release.outputs.upload_url}}
steps:
- name: Cancel previous runs
uses: styfle/cancel-workflow-action@0.6.0
with:
access_token: ${{github.token}} # needs other token https://github.com/styfle/cancel-workflow-action/issues/7
steps:
- name: Configure
id: configure
shell: bash
run: |
tag_regex='^refs/tags/'
if [[ $GITHUB_EVENT_NAME == pull-request ]]; then # pull request
if [[ $GITHUB_EVENT_NAME == pull-request ]]; then # pull request
sha="${{github.event.pull_request.head.sha}}"
elif [[ $GITHUB_REF =~ $tag_regex ]]; then # release
elif [[ $GITHUB_REF =~ $tag_regex ]]; then # release
sha="$GITHUB_SHA"
tag="${GITHUB_REF/refs\/tags\//}"
echo "::set-output name=tag::$tag"
else # push to branch
echo "tag=$tag" >>"$GITHUB_OUTPUT"
else # push to branch
sha="$GITHUB_SHA"
fi
echo "::set-output name=sha::$sha"
echo "sha=$sha" >>"$GITHUB_OUTPUT"
- name: Checkout
if: steps.configure.outputs.tag != null
uses: actions/checkout@v2
uses: actions/checkout@v4
with:
fetch-depth: 0
@@ -64,170 +65,174 @@ jobs:
- name: Create release
if: steps.configure.outputs.tag != null
id: create_release
uses: actions/create-release@v1
shell: bash
env:
GITHUB_TOKEN: ${{github.token}}
with:
tag_name: ${{github.ref}}
GH_TOKEN: ${{github.token}}
tag_name: ${{steps.configure.outputs.tag}}
target: ${{steps.configure.outputs.sha}}
release_name: ${{steps.prepare.outputs.title}}
body_path: ${{steps.prepare.outputs.body_path}}
draft: true
prerelease: ${{steps.prepare.outputs.is_beta == 'yes'}}
prerelease: ${{steps.prepare.outputs.is_beta}}
run: |
if [[ $prerelease == yes ]]; then
args="--prerelease"
fi
gh release create "$tag_name" --draft --verify-tag $args \
--target "$target" --title "$release_name" \
--notes-file "$body_path"
build-linux:
strategy:
fail-fast: false
matrix:
# these names correspond to the files in .ci/$distro
# These names correspond to the files in ".ci/$distro$version"
include:
- distro: ArchLinux
package: skip # we are packaged in arch already
- distro: Arch
package: skip # We are packaged in Arch already
allow-failure: yes
- distro: Debian10
- distro: Debian
version: 11
package: DEB
test: skip # running tests on all distros is superfluous
test: skip # Running tests on all distros is superfluous
- distro: Debian11
- distro: Debian
version: 12
package: DEB
- distro: Fedora35
- distro: Fedora
version: 40
package: RPM
test: skip
test: skip # Running tests on all distros is superfluous
- distro: Fedora36
- distro: Fedora
version: 41
package: RPM
- distro: UbuntuBionic
- distro: Ubuntu
version: 20.04
package: DEB
test: skip # Ubuntu 20.04 has a broken Qt for debug builds
- distro: Ubuntu
version: 22.04
package: DEB
test: skip # Running tests on all distros is superfluous
- distro: Ubuntu
version: 24.04
package: DEB
- distro: UbuntuFocal
package: DEB
test: skip # UbuntuFocal has a broken qt for debug builds
- distro: UbuntuJammy
package: DEB
name: ${{matrix.distro}}
name: ${{matrix.distro}} ${{matrix.version}}
needs: configure
runs-on: ubuntu-latest
continue-on-error: ${{matrix.allow-failure == 'yes'}}
env:
NAME: ${{matrix.distro}}
CACHE: /tmp/${{matrix.distro}}-cache # ${{runner.temp}} does not work?
# cache size over the entire repo is 10Gi link:
NAME: ${{matrix.distro}}${{matrix.version}}
CACHE: /tmp/${{matrix.distro}}${{matrix.version}}-cache # ${{runner.temp}} does not work?
# Cache size over the entire repo is 10Gi:
# https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
CCACHE_SIZE: 200M
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Get cache timestamp
- name: Generate cache timestamp
id: cache_timestamp
shell: bash
run: echo "::set-output name=timestamp::$(date -u '+%Y%m%d%H%M%S')"
run: echo "timestamp=$(date -u '+%Y%m%d%H%M%S')" >>"$GITHUB_OUTPUT"
- name: Restore cache
uses: actions/cache@v2
uses: actions/cache@v4
env:
timestamp: ${{steps.cache_timestamp.outputs.timestamp}}
with:
path: ${{env.CACHE}}
key: docker-${{matrix.distro}}-cache-${{env.timestamp}}
key: docker-${{matrix.distro}}${{matrix.version}}-cache-${{env.timestamp}}
restore-keys: |
docker-${{matrix.distro}}-cache-
docker-${{matrix.distro}}${{matrix.version}}-cache-
- name: Build ${{matrix.distro}} Docker image
- name: Build ${{matrix.distro}} ${{matrix.version}} Docker image
shell: bash
run: source .ci/docker.sh --build
- name: Build debug and test
if: matrix.test != 'skip'
shell: bash
env:
distro: '${{matrix.distro}}'
run: |
source .ci/docker.sh
RUN --server --debug --test --ccache "$CCACHE_SIZE" --parallel 2
RUN --server --debug --test --ccache "$CCACHE_SIZE" --parallel 4
- name: Build release package
id: package
id: build
if: matrix.package != 'skip'
shell: bash
env:
suffix: '-${{matrix.distro}}'
BUILD_DIR: build
SUFFIX: '-${{matrix.distro}}${{matrix.version}}'
type: '${{matrix.package}}'
distro: '${{matrix.distro}}'
run: |
source .ci/docker.sh
RUN --server --release --package "$type" --suffix "$suffix" \
--ccache "$CCACHE_SIZE" --parallel 2
RUN --server --release --package "$type" --dir "$BUILD_DIR" \
--ccache "$CCACHE_SIZE" --parallel 4
.ci/name_build.sh
- name: Upload artifact
if: matrix.package != 'skip'
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: ${{matrix.distro}}-package
path: ${{steps.package.outputs.path}}
name: ${{matrix.distro}}${{matrix.version}}-package
path: ${{steps.build.outputs.path}}
if-no-files-found: error
- name: Upload to release
if: matrix.package != 'skip' && needs.configure.outputs.tag != null
uses: actions/upload-release-asset@v1
shell: bash
env:
GITHUB_TOKEN: ${{github.token}}
with:
upload_url: ${{needs.configure.outputs.upload_url}}
asset_path: ${{steps.package.outputs.path}}
asset_name: ${{steps.package.outputs.name}}
asset_content_type: application/octet-stream
GH_TOKEN: ${{github.token}}
tag_name: ${{needs.configure.outputs.tag}}
asset_path: ${{steps.build.outputs.path}}
asset_name: ${{steps.build.outputs.name}}
run: gh release upload "$tag_name" "$asset_path#$asset_name"
build-macos:
strategy:
fail-fast: false
matrix:
include:
- target: Debug # tests only
os: macos-latest
xcode: 12.5.1
qt_version: 6
- target: 13
soc: Intel
os: macos-13
xcode: "14.3.1"
type: Release
core_count: 4
make_package: 1
- target: 14
soc: Apple
os: macos-14
xcode: "15.4"
type: Release
core_count: 3
make_package: 1
- target: 15
soc: Apple
os: macos-15
xcode: "16.2"
type: Release
core_count: 3
make_package: 1
- target: 15
soc: Apple
os: macos-15
xcode: "16.2"
type: Debug
do_tests: 1
core_count: 3
- target: 10.14_Mojave
os: macos-10.15 # runs on Catalina
xcode: 10.3 # allows compatibility with macOS 10.14
qt_version: 5
type: Release
# do_tests: 1 # tests do not work on qt5?
make_package: 1
- target: 10.15_Catalina
os: macos-10.15
xcode: 12.4
qt_version: 6
type: Release
do_tests: 1
make_package: 1
- target: 11_Big_Sur
os: macos-11
xcode: 13.2
qt_version: 6
type: Release
do_tests: 1
make_package: 1
# - target: 12_Monterey
# os: macos-12
# xcode: 13.3
# qt_version: 6
# type: Release
# do_tests: 1
# make_package: 1
name: macOS ${{matrix.target}}
name: macOS ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }}
needs: configure
runs-on: ${{matrix.os}}
continue-on-error: ${{matrix.allow-failure == 'yes'}}
@@ -237,140 +242,125 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Install dependencies using homebrew
- name: Install dependencies using Homebrew
shell: bash
# cmake cannot find the mysql connector
# neither of these works: mariadb-connector-c mysql-connector-c++
# CMake cannot find the MySQL connector
# Neither of these works: mariadb-connector-c mysql-connector-c++
env:
qt_version: 'qt@${{matrix.qt_version}}'
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1
run: |
brew update
brew install protobuf
brew install "$qt_version" --force-bottle
brew install protobuf qt --force-bottle
- name: Build on Xcode ${{matrix.xcode}}
shell: bash
id: build
env:
BUILDTYPE: '${{matrix.type}}'
MAKE_TEST: '${{matrix.do_tests}}'
MAKE_TEST: 1
MAKE_PACKAGE: '${{matrix.make_package}}'
PACKAGE_SUFFIX: '-macOS-${{matrix.target}}'
# set QTDIR to find qt5, qt6 can be found without this
QTDIR: '/usr/local/opt/qt5'
# Mac machines actually have 3 cores
run: .ci/compile.sh --server --parallel 3
PACKAGE_SUFFIX: '-macOS${{matrix.target}}_${{matrix.soc}}'
# macOS runner have 3 cores usually - only the macos-13 image has 4:
# https://docs.github.com/en/actions/using-github-hosted-runners/using-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories
# https://github.com/actions/runner-images?tab=readme-ov-file#available-images
run: .ci/compile.sh --server --parallel ${{matrix.core_count}}
- name: Upload artifact
if: matrix.make_package
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: macOS-${{matrix.target}}-dmg
name: macOS${{matrix.target}}${{ matrix.soc == 'Intel' && '_Intel' || '' }}${{ matrix.type == 'Debug' && '_Debug' || '' }}-dmg
path: ${{steps.build.outputs.path}}
if-no-files-found: error
- name: Upload to release
if: matrix.make_package && needs.configure.outputs.tag != null
uses: actions/upload-release-asset@v1
if: matrix.package != 'skip' && needs.configure.outputs.tag != null
shell: bash
env:
GITHUB_TOKEN: ${{github.token}}
with:
upload_url: ${{needs.configure.outputs.upload_url}}
GH_TOKEN: ${{github.token}}
tag_name: ${{needs.configure.outputs.tag}}
asset_path: ${{steps.build.outputs.path}}
asset_name: ${{steps.build.outputs.name}}
asset_content_type: application/octet-stream
run: gh release upload "$tag_name" "$asset_path#$asset_name"
build-windows:
strategy:
fail-fast: false
matrix:
include:
- target: Win-32bit
bit: 32
arch: x86
cmake_generator_platform: Win32
qt_version: 5.15.*
qt_arch: msvc2019
qt_tools: "tools_openssl_x86"
- target: Win7+-64bit
bit: 64
arch: x64
cmake_generator_platform: x64
- target: 7
qt_version: 5.15.*
qt_arch: msvc2019_64
qt_tools: "tools_openssl_x64"
- target: Win10+-64bit
bit: 64
arch: x64
cmake_generator_platform: x64
qt_version: 6.3.*
- target: 10
qt_version: 6.6.*
qt_arch: msvc2019_64
qt_tools: "tools_openssl_x64"
qt_modules: "qt5compat qtmultimedia qtwebsockets"
qt_modules: "qtimageformats qtmultimedia qtwebsockets"
name: ${{matrix.target}}
name: Windows ${{matrix.target}}
needs: configure
runs-on: windows-2019
runs-on: windows-2022
env:
CMAKE_GENERATOR: 'Visual Studio 16 2019'
CMAKE_GENERATOR: 'Visual Studio 17 2022'
steps:
- name: Add msbuild to PATH
id: add-msbuild
uses: microsoft/setup-msbuild@v1.1
uses: microsoft/setup-msbuild@v2
with:
msbuild-architecture: x64
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install Qt ${{matrix.qt_version}} for ${{matrix.target}}
uses: jurplel/install-qt-action@v3
- name: Install Qt ${{matrix.qt_version}}
uses: jurplel/install-qt-action@v4
with:
cache: true
setup-python: false
version: ${{matrix.qt_version}}
arch: win${{matrix.bit}}_${{matrix.qt_arch}}
arch: win64_${{matrix.qt_arch}}
tools: ${{matrix.qt_tools}}
modules: ${{matrix.qt_modules}}
- name: Run vcpkg
uses: lukka/run-vcpkg@v10.2
uses: lukka/run-vcpkg@v11
with:
runVcpkgInstall: true
appendedCacheKey: ${{matrix.bit}}-bit
doNotCache: false
env:
VCPKG_DEFAULT_TRIPLET: '${{matrix.arch}}-windows'
VCPKG_DEFAULT_TRIPLET: 'x64-windows'
VCPKG_DISABLE_METRICS: 1
- name: Build Cockatrice
id: build
shell: bash
env:
PACKAGE_SUFFIX: '-${{matrix.target}}'
PACKAGE_SUFFIX: '-Win${{matrix.target}}'
CMAKE_GENERATOR: '${{env.CMAKE_GENERATOR}}'
CMAKE_GENERATOR_PLATFORM: '${{matrix.cmake_generator_platform}}'
QTDIR: '${{github.workspace}}\Qt\${{matrix.qt_version}}\win${{matrix.bit}}_${{matrix.qt_arch}}'
run: .ci/compile.sh --server --release --test --package --parallel 2
CMAKE_GENERATOR_PLATFORM: 'x64'
QTDIR: '${{github.workspace}}\Qt\${{matrix.qt_version}}\win64_${{matrix.qt_arch}}'
# No need for --parallel flag, MTT is added in the compile script to let cmake/msbuild manage core count,
# project and process parallelism: https://devblogs.microsoft.com/cppblog/improved-parallelism-in-msbuild/
run: .ci/compile.sh --server --release --test --package
- name: Upload artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{matrix.target}}-installer
name: Windows${{matrix.target}}-installer
path: ${{steps.build.outputs.path}}
if-no-files-found: error
- name: Upload to release
if: needs.configure.outputs.tag != null
uses: actions/upload-release-asset@v1
if: matrix.package != 'skip' && needs.configure.outputs.tag != null
shell: bash
env:
GITHUB_TOKEN: ${{github.token}}
with:
upload_url: ${{needs.configure.outputs.upload_url}}
GH_TOKEN: ${{github.token}}
tag_name: ${{needs.configure.outputs.tag}}
asset_path: ${{steps.build.outputs.path}}
asset_name: ${{steps.build.outputs.name}}
asset_content_type: application/octet-stream
run: gh release upload "$tag_name" "$asset_path#$asset_name"

View File

@@ -6,6 +6,7 @@ on:
- '**.md'
- 'webclient/**'
- '.github/workflows/web-*.yml'
- '.github/workflows/translations-*.yml'
jobs:
format:
@@ -13,7 +14,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4
with:
fetch-depth: 20 # should be enough to find merge base

72
.github/workflows/translations-pull.yml vendored Normal file
View File

@@ -0,0 +1,72 @@
name: Update Translations
on:
workflow_dispatch:
schedule:
# runs in the middle of each month starting a quarter (UTC) = two weeks after new strings are built
- cron: '0 0 15 1,4,7,10 *'
pull_request:
paths:
- '.github/workflows/translations-pull.yml'
jobs:
translations:
# Do not run the scheduled workflow on forks
if: github.event_name != 'schedule' || github.repository_owner == 'Cockatrice'
name: Pull languages
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Pull translated strings from Transifex
uses: transifex/cli-action@v2
with:
# used config file: https://github.com/Cockatrice/Cockatrice/blob/master/.tx/config
# https://github.com/transifex/cli#pulling-files-from-transifex
token: ${{ secrets.TX_TOKEN }}
args: pull --force --all
- name: Create pull request
if: github.event_name != 'pull_request'
id: create_pr
uses: peter-evans/create-pull-request@v7
with:
add-paths: |
cockatrice/translations/*.ts
oracle/translations/*.ts
webclient/public/locales/*/translation.json
commit-message: Update translation files
# author is the owner of the commit
author: github-actions <github-actions@github.com>
branch: ci-update_translations
delete-branch: true
title: 'Update translations'
body: |
Pulled all translated strings from [Transifex][1].
---
*This PR is automatically generated and updated by the workflow at `.github/workflows/translations-pull.yml`. Review [action runs][2].*<br>
*After merging, all new languages and translations are available in the next build.*
[1]: https://app.transifex.com/cockatrice/cockatrice/
[2]: https://github.com/Cockatrice/Cockatrice/actions/workflows/translations-pull.yml?query=branch%3Amaster
labels: |
CI
Translation
draft: false
- name: PR Status
if: github.event_name != 'pull_request'
shell: bash
env:
STATUS: ${{ steps.create_pr.outputs.pull-request-operation }}
run: |
if [[ "$STATUS" == "none" ]]; then
echo "PR #${{ steps.create_pr.outputs.pull-request-number }} unchanged!" >> $GITHUB_STEP_SUMMARY
else
echo "PR #${{ steps.create_pr.outputs.pull-request-number }} $STATUS!" >> $GITHUB_STEP_SUMMARY
fi
echo "URL: ${{ steps.create_pr.outputs.pull-request-url }}" >> $GITHUB_STEP_SUMMARY

87
.github/workflows/translations-push.yml vendored Normal file
View File

@@ -0,0 +1,87 @@
name: Update Translation Source
on:
workflow_dispatch:
schedule:
# runs at the start of each quarter (UTC)
- cron: '0 0 1 1,4,7,10 *'
pull_request:
paths:
- '.github/workflows/translations-push.yml'
jobs:
translations:
# Do not run the scheduled workflow on forks
if: github.event_name != 'schedule' || github.repository_owner == 'Cockatrice'
name: Push strings
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Install lupdate
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends qttools5-dev-tools
- name: Update Cockatrice translation source
id: cockatrice
shell: bash
env:
FILE: 'cockatrice/cockatrice_en@source.ts'
DIRS: 'cockatrice/src common'
run: .ci/update_translation_source_strings.sh
- name: Update Oracle translation source
id: oracle
shell: bash
env:
FILE: 'oracle/oracle_en@source.ts'
DIRS: 'oracle/src'
run: .ci/update_translation_source_strings.sh
- name: Render template
id: template
uses: chuhlomin/render-template@v1
with:
template: .ci/update_translation_source_strings_template.md
vars: |
cockatrice_output: ${{ steps.cockatrice.outputs.output }}
oracle_output: ${{ steps.oracle.outputs.output }}
commit: ${{ github.sha }}
- name: Create pull request
if: github.event_name != 'pull_request'
id: create_pr
uses: peter-evans/create-pull-request@v7
with:
add-paths: |
cockatrice/cockatrice_en@source.ts
oracle/oracle_en@source.ts
commit-message: Update translation source strings
# author is the owner of the commit
author: github-actions <github-actions@github.com>
branch: ci-update_translation_source
delete-branch: true
title: 'Update source strings'
body: ${{ steps.template.outputs.result }}
labels: |
CI
Translation
draft: false
- name: PR Status
if: github.event_name != 'pull_request'
shell: bash
env:
STATUS: ${{ steps.create_pr.outputs.pull-request-operation }}
run: |
if [[ "$STATUS" == "none" ]]; then
echo "PR #${{ steps.create_pr.outputs.pull-request-number }} unchanged!" >> $GITHUB_STEP_SUMMARY
else
echo "PR #${{ steps.create_pr.outputs.pull-request-number }} $STATUS!" >> $GITHUB_STEP_SUMMARY
fi
echo "URL: ${{ steps.create_pr.outputs.pull-request-url }}" >> $GITHUB_STEP_SUMMARY

View File

@@ -1,66 +0,0 @@
name: Update translation source
on:
workflow_dispatch:
schedule:
# runs once per month
- cron: '0 0 1 * *'
jobs:
translations:
# Do not run the scheduled workflow on forks
if: github.event_name != 'schedule' || github.repository_owner == 'Cockatrice'
runs-on: ubuntu-latest
steps:
- name: Install lupdate
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends qttools5-dev-tools
- name: Checkout repo
uses: actions/checkout@v2
- name: Update cockatrice translations
shell: bash
run: |
shopt -s globstar # globstar is needed for recursive **
lupdate -version
echo "reading the following source files:"
# note: there are three strings to translate in common right now
echo {cockatrice,common}/**/*.{cpp,h}
echo "$(echo {cockatrice,common}/**/*.{cpp,h} | wc -w) files total"
lupdate {cockatrice,common}/**/*.{cpp,h} -ts cockatrice/translations/cockatrice_en@source.ts
- name: Update oracle translations
shell: bash
run: |
shopt -s globstar # globstar is needed for recursive **
lupdate -version
echo "reading the following source files:"
echo oracle/**/*.{cpp,h}
echo "$(echo oracle/**/*.{cpp,h} | wc -w) files total"
lupdate oracle/**/*.{cpp,h} -ts oracle/translations/oracle_en@source.ts
- name: Check for updates
id: check
shell: bash
run: |
set +e # do not fail, just save the exit state
git diff --exit-code
echo "::set-output name=deploy::$?"
- name: Commit changes
if: steps.check.outputs.deploy == '1'
shell: bash
working-directory: ${{env.OUTPUT_PATH}}
run: |
git config user.name github-actions
git config user.email github-actions@github.com
git add cockatrice/translations/cockatrice_en@source.ts oracle/translations/oracle_en@source.ts
git commit -m "Automated translation update ( $GITHUB_SHA )"
git push
deploy_commit=$(git rev-parse HEAD)
echo "Created commit: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/commit/$deploy_commit"

View File

@@ -28,15 +28,15 @@ jobs:
fail-fast: false
matrix:
node_version:
- 12
- 16
- lts/*
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v2
uses: actions/setup-node@v4
with:
node-version: ${{matrix.node_version}}
cache: 'npm'
@@ -50,4 +50,3 @@ jobs:
- name: Test app
run: npm run test

View File

@@ -17,10 +17,10 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v2
uses: actions/setup-node@v4
with:
cache: 'npm'
cache-dependency-path: 'webclient/package-lock.json'

4
.gitignore vendored
View File

@@ -6,8 +6,10 @@ mysql.cnf
.DS_Store
.idea/
*.aps
cmake-build-debug*
cmake-build*
preferences
compile_commands.json
.vs/
.vscode/
.cache
.gdb_history

View File

@@ -1,13 +1,26 @@
[main]
host = https://www.transifex.com
host = https://app.transifex.com
[cockatrice.cockatrice-translations-cockatrice-en-source-ts--master]
[o:cockatrice:p:cockatrice:r:cockatrice-cockatrice-en-source-ts--master]
resource_name = Cockatrice
source_lang = en
source_file = cockatrice/cockatrice_en@source.ts
file_filter = cockatrice/translations/cockatrice_<lang>.ts
source_file = cockatrice/translations/cockatrice_en@source.ts
source_lang = en
type = QT
minimum_perc = 10
[cockatrice.oracle-translations-oracle-en-source-ts--master]
[o:cockatrice:p:cockatrice:r:oracle-oracle-en-source-ts--master]
resource_name = Oracle
source_lang = en
source_file = oracle/oracle_en@source.ts
file_filter = oracle/translations/oracle_<lang>.ts
source_file = oracle/translations/oracle_en@source.ts
source_lang = en
type = QT
minimum_perc = 10
[o:cockatrice:p:cockatrice:r:webclient-src-i18n-default-json--master]
resource_name = Webclient
source_lang = en
source_file = webclient/src/i18n-default.json
file_filter = webclient/public/locales/<lang>/translation.json
type = KEYVALUEJSON
minimum_perc = 10

View File

@@ -74,16 +74,16 @@ endif()
# A project name is needed for CPack
# Version can be overriden by git tags, see cmake/getversion.cmake
project("Cockatrice" VERSION 2.8.1)
project("Cockatrice" VERSION 2.10.0)
# Set release name if not provided via env/cmake var
if(NOT DEFINED GIT_TAG_RELEASENAME)
set(GIT_TAG_RELEASENAME "Prismatic Bridge")
set(GIT_TAG_RELEASENAME "Rings of the Wild")
endif()
# Use c++17 for all targets
# Use c++20 for all targets
set(CMAKE_CXX_STANDARD
17
20
CACHE STRING "C++ ISO Standard"
)
set(CMAKE_CXX_STANDARD_REQUIRED True)
@@ -140,10 +140,14 @@ endif()
# Define proper compilation flags
if(MSVC)
# Visual Studio: Maximum optimization, disable warning C4251, establish C++17 compatibility
set(CMAKE_CXX_FLAGS_RELEASE "/Ox /MD /wd4251 /Zc:__cplusplus /std:c++17 /permissive-")
# Generate complete debugging information
#set(CMAKE_CXX_FLAGS_DEBUG "/Zi")
# Visual Studio: Disable Warning C4251, C++20 compatibility, Multi-threaded Builds, Warn Detection, Unwind Semantics
set(CMAKE_CXX_FLAGS "/wd4251 /Zc:__cplusplus /std:c++20 /permissive- /W4 /MP /EHsc")
# Visual Studio: Maximum Optimization, Multi-threaded DLL
set(CMAKE_CXX_FLAGS_RELEASE "/Ox /MD")
# Visual Studio: No Optimization, Multi-threaded Debug DLL, Debug Symbols
set(CMAKE_CXX_FLAGS_DEBUG "/Od /MDd /Zi")
add_compile_definitions(_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING)
elseif(CMAKE_COMPILER_IS_GNUCXX)
# linux/gcc, bsd/gcc, windows/mingw
include(CheckCXXCompilerFlag)
@@ -156,7 +160,7 @@ elseif(CMAKE_COMPILER_IS_GNUCXX)
endif()
if(APPLE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++17")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++20")
endif()
set(ADDITIONAL_DEBUG_FLAGS
@@ -179,7 +183,7 @@ else()
# other: osx/llvm, bsd/llvm
set(CMAKE_CXX_FLAGS_RELEASE "-O2")
if(WARNING_AS_ERROR)
set(CMAKE_CXX_FLAGS_DEBUG "-g -O0 -Wall -Wextra -Werror")
set(CMAKE_CXX_FLAGS_DEBUG "-g -O0 -Wall -Wextra -Werror -Wno-unused-parameter")
else()
set(CMAKE_CXX_FLAGS_DEBUG "-g -O0 -Wall -Wextra")
endif()
@@ -216,16 +220,26 @@ include(FindQtRuntime)
set(CMAKE_AUTOMOC TRUE)
# Find other needed libraries
find_package(Protobuf REQUIRED)
if(NOT EXISTS "${Protobuf_PROTOC_EXECUTABLE}")
find_package(Protobuf CONFIG)
if(NOT Protobuf_FOUND)
find_package(Protobuf REQUIRED)
endif()
if(${Protobuf_VERSION} VERSION_LESS "3.21.0.0" AND NOT EXISTS "${Protobuf_PROTOC_EXECUTABLE}")
message(FATAL_ERROR "No protoc command found!")
else()
message(STATUS "Found Protobuf ${Protobuf_VERSION} at: ${Protobuf_LIBRARIES}")
endif()
#Find OpenSSL
if(WIN32)
find_package(Win32SslRuntime)
find_package(OpenSSL REQUIRED)
if(OPENSSL_FOUND)
include_directories(${OPENSSL_INCLUDE_DIRS})
else()
message(
WARNING
"Could not find OpenSSL runtime libraries. They are not required for compiling, but needs to be available at runtime."
)
endif()
endif()
#Find VCredist
@@ -252,13 +266,15 @@ if(UNIX)
set(CPACK_DMG_VOLUME_NAME "${PROJECT_NAME}")
set(CPACK_SYSTEM_NAME "OSX")
set(CPACK_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}/cockatrice/resources/appicon.icns")
set(CPACK_DMG_DS_STORE_SETUP_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/cmake/CMakeDMGSetup.script")
set(CPACK_DMG_BACKGROUND_IMAGE "${CMAKE_CURRENT_SOURCE_DIR}/cmake/dmgBackground.tif")
else()
# linux
if(CPACK_GENERATOR STREQUAL "RPM")
set(CPACK_RPM_PACKAGE_LICENSE "GPLv2")
set(CPACK_RPM_MAIN_COMPONENT "cockatrice")
if(Qt6_FOUND)
set(CPACK_RPM_PACKAGE_REQUIRES "protobuf, qt6-qttools, qt6-qtsvg, qt6-qtmultimedia")
set(CPACK_RPM_PACKAGE_REQUIRES "protobuf, qt6-qttools, qt6-qtsvg, qt6-qtmultimedia, qt6-qtimageformats")
elseif(Qt5_FOUND)
set(CPACK_RPM_PACKAGE_REQUIRES "protobuf, qt5-qttools, qt5-qtsvg, qt5-qtmultimedia")
endif()
@@ -280,7 +296,7 @@ if(UNIX)
set(CPACK_DEBIAN_PACKAGE_SECTION "games")
set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "http://github.com/Cockatrice/Cockatrice")
if(Qt6_FOUND)
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libqt6multimedia6, libqt6svg6, qt6-qpa-plugins")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libqt6multimedia6, libqt6svg6, qt6-qpa-plugins, qt6-image-formats-plugins")
elseif(Qt5_FOUND)
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libqt5multimedia5-plugins, libqt5svg5")
endif()

View File

@@ -5,7 +5,7 @@
<p align='center'>
<a href="#cockatrice"><b>Cockatrice</b></a> <b>|</b>
<a href="#download-">Download</a> <b>|</b>
<a href="#get-involved--">Get Involved</a> <b>|</b>
<a href="#get-involved-">Get Involved</a> <b>|</b>
<a href="#community-resources">Community</a> <b>|</b>
<a href="#translations-">Translations</a> <b>|</b>
<a href="#build--">Build</a> <b>|</b>
@@ -18,7 +18,7 @@
<br><pre>
<b>To get started, &#8674; [view our webpage](https://cockatrice.github.io/)</b><br>
<b>To get support or suggest changes &#8674; [file an issue](https://github.com/Cockatrice/Cockatrice/issues) ([How?](https://github.com/Cockatrice/Cockatrice/wiki/How-to-Create-a-GitHub-Ticket-Regarding-Cockatrice))</b>
<b>To help with development, see how to [get involved](#get-involved--)</b>
<b>To help with development, see how to [get involved](#get-involved-)</b>
</pre><br>
@@ -40,9 +40,9 @@ Downloads are available for full releases and the current beta version in develo
- To be a Cockatrice Beta Tester, use this version. Find more information [here](https://github.com/Cockatrice/Cockatrice/wiki/Release-Channels)!
# Get Involved [![Discord](https://img.shields.io/discord/314987288398659595?label=Discord&logo=discord&logoColor=white)](https://discord.gg/3Z9yzmA) [![Gitter Chat](https://img.shields.io/gitter/room/Cockatrice/Cockatrice)](https://gitter.im/Cockatrice/Cockatrice)
# Get Involved [![Discord](https://img.shields.io/discord/314987288398659595?label=Discord&logo=discord&logoColor=white)](https://discord.gg/3Z9yzmA)
Join our [Discord community](https://discord.gg/3Z9yzmA) to connect with the project or fellow users of the app. The Cockatrice developers are also available on [Gitter](https://gitter.im/Cockatrice/Cockatrice). Come here to talk about the application, features, or just to hang out.<br>
Join our [Discord community](https://discord.gg/3Z9yzmA) to connect with the project, contributors or fellow users of the app. Come here to talk about the application, features, or just to hang out.<br>
For support regarding specific servers, please contact that server's admin or forum for support rather than asking here.<br>
To contribute code to the project, please review [the guidelines](https://github.com/Cockatrice/Cockatrice/blob/master/.github/CONTRIBUTING.md).
@@ -52,8 +52,6 @@ We maintain two tags for contributors to find issues to work on:
For both tags, we're willing to provide help to contributors in showing them where and how they can make changes, as well as code review for changes they submit.
Read the long-term project **roadmap** to see planned edits and milestones [here](https://docs.google.com/document/d/1Ewe5uSaRE2nR2pNPMaGmP6gVZdqgFbBgwSscGqIr4W0/edit).
We try to be responsive to new issues. We'll provide advice on how best to implement a feature; alternately, we can show you where the codebase is doing something similar before you get too far along.
Cockatrice uses the [Google Developer Documentation Style Guide](https://developers.google.com/style/) to ensure consistent documentation. We encourage you to improve the documentation by suggesting edits based on this guide.
@@ -67,13 +65,9 @@ Cockatrice uses the [Google Developer Documentation Style Guide](https://develop
- [reddit r/Cockatrice](https://reddit.com/r/cockatrice)
# Translations [![Transifex Project](https://img.shields.io/badge/translate-on%20transifex-brightgreen)](https://www.transifex.com/projects/p/cockatrice/)
# Translations [![Transifex Project](https://img.shields.io/badge/translate-on%20transifex-brightgreen)](https://transifex.com/cockatrice/cockatrice/)
Cockatrice uses Transifex for translations. You can help us bring Cockatrice and Oracle to your language or just edit single wordings right from within your browser by visiting our [Transifex project page](https://www.transifex.com/projects/p/cockatrice/).<br>
| Cockatrice | Oracle |
|:-:|:-:|
| [![Cockatrice Translation Status](https://www.transifex.com/projects/p/cockatrice/resource/cockatrice/chart/image_png/)](https://www.transifex.com/projects/p/cockatrice/) | [![Oracle Translation Status](https://www.transifex.com/projects/p/cockatrice/resource/oracle/chart/image_png/)](https://www.transifex.com/projects/p/cockatrice/) |
Cockatrice uses Transifex for translations. You can help us bring Cockatrice, Oracle and Webatrice to your language or just adjust single wordings right from within your browser by visiting our [Transifex project page](https://transifex.com/cockatrice/cockatrice/).<br>
Check out our [Translator FAQ](https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ) for more information about contributing!<br>

View File

@@ -0,0 +1,55 @@
on run argv
set image_name to item 1 of argv
tell application "Finder"
tell disk image_name
-- wait for the image to finish mounting
set open_attempts to 0
repeat while open_attempts < 4
try
open
delay 1
set open_attempts to 5
close
on error errStr number errorNumber
set open_attempts to open_attempts + 1
delay 10
end try
end repeat
delay 5
-- open the image the first time and save a DS_Store with just
-- background and icon setup
open
set current view of container window to icon view
set theViewOptions to the icon view options of container window
set background picture of theViewOptions to file ".background:background.tif"
set arrangement of theViewOptions to not arranged
set icon size of theViewOptions to 128
delay 5
close
-- next setup the position of the app and Applications symlink
-- plus hide all the window decoration
open
update without registering applications
tell container window
set sidebar width to 0
set statusbar visible to false
set toolbar visible to false
set the bounds to { 400, 100, 1400, 922 }
set position of item "Cockatrice.app" to { 139, 214 }
set position of item "Oracle.app" to { 139, 414 }
set position of item "Servatrice.app" to { 139, 614 }
set position of item "dbconverter.app" to { 1400, 1400 }
set position of item "Applications" to { 861, 414 }
end tell
update without registering applications
delay 5
close
end tell
delay 1
end tell
end run

View File

@@ -21,8 +21,8 @@ if(WITH_CLIENT)
Network
PrintSupport
Svg
Widgets
WebSockets
Widgets
)
endif()
if(WITH_ORACLE)
@@ -41,27 +41,24 @@ set(REQUIRED_QT_COMPONENTS ${REQUIRED_QT_COMPONENTS} ${_SERVATRICE_NEEDED} ${_CO
list(REMOVE_DUPLICATES REQUIRED_QT_COMPONENTS)
if(NOT FORCE_USE_QT5)
# Core5Compat is Qt6 Only, Linguist is now a component in Qt6 instead of an external package
# Linguist is now a component in Qt6 instead of an external package
find_package(
Qt6 6.2.3
COMPONENTS Core5Compat ${REQUIRED_QT_COMPONENTS}
OPTIONAL_COMPONENTS Linguist
COMPONENTS ${REQUIRED_QT_COMPONENTS} Linguist
QUIET HINTS ${Qt6_DIR}
)
endif()
if(Qt6_FOUND)
set(COCKATRICE_QT_VERSION_NAME Qt6)
if(Qt6LinguistTools_FOUND)
list(FIND Qt6LinguistTools_TARGETS Qt6::lrelease QT6_LRELEASE_INDEX)
if(QT6_LRELEASE_INDEX EQUAL -1)
message(WARNING "Qt6 lrelease not found.")
endif()
list(FIND Qt6LinguistTools_TARGETS Qt6::lrelease QT6_LRELEASE_INDEX)
if(QT6_LRELEASE_INDEX EQUAL -1)
message(WARNING "Qt6 lrelease not found.")
endif()
list(FIND Qt6LinguistTools_TARGETS Qt6::lupdate QT6_LUPDATE_INDEX)
if(QT6_LUPDATE_INDEX EQUAL -1)
message(WARNING "Qt6 lupdate not found.")
endif()
list(FIND Qt6LinguistTools_TARGETS Qt6::lupdate QT6_LUPDATE_INDEX)
if(QT6_LUPDATE_INDEX EQUAL -1)
message(WARNING "Qt6 lupdate not found.")
endif()
else()
find_package(
@@ -116,10 +113,5 @@ string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" COCKATRICE_Q
string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" ORACLE_QT_MODULES "${_ORACLE_NEEDED}")
string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" DB_CONVERTER_QT_MODULES "${_DBCONVERTER_NEEDED}")
string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" TEST_QT_MODULES "${_TEST_NEEDED}")
if(Qt6_FOUND)
list(APPEND SERVATRICE_QT_MODULES ${COCKATRICE_QT_VERSION_NAME}::Core5Compat)
list(APPEND COCKATRICE_QT_MODULES ${COCKATRICE_QT_VERSION_NAME}::Core5Compat)
list(APPEND ORACLE_QT_MODULES ${COCKATRICE_QT_VERSION_NAME}::Core5Compat)
endif()
message(STATUS "Found Qt ${${COCKATRICE_QT_VERSION_NAME}_VERSION} at: ${${COCKATRICE_QT_VERSION_NAME}_DIR}")

View File

@@ -9,7 +9,8 @@ if(WIN32)
set(REDIST_ARCH x86)
endif()
set(REDIST_FILE vcredist_${REDIST_ARCH}.exe)
# VS 2017 uses vcredist_ARCH.exe, VS 2022 uses vc_redist.ARCH.exe
set(REDIST_FILE_NAMES vcredist_${REDIST_ARCH}.exe vcredist.${REDIST_ARCH}.exe vc_redist.${REDIST_ARCH}.exe)
set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_SKIP TRUE)
include(InstallRequiredSystemLibraries)
@@ -22,10 +23,13 @@ if(WIN32)
get_filename_component(_path ${_path} DIRECTORY)
get_filename_component(_path ${_path}/../../ ABSOLUTE)
if(EXISTS "${_path}/${REDIST_FILE}") # VS 2017
set(VCREDISTRUNTIME_FOUND "YES")
set(VCREDISTRUNTIME_FILE ${_path}/${REDIST_FILE})
endif()
foreach(redist_file ${REDIST_FILE_NAMES})
if(EXISTS "${_path}/${redist_file}")
set(VCREDISTRUNTIME_FOUND "YES")
set(VCREDISTRUNTIME_FILE ${_path}/${redist_file})
break()
endif()
endforeach()
endif()
if(VCREDISTRUNTIME_FOUND)

View File

@@ -1,74 +0,0 @@
# Find the OpenSSL runtime libraries (.dll) for Windows that
# will be needed by Qt in order to access https urls.
if(NOT DEFINED WIN32 OR NOT ${WIN32})
message(STATUS "Non-Windows device trying to execute FindWin32SslRuntime, skipping")
return()
endif()
if("${CMAKE_GENERATOR_PLATFORM}" STREQUAL "x64")
message(STATUS "Looking for OpenSSL for ${CMAKE_GENERATOR_PLATFORM}")
file(TO_CMAKE_PATH "$ENV{PROGRAMFILES}" _programfiles)
set(_OPENSSL_ROOT_PATHS
"$ENV{VCPKG_PACKAGES_DIR}/x64-windows/bin" "C:/OpenSSL-Win64/bin" "C:/OpenSSL-Win64"
"C:/Tools/vcpkg/installed/x64-windows/bin" "${_programfiles}/OpenSSL-Win64"
"D:/a/Cockatrice/Qt/Tools/OpenSSL/Win_x64/bin"
)
unset(_programfiles)
elseif("${CMAKE_GENERATOR_PLATFORM}" STREQUAL "Win32")
message(STATUS "Looking for OpenSSL for ${CMAKE_GENERATOR_PLATFORM}")
file(TO_CMAKE_PATH "$ENV{PROGRAMFILES}" _programfiles)
set(_OPENSSL_ROOT_PATHS
"$ENV{VCPKG_PACKAGES_DIR}/x86-windows/bin"
"C:/OpenSSL-Win32/bin"
"C:/OpenSSL-Win32"
"C:/OpenSSL"
"C:/Tools/vcpkg/installed/x86-windows/bin"
"${_programfiles}/OpenSSL"
"${_programfiles}/OpenSSL-Win32"
"D:/a/Cockatrice/Qt/Tools/OpenSSL/Win_x86/bin"
)
unset(_programfiles)
endif()
message(STATUS "Looking for OpenSSL @ $ENV{CMAKE_GENERATOR_PLATFORM} in ${_OPENSSL_ROOT_PATHS}")
if("$ENV{CMAKE_GENERATOR_PLATFORM}" STREQUAL "x64")
find_file(
WIN32SSLRUNTIME_LIBEAY
NAMES libcrypto-1_1-x64.dll libcrypto.dll
PATHS ${_OPENSSL_ROOT_PATHS}
NO_DEFAULT_PATH
)
find_file(
WIN32SSLRUNTIME_SSLEAY
NAMES libssl-1_1-x64.dll libssl.dll
PATHS ${_OPENSSL_ROOT_PATHS}
NO_DEFAULT_PATH
)
elseif("$ENV{CMAKE_GENERATOR_PLATFORM}" STREQUAL "Win32")
find_file(
WIN32SSLRUNTIME_LIBEAY
NAMES libcrypto-1_1.dll libcrypto.dll
PATHS ${_OPENSSL_ROOT_PATHS}
NO_DEFAULT_PATH
)
find_file(
WIN32SSLRUNTIME_SSLEAY
NAMES libssl-1_1.dll libssl.dll
PATHS ${_OPENSSL_ROOT_PATHS}
NO_DEFAULT_PATH
)
endif()
if(WIN32SSLRUNTIME_LIBEAY AND WIN32SSLRUNTIME_SSLEAY)
set(WIN32SSLRUNTIME_LIBRARIES "${WIN32SSLRUNTIME_LIBEAY}" "${WIN32SSLRUNTIME_SSLEAY}")
set(WIN32SSLRUNTIME_FOUND "YES")
message(STATUS "Found OpenSSL ${WIN32SSLRUNTIME_LIBRARIES}")
else()
set(WIN32SSLRUNTIME_FOUND "NO")
message(
WARNING
"Could not find OpenSSL runtime libraries. They are not required for compiling, but needs to be available at runtime."
)
endif()
mark_as_advanced(WIN32SSLRUNTIME_LIBEAY WIN32SSLRUNTIME_SSLEAY)

View File

@@ -235,6 +235,13 @@ ${If} $PortableMode = 0
WriteUninstaller "$INSTDIR\uninstall.exe"
${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
IntFmt $0 "0x%08X" $0
; Enable Windows User-Mode Dumps
; https://learn.microsoft.com/en-us/windows/win32/wer/collecting-user-mode-dumps
WriteRegStr HKLM "Software\Microsoft\Windows\Windows Error Reporting\LocalDumps\cockatrice.exe" "DumpFolder" "%LOCALAPPDATA%\CrashDumps\Cockatrice"
WriteRegDWORD HKLM "Software\Microsoft\Windows\Windows Error Reporting\LocalDumps\cockatrice.exe" "DumpCount" "5"
WriteRegDWORD HKLM "Software\Microsoft\Windows\Windows Error Reporting\LocalDumps\cockatrice.exe" "DumpType" "2"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "DisplayIcon" "$INSTDIR\cockatrice.exe"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "DisplayName" "Cockatrice"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "DisplayVersion" "@CPACK_PACKAGE_VERSION_MAJOR@.@CPACK_PACKAGE_VERSION_MINOR@.@CPACK_PACKAGE_VERSION_PATCH@"
@@ -248,20 +255,20 @@ ${If} $PortableMode = 0
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "VersionMajor" "@CPACK_PACKAGE_VERSION_MAJOR@"
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "VersionMinor" "@CPACK_PACKAGE_VERSION_MINOR@"
IfFileExists "$INSTDIR\vcredist_x86.exe" VcRedist86Exists PastVcRedist86Check
IfFileExists "$INSTDIR\vc_redist.x86.exe" VcRedist86Exists PastVcRedist86Check
VcRedist86Exists:
ExecWait '"$INSTDIR\vcredist_x86.exe" /passive /norestart'
ExecWait '"$INSTDIR\vc_redist.x86.exe" /passive /norestart'
DetailPrint "Wait to ensure unlock of vc_redist file after installation..."
Sleep 3000
Delete "$INSTDIR\vcredist_x86.exe"
Delete "$INSTDIR\vc_redist.x86.exe"
PastVcRedist86Check:
IfFileExists "$INSTDIR\vcredist_x64.exe" VcRedist64Exists PastVcRedist64Check
IfFileExists "$INSTDIR\vc_redist.x64.exe" VcRedist64Exists PastVcRedist64Check
VcRedist64Exists:
ExecWait '"$INSTDIR\vcredist_x64.exe" /passive /norestart'
ExecWait '"$INSTDIR\vc_redist.x64.exe" /passive /norestart'
DetailPrint "Sleep to ensure unlock of vc_redist file after installation..."
Sleep 3000
Delete "$INSTDIR\vcredist_x64.exe"
Delete "$INSTDIR\vc_redist.x64.exe"
PastVcRedist64Check:
${Else}

BIN
cmake/dmgBackground.tif Normal file

Binary file not shown.

View File

@@ -19,7 +19,7 @@ function(get_commit_id)
PARENT_SCOPE
)
set(PROJECT_VERSION_LABEL
"custom(${GIT_COM_ID})"
"custom-${GIT_COM_ID}"
PARENT_SCOPE
)
endfunction()

View File

@@ -5,127 +5,159 @@
project(Cockatrice VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}")
set(cockatrice_SOURCES
src/abstractcarddragitem.cpp
src/abstractcarditem.cpp
src/abstractclient.cpp
src/abstractcounter.cpp
src/abstractgraphicsitem.cpp
src/arrowitem.cpp
src/arrowtarget.cpp
src/carddatabase.cpp
src/carddatabasemodel.cpp
src/carddbparser/carddatabaseparser.cpp
src/carddbparser/cockatricexml3.cpp
src/carddbparser/cockatricexml4.cpp
src/carddragitem.cpp
src/cardfilter.cpp
src/cardframe.cpp
src/cardinfopicture.cpp
src/cardinfotext.cpp
src/cardinfowidget.cpp
src/carditem.cpp
src/cardlist.cpp
src/cardzone.cpp
src/chatview/chatview.cpp
src/counter_general.cpp
src/customlineedit.cpp
src/deck_loader.cpp
src/decklistmodel.cpp
src/deckstats_interface.cpp
src/deckview.cpp
src/dlg_connect.cpp
src/dlg_create_token.cpp
src/dlg_creategame.cpp
src/dlg_edit_avatar.cpp
src/dlg_edit_password.cpp
src/dlg_edit_tokens.cpp
src/dlg_edit_user.cpp
src/dlg_filter_games.cpp
src/dlg_forgotpasswordchallenge.cpp
src/dlg_forgotpasswordrequest.cpp
src/dlg_forgotpasswordreset.cpp
src/dlg_load_deck_from_clipboard.cpp
src/dlg_load_remote_deck.cpp
src/dlg_manage_sets.cpp
src/dlg_register.cpp
src/dlg_settings.cpp
src/dlg_tip_of_the_day.cpp
src/dlg_update.cpp
src/dlg_viewlog.cpp
src/filter_string.cpp
src/filterbuilder.cpp
src/filtertree.cpp
src/filtertreemodel.cpp
src/gamescene.cpp
src/gameselector.cpp
src/gamesmodel.cpp
src/gameview.cpp
src/gettextwithmax.cpp
src/handcounter.cpp
src/handle_public_servers.cpp
src/handzone.cpp
src/keysignals.cpp
src/lineeditcompleter.cpp
src/localclient.cpp
src/localserver.cpp
src/localserverinterface.cpp
src/logger.cpp
src/game/cards/abstract_card_drag_item.cpp
src/game/cards/abstract_card_item.cpp
src/client/game_logic/abstract_client.cpp
src/game/board/abstract_counter.cpp
src/game/board/abstract_graphics_item.cpp
src/game/board/arrow_item.cpp
src/game/board/arrow_target.cpp
src/game/cards/card_database.cpp
src/game/cards/card_database_manager.cpp
src/game/cards/card_database_model.cpp
src/game/cards/card_database_parser/card_database_parser.cpp
src/game/cards/card_database_parser/cockatrice_xml_3.cpp
src/game/cards/card_database_parser/cockatrice_xml_4.cpp
src/game/cards/card_drag_item.cpp
src/game/filters/filter_card.cpp
src/client/ui/widgets/cards/card_info_frame_widget.cpp
src/client/ui/widgets/cards/card_info_picture_widget.cpp
src/client/ui/widgets/cards/card_info_text_widget.cpp
src/client/ui/widgets/cards/card_info_display_widget.cpp
src/client/ui/widgets/cards/card_size_widget.cpp
src/game/cards/card_item.cpp
src/game/cards/card_list.cpp
src/game/zones/card_zone.cpp
src/server/chat_view/chat_view.cpp
src/game/board/counter_general.cpp
src/deck/custom_line_edit.cpp
src/deck/deck_loader.cpp
src/deck/deck_list_model.cpp
src/deck/deck_stats_interface.cpp
src/deck/deck_view.cpp
src/dialogs/dlg_connect.cpp
src/dialogs/dlg_create_token.cpp
src/dialogs/dlg_create_game.cpp
src/dialogs/dlg_edit_avatar.cpp
src/dialogs/dlg_edit_password.cpp
src/dialogs/dlg_edit_tokens.cpp
src/dialogs/dlg_edit_user.cpp
src/dialogs/dlg_filter_games.cpp
src/dialogs/dlg_forgot_password_challenge.cpp
src/dialogs/dlg_forgot_password_request.cpp
src/dialogs/dlg_forgot_password_reset.cpp
src/dialogs/dlg_load_deck_from_clipboard.cpp
src/dialogs/dlg_load_remote_deck.cpp
src/dialogs/dlg_manage_sets.cpp
src/dialogs/dlg_move_top_cards_until.cpp
src/dialogs/dlg_register.cpp
src/dialogs/dlg_roll_dice.cpp
src/dialogs/dlg_settings.cpp
src/dialogs/dlg_tip_of_the_day.cpp
src/dialogs/dlg_update.cpp
src/dialogs/dlg_view_log.cpp
src/game/filters/filter_string.cpp
src/game/filters/filter_builder.cpp
src/game/filters/filter_tree.cpp
src/game/filters/filter_tree_model.cpp
src/client/ui/layouts/flow_layout.cpp
src/client/ui/layouts/horizontal_flow_layout.cpp
src/client/ui/layouts/vertical_flow_layout.cpp
src/client/ui/widgets/general/layout_containers/flow_widget.cpp
src/game/game_scene.cpp
src/game/game_selector.cpp
src/game/games_model.cpp
src/game/game_view.cpp
src/client/get_text_with_max.cpp
src/game/hand_counter.cpp
src/server/handle_public_servers.cpp
src/game/zones/hand_zone.cpp
src/client/game_logic/key_signals.cpp
src/client/ui/line_edit_completer.cpp
src/server/local_client.cpp
src/server/local_server.cpp
src/server/local_server_interface.cpp
src/utility/logger.cpp
src/client/ui/widgets/cards/card_info_picture_enlarged_widget.cpp
src/client/ui/widgets/cards/card_info_picture_with_text_overlay_widget.cpp
src/client/ui/widgets/general/display/labeled_input.cpp
src/client/ui/widgets/general/display/dynamic_font_size_label.cpp
src/client/ui/widgets/general/display/dynamic_font_size_push_button.cpp
src/client/ui/widgets/general/display/shadow_background_label.cpp
src/main.cpp
src/messagelogwidget.cpp
src/pending_command.cpp
src/phase.cpp
src/phasestoolbar.cpp
src/pictureloader.cpp
src/pilezone.cpp
src/pixmapgenerator.cpp
src/player.cpp
src/playerlistwidget.cpp
src/playertarget.cpp
src/releasechannel.cpp
src/remoteclient.cpp
src/remotedecklist_treewidget.cpp
src/remotereplaylist_treewidget.cpp
src/replay_timeline_widget.cpp
src/selectzone.cpp
src/sequenceEdit/sequenceedit.cpp
src/setsmodel.cpp
src/settings/carddatabasesettings.cpp
src/settings/downloadsettings.cpp
src/settings/gamefilterssettings.cpp
src/settings/layoutssettings.cpp
src/settings/messagesettings.cpp
src/settings/serverssettings.cpp
src/settings/settingsmanager.cpp
src/settingscache.cpp
src/shortcutssettings.cpp
src/soundengine.cpp
src/spoilerbackgroundupdater.cpp
src/stackzone.cpp
src/tab.cpp
src/tab_account.cpp
src/tab_admin.cpp
src/tab_deck_editor.cpp
src/tab_deck_storage.cpp
src/tab_game.cpp
src/tab_logs.cpp
src/tab_message.cpp
src/tab_replays.cpp
src/tab_room.cpp
src/tab_server.cpp
src/tab_supervisor.cpp
src/tablezone.cpp
src/tappedout_interface.cpp
src/thememanager.cpp
src/tip_of_the_day.cpp
src/translatecountername.cpp
src/update_downloader.cpp
src/user_context_menu.cpp
src/userconnection_information.cpp
src/userinfobox.cpp
src/userlist.cpp
src/window_main.cpp
src/zoneviewwidget.cpp
src/zoneviewzone.cpp
src/server/message_log_widget.cpp
src/client/ui/layouts/overlap_layout.cpp
src/client/ui/widgets/general/layout_containers/overlap_widget.cpp
src/client/ui/widgets/general/layout_containers/overlap_control_widget.cpp
src/server/pending_command.cpp
src/game/phase.cpp
src/client/ui/phases_toolbar.cpp
src/client/ui/picture_loader.cpp
src/game/zones/pile_zone.cpp
src/client/ui/pixel_map_generator.cpp
src/game/player/player.cpp
src/game/player/player_list_widget.cpp
src/game/player/player_target.cpp
src/client/ui/widgets/printing_selector/all_zones_card_amount_widget.cpp
src/client/ui/widgets/printing_selector/card_amount_widget.cpp
src/client/ui/widgets/printing_selector/printing_selector.cpp
src/client/ui/widgets/printing_selector/printing_selector_card_display_widget.cpp
src/client/ui/widgets/printing_selector/printing_selector_card_overlay_widget.cpp
src/client/ui/widgets/printing_selector/printing_selector_card_search_widget.cpp
src/client/ui/widgets/printing_selector/printing_selector_card_selection_widget.cpp
src/client/ui/widgets/printing_selector/printing_selector_card_sorting_widget.cpp
src/client/ui/widgets/printing_selector/printing_selector_view_options_toolbar_widget.cpp
src/client/ui/widgets/printing_selector/printing_selector_view_options_widget.cpp
src/client/ui/widgets/printing_selector/set_name_and_collectors_number_display_widget.cpp
src/client/network/release_channel.cpp
src/client/network/client_update_checker.cpp
src/server/remote/remote_client.cpp
src/server/remote/remote_decklist_tree_widget.cpp
src/server/remote/remote_replay_list_tree_widget.cpp
src/client/network/replay_timeline_widget.cpp
src/game/zones/select_zone.cpp
src/utility/sequence_edit.cpp
src/client/network/sets_model.cpp
src/settings/card_database_settings.cpp
src/settings/download_settings.cpp
src/settings/game_filters_settings.cpp
src/settings/layouts_settings.cpp
src/settings/message_settings.cpp
src/settings/recents_settings.cpp
src/settings/servers_settings.cpp
src/settings/settings_manager.cpp
src/settings/cache_settings.cpp
src/settings/shortcuts_settings.cpp
src/settings/shortcut_treeview.cpp
src/settings/card_override_settings.cpp
src/client/sound_engine.cpp
src/client/network/spoiler_background_updater.cpp
src/game/zones/stack_zone.cpp
src/client/tabs/tab.cpp
src/client/tabs/tab_account.cpp
src/client/tabs/tab_admin.cpp
src/client/tabs/tab_deck_editor.cpp
src/client/tabs/tab_deck_storage.cpp
src/client/tabs/tab_game.cpp
src/client/tabs/tab_logs.cpp
src/client/tabs/tab_message.cpp
src/client/tabs/tab_replays.cpp
src/client/tabs/tab_room.cpp
src/client/tabs/tab_server.cpp
src/client/tabs/tab_supervisor.cpp
src/game/zones/table_zone.cpp
src/client/tapped_out_interface.cpp
src/client/ui/theme_manager.cpp
src/client/ui/tip_of_the_day.cpp
src/client/translate_counter_name.cpp
src/client/update_downloader.cpp
src/server/user/user_context_menu.cpp
src/server/user/user_info_connection.cpp
src/server/user/user_info_box.cpp
src/server/user/user_list.cpp
src/client/ui/window_main.cpp
src/game/zones/view_zone_widget.cpp
src/game/zones/view_zone.cpp
${VERSION_STRING_CPP}
)
@@ -311,15 +343,9 @@ if(WIN32)
PATTERN "audio/qtaudio_wasapi.dll"
PATTERN "audio/qtaudio_windows.dll"
PATTERN "iconengines/qsvgicon.dll"
PATTERN "imageformats/qgif.dll"
PATTERN "imageformats/qicns.dll"
PATTERN "imageformats/qico.dll"
PATTERN "imageformats/qjpeg.dll"
PATTERN "imageformats/qsvg.dll"
PATTERN "imageformats/qtga.dll"
PATTERN "imageformats/qtiff.dll"
PATTERN "imageformats/qwbmp.dll"
PATTERN "imageformats/qwebp.dll"
PATTERN "imageformats/*.dll"
PATTERN "mediaservice/dsengine.dll"
PATTERN "mediaservice/wmfengine.dll"
PATTERN "platforms/qdirect2d.dll"
PATTERN "platforms/qminimal.dll"
PATTERN "platforms/qoffscreen.dll"
@@ -355,12 +381,12 @@ Data = Resources\")
COMPONENT Runtime
)
if(WIN32SSLRUNTIME_FOUND)
install(FILES ${WIN32SSLRUNTIME_LIBRARIES} DESTINATION ./)
if(OPENSSL_FOUND)
install(FILES ${OPENSSL_INCLUDE_DIRS} DESTINATION ./)
endif()
endif()
if(Qt6LinguistTools_FOUND)
if(Qt6_FOUND AND Qt6LinguistTools_FOUND)
#Qt6 Translations happen after the executable is built up
if(UPDATE_TRANSLATIONS)
qt6_add_translations(

View File

@@ -351,10 +351,10 @@
<file>resources/tips/images/cockatrice_wiki.png</file>
<file>resources/tips/images/coin_flip.png</file>
<file>resources/tips/images/counter_expression.png</file>
<file>resources/tips/images/discord.png</file>
<file>resources/tips/images/face_down.png</file>
<file>resources/tips/images/filter_games.png</file>
<file>resources/tips/images/github_logo.png</file>
<file>resources/tips/images/gitter.png</file>
<file>resources/tips/images/setpt.png</file>
<file>resources/tips/images/shortcuts.png</file>
<file>resources/tips/images/themes.png</file>

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -1,6 +1,7 @@
## Syntax Help
## Search Syntax Help
-----
The search bar recognizes a set of special commands similar to some other card databases. Here is a list with examples. Each entry can be clicked to test the query and has a small explanation. Note that all searches are case insensitive.
The search bar recognizes a set of special commands similar to some other card databases.<br>
In this list of examples below, each entry has an explanation and can be clicked to test the query. Note that all searches are case insensitive.
<dl>
<dt>Name:</dt>
<dd>[birds of paradise](#birds of paradise) <small>(Any card name containing the words birds, of, and paradise)</small></dd>
@@ -46,8 +47,7 @@ The search bar recognizes a set of special commands similar to some other card d
<dt><u>E</u>dition:</dt>
<dd>[set:lea](#set:lea) <small>(Cards that appear in Alpha, which has the set code LEA)</small></dd>
<dd>[e:lea,leb](#e:lea,leb) <small>(Cards that appear in Alpha or Beta)</small></dd>
<dd><a href="#e:lea,leb -(e:lea e:leb)">e:lea,leb -(e:lea e:leb)</a> <small>(Cards that appear in Alpha or Beta but not in both editions)</small></dd>
<dd>[e:lea or e:leb](#e:lea or e:leb) <small>(Cards that appear in Alpha or Beta)</small></dd>
<dt>Negate:</dt>
<dd>[c:wu -c:m](#c:wu -c:m) <small>(Any card that is white or blue, but not multicolored)</small></dd>

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

View File

@@ -7,9 +7,9 @@
</tip>
<tip>
<title>Suggesting New Tips</title>
<text>You can suggest new Tips of the Day by reaching out to the development team on &lt;a href="https://gitter.im/cockatrice/cockatrice"&gt;Gitter&lt;/a&gt;!</text>
<image>gitter.png</image>
<date>2018-03-01</date>
<text>You can suggest new Tips of the Day by reaching out to the development team on &lt;a href="https://discord.gg/3Z9yzmA"&gt;Discord&lt;/a&gt;!</text>
<image>discord.png</image>
<date>2023-10-18</date>
</tip>
<tip>
<title>Reporting Bugs</title>

View File

@@ -1,60 +0,0 @@
#include "cardinfopicture.h"
#include "carditem.h"
#include "main.h"
#include "pictureloader.h"
#include <QPainter>
#include <QStyle>
#include <QWidget>
CardInfoPicture::CardInfoPicture(QWidget *parent) : QWidget(parent), info(nullptr), pixmapDirty(true)
{
setMinimumHeight(100);
}
void CardInfoPicture::setCard(CardInfoPtr card)
{
if (info) {
disconnect(info.data(), nullptr, this, nullptr);
}
info = card;
if (info) {
connect(info.data(), SIGNAL(pixmapUpdated()), this, SLOT(updatePixmap()));
}
updatePixmap();
}
void CardInfoPicture::resizeEvent(QResizeEvent *)
{
updatePixmap();
}
void CardInfoPicture::updatePixmap()
{
pixmapDirty = true;
update();
}
void CardInfoPicture::loadPixmap()
{
if (info)
PictureLoader::getPixmap(resizedPixmap, info, size());
else
PictureLoader::getCardBackPixmap(resizedPixmap, size());
}
void CardInfoPicture::paintEvent(QPaintEvent *)
{
if (width() == 0 || height() == 0)
return;
if (pixmapDirty)
loadPixmap();
QPainter painter(this);
style()->drawItemPixmap(&painter, rect(), Qt::AlignHCenter, resizedPixmap);
}

View File

@@ -1,31 +0,0 @@
#ifndef CARDINFOPICTURE_H
#define CARDINFOPICTURE_H
#include "carddatabase.h"
#include <QWidget>
class AbstractCardItem;
class CardInfoPicture : public QWidget
{
Q_OBJECT
private:
CardInfoPtr info;
QPixmap resizedPixmap;
bool pixmapDirty;
public:
CardInfoPicture(QWidget *parent = nullptr);
protected:
void resizeEvent(QResizeEvent *event);
void paintEvent(QPaintEvent *);
void loadPixmap();
public slots:
void setCard(CardInfoPtr card);
void updatePixmap();
};
#endif

View File

@@ -1,63 +0,0 @@
#include "cardlist.h"
#include "carddatabase.h"
#include "carditem.h"
#include <algorithm>
CardList::CardList(bool _contentsKnown) : QList<CardItem *>(), contentsKnown(_contentsKnown)
{
}
CardItem *CardList::findCard(const int id, const bool remove, int *position)
{
if (!contentsKnown) {
if (empty())
return 0;
CardItem *temp = at(0);
if (remove)
removeAt(0);
if (position)
*position = id;
return temp;
} else
for (int i = 0; i < size(); i++) {
CardItem *temp = at(i);
if (temp->getId() == id) {
if (remove)
removeAt(i);
if (position)
*position = i;
return temp;
}
}
return 0;
}
class CardList::compareFunctor
{
private:
int flags;
public:
explicit compareFunctor(int _flags) : flags(_flags)
{
}
inline bool operator()(CardItem *a, CardItem *b) const
{
if (flags & SortByType) {
QString t1 = a->getInfo() ? a->getInfo()->getMainCardType() : QString();
QString t2 = b->getInfo() ? b->getInfo()->getMainCardType() : QString();
if ((t1 == t2) && (flags & SortByName))
return a->getName() < b->getName();
return t1 < t2;
} else
return a->getName() < b->getName();
}
};
void CardList::sort(int flags)
{
compareFunctor cf(flags);
std::sort(begin(), end(), cf);
}

View File

@@ -1,31 +0,0 @@
#ifndef CARDLIST_H
#define CARDLIST_H
#include <QList>
class CardItem;
class CardList : public QList<CardItem *>
{
private:
class compareFunctor;
protected:
bool contentsKnown;
public:
enum SortFlags
{
SortByName = 1,
SortByType = 2
};
CardList(bool _contentsKnown);
CardItem *findCard(const int id, const bool remove, int *position = NULL);
bool getContentsKnown() const
{
return contentsKnown;
}
void sort(int flags = SortByName);
};
#endif

View File

@@ -1,6 +1,6 @@
#include "abstractclient.h"
#include "abstract_client.h"
#include "client_metatypes.h"
#include "../../server/pending_command.h"
#include "featureset.h"
#include "get_pb_extension.h"
#include "pb/commands.pb.h"
@@ -18,7 +18,6 @@
#include "pb/event_user_left.pb.h"
#include "pb/event_user_message.pb.h"
#include "pb/server_message.pb.h"
#include "pending_command.h"
#include <google/protobuf/descriptor.h>
@@ -53,7 +52,7 @@ AbstractClient::AbstractClient(QObject *parent)
FeatureSet features;
features.initalizeFeatureList(clientFeatures);
connect(this, SIGNAL(sigQueuePendingCommand(PendingCommand *)), this, SLOT(queuePendingCommand(PendingCommand *)));
connect(this, &AbstractClient::sigQueuePendingCommand, this, &AbstractClient::queuePendingCommand);
}
AbstractClient::~AbstractClient()

View File

@@ -1,4 +1,4 @@
#include "keysignals.h"
#include "key_signals.h"
#include <QKeyEvent>

View File

@@ -1,4 +1,4 @@
#include "gettextwithmax.h"
#include "get_text_with_max.h"
QString getTextWithMax(QWidget *parent,
const QString &title,

View File

@@ -2,7 +2,7 @@
#ifndef GETTEXTWITHMAX_H
#define GETTEXTWITHMAX_H
#include "stringsizes.h"
#include "trice_limits.h"
#include <QInputDialog>

View File

@@ -0,0 +1,35 @@
#include "client_update_checker.h"
#include "../../settings/cache_settings.h"
#include "release_channel.h"
ClientUpdateChecker::ClientUpdateChecker(QObject *parent) : QObject(parent)
{
}
void ClientUpdateChecker::check()
{
auto releaseChannel = SettingsCache::instance().getUpdateReleaseChannel();
finishedCheckConnection =
connect(releaseChannel, &ReleaseChannel::finishedCheck, this, &ClientUpdateChecker::actFinishedCheck);
errorConnection = connect(releaseChannel, &ReleaseChannel::error, this, &ClientUpdateChecker::actError);
releaseChannel->checkForUpdates();
}
void ClientUpdateChecker::actFinishedCheck(bool needToUpdate, bool isCompatible, Release *release)
{
disconnect(finishedCheckConnection);
disconnect(errorConnection);
emit finishedCheck(needToUpdate, isCompatible, release);
}
void ClientUpdateChecker::actError(const QString &errorString)
{
disconnect(finishedCheckConnection);
disconnect(errorConnection);
emit error(errorString);
}

View File

@@ -0,0 +1,45 @@
#ifndef CLIENT_UPDATE_CHECKER_H
#define CLIENT_UPDATE_CHECKER_H
#include <QObject>
class Release;
/**
* We use a singleton instance of UpdateChannel, which can cause interference and feedback loops when multiple objects
* connect to it.
*
* This class encapsulates the usage of that UpdateChannel to ensure that the check only happens once per connection and
* the connection is destroyed after it's been used.
*/
class ClientUpdateChecker : public QObject
{
Q_OBJECT
QMetaObject::Connection finishedCheckConnection;
QMetaObject::Connection errorConnection;
void actFinishedCheck(bool needToUpdate, bool isCompatible, Release *release);
void actError(const QString &errorString);
public:
explicit ClientUpdateChecker(QObject *parent = nullptr);
/**
* Actually performs the check, using the currently selected update channel in the settings.
* Any resulting signals will only be sent once.
* This method should only be called ONCE per instance.
*/
void check();
signals:
/**
* Forwarded from UpdateChannel::finishedCheck
*/
void finishedCheck(bool needToUpdate, bool isCompatible, Release *release);
/**
* Forwarded from UpdateChannel::error
*/
void error(const QString &errorString);
};
#endif // CLIENT_UPDATE_CHECKER_H

View File

@@ -1,4 +1,4 @@
#include "releasechannel.h"
#include "release_channel.h"
#include "version_string.h"
@@ -21,11 +21,8 @@
#define GIT_SHORT_HASH_LEN 7
int ReleaseChannel::sharedIndex = 0;
ReleaseChannel::ReleaseChannel() : netMan(new QNetworkAccessManager(this)), response(nullptr), lastRelease(nullptr)
{
index = sharedIndex++;
}
ReleaseChannel::~ReleaseChannel()
@@ -38,11 +35,11 @@ void ReleaseChannel::checkForUpdates()
QString releaseChannelUrl = getReleaseChannelUrl();
qDebug() << "Searching for updates on the channel: " << releaseChannelUrl;
response = netMan->get(QNetworkRequest(releaseChannelUrl));
connect(response, SIGNAL(finished()), this, SLOT(releaseListFinished()));
connect(response, &QNetworkReply::finished, this, &ReleaseChannel::releaseListFinished);
}
// Different release channel checking functions for different operating systems
#if defined(Q_OS_OSX)
#if defined(Q_OS_MACOS)
bool ReleaseChannel::downloadMatchesCurrentOS(const QString &fileName)
{
static QRegularExpression version_regex("macOS-(\\d+)\\.(\\d+)");
@@ -62,9 +59,14 @@ bool ReleaseChannel::downloadMatchesCurrentOS(const QString &fileName)
bool ReleaseChannel::downloadMatchesCurrentOS(const QString &fileName)
{
#if Q_PROCESSOR_WORDSIZE == 4
return fileName.contains("win32");
return fileName.contains("32bit");
#elif Q_PROCESSOR_WORDSIZE == 8
return fileName.contains("win64");
const QString &version = QSysInfo::productVersion();
if (version.startsWith("7") || version.startsWith("8")) {
return fileName.contains("Win7");
} else {
return fileName.contains("Win10");
}
#else
return false;
#endif
@@ -107,7 +109,7 @@ void StableReleaseChannel::releaseListFinished()
QVariantMap resultMap = jsonResponse.toVariant().toMap();
if (!(resultMap.contains("name") && resultMap.contains("html_url") && resultMap.contains("tag_name") &&
resultMap.contains("published_at"))) {
qWarning() << "Invalid received from the release update server.";
qWarning() << "Invalid received from the release update server:" << resultMap;
emit error(tr("Invalid reply received from the release update server."));
return;
}
@@ -153,7 +155,7 @@ void StableReleaseChannel::releaseListFinished()
QString url = QString(STABLETAG_URL) + tagName;
qDebug() << "Searching for commit hash corresponding to stable channel tag: " << tagName;
response = netMan->get(QNetworkRequest(url));
connect(response, SIGNAL(finished()), this, SLOT(tagListFinished()));
connect(response, &QNetworkReply::finished, this, &StableReleaseChannel::tagListFinished);
}
void StableReleaseChannel::tagListFinished()
@@ -255,7 +257,7 @@ void BetaReleaseChannel::releaseListFinished()
qDebug() << "Searching for a corresponding file on the beta channel: " << betaBuildDownloadUrl;
response = netMan->get(QNetworkRequest(betaBuildDownloadUrl));
connect(response, SIGNAL(finished()), this, SLOT(fileListFinished()));
connect(response, &QNetworkReply::finished, this, &BetaReleaseChannel::fileListFinished);
}
void BetaReleaseChannel::fileListFinished()

View File

@@ -82,9 +82,6 @@ public:
~ReleaseChannel() override;
protected:
// shared by all instances
static int sharedIndex;
int index;
QNetworkAccessManager *netMan;
QNetworkReply *response;
Release *lastRelease;
@@ -94,10 +91,6 @@ protected:
virtual QString getReleaseChannelUrl() const = 0;
public:
int getIndex() const
{
return index;
}
Release *getLastRelease()
{
return lastRelease;

View File

@@ -0,0 +1,193 @@
#include "replay_timeline_widget.h"
#include <QPainter>
#include <QPainterPath>
#include <QPalette>
#include <QTimer>
ReplayTimelineWidget::ReplayTimelineWidget(QWidget *parent)
: QWidget(parent), maxBinValue(1), maxTime(1), timeScaleFactor(1.0), currentVisualTime(0), currentProcessedTime(0),
currentEvent(0)
{
replayTimer = new QTimer(this);
connect(replayTimer, &QTimer::timeout, this, &ReplayTimelineWidget::replayTimerTimeout);
rewindBufferingTimer = new QTimer(this);
rewindBufferingTimer->setSingleShot(true);
connect(rewindBufferingTimer, &QTimer::timeout, this, &ReplayTimelineWidget::processRewind);
}
void ReplayTimelineWidget::setTimeline(const QList<int> &_replayTimeline)
{
replayTimeline = _replayTimeline;
histogram.clear();
int binEndTime = BIN_LENGTH - 1;
int binValue = 0;
for (int i : replayTimeline) {
if (i > binEndTime) {
histogram.append(binValue);
if (binValue > maxBinValue)
maxBinValue = binValue;
while (i > binEndTime + BIN_LENGTH) {
histogram.append(0);
binEndTime += BIN_LENGTH;
}
binValue = 1;
binEndTime += BIN_LENGTH;
} else
++binValue;
}
histogram.append(binValue);
if (!replayTimeline.isEmpty())
maxTime = replayTimeline.last();
update();
}
void ReplayTimelineWidget::paintEvent(QPaintEvent * /* event */)
{
QPainter painter(this);
painter.drawRect(0, 0, width() - 1, height() - 1);
qreal binWidth = (qreal)width() / histogram.size();
QPainterPath path;
path.moveTo(0, height() - 1);
for (int i = 0; i < histogram.size(); ++i)
path.lineTo(qRound(i * binWidth), (height() - 1) * (1.0 - (qreal)histogram[i] / maxBinValue));
path.lineTo(width() - 1, height() - 1);
path.lineTo(0, height() - 1);
painter.fillPath(path, Qt::black);
const QColor barColor = QColor::fromHsv(120, 255, 255, 100);
quint64 w = (quint64)(width() - 1) * (quint64)currentVisualTime / maxTime;
painter.fillRect(0, 0, static_cast<int>(w), height() - 1, barColor);
}
void ReplayTimelineWidget::mousePressEvent(QMouseEvent *event)
{
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
int newTime = static_cast<int>((qint64)maxTime * (qint64)event->position().x() / width());
#else
int newTime = static_cast<int>((qint64)maxTime * (qint64)event->x() / width());
#endif
// don't buffer rewinds from clicks, since clicks usually don't happen fast enough to require buffering
skipToTime(newTime, false);
}
void ReplayTimelineWidget::skipToTime(int newTime, bool doRewindBuffering)
{
// check boundary conditions
if (newTime < 0) {
newTime = 0;
}
if (newTime > maxTime) {
newTime = maxTime;
}
newTime -= newTime % TIMER_INTERVAL_MS; // Time should always be a multiple of the interval
const bool isBackwardsSkip = newTime < currentProcessedTime;
currentVisualTime = newTime;
if (isBackwardsSkip) {
handleBackwardsSkip(doRewindBuffering);
} else {
processNewEvents(FORWARD_SKIP);
}
update();
}
/// @param doRewindBuffering When true, if multiple backward skips are made in quick succession, only a single rewind
/// is processed at the end. When false, the backwards skip will always cause an immediate rewind
void ReplayTimelineWidget::handleBackwardsSkip(bool doRewindBuffering)
{
if (doRewindBuffering) {
// We use a one-shot timer to implement the rewind buffering.
// The rewind only happens once the timer runs out.
// If another backwards skip happens, the timer will just get reset instead of rewinding.
rewindBufferingTimer->stop();
rewindBufferingTimer->start(SettingsCache::instance().getRewindBufferingMs());
} else {
// otherwise, process the rewind immediately
processRewind();
}
}
void ReplayTimelineWidget::processRewind()
{
// stop any queued-up rewinds
rewindBufferingTimer->stop();
// process the rewind
currentEvent = 0;
emit rewound();
processNewEvents(BACKWARD_SKIP);
}
QSize ReplayTimelineWidget::sizeHint() const
{
return {-1, 50};
}
QSize ReplayTimelineWidget::minimumSizeHint() const
{
return {400, 50};
}
void ReplayTimelineWidget::replayTimerTimeout()
{
currentVisualTime += TIMER_INTERVAL_MS;
processNewEvents(NORMAL_PLAYBACK);
if (!(currentVisualTime % 1000))
update();
}
/// Processes all unprocessed events up to the current time.
void ReplayTimelineWidget::processNewEvents(PlaybackMode playbackMode)
{
currentProcessedTime = currentVisualTime;
while ((currentEvent < replayTimeline.size()) && (replayTimeline[currentEvent] < currentProcessedTime)) {
Player::EventProcessingOptions options;
// backwards skip => always skip reveal windows
// forwards skip => skip reveal windows that don't happen within a big skip of the target
if (playbackMode == BACKWARD_SKIP || currentProcessedTime - replayTimeline[currentEvent] > BIG_SKIP_MS)
options |= Player::EventProcessingOption::SKIP_REVEAL_WINDOW;
// backwards skip => always skip tap animation
if (playbackMode == BACKWARD_SKIP)
options |= Player::EventProcessingOption::SKIP_TAP_ANIMATION;
emit processNextEvent(options);
++currentEvent;
}
if (currentEvent == replayTimeline.size()) {
emit replayFinished();
replayTimer->stop();
}
}
void ReplayTimelineWidget::setTimeScaleFactor(qreal _timeScaleFactor)
{
timeScaleFactor = _timeScaleFactor;
replayTimer->setInterval(static_cast<int>(TIMER_INTERVAL_MS / timeScaleFactor));
}
void ReplayTimelineWidget::startReplay()
{
replayTimer->start(static_cast<int>(TIMER_INTERVAL_MS / timeScaleFactor));
}
void ReplayTimelineWidget::stopReplay()
{
replayTimer->stop();
}
void ReplayTimelineWidget::skipByAmount(int amount)
{
skipToTime(currentVisualTime + amount, amount < 0);
}

View File

@@ -1,6 +1,8 @@
#ifndef REPLAY_TIMELINE_WIDGET
#define REPLAY_TIMELINE_WIDGET
#include "../../game/player/player.h"
#include <QList>
#include <QMouseEvent>
#include <QWidget>
@@ -12,23 +14,44 @@ class ReplayTimelineWidget : public QWidget
{
Q_OBJECT
signals:
void processNextEvent();
void processNextEvent(Player::EventProcessingOptions options);
void replayFinished();
void rewound();
private:
enum PlaybackMode
{
NORMAL_PLAYBACK,
FORWARD_SKIP,
BACKWARD_SKIP
};
static constexpr int TIMER_INTERVAL_MS = 200;
static constexpr int BIN_LENGTH = 5000;
QTimer *replayTimer;
QTimer *rewindBufferingTimer;
QList<int> replayTimeline;
QList<int> histogram;
static const int binLength;
int maxBinValue, maxTime;
qreal timeScaleFactor;
int currentTime;
int currentVisualTime; // time currently displayed by the timeline
int currentProcessedTime; // time that events are currently processed up to. Could differ from visual time due to
// rewind buffering
int currentEvent;
void skipToTime(int newTime, bool doRewindBuffering);
void handleBackwardsSkip(bool doRewindBuffering);
void processRewind();
void processNewEvents(PlaybackMode playbackMode);
private slots:
void replayTimerTimeout();
public:
static constexpr int SMALL_SKIP_MS = 1000;
static constexpr int BIG_SKIP_MS = 10000;
static constexpr qreal FAST_FORWARD_SCALE_FACTOR = 10.0;
explicit ReplayTimelineWidget(QWidget *parent = nullptr);
void setTimeline(const QList<int> &_replayTimeline);
QSize sizeHint() const override;
@@ -41,6 +64,7 @@ public:
public slots:
void startReplay();
void stopReplay();
void skipByAmount(int amount); // use a negative amount to skip backwards
protected:
void paintEvent(QPaintEvent *event) override;

View File

@@ -1,4 +1,4 @@
#include "setsmodel.h"
#include "sets_model.h"
#include <QSortFilterProxyModel>
@@ -195,6 +195,13 @@ void SetsModel::swapRows(int oldRow, int newRow)
emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
}
void SetsModel::restoreOriginalOrder()
{
int numRows = rowCount();
sets.defaultSort();
emit dataChanged(index(0, 0), index(numRows - 1, columnCount() - 1));
}
void SetsModel::sort(int column, Qt::SortOrder order)
{
QMultiMap<QString, CardSetPtr> setMap;

View File

@@ -1,7 +1,7 @@
#ifndef SETSMODEL_H
#define SETSMODEL_H
#include "carddatabase.h"
#include "../../game/cards/card_database.h"
#include <QAbstractTableModel>
#include <QMimeData>
@@ -49,7 +49,8 @@ public:
LongNameCol,
ShortNameCol,
SetTypeCol,
ReleaseDateCol
ReleaseDateCol,
PriorityCol
};
enum Role
{
@@ -80,6 +81,7 @@ public:
void sort(int column, Qt::SortOrder order = Qt::AscendingOrder);
void save(CardDatabase *db);
void restore(CardDatabase *db);
void restoreOriginalOrder();
};
class SetsDisplayModel : public QSortFilterProxyModel

View File

@@ -1,9 +1,10 @@
#include "spoilerbackgroundupdater.h"
#include "spoiler_background_updater.h"
#include "carddatabase.h"
#include "main.h"
#include "settingscache.h"
#include "window_main.h"
#include "../../game/cards/card_database.h"
#include "../../game/cards/card_database_manager.h"
#include "../../main.h"
#include "../../settings/cache_settings.h"
#include "../ui/window_main.h"
#include <QApplication>
#include <QCryptographicHash>
@@ -44,10 +45,10 @@ void SpoilerBackgroundUpdater::downloadFromURL(QUrl url, bool saveResults)
if (saveResults) {
// This will write out to the file (used for spoiler.xml)
connect(reply, SIGNAL(finished()), this, SLOT(actDownloadFinishedSpoilersFile()));
connect(reply, &QNetworkReply::finished, this, &SpoilerBackgroundUpdater::actDownloadFinishedSpoilersFile);
} else {
// This will check the status (used to see if we're in spoiler season or not)
connect(reply, SIGNAL(finished()), this, SLOT(actCheckIfSpoilerSeasonEnabled()));
connect(reply, &QNetworkReply::finished, this, &SpoilerBackgroundUpdater::actCheckIfSpoilerSeasonEnabled);
}
}
@@ -159,7 +160,7 @@ bool SpoilerBackgroundUpdater::saveDownloadedFile(QByteArray data)
// Data written, so reload the card database
qDebug() << "Spoiler Service Data Written";
const auto reloadOk = QtConcurrent::run([] { db->loadCardDatabases(); });
const auto reloadOk = QtConcurrent::run([] { CardDatabaseManager::getInstance()->loadCardDatabases(); });
// If the user has notifications enabled, let them know
// when the database was last updated
@@ -172,7 +173,11 @@ bool SpoilerBackgroundUpdater::saveDownloadedFile(QByteArray data)
timeStamp.chop(6); // Remove " (UTC)"
auto utcTime = QLocale().toDateTime(timeStamp, "ddd, MMM dd yyyy, hh:mm:ss");
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
utcTime.setTimeZone(QTimeZone::UTC);
#else
utcTime.setTimeSpec(Qt::UTC);
#endif
QString localTime = utcTime.toLocalTime().toString("MMM d, hh:mm");

View File

@@ -1,19 +1,22 @@
#include "soundengine.h"
#include "sound_engine.h"
#include "settingscache.h"
#include "../settings/cache_settings.h"
#include <QAudioOutput>
#include <QDir>
#include <QMediaPlayer>
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
#include <QAudioOutput>
#endif
#define DEFAULT_THEME_NAME "Default"
#define TEST_SOUND_FILENAME "player_join"
SoundEngine::SoundEngine(QObject *parent) : QObject(parent), player(nullptr)
{
ensureThemeDirectoryExists();
connect(&SettingsCache::instance(), SIGNAL(soundThemeChanged()), this, SLOT(themeChangedSlot()));
connect(&SettingsCache::instance(), SIGNAL(soundEnabledChanged()), this, SLOT(soundEnabledChanged()));
connect(&SettingsCache::instance(), &SettingsCache::soundThemeChanged, this, &SoundEngine::themeChangedSlot);
connect(&SettingsCache::instance(), &SettingsCache::soundEnabledChanged, this, &SoundEngine::soundEnabledChanged);
soundEnabledChanged();
themeChangedSlot();
@@ -23,7 +26,7 @@ SoundEngine::~SoundEngine()
{
if (player) {
player->deleteLater();
player = 0;
player = nullptr;
}
}
@@ -48,7 +51,7 @@ void SoundEngine::soundEnabledChanged()
}
}
void SoundEngine::playSound(QString fileName)
void SoundEngine::playSound(const QString &fileName)
{
if (!player) {
return;

View File

@@ -15,9 +15,9 @@ class SoundEngine : public QObject
{
Q_OBJECT
public:
SoundEngine(QObject *parent = nullptr);
~SoundEngine();
void playSound(QString fileName);
explicit SoundEngine(QObject *parent = nullptr);
~SoundEngine() override;
void playSound(const QString &fileName);
QStringMap &getAvailableThemes();
private:

View File

@@ -1,6 +1,6 @@
#include "tab.h"
#include "cardinfowidget.h"
#include "../ui/widgets/cards/card_info_display_widget.h"
#include <QApplication>
#include <QDebug>
@@ -18,7 +18,7 @@ void Tab::showCardInfoPopup(const QPoint &pos, const QString &cardName)
infoPopup->deleteLater();
}
currentCardName = cardName;
infoPopup = new CardInfoWidget(
infoPopup = new CardInfoDisplayWidget(
cardName, 0, Qt::Widget | Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint | Qt::WindowStaysOnTopHint);
infoPopup->setAttribute(Qt::WA_TransparentForMouseEvents);

View File

@@ -5,7 +5,7 @@
class QMenu;
class TabSupervisor;
class CardInfoWidget;
class CardInfoDisplayWidget;
class Tab : public QMainWindow
{
@@ -27,7 +27,7 @@ protected slots:
private:
QString currentCardName;
bool contentsChanged;
CardInfoWidget *infoPopup;
CardInfoDisplayWidget *infoPopup;
QList<QMenu *> tabMenus;
public:

View File

@@ -1,18 +1,18 @@
#include "tab_account.h"
#include "abstractclient.h"
#include "customlineedit.h"
#include "../../deck/custom_line_edit.h"
#include "../../server/pending_command.h"
#include "../../server/user/user_info_box.h"
#include "../../server/user/user_list.h"
#include "../game_logic/abstract_client.h"
#include "../sound_engine.h"
#include "pb/event_add_to_list.pb.h"
#include "pb/event_remove_from_list.pb.h"
#include "pb/event_user_joined.pb.h"
#include "pb/event_user_left.pb.h"
#include "pb/response_list_users.pb.h"
#include "pb/session_commands.pb.h"
#include "pending_command.h"
#include "soundengine.h"
#include "stringsizes.h"
#include "userinfobox.h"
#include "userlist.h"
#include "trice_limits.h"
#include <QHBoxLayout>
#include <QPushButton>
@@ -30,29 +30,22 @@ TabUserLists::TabUserLists(TabSupervisor *_tabSupervisor,
userInfoBox = new UserInfoBox(client, true);
userInfoBox->updateInfo(userInfo);
connect(allUsersList, SIGNAL(openMessageDialog(const QString &, bool)), this,
SIGNAL(openMessageDialog(const QString &, bool)));
connect(buddyList, SIGNAL(openMessageDialog(const QString &, bool)), this,
SIGNAL(openMessageDialog(const QString &, bool)));
connect(ignoreList, SIGNAL(openMessageDialog(const QString &, bool)), this,
SIGNAL(openMessageDialog(const QString &, bool)));
connect(allUsersList, &UserList::openMessageDialog, this, &TabUserLists::openMessageDialog);
connect(buddyList, &UserList::openMessageDialog, this, &TabUserLists::openMessageDialog);
connect(ignoreList, &UserList::openMessageDialog, this, &TabUserLists::openMessageDialog);
connect(client, SIGNAL(userJoinedEventReceived(const Event_UserJoined &)), this,
SLOT(processUserJoinedEvent(const Event_UserJoined &)));
connect(client, SIGNAL(userLeftEventReceived(const Event_UserLeft &)), this,
SLOT(processUserLeftEvent(const Event_UserLeft &)));
connect(client, SIGNAL(buddyListReceived(const QList<ServerInfo_User> &)), this,
SLOT(buddyListReceived(const QList<ServerInfo_User> &)));
connect(client, SIGNAL(ignoreListReceived(const QList<ServerInfo_User> &)), this,
SLOT(ignoreListReceived(const QList<ServerInfo_User> &)));
connect(client, SIGNAL(addToListEventReceived(const Event_AddToList &)), this,
SLOT(processAddToListEvent(const Event_AddToList &)));
connect(client, SIGNAL(removeFromListEventReceived(const Event_RemoveFromList &)), this,
SLOT(processRemoveFromListEvent(const Event_RemoveFromList &)));
connect(client, &AbstractClient::userJoinedEventReceived, this, &TabUserLists::processUserJoinedEvent);
connect(client, &AbstractClient::userLeftEventReceived, this, &TabUserLists::processUserLeftEvent);
connect(client, &AbstractClient::buddyListReceived, this, &TabUserLists::buddyListReceived);
connect(client, &AbstractClient::ignoreListReceived, this, &TabUserLists::ignoreListReceived);
connect(client, &AbstractClient::addToListEventReceived, this, &TabUserLists::processAddToListEvent);
connect(client, &AbstractClient::removeFromListEventReceived, this, &TabUserLists::processRemoveFromListEvent);
PendingCommand *pend = client->prepareSessionCommand(Command_ListUsers());
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
SLOT(processListUsersResponse(const Response &)));
connect(pend,
static_cast<void (PendingCommand::*)(const Response &, const CommandContainer &, const QVariant &)>(
&PendingCommand::finished),
this, &TabUserLists::processListUsersResponse);
client->sendCommand(pend);
QVBoxLayout *vbox = new QVBoxLayout;
@@ -63,9 +56,9 @@ TabUserLists::TabUserLists(TabSupervisor *_tabSupervisor,
addBuddyEdit = new LineEditUnfocusable;
addBuddyEdit->setMaxLength(MAX_NAME_LENGTH);
addBuddyEdit->setPlaceholderText(tr("Add to Buddy List"));
connect(addBuddyEdit, SIGNAL(returnPressed()), this, SLOT(addToBuddyList()));
connect(addBuddyEdit, &LineEditUnfocusable::returnPressed, this, &TabUserLists::addToBuddyList);
QPushButton *addBuddyButton = new QPushButton("Add");
connect(addBuddyButton, SIGNAL(clicked()), this, SLOT(addToBuddyList()));
connect(addBuddyButton, &QPushButton::clicked, this, &TabUserLists::addToBuddyList);
addToBuddyList->addWidget(addBuddyEdit);
addToBuddyList->addWidget(addBuddyButton);
@@ -73,9 +66,9 @@ TabUserLists::TabUserLists(TabSupervisor *_tabSupervisor,
addIgnoreEdit = new LineEditUnfocusable;
addIgnoreEdit->setMaxLength(MAX_NAME_LENGTH);
addIgnoreEdit->setPlaceholderText(tr("Add to Ignore List"));
connect(addIgnoreEdit, SIGNAL(returnPressed()), this, SLOT(addToIgnoreList()));
connect(addIgnoreEdit, &LineEditUnfocusable::returnPressed, this, &TabUserLists::addToIgnoreList);
QPushButton *addIgnoreButton = new QPushButton("Add");
connect(addIgnoreButton, SIGNAL(clicked()), this, SLOT(addToIgnoreList()));
connect(addIgnoreButton, &QPushButton::clicked, this, &TabUserLists::addToIgnoreList);
addToIgnoreList->addWidget(addIgnoreEdit);
addToIgnoreList->addWidget(addIgnoreButton);

View File

@@ -0,0 +1,250 @@
#include "tab_admin.h"
#include "../../server/pending_command.h"
#include "../game_logic/abstract_client.h"
#include "pb/admin_commands.pb.h"
#include "pb/event_replay_added.pb.h"
#include "pb/moderator_commands.pb.h"
#include "trice_limits.h"
#include <QDialogButtonBox>
#include <QGridLayout>
#include <QGroupBox>
#include <QLabel>
#include <QLineEdit>
#include <QMessageBox>
#include <QPushButton>
#include <QSpinBox>
ShutdownDialog::ShutdownDialog(QWidget *parent) : QDialog(parent)
{
QLabel *reasonLabel = new QLabel(tr("&Reason for shutdown:"));
reasonEdit = new QLineEdit;
reasonEdit->setMaxLength(MAX_TEXT_LENGTH);
reasonLabel->setBuddy(reasonEdit);
QLabel *minutesLabel = new QLabel(tr("&Time until shutdown (minutes):"));
minutesEdit = new QSpinBox;
minutesLabel->setBuddy(minutesEdit);
minutesEdit->setMinimum(0);
minutesEdit->setValue(5);
minutesEdit->setMaximum(999);
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
connect(buttonBox, &QDialogButtonBox::accepted, this, &ShutdownDialog::accept);
connect(buttonBox, &QDialogButtonBox::rejected, this, &ShutdownDialog::reject);
QGridLayout *mainLayout = new QGridLayout;
mainLayout->addWidget(reasonLabel, 0, 0);
mainLayout->addWidget(reasonEdit, 0, 1);
mainLayout->addWidget(minutesLabel, 1, 0);
mainLayout->addWidget(minutesEdit, 1, 1);
mainLayout->addWidget(buttonBox, 2, 0, 1, 2);
setLayout(mainLayout);
setWindowTitle(tr("Shut down server"));
}
QString ShutdownDialog::getReason() const
{
return reasonEdit->text();
}
int ShutdownDialog::getMinutes() const
{
return minutesEdit->value();
}
TabAdmin::TabAdmin(TabSupervisor *_tabSupervisor, AbstractClient *_client, bool _fullAdmin, QWidget *parent)
: Tab(_tabSupervisor, parent), locked(true), client(_client), fullAdmin(_fullAdmin)
{
updateServerMessageButton = new QPushButton;
connect(updateServerMessageButton, &QPushButton::clicked, this, &TabAdmin::actUpdateServerMessage);
shutdownServerButton = new QPushButton;
connect(shutdownServerButton, &QPushButton::clicked, this, &TabAdmin::actShutdownServer);
reloadConfigButton = new QPushButton;
connect(reloadConfigButton, &QPushButton::clicked, this, &TabAdmin::actReloadConfig);
grantReplayAccessButton = new QPushButton;
grantReplayAccessButton->setEnabled(false);
connect(grantReplayAccessButton, &QPushButton::clicked, this, &TabAdmin::actGrantReplayAccess);
replayIdToGrant = new QLineEdit;
replayIdToGrant->setMaximumWidth(500);
replayIdToGrant->setValidator(new QIntValidator(0, INT_MAX, this));
connect(replayIdToGrant, &QLineEdit::textChanged, this,
[=, this]() { grantReplayAccessButton->setEnabled(!replayIdToGrant->text().isEmpty()); });
auto *grandReplayAccessLayout = new QGridLayout(this);
grandReplayAccessLayout->addWidget(replayIdToGrant, 0, 0);
grandReplayAccessLayout->addWidget(grantReplayAccessButton, 0, 1);
activateUserButton = new QPushButton;
activateUserButton->setEnabled(false);
connect(activateUserButton, &QPushButton::clicked, this, &TabAdmin::actForceActivateUser);
userToActivate = new QLineEdit;
userToActivate->setMaximumWidth(500);
connect(userToActivate, &QLineEdit::textChanged, this,
[=, this]() { activateUserButton->setEnabled(!userToActivate->text().isEmpty()); });
auto *activateUserLayout = new QGridLayout(this);
activateUserLayout->addWidget(userToActivate, 0, 0);
activateUserLayout->addWidget(activateUserButton, 0, 1);
QVBoxLayout *vbox = new QVBoxLayout;
vbox->addWidget(updateServerMessageButton);
vbox->addWidget(shutdownServerButton);
vbox->addWidget(reloadConfigButton);
vbox->addLayout(grandReplayAccessLayout);
vbox->addLayout(activateUserLayout);
vbox->addStretch();
adminGroupBox = new QGroupBox;
adminGroupBox->setLayout(vbox);
adminGroupBox->setEnabled(false);
unlockButton = new QPushButton;
connect(unlockButton, &QPushButton::clicked, this, &TabAdmin::actUnlock);
lockButton = new QPushButton;
lockButton->setEnabled(false);
connect(lockButton, &QPushButton::clicked, this, &TabAdmin::actLock);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addWidget(adminGroupBox);
mainLayout->addWidget(unlockButton);
mainLayout->addWidget(lockButton);
retranslateUi();
QWidget *mainWidget = new QWidget(this);
mainWidget->setLayout(mainLayout);
setCentralWidget(mainWidget);
actUnlock();
}
void TabAdmin::retranslateUi()
{
updateServerMessageButton->setText(tr("Update server &message"));
shutdownServerButton->setText(tr("&Shut down server"));
reloadConfigButton->setText(tr("&Reload configuration"));
adminGroupBox->setTitle(tr("Server administration functions"));
replayIdToGrant->setPlaceholderText(tr("Replay ID"));
grantReplayAccessButton->setText(tr("Grant Replay Access"));
userToActivate->setPlaceholderText(tr("Username to Activate"));
activateUserButton->setText(tr("Force Activate User"));
unlockButton->setText(tr("&Unlock functions"));
lockButton->setText(tr("&Lock functions"));
}
void TabAdmin::actUpdateServerMessage()
{
client->sendCommand(client->prepareAdminCommand(Command_UpdateServerMessage()));
}
void TabAdmin::actShutdownServer()
{
ShutdownDialog dlg;
if (dlg.exec()) {
Command_ShutdownServer cmd;
cmd.set_reason(dlg.getReason().toStdString());
cmd.set_minutes(dlg.getMinutes());
client->sendCommand(AbstractClient::prepareAdminCommand(cmd));
}
}
void TabAdmin::actReloadConfig()
{
Command_ReloadConfig cmd;
client->sendCommand(client->prepareAdminCommand(cmd));
}
void TabAdmin::actGrantReplayAccess()
{
if (!replayIdToGrant) {
return;
}
Command_GrantReplayAccess cmd;
cmd.set_replay_id(replayIdToGrant->text().toUInt());
cmd.set_moderator_name(client->getUserName().toStdString());
auto *pend = client->prepareModeratorCommand(cmd);
connect(pend,
QOverload<const Response &, const CommandContainer &, const QVariant &>::of(&PendingCommand::finished),
this, &TabAdmin::grantReplayAccessProcessResponse);
client->sendCommand(pend);
}
void TabAdmin::actForceActivateUser()
{
if (!userToActivate) {
return;
}
Command_ForceActivateUser cmd;
cmd.set_username_to_activate(userToActivate->text().trimmed().toStdString());
cmd.set_moderator_name(client->getUserName().toStdString());
auto *pend = client->prepareModeratorCommand(cmd);
connect(pend,
QOverload<const Response &, const CommandContainer &, const QVariant &>::of(&PendingCommand::finished),
this, &TabAdmin::activateUserProcessResponse);
client->sendCommand(pend);
}
void TabAdmin::grantReplayAccessProcessResponse(const Response &response, const CommandContainer &, const QVariant &)
{
auto *event = new Event_ReplayAdded();
switch (response.response_code()) {
case Response::RespOk:
client->replayAddedEventReceived(*event);
QMessageBox::information(this, tr("Success"), tr("Replay access granted"));
break;
case Response::RespContextError:
QMessageBox::critical(this, tr("Error"), tr("Unable to grant replay access. Replay ID invalid"));
break;
default:
QMessageBox::critical(this, tr("Error"), tr("Unable to grant replay access. Internal error"));
break;
}
}
void TabAdmin::activateUserProcessResponse(const Response &response, const CommandContainer &, const QVariant &)
{
switch (response.response_code()) {
case Response::RespActivationAccepted:
QMessageBox::information(this, tr("Success"), tr("User successfully activated"));
break;
case Response::RespNameNotFound:
QMessageBox::critical(this, tr("Error"), tr("Unable to activate user. Username invalid"));
break;
case Response::RespActivationFailed:
QMessageBox::critical(this, tr("Error"), tr("Unable to activate user. User already active"));
break;
default:
QMessageBox::critical(this, tr("Error"), tr("Unable to activate user. Internal error"));
break;
}
}
void TabAdmin::actUnlock()
{
if (fullAdmin)
adminGroupBox->setEnabled(true);
lockButton->setEnabled(true);
unlockButton->setEnabled(false);
locked = false;
emit adminLockChanged(false);
}
void TabAdmin::actLock()
{
if (fullAdmin)
adminGroupBox->setEnabled(false);
lockButton->setEnabled(false);
unlockButton->setEnabled(true);
locked = true;
emit adminLockChanged(true);
}

View File

@@ -1,6 +1,8 @@
#ifndef TAB_ADMIN_H
#define TAB_ADMIN_H
#include "pb/commands.pb.h"
#include "pb/response.pb.h"
#include "tab.h"
#include <QDialog>
@@ -32,15 +34,21 @@ private:
bool locked;
AbstractClient *client;
bool fullAdmin;
QPushButton *updateServerMessageButton, *shutdownServerButton, *reloadConfigButton;
QPushButton *updateServerMessageButton, *shutdownServerButton, *reloadConfigButton, *grantReplayAccessButton,
*activateUserButton;
QGroupBox *adminGroupBox;
QPushButton *unlockButton, *lockButton;
QLineEdit *replayIdToGrant, *userToActivate;
signals:
void adminLockChanged(bool lock);
private slots:
void actUpdateServerMessage();
void actShutdownServer();
void actReloadConfig();
void actGrantReplayAccess();
void actForceActivateUser();
void grantReplayAccessProcessResponse(const Response &resp, const CommandContainer &, const QVariant &);
void activateUserProcessResponse(const Response &response, const CommandContainer &, const QVariant &);
void actUnlock();
void actLock();

View File

@@ -1,23 +1,25 @@
#include "tab_deck_editor.h"
#include "abstractclient.h"
#include "carddatabasemodel.h"
#include "cardframe.h"
#include "decklistmodel.h"
#include "deckstats_interface.h"
#include "dlg_load_deck_from_clipboard.h"
#include "filterbuilder.h"
#include "filtertreemodel.h"
#include "main.h"
#include "../../client/game_logic/abstract_client.h"
#include "../../client/tapped_out_interface.h"
#include "../../client/ui/widgets/cards/card_info_frame_widget.h"
#include "../../deck/deck_list_model.h"
#include "../../deck/deck_stats_interface.h"
#include "../../dialogs/dlg_load_deck_from_clipboard.h"
#include "../../game/cards/card_database_manager.h"
#include "../../game/cards/card_database_model.h"
#include "../../game/filters/filter_builder.h"
#include "../../game/filters/filter_tree_model.h"
#include "../../main.h"
#include "../../server/pending_command.h"
#include "../../settings/cache_settings.h"
#include "../ui/picture_loader.h"
#include "../ui/pixel_map_generator.h"
#include "../ui/widgets/printing_selector/printing_selector.h"
#include "pb/command_deck_upload.pb.h"
#include "pb/response.pb.h"
#include "pending_command.h"
#include "pictureloader.h"
#include "pixmapgenerator.h"
#include "settingscache.h"
#include "stringsizes.h"
#include "tab_supervisor.h"
#include "tappedout_interface.h"
#include "trice_limits.h"
#include <QAction>
#include <QApplication>
@@ -48,13 +50,6 @@
#include <QUrl>
#include <QVBoxLayout>
void SearchLineEdit::keyPressEvent(QKeyEvent *event)
{
if (treeView && ((event->key() == Qt::Key_Up) || (event->key() == Qt::Key_Down)))
QCoreApplication::sendEvent(treeView, event);
LineEditUnfocusable::keyPressEvent(event);
}
void TabDeckEditor::createDeckDock()
{
deckModel = new DeckListModel(this);
@@ -68,9 +63,13 @@ void TabDeckEditor::createDeckDock()
deckView->sortByColumn(1, Qt::AscendingOrder);
deckView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
deckView->installEventFilter(&deckViewKeySignals);
deckView->setContextMenuPolicy(Qt::CustomContextMenu);
connect(deckView->selectionModel(), SIGNAL(currentRowChanged(const QModelIndex &, const QModelIndex &)), this,
SLOT(updateCardInfoRight(const QModelIndex &, const QModelIndex &)));
connect(deckView->selectionModel(), SIGNAL(currentRowChanged(const QModelIndex &, const QModelIndex &)), this,
SLOT(updatePrintingSelectorDeckView(const QModelIndex &, const QModelIndex &)));
connect(deckView, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(actSwapCard()));
connect(deckView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(decklistCustomMenu(QPoint)));
connect(&deckViewKeySignals, SIGNAL(onShiftS()), this, SLOT(actSwapCard()));
connect(&deckViewKeySignals, SIGNAL(onEnter()), this, SLOT(actIncrement()));
connect(&deckViewKeySignals, SIGNAL(onCtrlAltEqual()), this, SLOT(actIncrement()));
@@ -162,7 +161,6 @@ void TabDeckEditor::createDeckDock()
deckDock = new QDockWidget(this);
deckDock->setObjectName("deckDock");
deckDock->setMinimumSize(QSize(200, 41));
deckDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
deckDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable |
QDockWidget::DockWidgetMovable);
@@ -177,7 +175,7 @@ void TabDeckEditor::createDeckDock()
void TabDeckEditor::createCardInfoDock()
{
cardInfo = new CardFrame();
cardInfo = new CardInfoFrameWidget();
cardInfo->setObjectName("cardInfo");
auto *cardInfoFrame = new QVBoxLayout;
cardInfoFrame->setObjectName("cardInfoFrame");
@@ -186,7 +184,6 @@ void TabDeckEditor::createCardInfoDock()
cardInfoDock = new QDockWidget(this);
cardInfoDock->setObjectName("cardInfoDock");
cardInfoDock->setMinimumSize(QSize(200, 41));
cardInfoDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
cardInfoDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable |
QDockWidget::DockWidgetMovable);
@@ -261,6 +258,32 @@ void TabDeckEditor::createFiltersDock()
connect(filterDock, SIGNAL(topLevelChanged(bool)), this, SLOT(dockTopLevelChanged(bool)));
}
void TabDeckEditor::createPrintingSelectorDock()
{
printingSelector = new PrintingSelector(this, this, deckModel, deckView);
printingSelector->setObjectName("printingSelector");
auto *printingSelectorFrame = new QVBoxLayout;
printingSelectorFrame->setObjectName("printingSelectorFrame");
printingSelectorFrame->addWidget(printingSelector);
printingSelectorDock = new QDockWidget(this);
printingSelectorDock->setObjectName("printingSelectorDock");
printingSelectorDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
printingSelectorDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable |
QDockWidget::DockWidgetMovable);
auto *printingSelectorDockContents = new QWidget();
printingSelectorDockContents->setObjectName("printingSelectorDockContents");
printingSelectorDockContents->setLayout(printingSelectorFrame);
printingSelectorDock->setWidget(printingSelectorDockContents);
printingSelectorDock->installEventFilter(this);
connect(printingSelectorDock, SIGNAL(topLevelChanged(bool)), this, SLOT(dockTopLevelChanged(bool)));
addDockWidget(Qt::RightDockWidgetArea, printingSelectorDock);
printingSelectorDock->setFloating(false);
}
void TabDeckEditor::createMenus()
{
aNewDeck = new QAction(QString(), this);
@@ -269,6 +292,15 @@ void TabDeckEditor::createMenus()
aLoadDeck = new QAction(QString(), this);
connect(aLoadDeck, SIGNAL(triggered()), this, SLOT(actLoadDeck()));
loadRecentDeckMenu = new QMenu(this);
connect(&SettingsCache::instance().recents(), &RecentsSettings::recentlyOpenedDeckPathsChanged, this,
&TabDeckEditor::updateRecentlyOpened);
aClearRecents = new QAction(QString(), this);
connect(aClearRecents, &QAction::triggered, this, &TabDeckEditor::actClearRecents);
updateRecentlyOpened();
aSaveDeck = new QAction(QString(), this);
connect(aSaveDeck, SIGNAL(triggered()), this, SLOT(actSaveDeck()));
@@ -319,6 +351,7 @@ void TabDeckEditor::createMenus()
deckMenu = new QMenu(this);
deckMenu->addAction(aNewDeck);
deckMenu->addAction(aLoadDeck);
deckMenu->addMenu(loadRecentDeckMenu);
deckMenu->addAction(aSaveDeck);
deckMenu->addAction(aSaveDeckAs);
deckMenu->addSeparator();
@@ -339,6 +372,7 @@ void TabDeckEditor::createMenus()
cardInfoDockMenu = viewMenu->addMenu(QString());
deckDockMenu = viewMenu->addMenu(QString());
filterDockMenu = viewMenu->addMenu(QString());
printingSelectorDockMenu = viewMenu->addMenu(QString());
aCardInfoDockVisible = cardInfoDockMenu->addAction(QString());
aCardInfoDockVisible->setCheckable(true);
@@ -361,6 +395,13 @@ void TabDeckEditor::createMenus()
aFilterDockFloating->setCheckable(true);
connect(aFilterDockFloating, SIGNAL(triggered()), this, SLOT(dockFloatingTriggered()));
aPrintingSelectorDockVisible = printingSelectorDockMenu->addAction(QString());
aPrintingSelectorDockVisible->setCheckable(true);
connect(aPrintingSelectorDockVisible, SIGNAL(triggered()), this, SLOT(dockVisibleTriggered()));
aPrintingSelectorDockFloating = printingSelectorDockMenu->addAction(QString());
aPrintingSelectorDockFloating->setCheckable(true);
connect(aPrintingSelectorDockFloating, SIGNAL(triggered()), this, SLOT(dockFloatingTriggered()));
viewMenu->addSeparator();
aResetLayout = viewMenu->addAction(QString());
@@ -376,7 +417,7 @@ void TabDeckEditor::createCentralFrame()
{
searchEdit = new SearchLineEdit;
searchEdit->setObjectName("searchEdit");
searchEdit->setPlaceholderText(tr("Search by card name"));
searchEdit->setPlaceholderText(tr("Search by card name (or search expressions)"));
searchEdit->setClearButtonEnabled(true);
searchEdit->addAction(loadColorAdjustedPixmap("theme:icons/search"), QLineEdit::LeadingPosition);
auto help = searchEdit->addAction(QPixmap("theme:icons/info"), QLineEdit::TrailingPosition);
@@ -397,7 +438,7 @@ void TabDeckEditor::createCentralFrame()
connect(&searchKeySignals, SIGNAL(onCtrlC()), this, SLOT(copyDatabaseCellContents()));
connect(help, &QAction::triggered, this, &TabDeckEditor::showSearchSyntaxHelp);
databaseModel = new CardDatabaseModel(db, true, this);
databaseModel = new CardDatabaseModel(CardDatabaseManager::getInstance(), true, this);
databaseModel->setObjectName("databaseModel");
databaseDisplayModel = new CardDatabaseDisplayModel(this);
databaseDisplayModel->setSourceModel(databaseModel);
@@ -416,6 +457,8 @@ void TabDeckEditor::createCentralFrame()
connect(databaseView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(databaseCustomMenu(QPoint)));
connect(databaseView->selectionModel(), SIGNAL(currentRowChanged(const QModelIndex &, const QModelIndex &)), this,
SLOT(updateCardInfoLeft(const QModelIndex &, const QModelIndex &)));
connect(databaseView->selectionModel(), SIGNAL(currentRowChanged(const QModelIndex &, const QModelIndex &)), this,
SLOT(updatePrintingSelectorDatabase(const QModelIndex &, const QModelIndex &)));
connect(databaseView, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(actAddCard()));
QByteArray dbHeaderState = SettingsCache::instance().layouts().getDeckEditorDbHeaderState();
@@ -455,6 +498,7 @@ void TabDeckEditor::createCentralFrame()
centralWidget = new QWidget(this);
centralWidget->setObjectName("centralWidget");
centralWidget->setLayout(centralFrame);
centralWidget->setMaximumSize(900, 5000);
setCentralWidget(centralWidget);
setDockOptions(QMainWindow::AnimatedDocks | QMainWindow::AllowNestedDocks | QMainWindow::AllowTabbedDocks);
}
@@ -465,11 +509,14 @@ void TabDeckEditor::databaseCustomMenu(QPoint point)
const CardInfoPtr info = currentCardInfo();
// add to deck and sideboard options
QAction *addToDeck, *addToSideboard;
QAction *addToDeck, *addToSideboard, *selectPrinting;
addToDeck = menu.addAction(tr("Add to Deck"));
addToSideboard = menu.addAction(tr("Add to Sideboard"));
selectPrinting = menu.addAction(tr("Select Printing"));
connect(addToDeck, SIGNAL(triggered()), this, SLOT(actAddCard()));
connect(addToSideboard, SIGNAL(triggered()), this, SLOT(actAddCardToSideboard()));
connect(selectPrinting, &QAction::triggered, this, [this, info] { this->showPrintingSelector(); });
// filling out the related cards submenu
auto *relatedMenu = new QMenu(tr("Show Related cards"));
@@ -488,36 +535,58 @@ void TabDeckEditor::databaseCustomMenu(QPoint point)
menu.exec(databaseView->mapToGlobal(point));
}
void TabDeckEditor::decklistCustomMenu(QPoint point)
{
QMenu menu;
const CardInfoPtr info = cardInfo->getInfo();
QAction *selectPrinting = menu.addAction(tr("Select Printing"));
connect(selectPrinting, &QAction::triggered, this, &TabDeckEditor::showPrintingSelector);
menu.exec(deckView->mapToGlobal(point));
}
void TabDeckEditor::showPrintingSelector()
{
printingSelector->setCard(cardInfo->getInfo(), DECK_ZONE_MAIN);
printingSelector->updateDisplay();
aPrintingSelectorDockVisible->setChecked(true);
printingSelectorDock->setVisible(true);
}
void TabDeckEditor::restartLayout()
{
deckDock->setVisible(true);
cardInfoDock->setVisible(true);
filterDock->setVisible(true);
printingSelectorDock->setVisible(false);
deckDock->setFloating(false);
cardInfoDock->setFloating(false);
filterDock->setFloating(false);
printingSelectorDock->setFloating(false);
aCardInfoDockVisible->setChecked(true);
aDeckDockVisible->setChecked(true);
aFilterDockVisible->setChecked(true);
aPrintingSelectorDockVisible->setChecked(false);
aCardInfoDockFloating->setChecked(false);
aDeckDockFloating->setChecked(false);
aFilterDockFloating->setChecked(false);
aPrintingSelectorDockFloating->setChecked(false);
addDockWidget(static_cast<Qt::DockWidgetArea>(2), deckDock);
addDockWidget(static_cast<Qt::DockWidgetArea>(2), cardInfoDock);
addDockWidget(static_cast<Qt::DockWidgetArea>(2), filterDock);
addDockWidget(static_cast<Qt::DockWidgetArea>(2), printingSelectorDock);
splitDockWidget(cardInfoDock, deckDock, Qt::Horizontal);
splitDockWidget(cardInfoDock, printingSelectorDock, Qt::Horizontal);
splitDockWidget(printingSelectorDock, deckDock, Qt::Horizontal);
splitDockWidget(cardInfoDock, printingSelectorDock, Qt::Horizontal);
splitDockWidget(cardInfoDock, filterDock, Qt::Vertical);
deckDock->setMinimumWidth(360);
deckDock->setMaximumWidth(360);
cardInfoDock->setMinimumSize(250, 360);
cardInfoDock->setMaximumSize(250, 360);
QTimer::singleShot(100, this, SLOT(freeDocksSize()));
}
@@ -531,6 +600,11 @@ void TabDeckEditor::freeDocksSize()
filterDock->setMinimumSize(100, 100);
filterDock->setMaximumSize(5000, 5000);
printingSelectorDock->setMinimumSize(525, 100);
printingSelectorDock->setMaximumSize(5000, 5000);
centralWidget->setMaximumSize(900, 5000);
}
void TabDeckEditor::refreshShortcuts()
@@ -573,14 +647,17 @@ void TabDeckEditor::loadLayout()
aCardInfoDockVisible->setChecked(cardInfoDock->isVisible());
aFilterDockVisible->setChecked(filterDock->isVisible());
aDeckDockVisible->setChecked(deckDock->isVisible());
aPrintingSelectorDockVisible->setChecked(printingSelectorDock->isVisible());
aCardInfoDockFloating->setEnabled(aCardInfoDockVisible->isChecked());
aDeckDockFloating->setEnabled(aDeckDockVisible->isChecked());
aFilterDockFloating->setEnabled(aFilterDockVisible->isChecked());
aPrintingSelectorDockFloating->setEnabled(aPrintingSelectorDockVisible->isChecked());
aCardInfoDockFloating->setChecked(cardInfoDock->isFloating());
aFilterDockFloating->setChecked(filterDock->isFloating());
aDeckDockFloating->setChecked(deckDock->isFloating());
aPrintingSelectorDockFloating->setChecked(printingSelectorDock->isFloating());
cardInfoDock->setMinimumSize(layouts.getDeckEditorCardSize());
cardInfoDock->setMaximumSize(layouts.getDeckEditorCardSize());
@@ -591,6 +668,9 @@ void TabDeckEditor::loadLayout()
deckDock->setMinimumSize(layouts.getDeckEditorDeckSize());
deckDock->setMaximumSize(layouts.getDeckEditorDeckSize());
printingSelectorDock->setMinimumSize(layouts.getDeckEditorPrintingSelectorSize());
printingSelectorDock->setMaximumSize(layouts.getDeckEditorPrintingSelectorSize());
QTimer::singleShot(100, this, SLOT(freeDocksSize()));
}
@@ -606,6 +686,7 @@ TabDeckEditor::TabDeckEditor(TabSupervisor *_tabSupervisor, QWidget *parent)
createDeckDock();
createCardInfoDock();
createFiltersDock();
createPrintingSelectorDock();
this->installEventFilter(this);
@@ -634,6 +715,8 @@ void TabDeckEditor::retranslateUi()
aNewDeck->setText(tr("&New deck"));
aLoadDeck->setText(tr("&Load deck..."));
loadRecentDeckMenu->setTitle(tr("Load recent deck..."));
aClearRecents->setText(tr("Clear"));
aSaveDeck->setText(tr("&Save deck"));
aSaveDeckAs->setText(tr("Save deck &as..."));
aLoadDeckFromClipboard->setText(tr("Load deck from cl&ipboard..."));
@@ -665,11 +748,13 @@ void TabDeckEditor::retranslateUi()
cardInfoDock->setWindowTitle(tr("Card Info"));
deckDock->setWindowTitle(tr("Deck"));
filterDock->setWindowTitle(tr("Filters"));
printingSelectorDock->setWindowTitle(tr("Printing Selector"));
viewMenu->setTitle(tr("&View"));
cardInfoDockMenu->setTitle(tr("Card Info"));
deckDockMenu->setTitle(tr("Deck"));
filterDockMenu->setTitle(tr("Filters"));
printingSelectorDockMenu->setTitle(tr("Printing"));
aCardInfoDockVisible->setText(tr("Visible"));
aCardInfoDockFloating->setText(tr("Floating"));
@@ -680,6 +765,9 @@ void TabDeckEditor::retranslateUi()
aFilterDockVisible->setText(tr("Visible"));
aFilterDockFloating->setText(tr("Floating"));
aPrintingSelectorDockVisible->setText(tr("Visible"));
aPrintingSelectorDockFloating->setText(tr("Floating"));
aResetLayout->setText(tr("Reset layout"));
}
@@ -705,6 +793,11 @@ void TabDeckEditor::updateComments()
setSaveStatus(true);
}
void TabDeckEditor::updateCardInfo(CardInfoPtr _card)
{
cardInfo->setCard(_card);
}
void TabDeckEditor::updateCardInfoLeft(const QModelIndex &current, const QModelIndex & /*previous*/)
{
cardInfo->setCard(current.sibling(current.row(), 0).data().toString());
@@ -714,8 +807,47 @@ void TabDeckEditor::updateCardInfoRight(const QModelIndex &current, const QModel
{
if (!current.isValid())
return;
if (!current.model()->hasChildren(current.sibling(current.row(), 0)))
cardInfo->setCard(current.sibling(current.row(), 1).data().toString());
if (!current.model()->hasChildren(current.sibling(current.row(), 0))) {
cardInfo->setCard(current.sibling(current.row(), 1).data().toString(),
current.sibling(current.row(), 4).data().toString());
}
}
void TabDeckEditor::updatePrintingSelectorDatabase(const QModelIndex &current, const QModelIndex & /*previous*/)
{
const QString cardName = current.sibling(current.row(), 0).data().toString();
const QString cardProviderID = CardDatabaseManager::getInstance()->getPreferredPrintingProviderIdForCard(cardName);
if (!current.isValid()) {
return;
}
if (!current.model()->hasChildren(current.sibling(current.row(), 0))) {
printingSelector->setCard(
CardDatabaseManager::getInstance()->getCardByNameAndProviderId(cardName, cardProviderID), DECK_ZONE_MAIN);
}
}
void TabDeckEditor::updatePrintingSelectorDeckView(const QModelIndex &current, const QModelIndex & /*previous*/)
{
const QString cardName = current.sibling(current.row(), 1).data().toString();
const QString cardProviderID = current.sibling(current.row(), 4).data().toString();
const QModelIndex gparent = current.parent().parent();
if (!gparent.isValid()) {
return;
}
const QString zoneName = gparent.sibling(gparent.row(), 1).data(Qt::EditRole).toString();
if (!current.isValid()) {
return;
}
if (!current.model()->hasChildren(current.sibling(current.row(), 0))) {
printingSelector->setCard(
CardDatabaseManager::getInstance()->getCardByNameAndProviderId(cardName, cardProviderID), zoneName);
}
}
void TabDeckEditor::updateSearch(const QString &search)
@@ -732,13 +864,25 @@ void TabDeckEditor::updateHash()
hashLabel->setText(deckModel->getDeckList()->getDeckHash());
}
void TabDeckEditor::updateRecentlyOpened()
{
loadRecentDeckMenu->clear();
for (const auto &deckPath : SettingsCache::instance().recents().getRecentlyOpenedDeckPaths()) {
QAction *aRecentlyOpenedDeck = new QAction(deckPath, this);
loadRecentDeckMenu->addAction(aRecentlyOpenedDeck);
connect(aRecentlyOpenedDeck, &QAction::triggered, this,
[=, this] { actOpenRecent(aRecentlyOpenedDeck->text()); });
}
loadRecentDeckMenu->addSeparator();
loadRecentDeckMenu->addAction(aClearRecents);
aClearRecents->setEnabled(SettingsCache::instance().recents().getRecentlyOpenedDeckPaths().length() > 0);
}
bool TabDeckEditor::confirmClose()
{
if (modified) {
tabSupervisor->setCurrentWidget(this);
QMessageBox::StandardButton ret = QMessageBox::warning(
this, tr("Are you sure?"), tr("The decklist has been modified.\nDo you want to save the changes?"),
QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
int ret = createSaveConfirmationWindow()->exec();
if (ret == QMessageBox::Save)
return actSaveDeck();
else if (ret == QMessageBox::Cancel)
@@ -755,8 +899,16 @@ void TabDeckEditor::closeRequest()
void TabDeckEditor::actNewDeck()
{
if (!confirmClose())
auto deckOpenLocation = confirmOpen(false);
if (deckOpenLocation == CANCELLED) {
return;
}
if (deckOpenLocation == NEW_TAB) {
emit openDeckEditor(nullptr);
return;
}
deckModel->cleanList();
nameEdit->setText(QString());
@@ -768,8 +920,11 @@ void TabDeckEditor::actNewDeck()
void TabDeckEditor::actLoadDeck()
{
if (!confirmClose())
auto deckOpenLocation = confirmOpen();
if (deckOpenLocation == CANCELLED) {
return;
}
QFileDialog dialog(this, tr("Load deck"));
dialog.setDirectory(SettingsCache::instance().getDeckPath());
@@ -778,17 +933,50 @@ void TabDeckEditor::actLoadDeck()
return;
QString fileName = dialog.selectedFiles().at(0);
openDeckFromFile(fileName, deckOpenLocation);
}
void TabDeckEditor::actOpenRecent(const QString &fileName)
{
auto deckOpenLocation = confirmOpen();
if (deckOpenLocation == CANCELLED) {
return;
}
openDeckFromFile(fileName, deckOpenLocation);
}
/**
* Actually opens the deck from file
* @param fileName The path of the deck to open
* @param deckOpenLocation Which tab to open the deck
*/
void TabDeckEditor::openDeckFromFile(const QString &fileName, DeckOpenLocation deckOpenLocation)
{
DeckLoader::FileFormat fmt = DeckLoader::getFormatFromName(fileName);
auto *l = new DeckLoader;
if (l->loadFromFile(fileName, fmt)) {
setSaveStatus(false);
setDeck(l);
} else
SettingsCache::instance().recents().updateRecentlyOpenedDeckPaths(fileName);
if (deckOpenLocation == NEW_TAB) {
emit openDeckEditor(l);
} else {
setSaveStatus(false);
setDeck(l);
}
} else {
delete l;
QMessageBox::critical(this, tr("Error"), tr("Could not open deck at %1").arg(fileName));
}
setSaveStatus(true);
}
void TabDeckEditor::actClearRecents()
{
SettingsCache::instance().recents().clearRecentlyOpenedDeckPaths();
}
void TabDeckEditor::saveDeckRemoteFinished(const Response &response)
{
if (response.response_code() != Response::RespOk)
@@ -855,15 +1043,23 @@ bool TabDeckEditor::actSaveDeckAs()
void TabDeckEditor::actLoadDeckFromClipboard()
{
if (!confirmClose())
auto deckOpenLocation = confirmOpen();
if (deckOpenLocation == CANCELLED) {
return;
}
DlgLoadDeckFromClipboard dlg(this);
if (!dlg.exec())
return;
setDeck(dlg.getDeckList());
setModified(true);
if (deckOpenLocation == NEW_TAB) {
emit openDeckEditor(dlg.getDeckList());
} else {
setDeck(dlg.getDeckList());
setModified(true);
}
setSaveStatus(true);
}
@@ -957,6 +1153,79 @@ void TabDeckEditor::recursiveExpand(const QModelIndex &index)
deckView->expand(index);
}
/**
* @brief Displays the save confirmation dialogue that is shown before loading a deck, if required. Takes into
* account the `openDeckInNewTab` settting.
*
* @param openInSameTabIfBlank Open the deck in the same tab instead of a new tab if the current tab is completely
* blank. Only relevant when the `openDeckInNewTab` setting is enabled.
*
* @returns An enum that indicates if and where to load the deck
*/
TabDeckEditor::DeckOpenLocation TabDeckEditor::confirmOpen(const bool openInSameTabIfBlank)
{
// handle `openDeckInNewTab` setting
if (SettingsCache::instance().getOpenDeckInNewTab()) {
if (openInSameTabIfBlank && isBlankNewDeck()) {
return SAME_TAB;
} else {
return NEW_TAB;
}
}
// early return if deck is unmodified
if (!modified) {
return SAME_TAB;
}
// do the save confirmation dialogue
tabSupervisor->setCurrentWidget(this);
QMessageBox *msgBox = createSaveConfirmationWindow();
QPushButton *newTabButton = msgBox->addButton(tr("Open in new tab"), QMessageBox::ApplyRole);
int ret = msgBox->exec();
// `exec()` returns an opaque value if a non-standard button was clicked.
// Directly check if newTabButton was clicked before switching over the standard buttons.
if (msgBox->clickedButton() == newTabButton) {
return NEW_TAB;
}
switch (ret) {
case QMessageBox::Save:
return actSaveDeck() ? SAME_TAB : CANCELLED;
case QMessageBox::Discard:
return SAME_TAB;
default:
return CANCELLED;
}
}
/**
* @brief Creates the base save confirmation dialogue box.
*
* @returns A QMessageBox that can be further modified
*/
QMessageBox *TabDeckEditor::createSaveConfirmationWindow()
{
QMessageBox *msgBox = new QMessageBox(this);
msgBox->setIcon(QMessageBox::Warning);
msgBox->setWindowTitle(tr("Are you sure?"));
msgBox->setText(tr("The decklist has been modified.\nDo you want to save the changes?"));
msgBox->setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
return msgBox;
}
/**
* @brief Returns true if this tab is a blank newly opened tab, as if it was just created with the `New Deck` action.
*/
bool TabDeckEditor::isBlankNewDeck() const
{
DeckLoader *const deck = deckModel->getDeckList();
return !modified && deck->getLastFileName().isEmpty() && deck->getLastRemoteDeckId() == -1;
}
CardInfoPtr TabDeckEditor::currentCardInfo() const
{
const QModelIndex currentIndex = databaseView->selectionModel()->currentIndex();
@@ -966,7 +1235,7 @@ CardInfoPtr TabDeckEditor::currentCardInfo() const
const QString cardName = currentIndex.sibling(currentIndex.row(), 0).data().toString();
return db->getCard(cardName);
return CardDatabaseManager::getInstance()->getCard(cardName);
}
void TabDeckEditor::addCardHelper(QString zoneName)
@@ -977,7 +1246,7 @@ void TabDeckEditor::addCardHelper(QString zoneName)
if (info->getIsToken())
zoneName = DECK_ZONE_TOKENS;
QModelIndex newCardIndex = deckModel->addCard(info->getName(), zoneName);
QModelIndex newCardIndex = deckModel->addPreferredPrintingCard(info->getName(), zoneName, false);
recursiveExpand(newCardIndex);
deckView->setCurrentIndex(newCardIndex);
setModified(true);
@@ -990,6 +1259,7 @@ void TabDeckEditor::actSwapCard()
if (!currentIndex.isValid())
return;
const QString cardName = currentIndex.sibling(currentIndex.row(), 1).data().toString();
const QString cardProviderID = currentIndex.sibling(currentIndex.row(), 4).data().toString();
const QModelIndex gparent = currentIndex.parent().parent();
if (!gparent.isValid())
@@ -999,8 +1269,10 @@ void TabDeckEditor::actSwapCard()
actDecrement();
const QString otherZoneName = zoneName == DECK_ZONE_MAIN ? DECK_ZONE_SIDE : DECK_ZONE_MAIN;
// Third argument (true) says create the card no mater what, even if not in DB
QModelIndex newCardIndex = deckModel->addCard(cardName, otherZoneName, true);
// Third argument (true) says create the card no matter what, even if not in DB
QModelIndex newCardIndex = deckModel->addCard(
cardName, CardDatabaseManager::getInstance()->getSpecificSetForCard(cardName, cardProviderID), otherZoneName,
true);
recursiveExpand(newCardIndex);
setModified(true);
@@ -1076,8 +1348,8 @@ void TabDeckEditor::actDecrementCardFromSideboard()
void TabDeckEditor::copyDatabaseCellContents()
{
QVariant data = databaseView->selectionModel()->currentIndex().data();
QApplication::clipboard()->setText(data.toString());
auto _data = databaseView->selectionModel()->currentIndex().data();
QApplication::clipboard()->setText(_data.toString());
}
void TabDeckEditor::actIncrement()
@@ -1103,7 +1375,8 @@ void TabDeckEditor::setDeck(DeckLoader *_deck)
deckView->expandAll();
setModified(false);
PictureLoader::cacheCardPixmaps(db->getCards(deckModel->getDeckList()->getCardList()));
PictureLoader::cacheCardPixmaps(
CardDatabaseManager::getInstance()->getCards(deckModel->getDeckList()->getCardList()));
deckView->expandAll();
setModified(false);
@@ -1160,6 +1433,9 @@ bool TabDeckEditor::eventFilter(QObject *o, QEvent *e)
} else if (o == filterDock) {
aFilterDockVisible->setChecked(false);
aFilterDockFloating->setEnabled(false);
} else if (o == printingSelectorDock) {
aPrintingSelectorDockVisible->setChecked(false);
aPrintingSelectorDockFloating->setEnabled(false);
}
}
if (o == this && e->type() == QEvent::Hide) {
@@ -1169,6 +1445,7 @@ bool TabDeckEditor::eventFilter(QObject *o, QEvent *e)
layouts.setDeckEditorCardSize(cardInfoDock->size());
layouts.setDeckEditorFilterSize(filterDock->size());
layouts.setDeckEditorDeckSize(deckDock->size());
layouts.setDeckEditorPrintingSelectorSize(printingSelectorDock->size());
}
return false;
}
@@ -1193,6 +1470,12 @@ void TabDeckEditor::dockVisibleTriggered()
aFilterDockFloating->setEnabled(aFilterDockVisible->isChecked());
return;
}
if (o == aPrintingSelectorDockVisible) {
printingSelectorDock->setVisible(aPrintingSelectorDockVisible->isChecked());
aPrintingSelectorDockFloating->setEnabled(aPrintingSelectorDockVisible->isChecked());
return;
}
}
void TabDeckEditor::dockFloatingTriggered()
@@ -1212,6 +1495,11 @@ void TabDeckEditor::dockFloatingTriggered()
filterDock->setFloating(aFilterDockFloating->isChecked());
return;
}
if (o == aPrintingSelectorDockFloating) {
printingSelectorDock->setFloating(aPrintingSelectorDockFloating->isChecked());
return;
}
}
void TabDeckEditor::dockTopLevelChanged(bool topLevel)
@@ -1231,6 +1519,11 @@ void TabDeckEditor::dockTopLevelChanged(bool topLevel)
aFilterDockFloating->setChecked(topLevel);
return;
}
if (o == printingSelectorDock) {
aPrintingSelectorDockFloating->setChecked(topLevel);
return;
}
}
void TabDeckEditor::saveDbHeaderState()
@@ -1281,6 +1574,6 @@ void TabDeckEditor::showSearchSyntaxHelp()
browser->document()->setDefaultStyleSheet(sheet);
browser->setHtml(text);
connect(browser, &QTextBrowser::anchorClicked, [=](const QUrl &link) { searchEdit->setText(link.fragment()); });
connect(browser, &QTextBrowser::anchorClicked, [this](const QUrl &link) { searchEdit->setText(link.fragment()); });
browser->show();
}

View File

@@ -1,9 +1,10 @@
#ifndef WINDOW_DECKEDITOR_H
#define WINDOW_DECKEDITOR_H
#include "carddatabase.h"
#include "customlineedit.h"
#include "keysignals.h"
#include "../../deck/custom_line_edit.h"
#include "../../game/cards/card_database.h"
#include "../game_logic/key_signals.h"
#include "../ui/widgets/printing_selector/printing_selector.h"
#include "tab.h"
#include <QAbstractItemModel>
@@ -14,7 +15,7 @@ class CardDatabaseDisplayModel;
class DeckListModel;
class QTreeView;
class CardFrame;
class CardInfoFrameWidget;
class QTextEdit;
class QLabel;
class DeckLoader;
@@ -22,29 +23,12 @@ class Response;
class FilterTreeModel;
class FilterBuilder;
class QGroupBox;
class QMessageBox;
class QHBoxLayout;
class QVBoxLayout;
class QPushButton;
class QDockWidget;
class SearchLineEdit : public LineEditUnfocusable
{
private:
QTreeView *treeView;
protected:
void keyPressEvent(QKeyEvent *event) override;
public:
SearchLineEdit() : LineEditUnfocusable(), treeView(nullptr)
{
}
void setTreeView(QTreeView *_treeView)
{
treeView = _treeView;
}
};
class TabDeckEditor : public Tab
{
Q_OBJECT
@@ -54,11 +38,17 @@ private slots:
void updateHash();
void updateCardInfoLeft(const QModelIndex &current, const QModelIndex &previous);
void updateCardInfoRight(const QModelIndex &current, const QModelIndex &previous);
void updatePrintingSelectorDatabase(const QModelIndex &current, const QModelIndex &previous);
void updatePrintingSelectorDeckView(const QModelIndex &current, const QModelIndex &previous);
void updateSearch(const QString &search);
void databaseCustomMenu(QPoint point);
void decklistCustomMenu(QPoint point);
void updateRecentlyOpened();
void actNewDeck();
void actLoadDeck();
void actOpenRecent(const QString &fileName);
void actClearRecents();
bool actSaveDeck();
bool actSaveDeckAs();
void actLoadDeckFromClipboard();
@@ -100,11 +90,26 @@ private slots:
void showSearchSyntaxHelp();
private:
/**
* @brief Which tab to open the new deck in
*/
enum DeckOpenLocation
{
CANCELLED,
SAME_TAB,
NEW_TAB
};
DeckOpenLocation confirmOpen(const bool openInSameTabIfBlank = true);
QMessageBox *createSaveConfirmationWindow();
bool isBlankNewDeck() const;
CardInfoPtr currentCardInfo() const;
void addCardHelper(QString zoneName);
void offsetCountAtIndex(const QModelIndex &idx, int offset);
void decrementCardHelper(QString zoneName);
void recursiveExpand(const QModelIndex &index);
void openDeckFromFile(const QString &fileName, DeckOpenLocation deckOpenLocation);
CardDatabaseModel *databaseModel;
CardDatabaseDisplayModel *databaseDisplayModel;
@@ -113,7 +118,8 @@ private:
QTreeView *deckView;
KeySignals deckViewKeySignals;
CardFrame *cardInfo;
CardInfoFrameWidget *cardInfo;
PrintingSelector *printingSelector;
SearchLineEdit *searchEdit;
KeySignals searchKeySignals;
@@ -128,16 +134,16 @@ private:
KeySignals filterViewKeySignals;
QWidget *filterBox;
QMenu *deckMenu, *viewMenu, *cardInfoDockMenu, *deckDockMenu, *filterDockMenu, *analyzeDeckMenu,
*saveDeckToClipboardMenu;
QAction *aNewDeck, *aLoadDeck, *aSaveDeck, *aSaveDeckAs, *aLoadDeckFromClipboard, *aSaveDeckToClipboard,
*aSaveDeckToClipboardRaw, *aPrintDeck, *aExportDeckDecklist, *aAnalyzeDeckDeckstats, *aAnalyzeDeckTappedout,
*aClose;
QMenu *deckMenu, *viewMenu, *cardInfoDockMenu, *deckDockMenu, *filterDockMenu, *printingSelectorDockMenu,
*analyzeDeckMenu, *saveDeckToClipboardMenu, *loadRecentDeckMenu;
QAction *aNewDeck, *aLoadDeck, *aClearRecents, *aSaveDeck, *aSaveDeckAs, *aLoadDeckFromClipboard,
*aSaveDeckToClipboard, *aSaveDeckToClipboardRaw, *aPrintDeck, *aExportDeckDecklist, *aAnalyzeDeckDeckstats,
*aAnalyzeDeckTappedout, *aClose;
QAction *aClearFilterAll, *aClearFilterOne;
QAction *aAddCard, *aAddCardToSideboard, *aRemoveCard, *aIncrement, *aDecrement;
QAction *aResetLayout;
QAction *aCardInfoDockVisible, *aCardInfoDockFloating, *aDeckDockVisible, *aDeckDockFloating, *aFilterDockVisible,
*aFilterDockFloating;
*aFilterDockFloating, *aPrintingSelectorDockVisible, *aPrintingSelectorDockFloating;
bool modified;
QVBoxLayout *centralFrame;
@@ -145,6 +151,7 @@ private:
QDockWidget *cardInfoDock;
QDockWidget *deckDock;
QDockWidget *filterDock;
QDockWidget *printingSelectorDock;
QWidget *centralWidget;
public:
@@ -158,12 +165,16 @@ public:
void createDeckDock();
void createCardInfoDock();
void createFiltersDock();
void createPrintingSelectorDock();
void createMenus();
void createCentralFrame();
void updateCardInfo(CardInfoPtr _card);
public slots:
void closeRequest() override;
void showPrintingSelector();
signals:
void openDeckEditor(const DeckLoader *deckLoader);
void deckEditorClosing(TabDeckEditor *tab);
};

View File

@@ -1,9 +1,12 @@
#include "tab_deck_storage.h"
#include "abstractclient.h"
#include "deck_loader.h"
#include "../../deck/deck_loader.h"
#include "../../server/pending_command.h"
#include "../../server/remote/remote_decklist_tree_widget.h"
#include "../../settings/cache_settings.h"
#include "../game_logic/abstract_client.h"
#include "../get_text_with_max.h"
#include "decklist.h"
#include "gettextwithmax.h"
#include "pb/command_deck_del.pb.h"
#include "pb/command_deck_del_dir.pb.h"
#include "pb/command_deck_download.pb.h"
@@ -12,13 +15,11 @@
#include "pb/response.pb.h"
#include "pb/response_deck_download.pb.h"
#include "pb/response_deck_upload.pb.h"
#include "pending_command.h"
#include "remotedecklist_treewidget.h"
#include "settingscache.h"
#include <QAction>
#include <QApplication>
#include <QDebug>
#include <QDesktopServices>
#include <QFileSystemModel>
#include <QGroupBox>
#include <QHBoxLayout>
@@ -27,6 +28,7 @@
#include <QMessageBox>
#include <QToolBar>
#include <QTreeView>
#include <QUrl>
#include <QVBoxLayout>
TabDeckStorage::TabDeckStorage(TabSupervisor *_tabSupervisor, AbstractClient *_client)
@@ -41,16 +43,33 @@ TabDeckStorage::TabDeckStorage(TabSupervisor *_tabSupervisor, AbstractClient *_c
localDirView->setColumnHidden(1, true);
localDirView->setRootIndex(localDirModel->index(localDirModel->rootPath(), 0));
localDirView->setSortingEnabled(true);
localDirView->setSelectionMode(QAbstractItemView::ExtendedSelection);
localDirView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
localDirView->header()->setSortIndicator(0, Qt::AscendingOrder);
leftToolBar = new QToolBar;
connect(localDirView, &QTreeView::doubleClicked, this, &TabDeckStorage::actLocalDoubleClick);
// Left side layout
/* put an invisible dummy QToolBar in the leftmost column so that the main toolbar is centered.
* Really ugly workaround, but I couldn't figure out the proper way to make it centered */
QToolBar *dummyToolBar = new QToolBar(this);
QSizePolicy sizePolicy = dummyToolBar->sizePolicy();
sizePolicy.setRetainSizeWhenHidden(true);
dummyToolBar->setSizePolicy(sizePolicy);
dummyToolBar->setVisible(false);
leftToolBar = new QToolBar(this);
leftToolBar->setOrientation(Qt::Horizontal);
leftToolBar->setIconSize(QSize(32, 32));
QHBoxLayout *leftToolBarLayout = new QHBoxLayout;
leftToolBarLayout->addStretch();
leftToolBarLayout->addWidget(leftToolBar);
leftToolBarLayout->addStretch();
QToolBar *leftRightmostToolBar = new QToolBar(this);
leftRightmostToolBar->setOrientation(Qt::Horizontal);
leftRightmostToolBar->setIconSize(QSize(32, 32));
QGridLayout *leftToolBarLayout = new QGridLayout;
leftToolBarLayout->addWidget(dummyToolBar, 0, 0, Qt::AlignLeft);
leftToolBarLayout->addWidget(leftToolBar, 0, 1, Qt::AlignHCenter);
leftToolBarLayout->addWidget(leftRightmostToolBar, 0, 2, Qt::AlignRight);
QVBoxLayout *leftVbox = new QVBoxLayout;
leftVbox->addWidget(localDirView);
@@ -58,6 +77,7 @@ TabDeckStorage::TabDeckStorage(TabSupervisor *_tabSupervisor, AbstractClient *_c
leftGroupBox = new QGroupBox;
leftGroupBox->setLayout(leftVbox);
// Right side layout
rightToolBar = new QToolBar;
rightToolBar->setOrientation(Qt::Horizontal);
rightToolBar->setIconSize(QSize(32, 32));
@@ -68,25 +88,38 @@ TabDeckStorage::TabDeckStorage(TabSupervisor *_tabSupervisor, AbstractClient *_c
serverDirView = new RemoteDeckList_TreeWidget(client);
connect(serverDirView, &QTreeView::doubleClicked, this, &TabDeckStorage::actRemoteDoubleClick);
QVBoxLayout *rightVbox = new QVBoxLayout;
rightVbox->addWidget(serverDirView);
rightVbox->addLayout(rightToolBarLayout);
rightGroupBox = new QGroupBox;
rightGroupBox->setLayout(rightVbox);
// combine layouts
QHBoxLayout *hbox = new QHBoxLayout;
hbox->addWidget(leftGroupBox);
hbox->addWidget(rightGroupBox);
// Left side actions
aOpenLocalDeck = new QAction(this);
aOpenLocalDeck->setIcon(QPixmap("theme:icons/pencil"));
connect(aOpenLocalDeck, SIGNAL(triggered()), this, SLOT(actOpenLocalDeck()));
aUpload = new QAction(this);
aUpload->setIcon(QPixmap("theme:icons/arrow_right_green"));
connect(aUpload, SIGNAL(triggered()), this, SLOT(actUpload()));
aNewLocalFolder = new QAction(this);
aNewLocalFolder->setIcon(qApp->style()->standardIcon(QStyle::SP_FileDialogNewFolder));
connect(aNewLocalFolder, &QAction::triggered, this, &TabDeckStorage::actNewLocalFolder);
aDeleteLocalDeck = new QAction(this);
aDeleteLocalDeck->setIcon(QPixmap("theme:icons/remove_row"));
connect(aDeleteLocalDeck, SIGNAL(triggered()), this, SLOT(actDeleteLocalDeck()));
aOpenDecksFolder = new QAction(this);
aOpenDecksFolder->setIcon(qApp->style()->standardIcon(QStyle::SP_DirOpenIcon));
connect(aOpenDecksFolder, &QAction::triggered, this, &TabDeckStorage::actOpenDecksFolder);
// Right side actions
aOpenRemoteDeck = new QAction(this);
aOpenRemoteDeck->setIcon(QPixmap("theme:icons/pencil"));
connect(aOpenRemoteDeck, SIGNAL(triggered()), this, SLOT(actOpenRemoteDeck()));
@@ -100,9 +133,14 @@ TabDeckStorage::TabDeckStorage(TabSupervisor *_tabSupervisor, AbstractClient *_c
aDeleteRemoteDeck->setIcon(QPixmap("theme:icons/remove_row"));
connect(aDeleteRemoteDeck, SIGNAL(triggered()), this, SLOT(actDeleteRemoteDeck()));
// Add actions to toolbars
leftToolBar->addAction(aOpenLocalDeck);
leftToolBar->addAction(aUpload);
leftToolBar->addAction(aNewLocalFolder);
leftToolBar->addAction(aDeleteLocalDeck);
leftRightmostToolBar->addAction(aOpenDecksFolder);
rightToolBar->addAction(aOpenRemoteDeck);
rightToolBar->addAction(aDownload);
rightToolBar->addAction(aNewFolder);
@@ -124,9 +162,11 @@ void TabDeckStorage::retranslateUi()
aUpload->setText(tr("Upload deck"));
aOpenRemoteDeck->setText(tr("Open in deck editor"));
aDownload->setText(tr("Download deck"));
aNewLocalFolder->setText(tr("New folder"));
aNewFolder->setText(tr("New folder"));
aDeleteLocalDeck->setText(tr("Delete"));
aDeleteRemoteDeck->setText(tr("Delete"));
aOpenDecksFolder->setText(tr("Open decks folder"));
}
QString TabDeckStorage::getTargetPath() const
@@ -147,43 +187,61 @@ QString TabDeckStorage::getTargetPath() const
}
}
void TabDeckStorage::actLocalDoubleClick(const QModelIndex &curLeft)
{
if (!localDirModel->isDir(curLeft)) {
actOpenLocalDeck();
}
}
void TabDeckStorage::actOpenLocalDeck()
{
QModelIndex curLeft = localDirView->selectionModel()->currentIndex();
if (localDirModel->isDir(curLeft))
return;
QString filePath = localDirModel->filePath(curLeft);
QModelIndexList curLefts = localDirView->selectionModel()->selectedRows();
for (const auto &curLeft : curLefts) {
if (localDirModel->isDir(curLeft))
continue;
QString filePath = localDirModel->filePath(curLeft);
DeckLoader deckLoader;
if (!deckLoader.loadFromFile(filePath, DeckLoader::CockatriceFormat))
return;
DeckLoader deckLoader;
if (!deckLoader.loadFromFile(filePath, DeckLoader::CockatriceFormat))
continue;
emit openDeckEditor(&deckLoader);
SettingsCache::instance().recents().updateRecentlyOpenedDeckPaths(filePath);
emit openDeckEditor(&deckLoader);
}
}
void TabDeckStorage::actUpload()
{
QModelIndex curLeft = localDirView->selectionModel()->currentIndex();
if (localDirModel->isDir(curLeft))
QModelIndexList curLefts = localDirView->selectionModel()->selectedRows();
if (curLefts.isEmpty()) {
return;
}
QString targetPath = getTargetPath();
if (targetPath.length() > MAX_NAME_LENGTH) {
qCritical() << "target path to upload to is too long" << targetPath;
return;
}
QString filePath = localDirModel->filePath(curLeft);
for (const auto &curLeft : curLefts) {
if (localDirModel->isDir(curLeft)) {
continue;
}
QString filePath = localDirModel->filePath(curLeft);
uploadDeck(filePath, targetPath);
}
}
void TabDeckStorage::uploadDeck(const QString &filePath, const QString &targetPath)
{
QFile deckFile(filePath);
QFileInfo deckFileInfo(deckFile);
QString deckString;
DeckLoader deck;
bool error = !deck.loadFromFile(filePath, DeckLoader::CockatriceFormat);
if (!error) {
deckString = deck.writeToString_Native();
error = deckString.length() > MAX_FILE_LENGTH;
}
if (error) {
if (!deck.loadFromFile(filePath, DeckLoader::CockatriceFormat)) {
QMessageBox::critical(this, tr("Error"), tr("Invalid deck file"));
return;
}
@@ -202,6 +260,12 @@ void TabDeckStorage::actUpload()
deck.setName(deck.getName().left(MAX_NAME_LENGTH));
}
QString deckString = deck.writeToString_Native();
if (deckString.length() > MAX_FILE_LENGTH) {
QMessageBox::critical(this, tr("Error"), tr("Invalid deck file"));
return;
}
Command_DeckUpload cmd;
cmd.set_path(targetPath.toStdString());
cmd.set_deck_list(deckString.toStdString());
@@ -226,34 +290,73 @@ void TabDeckStorage::uploadFinished(const Response &r, const CommandContainer &c
serverDirView->addFileToTree(resp.new_file(), serverDirView->getNodeByPath(QString::fromStdString(cmd.path())));
}
void TabDeckStorage::actDeleteLocalDeck()
void TabDeckStorage::actNewLocalFolder()
{
QModelIndex curLeft = localDirView->selectionModel()->currentIndex();
if (localDirModel->isDir(curLeft))
QModelIndex dirIndex;
if (curLeft.isValid() && !localDirModel->isDir(curLeft)) {
dirIndex = curLeft.parent();
} else {
dirIndex = curLeft;
}
bool ok;
QString folderName =
QInputDialog::getText(this, tr("New folder"), tr("Name of new folder:"), QLineEdit::Normal, "", &ok);
if (!ok || folderName.isEmpty())
return;
if (QMessageBox::warning(this, tr("Delete local file"),
tr("Are you sure you want to delete \"%1\"?").arg(localDirModel->fileName(curLeft)),
localDirModel->mkdir(dirIndex, folderName);
}
void TabDeckStorage::actDeleteLocalDeck()
{
const QModelIndexList curLefts = localDirView->selectionModel()->selectedRows();
if (curLefts.isEmpty()) {
return;
}
if (QMessageBox::warning(this, tr("Delete local file"), tr("Are you sure you want to delete the selected files?"),
QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes)
return;
localDirModel->remove(curLeft);
for (const auto &curLeft : curLefts) {
if (curLeft.isValid()) {
localDirModel->remove(curLeft);
}
}
}
void TabDeckStorage::actOpenDecksFolder()
{
QString dir = localDirModel->rootPath();
QDesktopServices::openUrl(QUrl::fromLocalFile(dir));
}
void TabDeckStorage::actRemoteDoubleClick(const QModelIndex &curRight)
{
if (dynamic_cast<RemoteDeckList_TreeModel::FileNode *>(serverDirView->getNode(curRight))) {
actOpenRemoteDeck();
}
}
void TabDeckStorage::actOpenRemoteDeck()
{
RemoteDeckList_TreeModel::FileNode *curRight =
dynamic_cast<RemoteDeckList_TreeModel::FileNode *>(serverDirView->getCurrentItem());
if (!curRight)
return;
for (const auto &curRight : serverDirView->getCurrentSelection()) {
RemoteDeckList_TreeModel::FileNode *node = dynamic_cast<RemoteDeckList_TreeModel::FileNode *>(curRight);
if (!node)
continue;
Command_DeckDownload cmd;
cmd.set_deck_id(curRight->getId());
Command_DeckDownload cmd;
cmd.set_deck_id(node->getId());
PendingCommand *pend = client->prepareSessionCommand(cmd);
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
SLOT(openRemoteDeckFinished(Response, CommandContainer)));
client->sendCommand(pend);
PendingCommand *pend = client->prepareSessionCommand(cmd);
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
SLOT(openRemoteDeckFinished(Response, CommandContainer)));
client->sendCommand(pend);
}
}
void TabDeckStorage::openRemoteDeckFinished(const Response &r, const CommandContainer &commandContainer)
@@ -273,30 +376,46 @@ void TabDeckStorage::openRemoteDeckFinished(const Response &r, const CommandCont
void TabDeckStorage::actDownload()
{
QString filePath;
QModelIndex curLeft = localDirView->selectionModel()->currentIndex();
if (!curLeft.isValid())
filePath = localDirModel->rootPath();
else {
while (!localDirModel->isDir(curLeft))
curLeft = curLeft.parent();
filePath = localDirModel->filePath(curLeft);
while (!localDirModel->isDir(curLeft)) {
curLeft = curLeft.parent();
}
RemoteDeckList_TreeModel::FileNode *curRight =
dynamic_cast<RemoteDeckList_TreeModel::FileNode *>(serverDirView->getCurrentItem());
if (!curRight)
return;
filePath += QString("/deck_%1.cod").arg(curRight->getId());
for (const auto curRight : serverDirView->selectionModel()->selectedRows()) {
downloadNodeAtIndex(curLeft, curRight);
}
}
Command_DeckDownload cmd;
cmd.set_deck_id(curRight->getId());
void TabDeckStorage::downloadNodeAtIndex(const QModelIndex &curLeft, const QModelIndex &curRight)
{
auto node = serverDirView->getNode(curRight);
if (const auto dirNode = dynamic_cast<RemoteDeckList_TreeModel::DirectoryNode *>(node)) {
// node at index is a folder
const QString name = dirNode->getName();
PendingCommand *pend = client->prepareSessionCommand(cmd);
pend->setExtraData(filePath);
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
SLOT(downloadFinished(Response, CommandContainer, QVariant)));
client->sendCommand(pend);
const auto dirIndex = curLeft.isValid() ? curLeft : localDirModel->index(localDirModel->rootPath());
const auto newDirIndex = localDirModel->mkdir(dirIndex, name);
int rows = serverDirView->model()->rowCount(curRight);
for (int i = 0; i < rows; i++) {
const auto childIndex = serverDirView->model()->index(i, 0, curRight);
downloadNodeAtIndex(newDirIndex, childIndex);
}
} else if (const auto fileNode = dynamic_cast<RemoteDeckList_TreeModel::FileNode *>(node)) {
// node at index is a deck
const QString dirPath = curLeft.isValid() ? localDirModel->filePath(curLeft) : localDirModel->rootPath();
const QString filePath = dirPath + QString("/deck_%1.cod").arg(fileNode->getId());
Command_DeckDownload cmd;
cmd.set_deck_id(fileNode->getId());
PendingCommand *pend = client->prepareSessionCommand(cmd);
pend->setExtraData(filePath);
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
SLOT(downloadFinished(Response, CommandContainer, QVariant)));
client->sendCommand(pend);
}
// node at index is invalid
}
void TabDeckStorage::downloadFinished(const Response &r,
@@ -352,12 +471,30 @@ void TabDeckStorage::newFolderFinished(const Response &response, const CommandCo
void TabDeckStorage::actDeleteRemoteDeck()
{
PendingCommand *pend;
RemoteDeckList_TreeModel::Node *curRight = serverDirView->getCurrentItem();
if (!curRight)
auto curRights = serverDirView->getCurrentSelection();
if (curRights.isEmpty()) {
return;
RemoteDeckList_TreeModel::DirectoryNode *dir = dynamic_cast<RemoteDeckList_TreeModel::DirectoryNode *>(curRight);
if (dir) {
}
if (QMessageBox::warning(this, tr("Delete remote decks"), tr("Are you sure you want to delete the selected decks?"),
QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) {
return;
}
for (const auto &curRight : curRights) {
deleteRemoteDeck(curRight);
}
}
void TabDeckStorage::deleteRemoteDeck(const RemoteDeckList_TreeModel::Node *curRight)
{
if (!curRight) {
return;
}
PendingCommand *pend;
if (const auto *dir = dynamic_cast<const RemoteDeckList_TreeModel::DirectoryNode *>(curRight)) {
QString targetPath = dir->getPath();
if (targetPath.isEmpty())
return;
@@ -365,22 +502,13 @@ void TabDeckStorage::actDeleteRemoteDeck()
qCritical() << "target path to delete is too long" << targetPath;
return;
}
if (QMessageBox::warning(this, tr("Delete remote folder"),
tr("Are you sure you want to delete \"%1\"?").arg(targetPath),
QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes)
return;
Command_DeckDelDir cmd;
cmd.set_path(targetPath.toStdString());
pend = client->prepareSessionCommand(cmd);
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
SLOT(deleteFolderFinished(Response, CommandContainer)));
} else {
RemoteDeckList_TreeModel::FileNode *deckNode = dynamic_cast<RemoteDeckList_TreeModel::FileNode *>(curRight);
if (QMessageBox::warning(this, tr("Delete remote deck"),
tr("Are you sure you want to delete \"%1\"?").arg(deckNode->getName()),
QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes)
return;
const auto *deckNode = dynamic_cast<const RemoteDeckList_TreeModel::FileNode *>(curRight);
Command_DeckDel cmd;
cmd.set_deck_id(deckNode->getId());
pend = client->prepareSessionCommand(cmd);

View File

@@ -1,6 +1,7 @@
#ifndef TAB_DECK_STORAGE_H
#define TAB_DECK_STORAGE_H
#include "../../server/remote/remote_decklist_tree_widget.h"
#include "tab.h"
class AbstractClient;
@@ -10,7 +11,6 @@ class QToolBar;
class QTreeWidget;
class QTreeWidgetItem;
class QGroupBox;
class RemoteDeckList_TreeWidget;
class CommandContainer;
class Response;
class DeckLoader;
@@ -26,16 +26,29 @@ private:
RemoteDeckList_TreeWidget *serverDirView;
QGroupBox *leftGroupBox, *rightGroupBox;
QAction *aOpenLocalDeck, *aUpload, *aDeleteLocalDeck, *aOpenRemoteDeck, *aDownload, *aNewFolder, *aDeleteRemoteDeck;
QAction *aOpenLocalDeck, *aUpload, *aNewLocalFolder, *aDeleteLocalDeck;
QAction *aOpenDecksFolder;
QAction *aOpenRemoteDeck, *aDownload, *aNewFolder, *aDeleteRemoteDeck;
QString getTargetPath() const;
void uploadDeck(const QString &filePath, const QString &targetPath);
void deleteRemoteDeck(const RemoteDeckList_TreeModel::Node *node);
void downloadNodeAtIndex(const QModelIndex &curLeft, const QModelIndex &curRight);
private slots:
void actLocalDoubleClick(const QModelIndex &curLeft);
void actOpenLocalDeck();
void actUpload();
void uploadFinished(const Response &r, const CommandContainer &commandContainer);
void actNewLocalFolder();
void actDeleteLocalDeck();
void actOpenDecksFolder();
void actRemoteDoubleClick(const QModelIndex &curRight);
void actOpenRemoteDeck();
void openRemoteDeckFinished(const Response &r, const CommandContainer &commandContainer);

View File

@@ -1,21 +1,32 @@
#include "tab_game.h"
#include "abstractclient.h"
#include "arrowitem.h"
#include "carddatabase.h"
#include "cardframe.h"
#include "carditem.h"
#include "deck_loader.h"
#include "deckview.h"
#include "dlg_creategame.h"
#include "dlg_load_remote_deck.h"
#include "dlg_manage_sets.h"
#include "gamescene.h"
#include "gameview.h"
#include "../../client/ui/widgets/cards/card_info_frame_widget.h"
#include "../../deck/deck_loader.h"
#include "../../deck/deck_view.h"
#include "../../dialogs/dlg_create_game.h"
#include "../../dialogs/dlg_load_remote_deck.h"
#include "../../dialogs/dlg_manage_sets.h"
#include "../../game/board/arrow_item.h"
#include "../../game/cards/card_database.h"
#include "../../game/cards/card_database_manager.h"
#include "../../game/cards/card_item.h"
#include "../../game/game_scene.h"
#include "../../game/game_view.h"
#include "../../game/player/player.h"
#include "../../game/player/player_list_widget.h"
#include "../../game/zones/view_zone.h"
#include "../../game/zones/view_zone_widget.h"
#include "../../main.h"
#include "../../server/message_log_widget.h"
#include "../../server/pending_command.h"
#include "../../settings/cache_settings.h"
#include "../game_logic/abstract_client.h"
#include "../network/replay_timeline_widget.h"
#include "../ui/line_edit_completer.h"
#include "../ui/phases_toolbar.h"
#include "../ui/picture_loader.h"
#include "../ui/window_main.h"
#include "get_pb_extension.h"
#include "lineeditcompleter.h"
#include "main.h"
#include "messagelogwidget.h"
#include "pb/command_concede.pb.h"
#include "pb/command_deck_select.pb.h"
#include "pb/command_delete_arrow.pb.h"
@@ -45,18 +56,8 @@
#include "pb/game_event_container.pb.h"
#include "pb/game_replay.pb.h"
#include "pb/response_deck_download.pb.h"
#include "pending_command.h"
#include "phasestoolbar.h"
#include "pictureloader.h"
#include "player.h"
#include "playerlistwidget.h"
#include "replay_timeline_widget.h"
#include "settingscache.h"
#include "stringsizes.h"
#include "tab_supervisor.h"
#include "window_main.h"
#include "zoneviewwidget.h"
#include "zoneviewzone.h"
#include "trice_limits.h"
#include <QAction>
#include <QCompleter>
@@ -105,11 +106,14 @@ DeckViewContainer::DeckViewContainer(int _playerId, TabGame *parent)
loadRemoteButton = new QPushButton;
readyStartButton = new ToggleButton;
readyStartButton->setEnabled(false);
forceStartGameButton = new QPushButton;
forceStartGameButton->setEnabled(parent->isHost());
sideboardLockButton = new ToggleButton;
sideboardLockButton->setEnabled(false);
connect(loadLocalButton, SIGNAL(clicked()), this, SLOT(loadLocalDeck()));
connect(readyStartButton, SIGNAL(clicked()), this, SLOT(readyStart()));
connect(forceStartGameButton, &QPushButton::clicked, this, &DeckViewContainer::forceStart);
connect(sideboardLockButton, SIGNAL(clicked()), this, SLOT(sideboardLockButtonClicked()));
connect(sideboardLockButton, SIGNAL(stateChanged()), this, SLOT(updateSideboardLockButtonText()));
@@ -124,6 +128,9 @@ DeckViewContainer::DeckViewContainer(int _playerId, TabGame *parent)
buttonHBox->addWidget(loadRemoteButton);
buttonHBox->addWidget(readyStartButton);
buttonHBox->addWidget(sideboardLockButton);
if (forceStartGameButton->isEnabled()) {
buttonHBox->addWidget(forceStartGameButton);
}
buttonHBox->setContentsMargins(0, 0, 0, 0);
buttonHBox->addStretch();
deckView = new DeckView;
@@ -146,6 +153,7 @@ void DeckViewContainer::retranslateUi()
loadLocalButton->setText(tr("Load deck..."));
loadRemoteButton->setText(tr("Load remote deck..."));
readyStartButton->setText(tr("Ready to start"));
forceStartGameButton->setText(tr("Force start"));
updateSideboardLockButtonText();
}
@@ -154,6 +162,7 @@ void DeckViewContainer::setButtonsVisible(bool _visible)
loadLocalButton->setVisible(_visible);
loadRemoteButton->setVisible(_visible);
readyStartButton->setVisible(_visible);
forceStartGameButton->setVisible(_visible);
sideboardLockButton->setVisible(_visible);
}
@@ -255,7 +264,25 @@ void TabGame::refreshShortcuts()
aResetLayout->setShortcuts(shortcuts.getShortcut("Player/aResetLayout"));
}
if (aFocusChat) {
aFocusChat->setShortcuts(shortcuts.getShortcut("tab_game/aFocusChat"));
aFocusChat->setShortcuts(shortcuts.getShortcut("Player/aFocusChat"));
}
if (aReplaySkipForward) {
aReplaySkipForward->setShortcuts(shortcuts.getShortcut("Replays/aSkipForward"));
}
if (aReplaySkipBackward) {
aReplaySkipBackward->setShortcuts(shortcuts.getShortcut("Replays/aSkipBackward"));
}
if (aReplaySkipForwardBig) {
aReplaySkipForwardBig->setShortcuts(shortcuts.getShortcut("Replays/aSkipForwardBig"));
}
if (aReplaySkipBackwardBig) {
aReplaySkipBackwardBig->setShortcuts(shortcuts.getShortcut("Replays/aSkipBackwardBig"));
}
if (replayPlayButton) {
replayPlayButton->setShortcut(shortcuts.getSingleShortcut("Replays/playButton"));
}
if (replayFastForwardButton) {
replayFastForwardButton->setShortcut(shortcuts.getSingleShortcut("Replays/fastForwardButton"));
}
}
@@ -307,7 +334,8 @@ void DeckViewContainer::deckSelectFinished(const Response &r)
{
const Response_DeckDownload &resp = r.GetExtension(Response_DeckDownload::ext);
DeckLoader newDeck(QString::fromStdString(resp.deck()));
PictureLoader::cacheCardPixmaps(db->getCards(newDeck.getCardList()));
// TODO CHANGE THIS TO BE SELECTED BY UUID
PictureLoader::cacheCardPixmaps(CardDatabaseManager::getInstance()->getCards(newDeck.getCardList()));
setDeck(newDeck);
}
@@ -318,6 +346,14 @@ void DeckViewContainer::readyStart()
parentGame->sendGameCommand(cmd, playerId);
}
void DeckViewContainer::forceStart()
{
Command_ReadyStart cmd;
cmd.set_force_start(true);
cmd.set_ready(true);
parentGame->sendGameCommand(cmd, playerId);
}
void DeckViewContainer::sideboardLockButtonClicked()
{
Command_SetSideboardLock cmd;
@@ -422,7 +458,9 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor,
: Tab(_tabSupervisor), clients(_clients), gameInfo(event.game_info()), roomGameTypes(_roomGameTypes),
hostId(event.host_id()), localPlayerId(event.player_id()), isLocalGame(_tabSupervisor->getIsLocalGame()),
spectator(event.spectator()), judge(event.judge()), gameStateKnown(false), resuming(event.resuming()),
currentPhase(-1), activeCard(nullptr), gameClosed(false), replay(nullptr), replayDock(nullptr)
currentPhase(-1), activeCard(nullptr), gameClosed(false), replay(nullptr), replayPlayButton(nullptr),
replayFastForwardButton(nullptr), aReplaySkipForward(nullptr), aReplaySkipBackward(nullptr),
aReplaySkipForwardBig(nullptr), aReplaySkipBackwardBig(nullptr), replayDock(nullptr)
{
// THIS CTOR IS USED ON GAMES
gameInfo.set_started(false);
@@ -498,6 +536,12 @@ void TabGame::updatePlayerListDockTitle()
(playerListDock->isWindow() ? tabText : QString()));
}
bool TabGame::isMainPlayerConceded() const
{
Player *player = players.value(localPlayerId, nullptr);
return player && player->getConceded();
}
void TabGame::retranslateUi()
{
QString tabText = " | " + (replay ? tr("Replay") : tr("Game")) + " #" + QString::number(gameInfo.game_id());
@@ -539,7 +583,11 @@ void TabGame::retranslateUi()
if (aGameInfo)
aGameInfo->setText(tr("Game &information"));
if (aConcede) {
aConcede->setText(tr("&Concede"));
if (isMainPlayerConceded()) {
aConcede->setText(tr("Un&concede"));
} else {
aConcede->setText(tr("&Concede"));
}
}
if (aLeaveGame) {
aLeaveGame->setText(tr("&Leave game"));
@@ -576,7 +624,7 @@ void TabGame::retranslateUi()
aResetLayout->setText(tr("Reset layout"));
cardInfo->retranslateUi();
cardInfoFrameWidget->retranslateUi();
QMapIterator<int, Player *> i(players);
while (i.hasNext())
@@ -593,39 +641,40 @@ void TabGame::closeRequest()
actLeaveGame();
}
void TabGame::replayNextEvent()
void TabGame::replayNextEvent(Player::EventProcessingOptions options)
{
processGameEventContainer(replay->event_list(timelineWidget->getCurrentEvent()), nullptr);
processGameEventContainer(replay->event_list(timelineWidget->getCurrentEvent()), nullptr, options);
}
void TabGame::replayFinished()
{
replayStartButton->setEnabled(true);
replayPauseButton->setEnabled(false);
replayFastForwardButton->setEnabled(false);
replayPlayButton->setChecked(false);
}
void TabGame::replayStartButtonClicked()
void TabGame::replayPlayButtonToggled(bool checked)
{
replayStartButton->setEnabled(false);
replayPauseButton->setEnabled(true);
replayFastForwardButton->setEnabled(true);
timelineWidget->startReplay();
}
void TabGame::replayPauseButtonClicked()
{
replayStartButton->setEnabled(true);
replayPauseButton->setEnabled(false);
replayFastForwardButton->setEnabled(false);
timelineWidget->stopReplay();
if (checked) { // start replay
timelineWidget->startReplay();
} else { // pause replay
timelineWidget->stopReplay();
}
}
void TabGame::replayFastForwardButtonToggled(bool checked)
{
timelineWidget->setTimeScaleFactor(checked ? 10.0 : 1.0);
timelineWidget->setTimeScaleFactor(checked ? ReplayTimelineWidget::FAST_FORWARD_SCALE_FACTOR : 1.0);
}
/**
* @brief Handles everything that needs to be reset when doing a replay rewind.
*/
void TabGame::replayRewind()
{
// reset chat log
messageLog->clearChat();
// reset phase markers
setActivePhase(-1);
}
void TabGame::incrementGameTime()
@@ -835,11 +884,16 @@ Player *TabGame::addPlayer(int playerId, const ServerInfo_User &info)
}
}
// update menu text when player concedes so that "concede" gets updated to "unconcede"
connect(newPlayer, &Player::playerCountChanged, this, &TabGame::retranslateUi);
emit playerAdded(newPlayer);
return newPlayer;
}
void TabGame::processGameEventContainer(const GameEventContainer &cont, AbstractClient *client)
void TabGame::processGameEventContainer(const GameEventContainer &cont,
AbstractClient *client,
Player::EventProcessingOptions options)
{
const GameEventContext &context = cont.context();
messageLog->containerProcessingStarted(context);
@@ -914,7 +968,7 @@ void TabGame::processGameEventContainer(const GameEventContainer &cont, Abstract
qDebug() << "unhandled game event: invalid player id";
break;
}
player->processGameEvent(eventType, event, context);
player->processGameEvent(eventType, event, context, options);
emitUserEvent();
}
}
@@ -986,7 +1040,7 @@ PendingCommand *TabGame::prepareGameCommand(const QList<const ::google::protobuf
return new PendingCommand(cont);
}
void TabGame::startGame(bool resuming)
void TabGame::startGame(bool _resuming)
{
currentPhase = -1;
@@ -999,7 +1053,7 @@ void TabGame::startGame(bool resuming)
mainWidget->setCurrentWidget(gamePlayAreaWidget);
if (!resuming) {
if (!_resuming) {
QMapIterator<int, Player *> playerIterator(players);
while (playerIterator.hasNext())
playerIterator.next().value()->setGameStarted();
@@ -1087,7 +1141,8 @@ void TabGame::eventGameStateChanged(const Event_GameStateChanged &event,
DeckViewContainer *deckViewContainer = deckViewContainers.value(playerId);
if (playerInfo.has_deck_list()) {
DeckLoader newDeck(QString::fromStdString(playerInfo.deck_list()));
PictureLoader::cacheCardPixmaps(db->getCards(newDeck.getCardList()));
PictureLoader::cacheCardPixmaps(
CardDatabaseManager::getInstance()->getCards(newDeck.getCardList()));
deckViewContainer->setDeck(newDeck);
player->setDeck(newDeck);
}
@@ -1354,7 +1409,7 @@ void TabGame::eventSetActivePhase(const Event_SetActivePhase &event,
void TabGame::newCardAdded(AbstractCardItem *card)
{
connect(card, SIGNAL(hovered(AbstractCardItem *)), cardInfo, SLOT(setCard(AbstractCardItem *)));
connect(card, SIGNAL(hovered(AbstractCardItem *)), cardInfoFrameWidget, SLOT(setCard(AbstractCardItem *)));
connect(card, SIGNAL(showCardInfoPopup(QPoint, QString)), this, SLOT(showCardInfoPopup(QPoint, QString)));
connect(card, SIGNAL(deleteCardInfoPopup(QString)), this, SLOT(deleteCardInfoPopup(QString)));
connect(card, SIGNAL(cardShiftClicked(QString)), this, SLOT(linkCardToChat(QString)));
@@ -1704,32 +1759,55 @@ void TabGame::createPlayAreaWidget(bool bReplay)
void TabGame::createReplayDock()
{
// timeline widget
timelineWidget = new ReplayTimelineWidget;
timelineWidget->setTimeline(replayTimeline);
connect(timelineWidget, SIGNAL(processNextEvent()), this, SLOT(replayNextEvent()));
connect(timelineWidget, SIGNAL(processNextEvent(Player::EventProcessingOptions)), this,
SLOT(replayNextEvent(Player::EventProcessingOptions)));
connect(timelineWidget, SIGNAL(replayFinished()), this, SLOT(replayFinished()));
connect(timelineWidget, &ReplayTimelineWidget::rewound, messageLog, &ChatView::clearChat);
connect(timelineWidget, &ReplayTimelineWidget::rewound, this, &TabGame::replayRewind);
// timeline skip shortcuts
aReplaySkipForward = new QAction(timelineWidget);
timelineWidget->addAction(aReplaySkipForward);
connect(aReplaySkipForward, &QAction::triggered, this,
[this] { timelineWidget->skipByAmount(ReplayTimelineWidget::SMALL_SKIP_MS); });
aReplaySkipBackward = new QAction(timelineWidget);
timelineWidget->addAction(aReplaySkipBackward);
connect(aReplaySkipBackward, &QAction::triggered, this,
[this] { timelineWidget->skipByAmount(-ReplayTimelineWidget::SMALL_SKIP_MS); });
aReplaySkipForwardBig = new QAction(timelineWidget);
timelineWidget->addAction(aReplaySkipForwardBig);
connect(aReplaySkipForwardBig, &QAction::triggered, this,
[this] { timelineWidget->skipByAmount(ReplayTimelineWidget::BIG_SKIP_MS); });
aReplaySkipBackwardBig = new QAction(timelineWidget);
timelineWidget->addAction(aReplaySkipBackwardBig);
connect(aReplaySkipBackwardBig, &QAction::triggered, this,
[this] { timelineWidget->skipByAmount(-ReplayTimelineWidget::BIG_SKIP_MS); });
// buttons
replayPlayButton = new QToolButton;
replayPlayButton->setIconSize(QSize(32, 32));
QIcon playButtonIcon = QIcon();
playButtonIcon.addPixmap(QPixmap("theme:replay/start"), QIcon::Normal, QIcon::Off);
playButtonIcon.addPixmap(QPixmap("theme:replay/pause"), QIcon::Normal, QIcon::On);
replayPlayButton->setIcon(playButtonIcon);
replayPlayButton->setCheckable(true);
connect(replayPlayButton, SIGNAL(toggled(bool)), this, SLOT(replayPlayButtonToggled(bool)));
replayStartButton = new QToolButton;
replayStartButton->setIconSize(QSize(32, 32));
replayStartButton->setIcon(QPixmap("theme:replay/start"));
connect(replayStartButton, SIGNAL(clicked()), this, SLOT(replayStartButtonClicked()));
replayPauseButton = new QToolButton;
replayPauseButton->setIconSize(QSize(32, 32));
replayPauseButton->setEnabled(false);
replayPauseButton->setIcon(QPixmap("theme:replay/pause"));
connect(replayPauseButton, SIGNAL(clicked()), this, SLOT(replayPauseButtonClicked()));
replayFastForwardButton = new QToolButton;
replayFastForwardButton->setIconSize(QSize(32, 32));
replayFastForwardButton->setEnabled(false);
replayFastForwardButton->setIcon(QPixmap("theme:replay/fastforward"));
replayFastForwardButton->setCheckable(true);
connect(replayFastForwardButton, SIGNAL(toggled(bool)), this, SLOT(replayFastForwardButtonToggled(bool)));
// putting everything together
replayControlLayout = new QHBoxLayout;
replayControlLayout->addWidget(timelineWidget, 10);
replayControlLayout->addWidget(replayStartButton);
replayControlLayout->addWidget(replayPauseButton);
replayControlLayout->addWidget(replayPlayButton);
replayControlLayout->addWidget(replayFastForwardButton);
replayControlWidget = new QWidget();
@@ -1758,20 +1836,20 @@ void TabGame::createDeckViewContainerWidget(bool bReplay)
deckViewContainerWidget->setLayout(deckViewContainerLayout);
}
void TabGame::viewCardInfo(const QString &cardName)
void TabGame::viewCardInfo(const QString &cardName, const QString &providerId) const
{
cardInfo->setCard(cardName);
cardInfoFrameWidget->setCard(cardName, providerId);
}
void TabGame::createCardInfoDock(bool bReplay)
{
Q_UNUSED(bReplay);
cardInfo = new CardFrame();
cardInfoFrameWidget = new CardInfoFrameWidget();
cardHInfoLayout = new QHBoxLayout;
cardVInfoLayout = new QVBoxLayout;
cardVInfoLayout->setContentsMargins(0, 0, 0, 0);
cardVInfoLayout->addWidget(cardInfo);
cardVInfoLayout->addWidget(cardInfoFrameWidget);
cardVInfoLayout->addLayout(cardHInfoLayout);
cardBoxLayoutWidget = new QWidget;
@@ -1813,7 +1891,7 @@ void TabGame::createPlayerListDock(bool bReplay)
void TabGame::createMessageDock(bool bReplay)
{
messageLog = new MessageLogWidget(tabSupervisor, tabSupervisor, this);
connect(messageLog, SIGNAL(cardNameHovered(QString)), cardInfo, SLOT(setCard(QString)));
connect(messageLog, SIGNAL(cardNameHovered(QString)), cardInfoFrameWidget, SLOT(setCard(QString)));
connect(messageLog, SIGNAL(showCardInfoPopup(QPoint, QString)), this, SLOT(showCardInfoPopup(QPoint, QString)));
connect(messageLog, SIGNAL(deleteCardInfoPopup(QString)), this, SLOT(deleteCardInfoPopup(QString)));

View File

@@ -1,10 +1,11 @@
#ifndef TAB_GAME_H
#define TAB_GAME_H
#include "../../client/tearoff_menu.h"
#include "../../game/player/player.h"
#include "pb/event_leave.pb.h"
#include "pb/serverinfo_game.pb.h"
#include "tab.h"
#include "tearoffmenu.h"
#include <QCompleter>
#include <QMap>
@@ -15,7 +16,7 @@ class CardDatabase;
class GameView;
class DeckView;
class GameScene;
class CardFrame;
class CardInfoFrameWidget;
class MessageLogWidget;
class QTimer;
class QSplitter;
@@ -47,7 +48,6 @@ class Event_Ping;
class Event_GameSay;
class Event_Kicked;
class Event_ReverseTurn;
class Player;
class CardZone;
class AbstractCardItem;
class CardItem;
@@ -86,7 +86,7 @@ class DeckViewContainer : public QWidget
{
Q_OBJECT
private:
QPushButton *loadLocalButton, *loadRemoteButton;
QPushButton *loadLocalButton, *loadRemoteButton, *forceStartGameButton;
ToggleButton *readyStartButton, *sideboardLockButton;
DeckView *deckView;
TabGame *parentGame;
@@ -95,6 +95,7 @@ private slots:
void loadLocalDeck();
void loadRemoteDeck();
void readyStart();
void forceStart();
void deckSelectFinished(const Response &r);
void sideboardPlanChanged();
void sideboardLockButtonClicked();
@@ -146,9 +147,10 @@ private:
int currentReplayStep;
QList<int> replayTimeline;
ReplayTimelineWidget *timelineWidget;
QToolButton *replayStartButton, *replayPauseButton, *replayFastForwardButton;
QToolButton *replayPlayButton, *replayFastForwardButton;
QAction *aReplaySkipForward, *aReplaySkipBackward, *aReplaySkipForwardBig, *aReplaySkipBackwardBig;
CardFrame *cardInfo;
CardInfoFrameWidget *cardInfoFrameWidget;
PlayerListWidget *playerListWidget;
QLabel *timeElapsedLabel;
MessageLogWidget *messageLog;
@@ -175,6 +177,8 @@ private:
Player *addPlayer(int playerId, const ServerInfo_User &info);
bool isMainPlayerConceded() const;
void startGame(bool resuming);
void stopGame();
void closeGame();
@@ -218,11 +222,11 @@ signals:
void openDeckEditor(const DeckLoader *deck);
void notIdle();
private slots:
void replayNextEvent();
void replayNextEvent(Player::EventProcessingOptions options);
void replayFinished();
void replayStartButtonClicked();
void replayPauseButtonClicked();
void replayPlayButtonToggled(bool checked);
void replayFastForwardButtonToggled(bool checked);
void replayRewind();
void incrementGameTime();
void adminLockChanged(bool lock);
@@ -304,13 +308,15 @@ public:
return activeCard;
}
void processGameEventContainer(const GameEventContainer &cont, AbstractClient *client);
void processGameEventContainer(const GameEventContainer &cont,
AbstractClient *client,
Player::EventProcessingOptions options);
PendingCommand *prepareGameCommand(const ::google::protobuf::Message &cmd);
PendingCommand *prepareGameCommand(const QList<const ::google::protobuf::Message *> &cmdList);
public slots:
void sendGameCommand(PendingCommand *pend, int playerId = -1);
void sendGameCommand(const ::google::protobuf::Message &command, int playerId = -1);
void viewCardInfo(const QString &cardName);
void viewCardInfo(const QString &cardName, const QString &providerId = "") const;
};
#endif

View File

@@ -1,12 +1,12 @@
#include "tab_logs.h"
#include "abstractclient.h"
#include "customlineedit.h"
#include "dlg_manage_sets.h"
#include "../../deck/custom_line_edit.h"
#include "../../dialogs/dlg_manage_sets.h"
#include "../../server/pending_command.h"
#include "../game_logic/abstract_client.h"
#include "pb/moderator_commands.pb.h"
#include "pb/response_viewlog_history.pb.h"
#include "pending_command.h"
#include "stringsizes.h"
#include "trice_limits.h"
#include <QCheckBox>
#include <QDialogButtonBox>
@@ -89,7 +89,7 @@ void TabLog::getClicked()
if (maximumResults->value() == 0)
maximumResults->setValue(1000);
int dateRange;
int dateRange = 0;
if (lastHour->isChecked())
dateRange = 1;

View File

@@ -1,16 +1,16 @@
#include "tab_message.h"
#include "abstractclient.h"
#include "chatview/chatview.h"
#include "customlineedit.h"
#include "main.h"
#include "../../deck/custom_line_edit.h"
#include "../../main.h"
#include "../../server/chat_view/chat_view.h"
#include "../../server/pending_command.h"
#include "../../settings/cache_settings.h"
#include "../game_logic/abstract_client.h"
#include "../sound_engine.h"
#include "pb/event_user_message.pb.h"
#include "pb/serverinfo_user.pb.h"
#include "pb/session_commands.pb.h"
#include "pending_command.h"
#include "settingscache.h"
#include "soundengine.h"
#include "stringsizes.h"
#include "trice_limits.h"
#include <QApplication>
#include <QDebug>
@@ -159,7 +159,7 @@ void TabMessage::showSystemPopup(const Event_UserMessage &event)
void TabMessage::messageClicked()
{
tabSupervisor->setCurrentIndex(tabSupervisor->indexOf(this));
QApplication::setActiveWindow(this);
activateWindow();
emit maximizeClient();
}

View File

@@ -0,0 +1,449 @@
#include "tab_replays.h"
#include "../../server/pending_command.h"
#include "../../server/remote/remote_replay_list_tree_widget.h"
#include "../../settings/cache_settings.h"
#include "../game_logic/abstract_client.h"
#include "pb/command_replay_delete_match.pb.h"
#include "pb/command_replay_download.pb.h"
#include "pb/command_replay_modify_match.pb.h"
#include "pb/event_replay_added.pb.h"
#include "pb/game_replay.pb.h"
#include "pb/response.pb.h"
#include "pb/response_replay_download.pb.h"
#include "tab_game.h"
#include <QAction>
#include <QApplication>
#include <QDesktopServices>
#include <QFileSystemModel>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QInputDialog>
#include <QMessageBox>
#include <QToolBar>
#include <QTreeView>
#include <QUrl>
#include <QVBoxLayout>
TabReplays::TabReplays(TabSupervisor *_tabSupervisor, AbstractClient *_client) : Tab(_tabSupervisor), client(_client)
{
localDirModel = new QFileSystemModel(this);
localDirModel->setRootPath(SettingsCache::instance().getReplaysPath());
localDirModel->sort(0, Qt::AscendingOrder);
localDirView = new QTreeView;
localDirView->setModel(localDirModel);
localDirView->setColumnHidden(1, true);
localDirView->setRootIndex(localDirModel->index(localDirModel->rootPath(), 0));
localDirView->setSortingEnabled(true);
localDirView->setSelectionMode(QAbstractItemView::ExtendedSelection);
localDirView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
localDirView->header()->setSortIndicator(0, Qt::AscendingOrder);
// Left side layout
/* put an invisible dummy QToolBar in the leftmost column so that the main toolbar is centered.
* Really ugly workaround, but I couldn't figure out the proper way to make it centered */
QToolBar *dummyToolBar = new QToolBar(this);
QSizePolicy sizePolicy = dummyToolBar->sizePolicy();
sizePolicy.setRetainSizeWhenHidden(true);
dummyToolBar->setSizePolicy(sizePolicy);
dummyToolBar->setVisible(false);
leftToolBar = new QToolBar(this);
leftToolBar->setOrientation(Qt::Horizontal);
leftToolBar->setIconSize(QSize(32, 32));
QToolBar *leftRightmostToolBar = new QToolBar(this);
leftRightmostToolBar->setOrientation(Qt::Horizontal);
leftRightmostToolBar->setIconSize(QSize(32, 32));
QGridLayout *leftToolBarLayout = new QGridLayout;
leftToolBarLayout->addWidget(dummyToolBar, 0, 0, Qt::AlignLeft);
leftToolBarLayout->addWidget(leftToolBar, 0, 1, Qt::AlignHCenter);
leftToolBarLayout->addWidget(leftRightmostToolBar, 0, 2, Qt::AlignRight);
QVBoxLayout *leftVbox = new QVBoxLayout;
leftVbox->addWidget(localDirView);
leftVbox->addLayout(leftToolBarLayout);
leftGroupBox = new QGroupBox;
leftGroupBox->setLayout(leftVbox);
// Right side layout
rightToolBar = new QToolBar;
rightToolBar->setOrientation(Qt::Horizontal);
rightToolBar->setIconSize(QSize(32, 32));
QHBoxLayout *rightToolBarLayout = new QHBoxLayout;
rightToolBarLayout->addStretch();
rightToolBarLayout->addWidget(rightToolBar);
rightToolBarLayout->addStretch();
serverDirView = new RemoteReplayList_TreeWidget(client);
QVBoxLayout *rightVbox = new QVBoxLayout;
rightVbox->addWidget(serverDirView);
rightVbox->addLayout(rightToolBarLayout);
rightGroupBox = new QGroupBox;
rightGroupBox->setLayout(rightVbox);
// combine layouts
QHBoxLayout *hbox = new QHBoxLayout;
hbox->addWidget(leftGroupBox);
hbox->addWidget(rightGroupBox);
// Left side actions
aOpenLocalReplay = new QAction(this);
aOpenLocalReplay->setIcon(QPixmap("theme:icons/view"));
connect(aOpenLocalReplay, SIGNAL(triggered()), this, SLOT(actOpenLocalReplay()));
connect(localDirView, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(actOpenLocalReplay()));
aRenameLocal = new QAction(this);
aRenameLocal->setIcon(QPixmap("theme:icons/pencil"));
connect(aRenameLocal, &QAction::triggered, this, &TabReplays::actRenameLocal);
aNewLocalFolder = new QAction(this);
aNewLocalFolder->setIcon(qApp->style()->standardIcon(QStyle::SP_FileDialogNewFolder));
connect(aNewLocalFolder, &QAction::triggered, this, &TabReplays::actNewLocalFolder);
aDeleteLocalReplay = new QAction(this);
aDeleteLocalReplay->setIcon(QPixmap("theme:icons/remove_row"));
connect(aDeleteLocalReplay, SIGNAL(triggered()), this, SLOT(actDeleteLocalReplay()));
aOpenReplaysFolder = new QAction(this);
aOpenReplaysFolder->setIcon(qApp->style()->standardIcon(QStyle::SP_DirOpenIcon));
connect(aOpenReplaysFolder, &QAction::triggered, this, &TabReplays::actOpenReplaysFolder);
// Right side actions
aOpenRemoteReplay = new QAction(this);
aOpenRemoteReplay->setIcon(QPixmap("theme:icons/view"));
connect(aOpenRemoteReplay, SIGNAL(triggered()), this, SLOT(actOpenRemoteReplay()));
connect(serverDirView, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(actOpenRemoteReplay()));
aDownload = new QAction(this);
aDownload->setIcon(QPixmap("theme:icons/arrow_left_green"));
connect(aDownload, SIGNAL(triggered()), this, SLOT(actDownload()));
aKeep = new QAction(this);
aKeep->setIcon(QPixmap("theme:icons/lock"));
connect(aKeep, SIGNAL(triggered()), this, SLOT(actKeepRemoteReplay()));
aDeleteRemoteReplay = new QAction(this);
aDeleteRemoteReplay->setIcon(QPixmap("theme:icons/remove_row"));
connect(aDeleteRemoteReplay, SIGNAL(triggered()), this, SLOT(actDeleteRemoteReplay()));
// Add actions to toolbars
leftToolBar->addAction(aOpenLocalReplay);
leftToolBar->addAction(aRenameLocal);
leftToolBar->addAction(aNewLocalFolder);
leftToolBar->addAction(aDeleteLocalReplay);
leftRightmostToolBar->addAction(aOpenReplaysFolder);
rightToolBar->addAction(aOpenRemoteReplay);
rightToolBar->addAction(aDownload);
rightToolBar->addAction(aKeep);
rightToolBar->addAction(aDeleteRemoteReplay);
retranslateUi();
QWidget *mainWidget = new QWidget(this);
mainWidget->setLayout(hbox);
setCentralWidget(mainWidget);
connect(client, SIGNAL(replayAddedEventReceived(const Event_ReplayAdded &)), this,
SLOT(replayAddedEventReceived(const Event_ReplayAdded &)));
}
void TabReplays::retranslateUi()
{
leftGroupBox->setTitle(tr("Local file system"));
rightGroupBox->setTitle(tr("Server replay storage"));
aOpenLocalReplay->setText(tr("Watch replay"));
aRenameLocal->setText(tr("Rename"));
aNewLocalFolder->setText(tr("New folder"));
aDeleteLocalReplay->setText(tr("Delete"));
aOpenReplaysFolder->setText(tr("Open replays folder"));
aOpenRemoteReplay->setText(tr("Watch replay"));
aDownload->setText(tr("Download replay"));
aKeep->setText(tr("Toggle expiration lock"));
aDeleteRemoteReplay->setText(tr("Delete"));
}
void TabReplays::actLocalDoubleClick(const QModelIndex &curLeft)
{
if (!localDirModel->isDir(curLeft)) {
actOpenLocalReplay();
}
}
void TabReplays::actOpenLocalReplay()
{
QModelIndexList curLefts = localDirView->selectionModel()->selectedRows();
for (const auto &curLeft : curLefts) {
if (localDirModel->isDir(curLeft))
continue;
QString filePath = localDirModel->filePath(curLeft);
QFile f(filePath);
if (!f.open(QIODevice::ReadOnly))
continue;
QByteArray _data = f.readAll();
f.close();
GameReplay *replay = new GameReplay;
replay->ParseFromArray(_data.data(), _data.size());
emit openReplay(replay);
}
}
void TabReplays::actRenameLocal()
{
QModelIndexList curLefts = localDirView->selectionModel()->selectedRows();
for (const auto &curLeft : curLefts) {
const QFileInfo info = localDirModel->fileInfo(curLeft);
const QString oldName = info.baseName();
const QString title = info.isDir() ? tr("Rename local folder") : tr("Rename local file");
bool ok;
QString newName = QInputDialog::getText(this, title, tr("New name:"), QLineEdit::Normal, oldName, &ok);
if (!ok) { // terminate all remaining selections if user cancels
return;
}
if (newName.isEmpty() || oldName == newName) {
continue;
}
QString newFileName = newName;
if (!info.suffix().isEmpty()) {
newFileName += "." + info.suffix();
}
const QString newFilePath = QFileInfo(info.dir(), newFileName).filePath();
if (!QFile::rename(info.filePath(), newFilePath)) {
QMessageBox::critical(this, tr("Error"), tr("Rename failed"));
}
}
}
void TabReplays::actNewLocalFolder()
{
QModelIndex curLeft = localDirView->selectionModel()->currentIndex();
QModelIndex dirIndex;
if (curLeft.isValid() && !localDirModel->isDir(curLeft)) {
dirIndex = curLeft.parent();
} else {
dirIndex = curLeft;
}
bool ok;
QString folderName =
QInputDialog::getText(this, tr("New folder"), tr("Name of new folder:"), QLineEdit::Normal, "", &ok);
if (!ok || folderName.isEmpty())
return;
localDirModel->mkdir(dirIndex, folderName);
}
void TabReplays::actDeleteLocalReplay()
{
QModelIndexList curLefts = localDirView->selectionModel()->selectedRows();
if (curLefts.isEmpty()) {
return;
}
if (QMessageBox::warning(this, tr("Delete local file"), tr("Are you sure you want to delete the selected files?"),
QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) {
return;
}
for (const auto &curLeft : curLefts) {
if (curLeft.isValid()) {
localDirModel->remove(curLeft);
}
}
}
void TabReplays::actOpenReplaysFolder()
{
QString dir = localDirModel->rootPath();
QDesktopServices::openUrl(QUrl::fromLocalFile(dir));
}
void TabReplays::actRemoteDoubleClick(const QModelIndex &curRight)
{
if (serverDirView->getReplay(curRight)) {
actOpenRemoteReplay();
}
}
void TabReplays::actOpenRemoteReplay()
{
auto const curRights = serverDirView->getSelectedReplays();
for (const auto curRight : curRights) {
if (!curRight) {
continue;
}
Command_ReplayDownload cmd;
cmd.set_replay_id(curRight->replay_id());
PendingCommand *pend = client->prepareSessionCommand(cmd);
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
SLOT(openRemoteReplayFinished(const Response &)));
client->sendCommand(pend);
}
}
void TabReplays::openRemoteReplayFinished(const Response &r)
{
if (r.response_code() != Response::RespOk)
return;
const Response_ReplayDownload &resp = r.GetExtension(Response_ReplayDownload::ext);
GameReplay *replay = new GameReplay;
replay->ParseFromString(resp.replay_data());
emit openReplay(replay);
}
void TabReplays::actDownload()
{
QModelIndex curLeft = localDirView->selectionModel()->currentIndex();
while (!localDirModel->isDir(curLeft)) {
curLeft = curLeft.parent();
}
for (const auto curRight : serverDirView->selectionModel()->selectedRows()) {
downloadNodeAtIndex(curLeft, curRight);
}
}
void TabReplays::downloadNodeAtIndex(const QModelIndex &curLeft, const QModelIndex &curRight)
{
if (const auto replayMatch = serverDirView->getReplayMatch(curRight)) {
// node at index is a folder
const QString name =
QString::number(replayMatch->game_id()) + "_" + QString::fromStdString(replayMatch->game_name());
const auto dirIndex = curLeft.isValid() ? curLeft : localDirModel->index(localDirModel->rootPath());
const auto newDirIndex = localDirModel->mkdir(dirIndex, name);
int rows = serverDirView->model()->rowCount(curRight);
for (int i = 0; i < rows; i++) {
const auto childIndex = serverDirView->model()->index(i, 0, curRight);
downloadNodeAtIndex(newDirIndex, childIndex);
}
} else if (const auto replay = serverDirView->getReplay(curRight)) {
// node at index is a replay
const QString dirPath = curLeft.isValid() ? localDirModel->filePath(curLeft) : localDirModel->rootPath();
const QString filePath = dirPath + QString("/replay_%1.cor").arg(replay->replay_id());
Command_ReplayDownload cmd;
cmd.set_replay_id(replay->replay_id());
PendingCommand *pend = client->prepareSessionCommand(cmd);
pend->setExtraData(filePath);
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
SLOT(downloadFinished(Response, CommandContainer, QVariant)));
client->sendCommand(pend);
}
// node at index was invalid
}
void TabReplays::downloadFinished(const Response &r,
const CommandContainer & /* commandContainer */,
const QVariant &extraData)
{
if (r.response_code() != Response::RespOk)
return;
const Response_ReplayDownload &resp = r.GetExtension(Response_ReplayDownload::ext);
QString filePath = extraData.toString();
const std::string &_data = resp.replay_data();
QFile f(filePath);
f.open(QIODevice::WriteOnly);
f.write((const char *)_data.data(), _data.size());
f.close();
}
void TabReplays::actKeepRemoteReplay()
{
const auto curRights = serverDirView->getSelectedReplayMatches();
if (curRights.isEmpty()) {
return;
}
for (const auto curRight : curRights) {
Command_ReplayModifyMatch cmd;
cmd.set_game_id(curRight->game_id());
cmd.set_do_not_hide(!curRight->do_not_hide());
PendingCommand *pend = client->prepareSessionCommand(cmd);
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
SLOT(keepRemoteReplayFinished(Response, CommandContainer)));
client->sendCommand(pend);
}
}
void TabReplays::keepRemoteReplayFinished(const Response &r, const CommandContainer &commandContainer)
{
if (r.response_code() != Response::RespOk)
return;
const Command_ReplayModifyMatch &cmd =
commandContainer.session_command(0).GetExtension(Command_ReplayModifyMatch::ext);
ServerInfo_ReplayMatch temp;
temp.set_do_not_hide(cmd.do_not_hide());
serverDirView->updateMatchInfo(cmd.game_id(), temp);
}
void TabReplays::actDeleteRemoteReplay()
{
const auto curRights = serverDirView->getSelectedReplayMatches();
if (curRights.isEmpty()) {
return;
}
if (QMessageBox::warning(this, tr("Delete remote replay"),
tr("Are you sure you want to delete the selected replays?"),
QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) {
return;
}
for (const auto curRight : curRights) {
Command_ReplayDeleteMatch cmd;
cmd.set_game_id(curRight->game_id());
PendingCommand *pend = client->prepareSessionCommand(cmd);
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
SLOT(deleteRemoteReplayFinished(Response, CommandContainer)));
client->sendCommand(pend);
}
}
void TabReplays::deleteRemoteReplayFinished(const Response &r, const CommandContainer &commandContainer)
{
if (r.response_code() != Response::RespOk)
return;
const Command_ReplayDeleteMatch &cmd =
commandContainer.session_command(0).GetExtension(Command_ReplayDeleteMatch::ext);
serverDirView->removeMatchInfo(cmd.game_id());
}
void TabReplays::replayAddedEventReceived(const Event_ReplayAdded &event)
{
if (event.has_match_info()) {
// 99.9% of events will have match info (Normal Workflow)
serverDirView->addMatchInfo(event.match_info());
} else {
// When a Moderator force adds a replay, we need to refresh their view
serverDirView->refreshTree();
}
}

View File

@@ -25,12 +25,22 @@ private:
RemoteReplayList_TreeWidget *serverDirView;
QGroupBox *leftGroupBox, *rightGroupBox;
QAction *aOpenLocalReplay, *aDeleteLocalReplay, *aOpenRemoteReplay, *aDownload, *aKeep, *aDeleteRemoteReplay;
private slots:
void actOpenLocalReplay();
QAction *aOpenLocalReplay, *aRenameLocal, *aNewLocalFolder, *aDeleteLocalReplay;
QAction *aOpenReplaysFolder;
QAction *aOpenRemoteReplay, *aDownload, *aKeep, *aDeleteRemoteReplay;
void downloadNodeAtIndex(const QModelIndex &curLeft, const QModelIndex &curRight);
private slots:
void actLocalDoubleClick(const QModelIndex &curLeft);
void actRenameLocal();
void actOpenLocalReplay();
void actNewLocalFolder();
void actDeleteLocalReplay();
void actOpenReplaysFolder();
void actRemoteDoubleClick(const QModelIndex &curLeft);
void actOpenRemoteReplay();
void openRemoteReplayFinished(const Response &r);

View File

@@ -1,11 +1,14 @@
#include "tab_room.h"
#include "abstractclient.h"
#include "chatview/chatview.h"
#include "dlg_settings.h"
#include "gameselector.h"
#include "../../client/game_logic/abstract_client.h"
#include "../../dialogs/dlg_settings.h"
#include "../../game/game_selector.h"
#include "../../main.h"
#include "../../server/chat_view/chat_view.h"
#include "../../server/pending_command.h"
#include "../../server/user/user_list.h"
#include "../../settings/cache_settings.h"
#include "get_pb_extension.h"
#include "main.h"
#include "pb/event_join_room.pb.h"
#include "pb/event_leave_room.pb.h"
#include "pb/event_list_games.pb.h"
@@ -13,12 +16,9 @@
#include "pb/event_room_say.pb.h"
#include "pb/room_commands.pb.h"
#include "pb/serverinfo_room.pb.h"
#include "pending_command.h"
#include "settingscache.h"
#include "stringsizes.h"
#include "tab_account.h"
#include "tab_supervisor.h"
#include "userlist.h"
#include "trice_limits.h"
#include <QApplication>
#include <QCompleter>
@@ -155,7 +155,7 @@ void TabRoom::retranslateUi()
void TabRoom::focusTab()
{
QApplication::setActiveWindow(this);
activateWindow();
tabSupervisor->setCurrentIndex(tabSupervisor->indexOf(this));
emit maximizeClient();
}

View File

@@ -1,7 +1,7 @@
#ifndef TAB_ROOM_H
#define TAB_ROOM_H
#include "lineeditcompleter.h"
#include "../ui/line_edit_completer.h"
#include "tab.h"
#include <QFocusEvent>

View File

@@ -1,13 +1,13 @@
#include "tab_server.h"
#include "abstractclient.h"
#include "../../client/game_logic/abstract_client.h"
#include "../../server/pending_command.h"
#include "../../server/user/user_list.h"
#include "pb/event_list_rooms.pb.h"
#include "pb/event_server_message.pb.h"
#include "pb/response_join_room.pb.h"
#include "pb/session_commands.pb.h"
#include "pending_command.h"
#include "tab_supervisor.h"
#include "userlist.h"
#include <QCheckBox>
#include <QDebug>

View File

@@ -1,7 +1,10 @@
#include "tab_supervisor.h"
#include "abstractclient.h"
#include "main.h"
#include "../../client/game_logic/abstract_client.h"
#include "../../main.h"
#include "../../server/user/user_list.h"
#include "../../settings/cache_settings.h"
#include "../ui/pixel_map_generator.h"
#include "pb/event_game_joined.pb.h"
#include "pb/event_notify_user.pb.h"
#include "pb/event_user_message.pb.h"
@@ -11,8 +14,6 @@
#include "pb/room_event.pb.h"
#include "pb/serverinfo_room.pb.h"
#include "pb/serverinfo_user.pb.h"
#include "pixmapgenerator.h"
#include "settingscache.h"
#include "tab_account.h"
#include "tab_admin.h"
#include "tab_deck_editor.h"
@@ -23,7 +24,6 @@
#include "tab_replays.h"
#include "tab_room.h"
#include "tab_server.h"
#include "userlist.h"
#include <QApplication>
#include <QDebug>
@@ -496,6 +496,7 @@ TabDeckEditor *TabSupervisor::addDeckEditorTab(const DeckLoader *deckToOpen)
if (deckToOpen)
tab->setDeck(new DeckLoader(*deckToOpen));
connect(tab, SIGNAL(deckEditorClosing(TabDeckEditor *)), this, SLOT(deckEditorClosed(TabDeckEditor *)));
connect(tab, SIGNAL(openDeckEditor(const DeckLoader *)), this, SLOT(addDeckEditorTab(const DeckLoader *)));
int tabIndex = myAddTab(tab);
addCloseButtonToTab(tab, tabIndex);
deckEditorTabs.append(tab);
@@ -541,7 +542,7 @@ void TabSupervisor::processGameEventContainer(const GameEventContainer &cont)
{
TabGame *tab = gameTabs.value(cont.game_id());
if (tab)
tab->processGameEventContainer(cont, qobject_cast<AbstractClient *>(sender()));
tab->processGameEventContainer(cont, qobject_cast<AbstractClient *>(sender()), {});
else
qDebug() << "gameEvent: invalid gameId";
}
@@ -687,12 +688,12 @@ void TabSupervisor::processNotifyUserEvent(const Event_NotifyUser &event)
bool TabSupervisor::isOwnUserRegistered() const
{
return static_cast<bool>(getUserInfo()->user_level() & ServerInfo_User::IsRegistered);
return userInfo != nullptr && (userInfo->user_level() & ServerInfo_User::IsRegistered) != 0;
}
QString TabSupervisor::getOwnUsername() const
{
return userInfo ? QString::fromStdString(userInfo->name()) : QString();
return userInfo != nullptr ? QString::fromStdString(userInfo->name()) : QString();
}
bool TabSupervisor::isUserBuddy(const QString &userName) const
@@ -729,9 +730,22 @@ const ServerInfo_User *TabSupervisor::getOnlineUser(const QString &userName) con
for (i = userList.begin(); i != userList.end(); ++i)
if (i.key().toLower() == userNameToMatchLower) {
const ServerInfo_User &userInfo = i.value()->getUserInfo();
return &userInfo;
const ServerInfo_User &_userInfo = i.value()->getUserInfo();
return &_userInfo;
}
return nullptr;
};
bool TabSupervisor::switchToGameTabIfAlreadyExists(const int gameId)
{
bool isGameTabExists = false;
if (gameTabs.contains(gameId)) {
isGameTabExists = true;
TabGame *tabGame = gameTabs[gameId];
const int gameTabIndex = indexOf(tabGame);
setCurrentIndex(gameTabIndex);
}
return isGameTabExists;
}

View File

@@ -1,8 +1,8 @@
#ifndef TAB_SUPERVISOR_H
#define TAB_SUPERVISOR_H
#include "chatview/userlistProxy.h"
#include "deck_loader.h"
#include "../../deck/deck_loader.h"
#include "../../server/chat_view/user_list_proxy.h"
#include <QAbstractButton>
#include <QCommonStyle>
@@ -120,6 +120,7 @@ public:
bool isUserBuddy(const QString &userName) const;
bool isUserIgnored(const QString &userName) const;
const ServerInfo_User *getOnlineUser(const QString &userName) const;
bool switchToGameTabIfAlreadyExists(const int gameId);
void actShowPopup(const QString &message);
signals:
void setMenu(const QList<QMenu *> &newMenuList = QList<QMenu *>());

View File

@@ -1,4 +1,4 @@
#include "tappedout_interface.h"
#include "tapped_out_interface.h"
#include "decklist.h"
@@ -7,7 +7,6 @@
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QRegExp>
#include <QRegularExpression>
#include <QUrlQuery>
@@ -15,13 +14,13 @@ TappedOutInterface::TappedOutInterface(CardDatabase &_cardDatabase, QObject *par
: QObject(parent), cardDatabase(_cardDatabase)
{
manager = new QNetworkAccessManager(this);
connect(manager, SIGNAL(finished(QNetworkReply *)), this, SLOT(queryFinished(QNetworkReply *)));
connect(manager, &QNetworkAccessManager::finished, this, &TappedOutInterface::queryFinished);
}
void TappedOutInterface::queryFinished(QNetworkReply *reply)
{
if (reply->error() != QNetworkReply::NoError) {
QMessageBox::critical(0, tr("Error"), reply->errorString());
QMessageBox::critical(nullptr, tr("Error"), reply->errorString());
reply->deleteLater();
deleteLater();
return;
@@ -42,26 +41,26 @@ void TappedOutInterface::queryFinished(QNetworkReply *reply)
* from the html. Css pseudo selector for errors: $("div.alert-danger > ul > li")
*/
QString data(reply->readAll());
QString errorMessage = tr("Unable to analyze the deck.");
QStringList errorMessageList = {tr("Unable to analyze the deck.")};
QRegExp rx("<div class=\"alert alert-danger.*<ul>(.*)</ul>");
rx.setMinimal(true);
int found = rx.indexIn(data);
if (found >= 0) {
QString errors = rx.cap(1);
QRegExp rx2("<li>(.*)</li>");
rx2.setMinimal(true);
int captures = rx2.captureCount();
for (int i = 1; i <= captures; i++) {
errorMessage += QString("\n") + rx2.cap(i).remove(QRegularExpression("<[^>]*>")).simplified();
static const QRegularExpression rx("<div class=\"alert alert-danger.*?<ul>(.*?)</ul>");
auto match = rx.match(data);
if (match.hasMatch()) {
QString errors = match.captured(1);
static const QRegularExpression rx2("<li>(.*?)</li>");
static const QRegularExpression rxremove("<[^>]*>");
auto matchIterator = rx2.globalMatch(errors);
while (matchIterator.hasNext()) {
auto match2 = matchIterator.next();
errorMessageList.append(match2.captured(1).remove(rxremove).simplified());
}
}
QString errorMessage = errorMessageList.join("\n");
qDebug() << "Tappedout: bad reply, http status" << httpStatus << "size" << data.size() << "message"
<< errorMessage;
QMessageBox::critical(0, tr("Error"), errorMessage);
QMessageBox::critical(nullptr, tr("Error"), errorMessage);
}
reply->deleteLater();

View File

@@ -1,7 +1,7 @@
#ifndef TAPPEDOUT_INTERFACE_H
#define TAPPEDOUT_INTERFACE_H
#include "carddatabase.h"
#include "../game/cards/card_database.h"
#include "decklist.h"
#include <QObject>

View File

@@ -1,29 +1,29 @@
#pragma once
#include "settingscache.h"
#include "../settings/cache_settings.h"
#include <QMenu>
class TearOffMenu : public QMenu
{
public:
TearOffMenu(const QString &title, QWidget *parent = nullptr) : QMenu(title, parent)
explicit TearOffMenu(const QString &title, QWidget *parent = nullptr) : QMenu(title, parent)
{
connect(&SettingsCache::instance(), &SettingsCache::useTearOffMenusChanged, this,
[=](bool state) { setTearOffEnabled(state); });
[this](const bool state) { setTearOffEnabled(state); });
setTearOffEnabled(SettingsCache::instance().getUseTearOffMenus());
}
TearOffMenu(QWidget *parent = nullptr) : QMenu(parent)
explicit TearOffMenu(QWidget *parent = nullptr) : QMenu(parent)
{
connect(&SettingsCache::instance(), &SettingsCache::useTearOffMenusChanged, this,
[=](bool state) { setTearOffEnabled(state); });
[this](const bool state) { setTearOffEnabled(state); });
setTearOffEnabled(SettingsCache::instance().getUseTearOffMenus());
}
TearOffMenu *addTearOffMenu(const QString &title)
{
TearOffMenu *menu = new TearOffMenu(title, this);
auto *menu = new TearOffMenu(title, this);
addMenu(menu);
return menu;
}

View File

@@ -1,4 +1,4 @@
#include "translatecountername.h"
#include "translate_counter_name.h"
const QMap<QString, QString> TranslateCounterName::translated = {
{"life", QT_TRANSLATE_NOOP("TranslateCounterName", "Life")},

View File

@@ -0,0 +1,337 @@
/**
* @file flow_layout.cpp
* @brief Implementation of the FlowLayout class, a custom layout for dynamically organizing widgets
* in rows within the constraints of available width or parent scroll areas.
*/
#include "flow_layout.h"
#include "../widgets/general/layout_containers/flow_widget.h"
#include <QDebug>
#include <QLayoutItem>
#include <QScrollArea>
#include <QStyle>
/**
* @brief Constructs a FlowLayout instance with the specified parent widget, margin, and spacing values.
* @param parent The parent widget for this layout.
* @param margin The layout margin.
* @param hSpacing The horizontal spacing between items.
* @param vSpacing The vertical spacing between items.
*/
FlowLayout::FlowLayout(QWidget *parent, const int margin, const int hSpacing, const int vSpacing)
: QLayout(parent), horizontalMargin(hSpacing), verticalMargin(vSpacing)
{
setContentsMargins(margin, margin, margin, margin);
}
/**
* @brief Destructor for FlowLayout, which cleans up all items in the layout.
*/
FlowLayout::~FlowLayout()
{
QLayoutItem *item;
while ((item = FlowLayout::takeAt(0))) {
delete item;
}
}
/**
* @brief Indicates the layout's support for expansion in both horizontal and vertical directions.
* @return The orientations (Qt::Horizontal | Qt::Vertical) this layout can expand to fill.
*/
Qt::Orientations FlowLayout::expandingDirections() const
{
return Qt::Horizontal | Qt::Vertical;
}
/**
* @brief Indicates that this layout's height depends on its width.
* @return True, as the layout adjusts its height to fit the specified width.
*/
bool FlowLayout::hasHeightForWidth() const
{
return true;
}
/**
* @brief Calculates the required height to display all items within the specified width.
* @param width The available width for arranging items.
* @return The total height needed to fit all items in rows constrained by the specified width.
*/
int FlowLayout::heightForWidth(const int width) const
{
int height = 0;
int rowWidth = 0;
int rowHeight = 0;
for (const QLayoutItem *item : items) {
if (item == nullptr || item->isEmpty()) {
continue;
}
int itemWidth = item->sizeHint().width() + horizontalSpacing();
if (rowWidth + itemWidth > width) { // Start a new row if the row width exceeds available width
height += rowHeight + verticalSpacing();
rowWidth = itemWidth;
rowHeight = item->sizeHint().height() + verticalSpacing();
} else {
rowWidth += itemWidth;
rowHeight = qMax(rowHeight, item->sizeHint().height());
}
}
height += rowHeight; // Add the final row's height
return height;
}
/**
* @brief Arranges layout items in rows within the specified rectangle bounds.
* @param rect The area within which to position layout items.
*/
void FlowLayout::setGeometry(const QRect &rect)
{
QLayout::setGeometry(rect); // Sets the geometry of the layout based on the given rectangle.
int left, top, right, bottom;
getContentsMargins(&left, &top, &right, &bottom); // Retrieves the layout's content margins.
// Adjust the rectangle to exclude margins.
const QRect adjustedRect = rect.adjusted(+left, +top, -right, -bottom);
// Calculate the available width for items, considering either the adjusted rectangle's width
// or the parent scroll area width, if applicable.
const int availableWidth = qMax(adjustedRect.width(), getParentScrollAreaWidth());
// Arrange all rows of items within the available width and get the total height used.
const int totalHeight = layoutAllRows(adjustedRect.x(), adjustedRect.y(), availableWidth);
// If the layout's parent is a QWidget, update its minimum size to ensure it can accommodate
// the arranged items' dimensions.
if (QWidget *parentWidgetPtr = parentWidget()) {
parentWidgetPtr->setMinimumSize(availableWidth, totalHeight);
}
}
/**
* @brief Arranges items in rows based on the available width.
* Items are added to a row until the row's width exceeds `availableWidth`.
* Then, a new row is started.
* @param originX The starting x-coordinate for the row layout.
* @param originY The starting y-coordinate for the row layout.
* @param availableWidth The available width to lay out items.
* @return The y-coordinate of the final row's end position.
*/
int FlowLayout::layoutAllRows(const int originX, const int originY, const int availableWidth)
{
QVector<QLayoutItem *> rowItems; // Temporary storage for items in the current row.
int currentXPosition = originX; // Tracks the x-coordinate for placing items in the current row.
int currentYPosition = originY; // Tracks the y-coordinate, updated after each row.
int rowHeight = 0; // Tracks the maximum height of items in the current row.
// Iterate through all layout items to arrange them.
for (QLayoutItem *item : items) {
if (item == nullptr || item->isEmpty()) {
continue;
}
QSize itemSize = item->sizeHint(); // The suggested size for the item.
const int itemWidth = itemSize.width() + horizontalSpacing();
// Check if the item fits in the current row's remaining width.
if (currentXPosition + itemWidth > availableWidth) {
// If not, layout the current row and start a new row.
layoutSingleRow(rowItems, originX, currentYPosition);
rowItems.clear(); // Clear the temporary storage for the new row.
currentXPosition = originX; // Reset x-position to the start of the new row.
currentYPosition += rowHeight + verticalSpacing(); // Move y-position down for the new row.
rowHeight = 0; // Reset row height for the new row.
}
// Add the item to the current row.
rowItems.append(item);
rowHeight = qMax(rowHeight, itemSize.height()); // Update the row height to the tallest item.
currentXPosition += itemSize.width() + horizontalSpacing(); // Move x-position for the next item.
}
// Layout the final row if there are remaining items.
layoutSingleRow(rowItems, originX, currentYPosition);
currentYPosition += rowHeight; // Add the final row's height
return currentYPosition;
}
/**
* @brief Helper function for arranging a single row of items within specified bounds.
* @param rowItems Items to be arranged in the row.
* @param x The x-coordinate for starting the row.
* @param y The y-coordinate for starting the row.
*/
void FlowLayout::layoutSingleRow(const QVector<QLayoutItem *> &rowItems, int x, const int y)
{
// Iterate through each item in the row and position it.
for (QLayoutItem *item : rowItems) {
if (item == nullptr || item->isEmpty()) {
continue;
}
QSize itemMaxSize = item->widget()->maximumSize(); // Get the item's maximum allowable size.
// Constrain the item's width and height to its size hint or maximum size.
int itemWidth = qMin(item->sizeHint().width(), itemMaxSize.width());
int itemHeight = qMin(item->sizeHint().height(), itemMaxSize.height());
// Set the item's geometry based on the calculated size and position.
item->setGeometry(QRect(QPoint(x, y), QSize(itemWidth, itemHeight)));
// Move the x-position for the next item, including horizontal spacing.
x += itemWidth + horizontalSpacing();
}
}
/**
* @brief Returns the preferred size for this layout.
* @return The maximum of all item size hints as a QSize.
*/
QSize FlowLayout::sizeHint() const
{
QSize size;
for (const QLayoutItem *item : items) {
if (item != nullptr && !item->isEmpty()) {
size = size.expandedTo(item->sizeHint());
}
}
return size.isValid() ? size : QSize(0, 0);
}
/**
* @brief Returns the minimum size required to display all layout items.
* @return The minimum QSize needed by the layout.
*/
QSize FlowLayout::minimumSize() const
{
QSize size;
for (const QLayoutItem *item : items) {
if (item != nullptr && !item->isEmpty()) {
size = size.expandedTo(item->minimumSize());
}
}
size.setWidth(qMin(size.width(), getParentScrollAreaWidth()));
size.setHeight(qMin(size.height(), getParentScrollAreaHeight()));
return size.isValid() ? size : QSize(0, 0);
}
/**
* @brief Adds a new item to the layout.
* @param item The layout item to add.
*/
void FlowLayout::addItem(QLayoutItem *item)
{
if (item != nullptr) {
items.append(item);
}
}
/**
* @brief Retrieves the count of items in the layout.
* @return The number of layout items.
*/
int FlowLayout::count() const
{
return static_cast<int>(items.size());
}
/**
* @brief Returns the layout item at the specified index.
* @param index The index of the item to retrieve.
* @return A pointer to the item at the specified index, or nullptr if out of range.
*/
QLayoutItem *FlowLayout::itemAt(const int index) const
{
return (index >= 0 && index < items.size()) ? items.value(index) : nullptr;
}
/**
* @brief Removes and returns the item at the specified index.
* @param index The index of the item to remove.
* @return A pointer to the removed item, or nullptr if out of range.
*/
QLayoutItem *FlowLayout::takeAt(const int index)
{
return (index >= 0 && index < items.size()) ? items.takeAt(index) : nullptr;
}
/**
* @brief Gets the horizontal spacing between items.
* @return The horizontal spacing if set, otherwise a smart default.
*/
int FlowLayout::horizontalSpacing() const
{
return (horizontalMargin >= 0) ? horizontalMargin : smartSpacing(QStyle::PM_LayoutHorizontalSpacing);
}
/**
* @brief Gets the vertical spacing between items.
* @return The vertical spacing if set, otherwise a smart default.
*/
int FlowLayout::verticalSpacing() const
{
return (verticalMargin >= 0) ? verticalMargin : smartSpacing(QStyle::PM_LayoutVerticalSpacing);
}
/**
* @brief Calculates smart spacing based on the parent widget style.
* @param pm The pixel metric to calculate.
* @return The calculated spacing value.
*/
int FlowLayout::smartSpacing(const QStyle::PixelMetric pm) const
{
QObject *parent = this->parent();
if (!parent) {
return -1;
}
if (parent->isWidgetType()) {
const auto *pw = dynamic_cast<QWidget *>(parent);
return pw->style()->pixelMetric(pm, nullptr, pw);
}
return dynamic_cast<QLayout *>(parent)->spacing();
}
/**
* @brief Gets the width of the parent scroll area, if any.
* @return The width of the scroll area's viewport, or 0 if not found.
*/
int FlowLayout::getParentScrollAreaWidth() const
{
QWidget *parent = parentWidget();
while (parent) {
if (const auto *scrollArea = qobject_cast<QScrollArea *>(parent)) {
return scrollArea->viewport()->width();
}
parent = parent->parentWidget();
}
return 0;
}
/**
* @brief Gets the height of the parent scroll area, if any.
* @return The height of the scroll area's viewport, or 0 if not found.
*/
int FlowLayout::getParentScrollAreaHeight() const
{
QWidget *parent = parentWidget();
while (parent) {
if (const auto *scrollArea = qobject_cast<QScrollArea *>(parent)) {
return scrollArea->viewport()->height();
}
parent = parent->parentWidget();
}
return 0;
}

View File

@@ -0,0 +1,43 @@
#ifndef FLOW_LAYOUT_H
#define FLOW_LAYOUT_H
#include <QLayout>
#include <QList>
#include <QWidget>
#include <qstyle.h>
class FlowLayout : public QLayout
{
public:
explicit FlowLayout(QWidget *parent = nullptr);
FlowLayout(QWidget *parent, int margin, int hSpacing, int vSpacing);
~FlowLayout() override;
void addItem(QLayoutItem *item) override;
[[nodiscard]] int count() const override;
[[nodiscard]] QLayoutItem *itemAt(int index) const override;
QLayoutItem *takeAt(int index) override;
[[nodiscard]] int horizontalSpacing() const;
[[nodiscard]] Qt::Orientations expandingDirections() const override;
[[nodiscard]] bool hasHeightForWidth() const override;
[[nodiscard]] int heightForWidth(int width) const override;
[[nodiscard]] int verticalSpacing() const;
[[nodiscard]] int doLayout(const QRect &rect, bool testOnly) const;
[[nodiscard]] int smartSpacing(QStyle::PixelMetric pm) const;
[[nodiscard]] int getParentScrollAreaWidth() const;
[[nodiscard]] int getParentScrollAreaHeight() const;
void setGeometry(const QRect &rect) override;
virtual int layoutAllRows(int originX, int originY, int availableWidth);
virtual void layoutSingleRow(const QVector<QLayoutItem *> &rowItems, int x, int y);
[[nodiscard]] QSize sizeHint() const override;
[[nodiscard]] QSize minimumSize() const override;
protected:
QList<QLayoutItem *> items; // List to store layout items
int horizontalMargin;
int verticalMargin;
};
#endif // FLOW_LAYOUT_H

View File

@@ -0,0 +1,142 @@
#include "horizontal_flow_layout.h"
/**
* @brief Constructs a HorizontalFlowLayout instance with the specified parent widget.
* This layout arranges items in columns within the given height, automatically adjusting its width.
* @param parent The parent widget to which this layout belongs.
* @param margin The layout margin.
* @param hSpacing The horizontal spacing between items.
* @param vSpacing The vertical spacing between items.
*/
HorizontalFlowLayout::HorizontalFlowLayout(QWidget *parent, const int margin, const int hSpacing, const int vSpacing)
: FlowLayout(parent, margin, hSpacing, vSpacing)
{
}
/**
* @brief Destructor for HorizontalFlowLayout, responsible for cleaning up layout items.
*/
HorizontalFlowLayout::~HorizontalFlowLayout()
{
QLayoutItem *item;
while ((item = FlowLayout::takeAt(0))) {
delete item;
}
}
/**
* @brief Calculates the required width to display all items, given a specified height.
* This method arranges items into columns and determines the total width needed.
* @param height The available height for arranging layout items.
* @return The total width required to fit all items, organized in columns constrained by the given height.
*/
int HorizontalFlowLayout::heightForWidth(const int height) const
{
int width = 0;
int colWidth = 0;
int colHeight = 0;
for (const QLayoutItem *item : items) {
if (item == nullptr || item->isEmpty()) {
continue;
}
int itemHeight = item->sizeHint().height();
if (colHeight + itemHeight > height) {
width += colWidth;
colHeight = itemHeight;
colWidth = item->sizeHint().width();
} else {
colHeight += itemHeight;
colWidth = qMax(colWidth, item->sizeHint().width());
}
}
width += colWidth; // Add width of the last column
return width;
}
/**
* @brief Sets the geometry of the layout items, arranging them in columns within the given height.
* @param rect The rectangle area defining the layout space.
*/
void HorizontalFlowLayout::setGeometry(const QRect &rect)
{
const int availableHeight = qMax(rect.height(), getParentScrollAreaHeight());
const int totalWidth = layoutAllColumns(rect.x(), rect.y(), availableHeight);
if (QWidget *parentWidgetPtr = parentWidget()) {
parentWidgetPtr->setMinimumSize(totalWidth, availableHeight);
}
}
/**
* @brief Lays out items into columns according to the available height, starting from a given origin.
* Each column is arranged within `availableHeight`, wrapping to a new column as necessary.
* @param originX The x-coordinate for the layout start position.
* @param originY The y-coordinate for the layout start position.
* @param availableHeight The height within which each column is constrained.
* @return The total width after arranging all columns.
*/
int HorizontalFlowLayout::layoutAllColumns(const int originX, const int originY, const int availableHeight)
{
QVector<QLayoutItem *> colItems; // Holds items for the current column
int currentXPosition = originX; // Tracks the x-coordinate while placing items
int currentYPosition = originY; // Tracks the y-coordinate, resetting for each new column
int colWidth = 0; // Tracks the maximum width of items in the current column
for (QLayoutItem *item : items) {
if (item == nullptr || item->isEmpty()) {
continue;
}
QSize itemSize = item->sizeHint(); // The suggested size for the current item
// Check if the current item fits in the remaining height of the current column
if (currentYPosition + itemSize.height() > availableHeight) {
// If not, layout the current column and start a new column
layoutSingleColumn(colItems, currentXPosition, originY);
colItems.clear(); // Reset the list for the new column
currentYPosition = originY; // Reset y-position to the column's start
currentXPosition += colWidth; // Move x-position to the next column
colWidth = 0; // Reset column width for the new column
}
// Add the item to the current column
colItems.append(item);
colWidth = qMax(colWidth, itemSize.width()); // Update the column's width to the widest item
currentYPosition += itemSize.height(); // Move y-position for the next item
}
// Layout the final column if there are any remaining items
layoutSingleColumn(colItems, currentXPosition, originY);
// Return the total width used, including the last column's width
return currentXPosition + colWidth;
}
/**
* @brief Arranges a single column of items within specified x and y starting positions.
* @param colItems A list of items to be arranged in the column.
* @param x The starting x-coordinate for the column.
* @param y The starting y-coordinate for the column.
*/
void HorizontalFlowLayout::layoutSingleColumn(const QVector<QLayoutItem *> &colItems, const int x, int y)
{
for (QLayoutItem *item : colItems) {
if (item != nullptr && item->isEmpty()) {
continue;
}
// Get the maximum allowed size for the item
QSize itemMaxSize = item->widget()->maximumSize();
// Constrain the item's width and height to its size hint or maximum size
const int itemWidth = qMin(item->sizeHint().width(), itemMaxSize.width());
const int itemHeight = qMin(item->sizeHint().height(), itemMaxSize.height());
// Set the item's geometry based on the computed size and position
item->setGeometry(QRect(QPoint(x, y), QSize(itemWidth, itemHeight)));
// Move the y-position down by the item's height to place the next item below
y += itemHeight;
}
}

Some files were not shown because too many files have changed in this diff Show More