mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-27 00:14:40 -07:00
* [DeckShare] Create temporary share links for local and server decks
* [DeckShare] Address review findings and harden the share flows
Gate every share entry point on login, de-duplicate the share-link and
color-identity logic behind DeckShareUtils and an injected querier, and
replace the silent tray/status-bar notices with always-visible dialogs.
- abstract_tab_deck_editor: explain that sharing requires a connection
instead of silently doing nothing when logged out
- tab_deck_storage: disable the share action on disconnect, reject
folder/deck mixes and the root folder with clear warnings, re-enable
Create on every entry/response so a dropped connection cannot leave
the button disabled
- tab_deck_storage_visual: same login gate for the context-menu entry,
visible success/error dialogs, and a symmetric in-flight guard
- getDeckColorIdentity now takes a CardDatabaseQuerier, dropping the
CardDatabaseManager singleton access and enabling unit tests
* [DeckShare] Fix share-link expiry build on the minimum-supported Qt
QTimeZone::UTC (the Initialization enum) only exists since Qt 6.7, so
Debian 12 and Ubuntu 24.04 (Qt 6.4) fail to compile the share-link expiry
handling in the share dialog and the two deck-storage tabs. Mirror the
existing games_model guard and fall back to Qt::UTC on older Qt.
* [DeckShare] Use the stable server client for the visual deck storage tab
* [DeckShare] Extract the share-creation response handling into DeckShareUtils
* [DeckShare] Drop includes left unused by the share-response extraction
* [DeckShare] Format share expiry with the locale-aware short format
* [DeckShare] Build share links with QUrl and QUrlQuery for percent-encoding
* [DeckShare] Replace the duplicate computeColorIdentity with the shared getDeckColorIdentity
* [DeckShare] Recover the share controls when the server never answers
* [DeckShare] Provide the full share hint in each plural form
* [DeckShare] Join the selected-count label with a non-translatable separator
* [DeckShare] Retranslate the share button tooltip with the storage widget
* [DeckShare] Forward retranslateUi to the visual deck storage widget
* [DeckShare] Let the share bar owners supply the hint text
* [DeckShare] End the share-related headers and sources with a trailing newline
* [DeckShare] Keep the settings include in the project include block
* [DeckShare] Include the network settings header used by the share timeout
* [DeckShare] Resolve the share theme icon through themePixmap
QPixmap("theme:icons/share") has no file extension, so ThemeManager::assetPath()
is bypassed and the pixmap is always null. Use themePixmap(QStringLiteral("icons/share"))
like every other toolbar action, so the .svg (and dark/light variants) resolves.
* [DeckShare] Keep the share selection consistent with the visible decks
Filtered-out previews are hidden but kept alive, so selectedFilePaths() counted
them in the share and the selection highlight. Only decks the user can see are
now shared, and a deck that stops matching the filters is deselectd as the deck
pass runs, keeping the %n count and the highlight in sync with the screen.
* [DeckShare] Abandon an in-flight tree share on cancel
Leaving share mode never stopped the timeout timer, and a late response still
ran shareFromTreeFinished, copying the link and announcing success for a share
the user backed out of. Stopping the timer and tracking the outstanding request
by sequence number means a stale reply (or a timed-out one) after cancel is
ignored, and cancelling + re-entering share mode can no longer confuse the two
requests.
* [DeckShare] Abandon an in-flight tile share on cancel
exitShareMode() left shareTimeoutTimer running and did not abandon the pending
Command_DeckShareCreate, so a timer pop or a late success still reported the
share after the user cancelled. Stop the timer and ignore stale responses via a
sequence number, mirroring the tree tab.
* [DeckShare] Wire the status-changed handler after shareBar exists
handleConnectionChanged() dereferences shareBar->isVisible(), but the connection
was set up before shareBar was constructed and shareBar had no in-class
initializer. On any status change delivered before construction the slot read an
indeterminate pointer. Seed the connection (and the initial share availability)
after shareBar exists and give shareBar a = nullptr initializer.
* [DeckShare] Explain why a blank deck cannot be shared
A blank deck exited the share flow silently. The menu only disables the entry
via setSaveStatus(), a different predicate, so the path is reachable (e.g. add a
card and remove it again). Mirror the not-logged-in branch with a short
information dialog.
* [DeckShare] Restore the banner-text doc comment
Re-add the doc block above refreshBannerCardText() that was removed as part of
the share-selection work; it documents the coupling to refreshBannerCardToolTip.
* [DeckShare] Resolve the stable server client in the deck editor gate
actShareDeck went through tabSupervisor->getClient(), which hands back a
LocalClient while an offline game is running. LocalClient never sets its status,
so a logged-in user could not share from the deck editor during a local game,
and got a misleading "You must be connected" message. Expose the supervisor's
stable remote client and use it for the gate and the dialog, matching the other
share tabs.
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
219 lines
10 KiB
C++
219 lines
10 KiB
C++
#include "../../../interface/widgets/menus/deck_editor_menu.h"
|
|
|
|
#include "../../../client/settings/cache_settings.h"
|
|
#include "../../../client/settings/shortcuts_settings.h"
|
|
#include "../tabs/abstract_tab_deck_editor.h"
|
|
|
|
#include <libcockatrice/settings/recents_settings.h>
|
|
DeckEditorMenu::DeckEditorMenu(AbstractTabDeckEditor *parent) : QMenu(parent), deckEditor(parent)
|
|
{
|
|
aNewDeck = new QAction(QString(), this);
|
|
connect(aNewDeck, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::actNewDeck);
|
|
|
|
aLoadDeck = new QAction(QString(), this);
|
|
connect(aLoadDeck, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::actLoadDeck);
|
|
|
|
loadRecentDeckMenu = new QMenu(this);
|
|
connect(&SettingsCache::instance().recents(), &RecentsSettings::recentlyOpenedDeckPathsChanged, this,
|
|
&DeckEditorMenu::updateRecentlyOpened);
|
|
|
|
aClearRecents = new QAction(QString(), this);
|
|
connect(aClearRecents, &QAction::triggered, this, &DeckEditorMenu::actClearRecents);
|
|
|
|
updateRecentlyOpened();
|
|
|
|
aSaveDeck = new QAction(QString(), this);
|
|
connect(aSaveDeck, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::actSaveDeck);
|
|
|
|
aSaveDeckAs = new QAction(QString(), this);
|
|
connect(aSaveDeckAs, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::actSaveDeckAs);
|
|
|
|
aShareDeck = new QAction(QString(), this);
|
|
connect(aShareDeck, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::actShareDeck);
|
|
|
|
aLoadDeckFromClipboard = new QAction(QString(), this);
|
|
connect(aLoadDeckFromClipboard, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::actLoadDeckFromClipboard);
|
|
|
|
aEditDeckInClipboard = new QAction(QString(), this);
|
|
connect(aEditDeckInClipboard, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::actEditDeckInClipboard);
|
|
|
|
aEditDeckInClipboardRaw = new QAction(QString(), this);
|
|
connect(aEditDeckInClipboardRaw, &QAction::triggered, deckEditor,
|
|
&AbstractTabDeckEditor::actEditDeckInClipboardRaw);
|
|
|
|
aSaveDeckToClipboard = new QAction(QString(), this);
|
|
connect(aSaveDeckToClipboard, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::actSaveDeckToClipboard);
|
|
|
|
aSaveDeckToClipboardNoSetInfo = new QAction(QString(), this);
|
|
connect(aSaveDeckToClipboardNoSetInfo, &QAction::triggered, deckEditor,
|
|
&AbstractTabDeckEditor::actSaveDeckToClipboardNoSetInfo);
|
|
|
|
aSaveDeckToClipboardRaw = new QAction(QString(), this);
|
|
connect(aSaveDeckToClipboardRaw, &QAction::triggered, deckEditor,
|
|
&AbstractTabDeckEditor::actSaveDeckToClipboardRaw);
|
|
|
|
aSaveDeckToClipboardRawNoSetInfo = new QAction(QString(), this);
|
|
connect(aSaveDeckToClipboardRawNoSetInfo, &QAction::triggered, deckEditor,
|
|
&AbstractTabDeckEditor::actSaveDeckToClipboardRawNoSetInfo);
|
|
|
|
aPrintDeck = new QAction(QString(), this);
|
|
connect(aPrintDeck, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::actPrintDeck);
|
|
|
|
aLoadDeckFromWebsite = new QAction(QString(), this);
|
|
connect(aLoadDeckFromWebsite, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::actLoadDeckFromWebsite);
|
|
|
|
aExportDeckDecklist = new QAction(QString(), this);
|
|
connect(aExportDeckDecklist, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::actExportDeckDecklist);
|
|
|
|
aExportDeckDecklistXyz = new QAction(QString(), this);
|
|
connect(aExportDeckDecklistXyz, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::actExportDeckDecklistXyz);
|
|
|
|
aAnalyzeDeckDeckstats = new QAction(QString(), this);
|
|
connect(aAnalyzeDeckDeckstats, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::actAnalyzeDeckDeckstats);
|
|
|
|
aAnalyzeDeckTappedout = new QAction(QString(), this);
|
|
connect(aAnalyzeDeckTappedout, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::actAnalyzeDeckTappedout);
|
|
|
|
analyzeDeckMenu = new QMenu(this);
|
|
analyzeDeckMenu->addAction(aExportDeckDecklist);
|
|
analyzeDeckMenu->addAction(aExportDeckDecklistXyz);
|
|
analyzeDeckMenu->addSeparator();
|
|
analyzeDeckMenu->addAction(aAnalyzeDeckDeckstats);
|
|
analyzeDeckMenu->addAction(aAnalyzeDeckTappedout);
|
|
|
|
aClose = new QAction(QString(), this);
|
|
connect(aClose, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::closeRequest);
|
|
|
|
editDeckInClipboardMenu = new QMenu(this);
|
|
editDeckInClipboardMenu->addAction(aEditDeckInClipboard);
|
|
editDeckInClipboardMenu->addAction(aEditDeckInClipboardRaw);
|
|
|
|
saveDeckToClipboardMenu = new QMenu(this);
|
|
saveDeckToClipboardMenu->addAction(aSaveDeckToClipboard);
|
|
saveDeckToClipboardMenu->addAction(aSaveDeckToClipboardNoSetInfo);
|
|
saveDeckToClipboardMenu->addAction(aSaveDeckToClipboardRaw);
|
|
saveDeckToClipboardMenu->addAction(aSaveDeckToClipboardRawNoSetInfo);
|
|
|
|
addAction(aNewDeck);
|
|
addAction(aLoadDeck);
|
|
addMenu(loadRecentDeckMenu);
|
|
addAction(aSaveDeck);
|
|
addAction(aSaveDeckAs);
|
|
addAction(aShareDeck);
|
|
addSeparator();
|
|
addAction(aLoadDeckFromClipboard);
|
|
addMenu(editDeckInClipboardMenu);
|
|
addMenu(saveDeckToClipboardMenu);
|
|
addSeparator();
|
|
addAction(aPrintDeck);
|
|
addAction(aLoadDeckFromWebsite);
|
|
addMenu(analyzeDeckMenu);
|
|
addSeparator();
|
|
addAction(deckEditor->filterDockWidget->aClearFilterOne);
|
|
addAction(deckEditor->filterDockWidget->aClearFilterAll);
|
|
addSeparator();
|
|
addAction(aClose);
|
|
|
|
retranslateUi();
|
|
connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this,
|
|
&DeckEditorMenu::refreshShortcuts);
|
|
refreshShortcuts();
|
|
}
|
|
|
|
void DeckEditorMenu::setSaveStatus(bool newStatus)
|
|
{
|
|
aSaveDeck->setEnabled(newStatus);
|
|
aSaveDeckAs->setEnabled(newStatus);
|
|
aShareDeck->setEnabled(newStatus);
|
|
aSaveDeckToClipboard->setEnabled(newStatus);
|
|
aSaveDeckToClipboardNoSetInfo->setEnabled(newStatus);
|
|
aSaveDeckToClipboardRaw->setEnabled(newStatus);
|
|
aSaveDeckToClipboardRawNoSetInfo->setEnabled(newStatus);
|
|
saveDeckToClipboardMenu->setEnabled(newStatus);
|
|
aPrintDeck->setEnabled(newStatus);
|
|
analyzeDeckMenu->setEnabled(newStatus);
|
|
}
|
|
|
|
void DeckEditorMenu::updateRecentlyOpened()
|
|
{
|
|
loadRecentDeckMenu->clear();
|
|
for (const auto &deckPath : SettingsCache::instance().recents().getRecentlyOpenedDeckPaths()) {
|
|
QAction *aRecentlyOpenedDeck = new QAction(deckPath, this);
|
|
loadRecentDeckMenu->addAction(aRecentlyOpenedDeck);
|
|
connect(aRecentlyOpenedDeck, &QAction::triggered, deckEditor,
|
|
[=, this] { deckEditor->actOpenRecent(aRecentlyOpenedDeck->text()); });
|
|
}
|
|
loadRecentDeckMenu->addSeparator();
|
|
loadRecentDeckMenu->addAction(aClearRecents);
|
|
aClearRecents->setEnabled(SettingsCache::instance().recents().getRecentlyOpenedDeckPaths().length() > 0);
|
|
}
|
|
|
|
void DeckEditorMenu::actClearRecents()
|
|
{
|
|
SettingsCache::instance().recents().clearRecentlyOpenedDeckPaths();
|
|
}
|
|
|
|
void DeckEditorMenu::retranslateUi()
|
|
{
|
|
setTitle(tr("&Deck Editor"));
|
|
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..."));
|
|
aShareDeck->setText(tr("Share deck..."));
|
|
|
|
aLoadDeckFromClipboard->setText(tr("Load deck from cl&ipboard..."));
|
|
|
|
editDeckInClipboardMenu->setTitle(tr("Edit deck in clipboard"));
|
|
aEditDeckInClipboard->setText(tr("Annotated"));
|
|
aEditDeckInClipboardRaw->setText(tr("Not Annotated"));
|
|
|
|
saveDeckToClipboardMenu->setTitle(tr("Save deck to clipboard"));
|
|
aSaveDeckToClipboard->setText(tr("Annotated"));
|
|
aSaveDeckToClipboardNoSetInfo->setText(tr("Annotated (No set info)"));
|
|
aSaveDeckToClipboardRaw->setText(tr("Not Annotated"));
|
|
aSaveDeckToClipboardRawNoSetInfo->setText(tr("Not Annotated (No set info)"));
|
|
|
|
aPrintDeck->setText(tr("&Print deck..."));
|
|
|
|
aLoadDeckFromWebsite->setText(tr("Load deck from online service..."));
|
|
analyzeDeckMenu->setTitle(tr("&Send deck to online service"));
|
|
aExportDeckDecklist->setText(tr("Create decklist (decklist.org)"));
|
|
aExportDeckDecklistXyz->setText(tr("Create decklist (decklist.xyz)"));
|
|
aAnalyzeDeckDeckstats->setText(tr("Analyze deck (deckstats.net)"));
|
|
aAnalyzeDeckTappedout->setText(tr("Analyze deck (tappedout.net)"));
|
|
|
|
aClose->setText(tr("&Close"));
|
|
}
|
|
|
|
void DeckEditorMenu::refreshShortcuts()
|
|
{
|
|
ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts();
|
|
aNewDeck->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aNewDeck"));
|
|
aLoadDeck->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aLoadDeck"));
|
|
aSaveDeck->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aSaveDeck"));
|
|
aSaveDeckAs->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aSaveDeckAs"));
|
|
aLoadDeckFromClipboard->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aLoadDeckFromClipboard"));
|
|
aEditDeckInClipboard->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aEditDeckInClipboard"));
|
|
aEditDeckInClipboardRaw->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aEditDeckInClipboardRaw"));
|
|
aPrintDeck->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aPrintDeck"));
|
|
|
|
aLoadDeckFromWebsite->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aLoadDeckFromWebsite"));
|
|
|
|
aExportDeckDecklist->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aExportDeckDecklist"));
|
|
aExportDeckDecklistXyz->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aExportDeckDecklistXyz"));
|
|
aAnalyzeDeckDeckstats->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aAnalyzeDeck"));
|
|
aAnalyzeDeckTappedout->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aAnalyzeDeckTappedout"));
|
|
|
|
aClose->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aClose"));
|
|
|
|
aSaveDeckToClipboard->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aSaveDeckToClipboard"));
|
|
aSaveDeckToClipboardNoSetInfo->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aSaveDeckToClipboardNoSetInfo"));
|
|
aSaveDeckToClipboardRaw->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aSaveDeckToClipboardRaw"));
|
|
aSaveDeckToClipboardRawNoSetInfo->setShortcuts(
|
|
shortcuts.getShortcut("TabDeckEditor/aSaveDeckToClipboardRawNoSetInfo"));
|
|
|
|
aClose->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aClose"));
|
|
}
|